Commit 7bb22d37 authored by Eric Domke's avatar Eric Domke
Browse files

Improve SVG Support: TSpans, Paths, etc.

- Fix issues with calculating the next coordinate after a close figure
and a relative move
- Allow the std. deviation in a gaussian blur to be a float.
- Allow "none" for a unit collection
- Allow a marker to scale with either the stroke width or the use a
custom coordinate space
- Allow gradients to reference other gradients defined later in the SVG
- Respect the font and fill styles of TextSpan elements.  Current
support is still partial.
- Fix href attribute bug on the SVG use element
parent 14a3b6a9
...@@ -7,6 +7,7 @@ namespace Svg.DataTypes ...@@ -7,6 +7,7 @@ namespace Svg.DataTypes
{ {
public enum SvgFontWeight public enum SvgFontWeight
{ {
inherit,
normal, normal,
bold bold
} }
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Svg
{
public enum SvgMarkerUnits
{
strokeWidth,
userSpaceOnUse
}
}
...@@ -45,6 +45,7 @@ namespace Svg ...@@ -45,6 +45,7 @@ namespace Svg
{ {
if (value is string) if (value is string)
{ {
if (string.Compare(((string)value).Trim(), "none", StringComparison.InvariantCultureIgnoreCase) == 0) return null;
string[] points = ((string)value).Trim().Split(new char[] { ',', ' ', '\r', '\n', '\t' }, StringSplitOptions.RemoveEmptyEntries); string[] points = ((string)value).Trim().Split(new char[] { ',', ' ', '\r', '\n', '\t' }, StringSplitOptions.RemoveEmptyEntries);
SvgUnitCollection units = new SvgUnitCollection(); SvgUnitCollection units = new SvgUnitCollection();
......
...@@ -13,7 +13,7 @@ namespace Svg ...@@ -13,7 +13,7 @@ namespace Svg
{ {
private Uri _referencedElement; private Uri _referencedElement;
[SvgAttribute("xlink:href")] [SvgAttribute("href", SvgAttributeAttribute.XLinkNamespace)]
public virtual Uri ReferencedElement public virtual Uri ReferencedElement
{ {
get { return this._referencedElement; } get { return this._referencedElement; }
......
...@@ -14,7 +14,7 @@ namespace Svg.FilterEffects ...@@ -14,7 +14,7 @@ namespace Svg.FilterEffects
[SvgElement("feGaussianBlur")] [SvgElement("feGaussianBlur")]
public class SvgGaussianBlur : SvgFilterPrimitive public class SvgGaussianBlur : SvgFilterPrimitive
{ {
private int _stdDeviation; private float _stdDeviation;
private BlurType _blurType; private BlurType _blurType;
private int[] _kernel; private int[] _kernel;
...@@ -26,12 +26,13 @@ namespace Svg.FilterEffects ...@@ -26,12 +26,13 @@ namespace Svg.FilterEffects
{ {
} }
public SvgGaussianBlur(int stdDeviation) public SvgGaussianBlur(float stdDeviation)
: this(stdDeviation, BlurType.Both) : this(stdDeviation, BlurType.Both)
{ {
} }
public SvgGaussianBlur(int stdDeviation, BlurType blurType) : base() public SvgGaussianBlur(float stdDeviation, BlurType blurType)
: base()
{ {
_stdDeviation = stdDeviation; _stdDeviation = stdDeviation;
_blurType = blurType; _blurType = blurType;
...@@ -42,13 +43,13 @@ namespace Svg.FilterEffects ...@@ -42,13 +43,13 @@ namespace Svg.FilterEffects
private void PreCalculate() private void PreCalculate()
{ {
int sz = _stdDeviation * 2 + 1; int sz = (int)(_stdDeviation * 2 + 1);
_kernel = new int[sz]; _kernel = new int[sz];
_multable = new int[sz, 256]; _multable = new int[sz, 256];
for (int i = 1; i <= _stdDeviation; i++) for (int i = 1; i <= _stdDeviation; i++)
{ {
int szi = _stdDeviation - i; int szi = (int)(_stdDeviation - i);
int szj = _stdDeviation + i; int szj = (int)(_stdDeviation + i);
_kernel[szj] = _kernel[szi] = (szi + 1) * (szi + 1); _kernel[szj] = _kernel[szi] = (szi + 1) * (szi + 1);
_kernelSum += (_kernel[szj] + _kernel[szi]); _kernelSum += (_kernel[szj] + _kernel[szi]);
for (int j = 0; j < 256; j++) for (int j = 0; j < 256; j++)
...@@ -56,11 +57,11 @@ namespace Svg.FilterEffects ...@@ -56,11 +57,11 @@ namespace Svg.FilterEffects
_multable[szj, j] = _multable[szi, j] = _kernel[szj] * j; _multable[szj, j] = _multable[szi, j] = _kernel[szj] * j;
} }
} }
_kernel[_stdDeviation] = (_stdDeviation + 1) * (_stdDeviation + 1); _kernel[(int)_stdDeviation] = (int)((_stdDeviation + 1) * (_stdDeviation + 1));
_kernelSum += _kernel[_stdDeviation]; _kernelSum += _kernel[(int)_stdDeviation];
for (int j = 0; j < 256; j++) for (int j = 0; j < 256; j++)
{ {
_multable[_stdDeviation, j] = _kernel[_stdDeviation] * j; _multable[(int)_stdDeviation, j] = _kernel[(int)_stdDeviation] * j;
} }
} }
...@@ -104,7 +105,7 @@ namespace Svg.FilterEffects ...@@ -104,7 +105,7 @@ namespace Svg.FilterEffects
for (int i = 0; i < pixelCount; i++) for (int i = 0; i < pixelCount; i++)
{ {
bsum = gsum = rsum = asum = 0; bsum = gsum = rsum = asum = 0;
read = i - _stdDeviation; read = (int)(i - _stdDeviation);
for (int z = 0; z < _kernel.Length; z++) for (int z = 0; z < _kernel.Length; z++)
{ {
if (read < start) if (read < start)
...@@ -156,7 +157,7 @@ namespace Svg.FilterEffects ...@@ -156,7 +157,7 @@ namespace Svg.FilterEffects
index = 0; index = 0;
for (int i = 0; i < src.Height; i++) for (int i = 0; i < src.Height; i++)
{ {
int y = i - _stdDeviation; int y = (int)(i - _stdDeviation);
start = y * src.Width; start = y * src.Width;
for (int j = 0; j < src.Width; j++) for (int j = 0; j < src.Width; j++)
{ {
...@@ -223,12 +224,12 @@ namespace Svg.FilterEffects ...@@ -223,12 +224,12 @@ namespace Svg.FilterEffects
/// Gets or sets the radius of the blur (only allows for one value - not the two specified in the SVG Spec) /// Gets or sets the radius of the blur (only allows for one value - not the two specified in the SVG Spec)
/// </summary> /// </summary>
[SvgAttribute("stdDeviation")] [SvgAttribute("stdDeviation")]
public int StdDeviation public float StdDeviation
{ {
get { return _stdDeviation; } get { return _stdDeviation; }
set set
{ {
if (value < 1) if (value <= 0)
{ {
throw new InvalidOperationException("Radius must be greater then 0"); throw new InvalidOperationException("Radius must be greater then 0");
} }
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Svg
{
/// <summary>
/// A wrapper for a paint server which isn't defined currently in the parse process, but
/// should be defined by the time the image needs to render.
/// </summary>
public class SvgDeferredPaintServer : SvgPaintServer
{
private bool _serverLoaded = false;
private SvgPaintServer _concreteServer;
public SvgDocument Document { get; set; }
public string DeferredId { get; set; }
public SvgDeferredPaintServer() {}
public SvgDeferredPaintServer(SvgDocument document, string id)
{
this.Document = document;
this.DeferredId = id;
}
private void EnsureServer()
{
if (!_serverLoaded)
{
_concreteServer = this.Document.IdManager.GetElementById(this.DeferredId) as SvgPaintServer;
_serverLoaded = true;
}
}
public override System.Drawing.Brush GetBrush(SvgVisualElement styleOwner, float opacity)
{
EnsureServer();
return _concreteServer.GetBrush(styleOwner, opacity);
}
public override SvgElement DeepCopy()
{
return DeepCopy<SvgDeferredPaintServer>();
}
public override SvgElement DeepCopy<T>()
{
var newObj = base.DeepCopy<T>() as SvgDeferredPaintServer;
newObj.Document = this.Document;
newObj.DeferredId = this.DeferredId;
return newObj;
}
public override bool Equals(object obj)
{
var other = obj as SvgDeferredPaintServer;
if (other == null)
return false;
return this.Document == other.Document && this.DeferredId == other.DeferredId;
}
public override int GetHashCode()
{
if (this.Document == null || this.DeferredId == null) return 0;
return this.Document.GetHashCode() ^ this.DeferredId.GetHashCode();
}
public override string ToString()
{
return (_serverLoaded ? _serverLoaded.ToString() : string.Format("deferred: {0}", this.DeferredId));
}
public static T TryGet<T>(SvgPaintServer server) where T : SvgPaintServer
{
var deferred = server as SvgDeferredPaintServer;
if (deferred == null)
{
return server as T;
}
else
{
return deferred._concreteServer as T;
}
}
}
}
...@@ -11,7 +11,7 @@ namespace Svg ...@@ -11,7 +11,7 @@ namespace Svg
{ {
private SvgCoordinateUnits _gradientUnits; private SvgCoordinateUnits _gradientUnits;
private SvgGradientSpreadMethod _spreadMethod = SvgGradientSpreadMethod.Pad; private SvgGradientSpreadMethod _spreadMethod = SvgGradientSpreadMethod.Pad;
private SvgGradientServer _inheritGradient; private SvgPaintServer _inheritGradient;
private List<SvgGradientStop> _stops; private List<SvgGradientStop> _stops;
/// <summary> /// <summary>
...@@ -86,13 +86,12 @@ namespace Svg ...@@ -86,13 +86,12 @@ namespace Svg
/// Gets or sets another gradient fill from which to inherit the stops from. /// Gets or sets another gradient fill from which to inherit the stops from.
/// </summary> /// </summary>
[SvgAttribute("href")] [SvgAttribute("href")]
public SvgGradientServer InheritGradient public SvgPaintServer InheritGradient
{ {
get { return this._inheritGradient; } get { return this._inheritGradient; }
set set
{ {
this._inheritGradient = value; this._inheritGradient = value;
this.InheritStops();
} }
} }
...@@ -190,18 +189,15 @@ namespace Svg ...@@ -190,18 +189,15 @@ namespace Svg
return blend; return blend;
} }
/// <summary> protected void LoadStops()
// If this gradient contains no stops then it will search any inherited gradients for stops.
/// </summary>
protected virtual void InheritStops()
{ {
if (this.Stops.Count == 0 && this.InheritGradient != null) var core = SvgDeferredPaintServer.TryGet<SvgGradientServer>(_inheritGradient);
if (this.Stops.Count == 0 && core != null)
{ {
_stops.AddRange(this.InheritGradient.Stops); _stops.AddRange(core.Stops);
} }
} }
public override SvgElement DeepCopy<T>() public override SvgElement DeepCopy<T>()
{ {
var newObj = base.DeepCopy<T>() as SvgGradientServer; var newObj = base.DeepCopy<T>() as SvgGradientServer;
......
...@@ -75,6 +75,7 @@ namespace Svg ...@@ -75,6 +75,7 @@ namespace Svg
public override Brush GetBrush(SvgVisualElement owner, float opacity) public override Brush GetBrush(SvgVisualElement owner, float opacity)
{ {
LoadStops();
// Need at least 2 colours to do the gradient fill // Need at least 2 colours to do the gradient fill
if (this.Stops.Count < 2) if (this.Stops.Count < 2)
{ {
......
...@@ -12,8 +12,16 @@ namespace Svg ...@@ -12,8 +12,16 @@ namespace Svg
[SvgElement("marker")] [SvgElement("marker")]
public class SvgMarker : SvgVisualElement, ISvgViewPort public class SvgMarker : SvgVisualElement, ISvgViewPort
{ {
private SvgMarkerUnits _svgMarkerUnits = SvgMarkerUnits.strokeWidth;
private SvgOrient _svgOrient = new SvgOrient(); private SvgOrient _svgOrient = new SvgOrient();
[SvgAttribute("markerUnits")]
public virtual SvgMarkerUnits MarkerUnits
{
get { return _svgMarkerUnits; }
set { _svgMarkerUnits = value; }
}
[SvgAttribute("refX")] [SvgAttribute("refX")]
public virtual SvgUnit RefX public virtual SvgUnit RefX
{ {
......
...@@ -38,7 +38,18 @@ namespace Svg ...@@ -38,7 +38,18 @@ namespace Svg
{ {
return (SvgPaintServer)document.IdManager.GetElementById(value); return (SvgPaintServer)document.IdManager.GetElementById(value);
} }
else // Otherwise try and parse as colour else if (value.StartsWith("#")) // Otherwise try and parse as colour
{
try
{
return new SvgColourServer((Color)_colourConverter.ConvertFrom(value.Trim()));
}
catch
{
return new SvgDeferredPaintServer(document, value);
}
}
else
{ {
return new SvgColourServer((Color)_colourConverter.ConvertFrom(value.Trim())); return new SvgColourServer((Color)_colourConverter.ConvertFrom(value.Trim()));
} }
......
...@@ -76,6 +76,7 @@ namespace Svg ...@@ -76,6 +76,7 @@ namespace Svg
public override Brush GetBrush(SvgVisualElement renderingElement, float opacity) public override Brush GetBrush(SvgVisualElement renderingElement, float opacity)
{ {
LoadStops();
float radius = this.Radius.ToDeviceValue(renderingElement); float radius = this.Radius.ToDeviceValue(renderingElement);
if (radius <= 0) if (radius <= 0)
......
...@@ -11,7 +11,10 @@ namespace Svg.Pathing ...@@ -11,7 +11,10 @@ namespace Svg.Pathing
// Important for custom line caps. Force the path the close with an explicit line, not just an implicit close of the figure. // Important for custom line caps. Force the path the close with an explicit line, not just an implicit close of the figure.
if (graphicsPath.PathPoints.Length > 1 && !graphicsPath.PathPoints[0].Equals(graphicsPath.PathPoints[graphicsPath.PathPoints.Length - 1])) if (graphicsPath.PathPoints.Length > 1 && !graphicsPath.PathPoints[0].Equals(graphicsPath.PathPoints[graphicsPath.PathPoints.Length - 1]))
{ {
graphicsPath.AddLine(graphicsPath.PathPoints[graphicsPath.PathPoints.Length - 1], graphicsPath.PathPoints[0]); int i = graphicsPath.PathTypes.Length - 1;
while (i >= 0 && graphicsPath.PathTypes[i] > 0) i--;
if (i < 0) i = 0;
graphicsPath.AddLine(graphicsPath.PathPoints[graphicsPath.PathPoints.Length - 1], graphicsPath.PathPoints[i]);
} }
graphicsPath.CloseFigure(); graphicsPath.CloseFigure();
} }
......
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
using System.Linq;
using System.Drawing; using System.Drawing;
using System.Drawing.Drawing2D; using System.Drawing.Drawing2D;
using System.Xml.Serialization; using System.Xml.Serialization;
...@@ -146,29 +147,13 @@ namespace Svg ...@@ -146,29 +147,13 @@ namespace Svg
if (this.MarkerStart != null) if (this.MarkerStart != null)
{ {
var marker = this.OwnerDocument.GetElementById<SvgMarker>(this.MarkerStart.ToString()); var marker = this.OwnerDocument.GetElementById<SvgMarker>(this.MarkerStart.ToString());
var markerPath = marker.Path.Clone() as GraphicsPath; pen.CustomStartCap = CapFromMarker(marker, strokeWidth);
var transMatrix = new Matrix();
transMatrix.Scale(.5f, .5f);
transMatrix.RotateAt(90f, new PointF(marker.RefX.ToDeviceValue(), marker.RefY.ToDeviceValue()));
transMatrix.Translate(-1 * marker.RefX.ToDeviceValue(), -1 * marker.RefY.ToDeviceValue());
markerPath.Transform(transMatrix);
pen.CustomStartCap = new CustomLineCap(markerPath, null);
} }
if (this.MarkerEnd != null) if (this.MarkerEnd != null)
{ {
var marker = this.OwnerDocument.GetElementById<SvgMarker>(this.MarkerEnd.ToString()); var marker = this.OwnerDocument.GetElementById<SvgMarker>(this.MarkerEnd.ToString());
var markerPath = marker.Path.Clone() as GraphicsPath; pen.CustomEndCap = CapFromMarker(marker, strokeWidth);
var transMatrix = new Matrix();
transMatrix.Scale(.5f, .5f);
transMatrix.Rotate(90f);
transMatrix.Translate(-1 * marker.RefX.ToDeviceValue(), -1 * marker.RefY.ToDeviceValue());
markerPath.Transform(transMatrix);
pen.CustomEndCap = new CustomLineCap(markerPath, null);
} }
renderer.DrawPath(pen, this.Path); renderer.DrawPath(pen, this.Path);
...@@ -176,6 +161,26 @@ namespace Svg ...@@ -176,6 +161,26 @@ namespace Svg
} }
} }
private CustomLineCap CapFromMarker(SvgMarker marker, float strokeWidth)
{
var markerPath = marker.Path.Clone() as GraphicsPath;
var transMatrix = new Matrix();
transMatrix.Translate(-1 * marker.RefX.ToDeviceValue(), -1 * marker.RefY.ToDeviceValue(), MatrixOrder.Append);
transMatrix.Rotate(90f, MatrixOrder.Append);
// With the current aliasing structure, 1px lines still render as 2px lines
if (strokeWidth < 2)
{
transMatrix.Scale(.5f, .5f, MatrixOrder.Append);
}
else if (marker.MarkerUnits == SvgMarkerUnits.userSpaceOnUse)
{
transMatrix.Scale(1f / strokeWidth, 1f / strokeWidth, MatrixOrder.Append);
}
markerPath.Transform(transMatrix);
return new CustomLineCap(markerPath, null);
}
public override SvgElement DeepCopy() public override SvgElement DeepCopy()
{ {
......
...@@ -222,9 +222,9 @@ namespace Svg ...@@ -222,9 +222,9 @@ namespace Svg
{ {
var lastSegment = segments.Last; var lastSegment = segments.Last;
// if the last element is a SvgClosePathSegment the position of the previous element should be used because the position of SvgClosePathSegment is 0,0 // if the last element is a SvgClosePathSegment the position of the previous move to should be used because the position of SvgClosePathSegment is 0,0
if (lastSegment is SvgClosePathSegment) if (lastSegment is SvgClosePathSegment)
lastSegment = segments[segments.Count - 2]; lastSegment = segments.OfType<SvgMoveToSegment>().Last();
if (isRelativeX) if (isRelativeX)
{ {
...@@ -290,6 +290,7 @@ namespace Svg ...@@ -290,6 +290,7 @@ namespace Svg
{ {
if (value is string) if (value is string)
{ {
if (string.IsNullOrEmpty((string)value)) return new SvgPathSegmentList();
return Parse((string)value); return Parse((string)value);
} }
......
...@@ -99,6 +99,7 @@ ...@@ -99,6 +99,7 @@
<Compile Include="Clipping and Masking\SvgClipRule.cs" /> <Compile Include="Clipping and Masking\SvgClipRule.cs" />
<Compile Include="Clipping and Masking\SvgClipPath.cs" /> <Compile Include="Clipping and Masking\SvgClipPath.cs" />
<Compile Include="Clipping and Masking\SvgMask.cs" /> <Compile Include="Clipping and Masking\SvgMask.cs" />
<Compile Include="DataTypes\SvgMarkerUnits.cs" />
<Compile Include="DataTypes\SvgOrient.cs" /> <Compile Include="DataTypes\SvgOrient.cs" />
<Compile Include="DataTypes\ISvgViewPort.cs" /> <Compile Include="DataTypes\ISvgViewPort.cs" />
<Compile Include="DataTypes\SvgAspectRatio.cs" /> <Compile Include="DataTypes\SvgAspectRatio.cs" />
...@@ -112,6 +113,7 @@ ...@@ -112,6 +113,7 @@
<Compile Include="DataTypes\SvgViewBox.cs" /> <Compile Include="DataTypes\SvgViewBox.cs" />
<Compile Include="Document Structure\SvgTitle.cs" /> <Compile Include="Document Structure\SvgTitle.cs" />
<Compile Include="Document Structure\SvgDocumentMetadata.cs" /> <Compile Include="Document Structure\SvgDocumentMetadata.cs" />
<Compile Include="Painting\SvgDeferredPaintServer.cs" />
<Compile Include="Painting\SvgMarker.cs" /> <Compile Include="Painting\SvgMarker.cs" />
<Compile Include="Document Structure\SvgDefinitionList.cs" /> <Compile Include="Document Structure\SvgDefinitionList.cs" />
<Compile Include="Document Structure\SvgDescription.cs" /> <Compile Include="Document Structure\SvgDescription.cs" />
...@@ -173,6 +175,7 @@ ...@@ -173,6 +175,7 @@
<Compile Include="DataTypes\SvgUnit.cs" /> <Compile Include="DataTypes\SvgUnit.cs" />
<Compile Include="DataTypes\SvgUnitConverter.cs" /> <Compile Include="DataTypes\SvgUnitConverter.cs" />
<Compile Include="SvgTextReader.cs" /> <Compile Include="SvgTextReader.cs" />
<Compile Include="Text\FontData.cs" />
<Compile Include="Text\SvgText.cs" /> <Compile Include="Text\SvgText.cs" />
<Compile Include="Text\SvgTextAnchor.cs" /> <Compile Include="Text\SvgTextAnchor.cs" />
<Compile Include="Text\SvgTextSpan.cs" /> <Compile Include="Text\SvgTextSpan.cs" />
......
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using Svg.DataTypes;
using System.Text.RegularExpressions;
namespace Svg
{
internal class FontData
{
private SvgUnit _fontSize;
private SvgFontWeight _fontWeight = SvgFontWeight.inherit;
private string _font;
private string _fontFamily;
private const string DefaultFontFamily = "Times New Roman";
public FontData()
{
this._fontSize = new SvgUnit(0.0f);
}
/// <summary>
/// Indicates which font family is to be used to render the text.
/// </summary>
public virtual string FontFamily
{
get { return this._fontFamily ?? DefaultFontFamily; }
set
{
this._fontFamily = ValidateFontFamily(value);
}
}
/// <summary>
/// Refers to the size of the font from baseline to baseline when multiple lines of text are set solid in a multiline layout environment.
/// </summary>
public virtual SvgUnit FontSize
{
get { return this._fontSize; }
set { this._fontSize = value; }
}
/// <summary>
/// Refers to the boldness of the font.
/// </summary>
public virtual SvgFontWeight FontWeight
{
get { return this._fontWeight; }
set { this._fontWeight = value; }
}
/// <summary>
/// Set all font information.
/// </summary>
public string Font
{
get { return this._font; }
set
{
var parts = value.Split(',');
foreach (var part in parts)
{
//This deals with setting font size. Looks for either <number>px or <number>pt style="font: bold 16px/normal 'trebuchet ms', verdana, sans-serif;"
Regex rx = new Regex(@"(\d+)+(?=pt|px)");
var res = rx.Match(part);
if (res.Success)
{
int fontSize = 10;
int.TryParse(res.Value, out fontSize);
this.FontSize = new SvgUnit((float)fontSize);
}
//this assumes "bold" has spaces around it. e.g.: style="font: bold 16px/normal
rx = new Regex(@"\sbold\s");
res = rx.Match(part);
if (res.Success)
{
this.FontWeight = SvgFontWeight.bold;
}
}
var font = ValidateFontFamily(value);
this._fontFamily = font;
this._font = font; //not sure this is used?
}
}
public Font GetFont(ISvgStylable owner, FontData inherit)
{
float fontSize = this.FontSize.ToDeviceValue(owner);
if (fontSize == 0.0f)
{
fontSize = (inherit == null ? 1.0f : inherit.FontSize.ToDeviceValue(owner));
fontSize = (fontSize == 0.0f ? 1.0f : fontSize);
}
var baseWeight = (_fontWeight == SvgFontWeight.inherit && inherit != null ? inherit.FontWeight : _fontWeight);
var fontWeight = (baseWeight == SvgFontWeight.bold ? FontStyle.Bold : FontStyle.Regular);
var family = _fontFamily ?? (inherit == null ? DefaultFontFamily : inherit.FontFamily);
return new Font(family, fontSize, fontWeight, GraphicsUnit.Pixel);
}
private static string ValidateFontFamily(string fontFamilyList)
{
// Split font family list on "," and then trim start and end spaces and quotes.
var fontParts = fontFamilyList.Split(new[] { ',' }).Select(fontName => fontName.Trim(new[] { '"', ' ', '\'' }));
var families = System.Drawing.FontFamily.Families;
// Find a the first font that exists in the list of installed font families.
//styles from IE get sent through as lowercase.
foreach (var f in fontParts.Where(f => families.Any(family => family.Name.ToLower() == f.ToLower())))
{
return f;
}
// No valid font family found from the list requested.
return null;
}
public override bool Equals(object obj)
{
var thisType = obj as FontData;
if (thisType != null) return Equals(thisType);
return base.Equals(obj);
}
public bool Equals(FontData obj)
{
return obj._fontFamily == this._fontFamily && obj._fontSize == this._fontSize && obj._fontWeight == this._fontWeight;
}
public override int GetHashCode()
{
return (this._fontFamily == null ? 0 : this._fontFamily.GetHashCode()) ^
(this._fontSize == null ? 0 : this._fontSize.GetHashCode()) ^
this._fontWeight.GetHashCode();
}
}
}
...@@ -23,10 +23,7 @@ namespace Svg ...@@ -23,10 +23,7 @@ namespace Svg
private SvgUnit _dx; private SvgUnit _dx;
private SvgUnit _letterSpacing; private SvgUnit _letterSpacing;
private SvgUnit _wordSpacing; private SvgUnit _wordSpacing;
private SvgUnit _fontSize; private FontData _fontData;
private SvgFontWeight _fontWeight;
private string _font;
private string _fontFamily;
private GraphicsPath _path; private GraphicsPath _path;
private SvgTextAnchor _textAnchor = SvgTextAnchor.Start; private SvgTextAnchor _textAnchor = SvgTextAnchor.Start;
private static readonly SvgRenderer _stringMeasure; private static readonly SvgRenderer _stringMeasure;
...@@ -47,8 +44,7 @@ namespace Svg ...@@ -47,8 +44,7 @@ namespace Svg
/// </summary> /// </summary>
public SvgText() public SvgText()
{ {
this._fontFamily = DefaultFontFamily; this._fontData = new FontData();
this._fontSize = new SvgUnit(0.0f);
this._dy = new SvgUnit(0.0f); this._dy = new SvgUnit(0.0f);
this._dx = new SvgUnit(0.0f); this._dx = new SvgUnit(0.0f);
} }
...@@ -184,12 +180,8 @@ namespace Svg ...@@ -184,12 +180,8 @@ namespace Svg
[SvgAttribute("font-family")] [SvgAttribute("font-family")]
public virtual string FontFamily public virtual string FontFamily
{ {
get { return this._fontFamily; } get { return this._fontData.FontFamily; }
set set { this._fontData.FontFamily = value; this.IsPathDirty = true; }
{
this._fontFamily = ValidateFontFamily(value);
this.IsPathDirty = true;
}
} }
/// <summary> /// <summary>
...@@ -198,8 +190,8 @@ namespace Svg ...@@ -198,8 +190,8 @@ namespace Svg
[SvgAttribute("font-size")] [SvgAttribute("font-size")]
public virtual SvgUnit FontSize public virtual SvgUnit FontSize
{ {
get { return this._fontSize; } get { return this._fontData.FontSize; }
set { this._fontSize = value; this.IsPathDirty = true; } set { this._fontData.FontSize = value; this.IsPathDirty = true; }
} }
...@@ -209,8 +201,8 @@ namespace Svg ...@@ -209,8 +201,8 @@ namespace Svg
[SvgAttribute("font-weight")] [SvgAttribute("font-weight")]
public virtual SvgFontWeight FontWeight public virtual SvgFontWeight FontWeight
{ {
get { return this._fontWeight; } get { return this._fontData.FontWeight; }
set { this._fontWeight = value; this.IsPathDirty = true; } set { this._fontData.FontWeight = value; this.IsPathDirty = true; }
} }
...@@ -220,36 +212,8 @@ namespace Svg ...@@ -220,36 +212,8 @@ namespace Svg
[SvgAttribute("font")] [SvgAttribute("font")]
public virtual string Font public virtual string Font
{ {
get { return this._font; } get { return this._fontData.Font; }
set set { _fontData.Font = value; this.IsPathDirty = true; }
{
var parts = value.Split(',');
foreach (var part in parts)
{
//This deals with setting font size. Looks for either <number>px or <number>pt style="font: bold 16px/normal 'trebuchet ms', verdana, sans-serif;"
Regex rx = new Regex(@"(\d+)+(?=pt|px)");
var res = rx.Match(part);
if (res.Success)
{
int fontSize = 10;
int.TryParse(res.Value, out fontSize);
this.FontSize = new SvgUnit((float)fontSize);
}
//this assumes "bold" has spaces around it. e.g.: style="font: bold 16px/normal
rx = new Regex(@"\sbold\s");
res = rx.Match(part);
if (res.Success)
{
this.FontWeight = SvgFontWeight.bold;
}
}
var font = ValidateFontFamily(value);
this._fontFamily = font;
this._font = font; //not sure this is used?
this.IsPathDirty = true;
}
} }
/// <summary> /// <summary>
...@@ -261,7 +225,17 @@ namespace Svg ...@@ -261,7 +225,17 @@ namespace Svg
/// <value>The fill.</value> /// <value>The fill.</value>
public override SvgPaintServer Fill public override SvgPaintServer Fill
{ {
get { return (this.Attributes["fill"] == null) ? new SvgColourServer(Color.Black) : (SvgPaintServer)this.Attributes["fill"]; } get {
var spans = this.Children.Where(x => x is SvgTextSpan).Select(x => x as SvgTextSpan).ToList();
if (spans.Count == 1 && spans[0].Fill != SvgColourServer.NotSet)
{
return spans[0].Fill;
}
else
{
return (this.Attributes["fill"] == null) ? new SvgColourServer(Color.Black) : (SvgPaintServer)this.Attributes["fill"];
}
}
set { this.Attributes["fill"] = value; } set { this.Attributes["fill"] = value; }
} }
...@@ -320,34 +294,42 @@ namespace Svg ...@@ -320,34 +294,42 @@ namespace Svg
if (_path == null || this.IsPathDirty) if (_path == null || this.IsPathDirty)
{ {
float fontSize = this.FontSize.ToDeviceValue(this); var font = this._fontData.GetFont(this, null);
if (fontSize == 0.0f)
{
fontSize = 1.0f;
}
FontStyle fontWeight = (this.FontWeight == SvgFontWeight.bold ? FontStyle.Bold : FontStyle.Regular);
Font font = new Font(this._fontFamily, fontSize, fontWeight, GraphicsUnit.Pixel);
_path = new GraphicsPath(); _path = new GraphicsPath();
_path.StartFigure(); _path.StartFigure();
if (!string.IsNullOrEmpty(this.Text)) if (!string.IsNullOrEmpty(this.Text))
DrawString(_path, this.X, this.Y, this.Dx, this.Dy, font, fontSize, this.Text); DrawString(_path, this.X, this.Y, this.Dx, this.Dy, font, this.Text);
foreach (var tspan in this.Children.Where(x => x is SvgTextSpan).Select(x => x as SvgTextSpan)) foreach (var tspan in this.Children.Where(x => x is SvgTextSpan).Select(x => x as SvgTextSpan))
{ {
if (!string.IsNullOrEmpty(tspan.Text)) if (!string.IsNullOrEmpty(tspan.Text))
{
if (tspan.FontInfo.Equals(this._fontData))
{
DrawString( DrawString(
_path, _path,
tspan.X == SvgUnit.Empty ? this.X: tspan.X, tspan.X == SvgUnit.Empty ? this.X : tspan.X,
tspan.Y == SvgUnit.Empty ? this.Y : tspan.Y, tspan.Y == SvgUnit.Empty ? this.Y : tspan.Y,
tspan.DX, tspan.DX,
tspan.DY, tspan.DY,
font, font,
fontSize,
tspan.Text); tspan.Text);
} }
else
{
DrawString(
_path,
tspan.X == SvgUnit.Empty ? this.X : tspan.X,
tspan.Y == SvgUnit.Empty ? this.Y : tspan.Y,
tspan.DX,
tspan.DY,
tspan.FontInfo.GetFont(this, _fontData),
tspan.Text);
}
}
}
_path.CloseFigure(); _path.CloseFigure();
this.IsPathDirty = false; this.IsPathDirty = false;
...@@ -377,7 +359,7 @@ namespace Svg ...@@ -377,7 +359,7 @@ namespace Svg
return DefaultFontFamily; return DefaultFontFamily;
} }
private void DrawString(GraphicsPath path, SvgUnit x, SvgUnit y, SvgUnit dx, SvgUnit dy, Font font, float fontSize, string text) private void DrawString(GraphicsPath path, SvgUnit x, SvgUnit y, SvgUnit dx, SvgUnit dy, Font font, string text)
{ {
PointF location = PointF.Empty; PointF location = PointF.Empty;
SizeF stringBounds; SizeF stringBounds;
...@@ -421,13 +403,13 @@ namespace Svg ...@@ -421,13 +403,13 @@ namespace Svg
char[] characters = word.ToCharArray(); char[] characters = word.ToCharArray();
foreach (char currentCharacter in characters) foreach (char currentCharacter in characters)
{ {
path.AddString(currentCharacter.ToString(), new FontFamily(this._fontFamily), (int)font.Style, fontSize, location, StringFormat.GenericTypographic); path.AddString(currentCharacter.ToString(), font.FontFamily, (int)font.Style, font.Size, location, StringFormat.GenericTypographic);
location = new PointF(path.GetBounds().Width + start + letterSpacing, location.Y); location = new PointF(path.GetBounds().Width + start + letterSpacing, location.Y);
} }
} }
else else
{ {
path.AddString(word, new FontFamily(this._fontFamily), (int)font.Style, fontSize, location, StringFormat.GenericTypographic); path.AddString(word, font.FontFamily, (int)font.Style, font.Size, location, StringFormat.GenericTypographic);
} }
// Move the location of the word to be written along // Move the location of the word to be written along
...@@ -438,7 +420,7 @@ namespace Svg ...@@ -438,7 +420,7 @@ namespace Svg
{ {
if (!string.IsNullOrEmpty(text)) if (!string.IsNullOrEmpty(text))
{ {
path.AddString(text, new FontFamily(this._fontFamily), (int)font.Style, fontSize, location, StringFormat.GenericTypographic); path.AddString(text, font.FontFamily, (int)font.Style, font.Size, location, StringFormat.GenericTypographic);
} }
} }
......
...@@ -4,6 +4,7 @@ using System.Collections.Generic; ...@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Drawing.Drawing2D; using System.Drawing.Drawing2D;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using Svg.DataTypes;
namespace Svg namespace Svg
{ {
...@@ -14,6 +15,7 @@ namespace Svg ...@@ -14,6 +15,7 @@ namespace Svg
private SvgUnit _y; private SvgUnit _y;
private SvgUnit _dx; private SvgUnit _dx;
private SvgUnit _dy; private SvgUnit _dy;
private FontData _fontData = new FontData();
/// <summary> /// <summary>
/// Gets or sets the X. /// Gets or sets the X.
...@@ -60,6 +62,57 @@ namespace Svg ...@@ -60,6 +62,57 @@ namespace Svg
set { this._dy = value; } set { this._dy = value; }
} }
/// <summary>
/// Gets or sets the fill <see cref="SvgPaintServer"/> of this element.
/// </summary>
[SvgAttribute("fill")]
public virtual SvgPaintServer Fill
{
get { return (this.Attributes["fill"] == null) ? SvgColourServer.NotSet : (SvgPaintServer)this.Attributes["fill"]; }
set { this.Attributes["fill"] = value; }
}
/// <summary>
/// Indicates which font family is to be used to render the text.
/// </summary>
[SvgAttribute("font-family")]
public virtual string FontFamily
{
get { return this._fontData.FontFamily; }
set { this._fontData.FontFamily = value; }
}
/// <summary>
/// Refers to the size of the font from baseline to baseline when multiple lines of text are set solid in a multiline layout environment.
/// </summary>
[SvgAttribute("font-size")]
public virtual SvgUnit FontSize
{
get { return this._fontData.FontSize; }
set { this._fontData.FontSize = value; }
}
/// <summary>
/// Refers to the boldness of the font.
/// </summary>
[SvgAttribute("font-weight")]
public virtual SvgFontWeight FontWeight
{
get { return this._fontData.FontWeight; }
set { this._fontData.FontWeight = value; }
}
/// <summary>
/// Set all font information.
/// </summary>
[SvgAttribute("font")]
public virtual string Font
{
get { return this._fontData.Font; }
set { _fontData.Font = value; }
}
/// <summary> /// <summary>
/// Gets or sets the text to be rendered. /// Gets or sets the text to be rendered.
...@@ -89,6 +142,6 @@ namespace Svg ...@@ -89,6 +142,6 @@ namespace Svg
return newObj; return newObj;
} }
internal FontData FontInfo { get { return _fontData; } }
} }
} }
\ No newline at end of file
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment