diff --git a/Source/Paths/SvgClosePathSegment.cs b/Source/Paths/SvgClosePathSegment.cs index d3909d7f4fcd0361ef14975955c6ac799c08e35d..7696aa289d5d0f357b2df9d848dc785904311766 100644 --- a/Source/Paths/SvgClosePathSegment.cs +++ b/Source/Paths/SvgClosePathSegment.cs @@ -9,7 +9,7 @@ namespace Svg.Pathing public override void AddToPath(System.Drawing.Drawing2D.GraphicsPath graphicsPath) { // 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.PointCount > 0 && !graphicsPath.PathPoints[0].Equals(graphicsPath.PathPoints[graphicsPath.PathPoints.Length - 1])) { int i = graphicsPath.PathTypes.Length - 1; while (i >= 0 && graphicsPath.PathTypes[i] > 0) i--; diff --git a/Source/Svg.csproj b/Source/Svg.csproj index 9b7a51b156284d734958b6fa4b00b1676cfaee14..484012ee47bc9309967e9c17ccc3f158b23fb61d 100644 --- a/Source/Svg.csproj +++ b/Source/Svg.csproj @@ -136,6 +136,7 @@ + @@ -183,6 +184,7 @@ + diff --git a/Source/SvgContentNode.cs b/Source/SvgContentNode.cs new file mode 100644 index 0000000000000000000000000000000000000000..5a2315f0640f029a13dbb7fa48c19c54dac90b9f --- /dev/null +++ b/Source/SvgContentNode.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Svg +{ + public class SvgContentNode : ISvgNode + { + public string Content { get; set; } + } +} diff --git a/Source/SvgDocument.cs b/Source/SvgDocument.cs index 4aa51887466e614bf0d396252d5c864011052035..adcd71871d745accfa5b3dfa068f4a7e980b84f8 100644 --- a/Source/SvgDocument.cs +++ b/Source/SvgDocument.cs @@ -8,6 +8,7 @@ using System.Drawing.Text; using System.IO; using System.Text; using System.Xml; +using System.Linq; namespace Svg { @@ -184,7 +185,6 @@ namespace Svg using (var reader = new SvgTextReader(stream, entities)) { var elementStack = new Stack(); - var value = new StringBuilder(); bool elementEmpty; SvgElement element = null; SvgElement parent; @@ -218,7 +218,10 @@ namespace Svg { parent = elementStack.Peek(); if (parent != null && element != null) + { parent.Children.Add(element); + parent.Nodes.Add(element); + } } // Push element into stack @@ -236,21 +239,24 @@ namespace Svg // Pop the element out of the stack element = elementStack.Pop(); - if (value.Length > 0 && element != null) + if (element.Nodes.OfType().Any()) { - element.Content = value.ToString(); - - // Reset content value for new element - value.Length = 0; + element.Content = (from e in element.Nodes select e.Content).Aggregate((p, c) => p + c); + } + else + { + element.Nodes.Clear(); // No sense wasting the space where it isn't needed } break; case XmlNodeType.CDATA: case XmlNodeType.Text: - value.Append(reader.Value); + element = elementStack.Peek(); + element.Nodes.Add(new SvgContentNode() { Content = reader.Value }); break; case XmlNodeType.EntityReference: reader.ResolveEntity(); - value.Append(reader.Value); + element = elementStack.Peek(); + element.Nodes.Add(new SvgContentNode() { Content = reader.Value }); break; } } diff --git a/Source/SvgElement.cs b/Source/SvgElement.cs index 4a5df1e42b8c4227b87c52f43d702829eb061d02..5f113a743ab8686e36c141abae95f30f7060b05d 100644 --- a/Source/SvgElement.cs +++ b/Source/SvgElement.cs @@ -15,7 +15,7 @@ namespace Svg /// /// The base class of which all SVG elements are derived from. /// - public abstract class SvgElement : ISvgElement, ISvgTransformable, ICloneable + public abstract class SvgElement : ISvgElement, ISvgTransformable, ICloneable, ISvgNode { //optimization protected class PropertyAttributeTuple @@ -42,6 +42,7 @@ namespace Svg private static readonly object _loadEventKey = new object(); private Matrix _graphicsMatrix; private SvgCustomAttributeCollection _customAttributes; + private List _nodes = new List(); /// /// Gets the name of the element. @@ -117,6 +118,11 @@ namespace Svg get { return this._children; } } + public IList Nodes + { + get { return this._nodes; } + } + public IEnumerable Descendants() { return this.AsEnumerable().Descendants(); @@ -611,8 +617,8 @@ namespace Svg childPath = (GraphicsPath)childPath.Clone(); if(child.Transforms != null) childPath.Transform(child.Transforms.GetMatrix()); - - path.AddPath(childPath, false); + + if (childPath.PointCount > 0) path.AddPath(childPath, false); } } } @@ -921,7 +927,6 @@ namespace Svg } #endregion graphical EVENTS - } public class SVGArg : EventArgs @@ -1035,10 +1040,16 @@ namespace Svg public bool CtrlKey; } + public interface ISvgNode + { + string Content { get; } + } + internal interface ISvgElement { SvgElement Parent {get;} SvgElementCollection Children { get; } + IList Nodes { get; } void Render(SvgRenderer renderer); } diff --git a/Source/SvgRenderer.cs b/Source/SvgRenderer.cs index 83cf6958ef6046d62c4756589cf6bd91704564e6..40417de9d8ef4f5342dd8329e275f2a031abb297 100644 --- a/Source/SvgRenderer.cs +++ b/Source/SvgRenderer.cs @@ -157,6 +157,7 @@ namespace Svg StringFormat format = StringFormat.GenericTypographic; format.SetMeasurableCharacterRanges(new CharacterRange[]{new CharacterRange(0, text.Length)}); + format.FormatFlags |= StringFormatFlags.MeasureTrailingSpaces; Region[] r = this._innerGraphics.MeasureCharacterRanges(text, font, new Rectangle(0, 0, 1000, 1000), format); RectangleF rect = r[0].GetBounds(this._innerGraphics); diff --git a/Source/Text/SvgText.cs b/Source/Text/SvgText.cs index 8f3cc331d1f4db19c28e68514dbaa66e384329fe..343763b3e72813b6698bb238f7789875a12d4b10 100644 --- a/Source/Text/SvgText.cs +++ b/Source/Text/SvgText.cs @@ -1,13 +1,10 @@ -using System; +using System; using System.Collections.Generic; +using System.Linq; using System.Text; -using System.Text.RegularExpressions; -using System.ComponentModel; using System.Drawing; using System.Drawing.Drawing2D; -using System.Drawing.Text; using Svg.DataTypes; -using System.Linq; namespace Svg { @@ -15,43 +12,12 @@ namespace Svg /// The element defines a graphics element consisting of text. /// [SvgElement("text")] - public class SvgText : SvgVisualElement + public class SvgText : SvgTextBase { - private SvgUnit _x; - private SvgUnit _y; - private SvgUnit _dy; - private SvgUnit _dx; - private SvgUnit _letterSpacing; - private SvgUnit _wordSpacing; - private SvgUnit _fontSize; - private SvgFontWeight _fontWeight; - private string _font; - private string _fontFamily; - private GraphicsPath _path; - private SvgTextAnchor _textAnchor = SvgTextAnchor.Start; - private static readonly SvgRenderer _stringMeasure; - private const string DefaultFontFamily = "Times New Roman"; - /// /// Initializes the class. /// - static SvgText() - { - Bitmap bitmap = new Bitmap(1, 1); - _stringMeasure = SvgRenderer.FromImage(bitmap); - _stringMeasure.TextRenderingHint = TextRenderingHint.AntiAlias; - } - - /// - /// Initializes a new instance of the class. - /// - public SvgText() - { - this._fontFamily = DefaultFontFamily; - this._fontSize = new SvgUnit(0.0f); - this._dy = new SvgUnit(0.0f); - this._dx = new SvgUnit(0.0f); - } + public SvgText() : base() {} /// /// Initializes a new instance of the class. @@ -61,446 +27,25 @@ namespace Svg { this.Text = text; } - - /// - /// Gets or sets the text to be rendered. - /// - public virtual string Text - { - get { return base.Content; } - set { base.Content = value; this.IsPathDirty = true; this.Content = value; } - } - - /// - /// Gets or sets the text anchor. - /// - /// The text anchor. - [SvgAttribute("text-anchor")] - public virtual SvgTextAnchor TextAnchor - { - get { return this._textAnchor; } - set { this._textAnchor = value; this.IsPathDirty = true; } - } - - /// - /// Gets or sets the X. - /// - /// The X. - [SvgAttribute("x")] - public virtual SvgUnit X - { - get { return this._x; } - set - { - if(_x != value) - { - this._x = value; - this.IsPathDirty = true; - OnAttributeChanged(new AttributeEventArgs{ Attribute = "x", Value = value }); - } - } - } - - /// - /// Gets or sets the dX. - /// - /// The dX. - [SvgAttribute("dx")] - public virtual SvgUnit Dx - { - get { return this._dx; } - set - { - if (_dx != value) - { - this._dx = value; - this.IsPathDirty = true; - OnAttributeChanged(new AttributeEventArgs { Attribute = "dx", Value = value }); - } - } - } - - /// - /// Gets or sets the Y. - /// - /// The Y. - [SvgAttribute("y")] - public virtual SvgUnit Y - { - get { return this._y; } - set - { - if(_y != value) - { - this._y = value; - this.IsPathDirty = true; - OnAttributeChanged(new AttributeEventArgs{ Attribute = "y", Value = value }); - } - } - } - - /// - /// Gets or sets the dY. - /// - /// The dY. - [SvgAttribute("dy")] - public virtual SvgUnit Dy - { - get { return this._dy; } - set - { - if (_dy != value) - { - this._dy = value; - this.IsPathDirty = true; - OnAttributeChanged(new AttributeEventArgs { Attribute = "dy", Value = value }); - } - } - } - - /// - /// Specifies spacing behavior between text characters. - /// - [SvgAttribute("letter-spacing")] - public virtual SvgUnit LetterSpacing - { - get { return this._letterSpacing; } - set { this._letterSpacing = value; this.IsPathDirty = true; } - } - - /// - /// Specifies spacing behavior between words. - /// - [SvgAttribute("word-spacing")] - public virtual SvgUnit WordSpacing - { - get { return this._wordSpacing; } - set { this._wordSpacing = value; this.IsPathDirty = true; } - } - - /// - /// Indicates which font family is to be used to render the text. - /// - [SvgAttribute("font-family")] - public virtual string FontFamily - { - get { return this._fontFamily; } - set - { - this._fontFamily = ValidateFontFamily(value); - this.IsPathDirty = true; - } - } - - /// - /// Refers to the size of the font from baseline to baseline when multiple lines of text are set solid in a multiline layout environment. - /// - [SvgAttribute("font-size")] - public virtual SvgUnit FontSize - { - get { return this._fontSize; } - set { this._fontSize = value; this.IsPathDirty = true; } - } - - - /// - /// Refers to the boldness of the font. - /// - [SvgAttribute("font-weight")] - public virtual SvgFontWeight FontWeight - { - get { return this._fontWeight; } - set { this._fontWeight = value; this.IsPathDirty = true; } - } - - - /// - /// Set all font information. - /// - [SvgAttribute("font")] - public virtual 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 px or 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; - } - } - - /// - /// Gets or sets the fill. - /// - /// - /// Unlike other s, has a default fill of black rather than transparent. - /// - /// The fill. - public override SvgPaintServer Fill - { - get { return (this.Attributes["fill"] == null) ? new SvgColourServer(Color.Black) : (SvgPaintServer)this.Attributes["fill"]; } - set { this.Attributes["fill"] = value; } - } - - /// - /// Returns a that represents the current . - /// - /// - /// A that represents the current . - /// - public override string ToString() - { - return this.Text; - } - - /// - /// Gets or sets a value to determine if anti-aliasing should occur when the element is being rendered. - /// - /// - protected override bool RequiresSmoothRendering - { - get { return true; } - } - - /// - /// Gets the bounds of the element. - /// - /// The bounds. - public override System.Drawing.RectangleF Bounds - { - get { return this.Path.GetBounds(); } - } - - static private RectangleF MeasureString(SvgRenderer renderer, string text, Font font) - { - GraphicsPath p = new GraphicsPath(); - p.AddString(text, font.FontFamily, 0, font.Size, new PointF(0.0f, 0.0f), StringFormat.GenericTypographic); - - p.Transform(renderer.Transform); - return p.GetBounds(); - } - - /// - /// Gets the for this element. - /// - /// - public override System.Drawing.Drawing2D.GraphicsPath Path - { - get - { - // Make sure the path is always null if there is no text - //if there is a TSpan inside of this text element then path should not be null (even if this text is empty!) - if ((string.IsNullOrEmpty(this.Text) || this.Text.Trim().Length < 1) && this.Children.Where(x => x is SvgTextSpan).Select(x => x as SvgTextSpan).Count() == 0) - return _path = null; - //NOT SURE WHAT THIS IS ABOUT - Path gets created again anyway - WTF? - // When an empty string is passed to GraphicsPath, it rises an InvalidArgumentException... - - if (_path == null || this.IsPathDirty) - { - float fontSize = this.FontSize.ToDeviceValue(this); - 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.StartFigure(); - - if (!string.IsNullOrEmpty(this.Text)) - DrawString(_path, this.X, this.Y, this.Dx, this.Dy, font, fontSize, this.Text); - - foreach (var tspan in this.Children.Where(x => x is SvgTextSpan).Select(x => x as SvgTextSpan)) - { - if (!string.IsNullOrEmpty(tspan.Text)) - DrawString( - _path, - tspan.X == SvgUnit.Empty ? this.X: tspan.X, - tspan.Y == SvgUnit.Empty ? this.Y : tspan.Y, - tspan.DX, - tspan.DY, - font, - fontSize, - tspan.Text); - } - - _path.CloseFigure(); - this.IsPathDirty = false; - } - return _path; - } - protected set - { - _path = value; - } - } - - private static string ValidateFontFamily(string fontFamilyList) + + public override SvgElement DeepCopy() { - // 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 DefaultFontFamily; + return DeepCopy(); } - private void DrawString(GraphicsPath path, SvgUnit x, SvgUnit y, SvgUnit dx, SvgUnit dy, Font font, float fontSize, string text) - { - PointF location = PointF.Empty; - SizeF stringBounds; - - lock (_stringMeasure) - { - stringBounds = _stringMeasure.MeasureString(text, font); - } - - float xToDevice = x.ToDeviceValue(this) + dx.ToDeviceValue(this); - float yToDevice = y.ToDeviceValue(this, true) + dy.ToDeviceValue(this, true); - - // Minus FontSize because the x/y coords mark the bottom left, not bottom top. - switch (this.TextAnchor) - { - case SvgTextAnchor.Start: - location = new PointF(xToDevice, yToDevice - stringBounds.Height); - break; - case SvgTextAnchor.Middle: - location = new PointF(xToDevice - (stringBounds.Width / 2), yToDevice - stringBounds.Height); - break; - case SvgTextAnchor.End: - location = new PointF(xToDevice - stringBounds.Width, yToDevice - stringBounds.Height); - break; - } - - // No way to do letter-spacing or word-spacing, so do manually - if (this.LetterSpacing.Value > 0.0f || this.WordSpacing.Value > 0.0f) - { - // Cut up into words, or just leave as required - string[] words = (this.WordSpacing.Value > 0.0f) ? text.Split(' ') : new string[] { text }; - float wordSpacing = this.WordSpacing.ToDeviceValue(this); - float letterSpacing = this.LetterSpacing.ToDeviceValue(this); - float start = this.X.ToDeviceValue(this); - - foreach (string word in words) - { - // Only do if there is line spacing, just write the word otherwise - if (this.LetterSpacing.Value > 0.0f) - { - char[] characters = word.ToCharArray(); - foreach (char currentCharacter in characters) - { - path.AddString(currentCharacter.ToString(), new FontFamily(this._fontFamily), (int)font.Style, fontSize, location, StringFormat.GenericTypographic); - location = new PointF(path.GetBounds().Width + start + letterSpacing, location.Y); - } - } - else - { - path.AddString(word, new FontFamily(this._fontFamily), (int)font.Style, fontSize, location, StringFormat.GenericTypographic); - } - - // Move the location of the word to be written along - location = new PointF(path.GetBounds().Width + start + wordSpacing, location.Y); - } - } - else - { - if (!string.IsNullOrEmpty(text)) - { - path.AddString(text, new FontFamily(this._fontFamily), (int)font.Style, fontSize, location, StringFormat.GenericTypographic); - } - } - - } - - [SvgAttribute("onchange")] - public event EventHandler Change; - - //change - protected void OnChange(string newString, string sessionID) - { - RaiseChange(this, new StringArg {s = newString, SessionID = sessionID}); - } - - protected void RaiseChange(object sender, StringArg s) + public override SvgElement DeepCopy() { - var handler = Change; - if (handler != null) - { - handler(sender, s); - } + var newObj = base.DeepCopy() as SvgText; + newObj.TextAnchor = this.TextAnchor; + newObj.WordSpacing = this.WordSpacing; + newObj.LetterSpacing = this.LetterSpacing; + newObj.Font = this.Font; + newObj.FontFamily = this.FontFamily; + newObj.FontSize = this.FontSize; + newObj.FontWeight = this.FontWeight; + newObj.X = this.X; + newObj.Y = this.Y; + return newObj; } - -#if Net4 - public override void RegisterEvents(ISvgEventCaller caller) - { - //register basic events - base.RegisterEvents(caller); - - //add change event for text - caller.RegisterAction(this.ID + "/onchange", OnChange); - } - - public override void UnregisterEvents(ISvgEventCaller caller) - { - //unregister base events - base.UnregisterEvents(caller); - - //unregister change event - caller.UnregisterAction(this.ID + "/onchange"); - - } -#endif - - public override SvgElement DeepCopy() - { - return DeepCopy(); - } - - public override SvgElement DeepCopy() - { - var newObj = base.DeepCopy() as SvgText; - newObj.TextAnchor = this.TextAnchor; - newObj.WordSpacing = this.WordSpacing; - newObj.LetterSpacing = this.LetterSpacing; - newObj.Font = this.Font; - newObj.FontFamily = this.FontFamily; - newObj.FontSize = this.FontSize; - newObj.FontWeight = this.FontWeight; - newObj.X = this.X; - newObj.Y = this.Y; - return newObj; - } } } diff --git a/Source/Text/SvgTextBase.cs b/Source/Text/SvgTextBase.cs new file mode 100644 index 0000000000000000000000000000000000000000..2679d1fd7d1e03cc6ed402fd7201fa4a855693b5 --- /dev/null +++ b/Source/Text/SvgTextBase.cs @@ -0,0 +1,601 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Text; +using Svg.DataTypes; +using System.Linq; + +namespace Svg +{ + public enum XmlSpaceHandling + { + @default, + preserve + } + + public abstract class SvgTextBase : SvgVisualElement + { + private SvgUnit _x; + private SvgUnit _y; + private SvgUnit _dy; + private SvgUnit _dx; + private SvgUnit _letterSpacing; + private SvgUnit _wordSpacing; + private SvgUnit _fontSize; + private SvgFontWeight _fontWeight; + private string _font; + protected string _fontFamily; + private SvgTextAnchor _textAnchor = SvgTextAnchor.Start; + private static readonly SvgRenderer _stringMeasure; + private const string DefaultFontFamily = "Times New Roman"; + + private XmlSpaceHandling _space = XmlSpaceHandling.@default; + + /// + /// Initializes the class. + /// + static SvgTextBase() + { + Bitmap bitmap = new Bitmap(1, 1); + _stringMeasure = SvgRenderer.FromImage(bitmap); + _stringMeasure.TextRenderingHint = TextRenderingHint.AntiAlias; + } + + /// + /// Initializes a new instance of the class. + /// + public SvgTextBase() + { + this._fontSize = new SvgUnit(0.0f); + this._dy = new SvgUnit(0.0f); + this._dx = new SvgUnit(0.0f); + } + + /// + /// Gets or sets the text to be rendered. + /// + public virtual string Text + { + get { return base.Content; } + set { base.Content = value; this.IsPathDirty = true; this.Content = value; } + } + + /// + /// Gets or sets the text anchor. + /// + /// The text anchor. + [SvgAttribute("text-anchor")] + public virtual SvgTextAnchor TextAnchor + { + get { return this._textAnchor; } + set { this._textAnchor = value; this.IsPathDirty = true; } + } + + /// + /// Gets or sets the X. + /// + /// The X. + [SvgAttribute("x")] + public virtual SvgUnit X + { + get { return this._x; } + set + { + if(_x != value) + { + this._x = value; + this.IsPathDirty = true; + OnAttributeChanged(new AttributeEventArgs{ Attribute = "x", Value = value }); + } + } + } + + /// + /// Gets or sets the dX. + /// + /// The dX. + [SvgAttribute("dx")] + public virtual SvgUnit Dx + { + get { return this._dx; } + set + { + if (_dx != value) + { + this._dx = value; + this.IsPathDirty = true; + OnAttributeChanged(new AttributeEventArgs { Attribute = "dx", Value = value }); + } + } + } + + /// + /// Gets or sets the Y. + /// + /// The Y. + [SvgAttribute("y")] + public virtual SvgUnit Y + { + get { return this._y; } + set + { + if(_y != value) + { + this._y = value; + this.IsPathDirty = true; + OnAttributeChanged(new AttributeEventArgs{ Attribute = "y", Value = value }); + } + } + } + + /// + /// Gets or sets the dY. + /// + /// The dY. + [SvgAttribute("dy")] + public virtual SvgUnit Dy + { + get { return this._dy; } + set + { + if (_dy != value) + { + this._dy = value; + this.IsPathDirty = true; + OnAttributeChanged(new AttributeEventArgs { Attribute = "dy", Value = value }); + } + } + } + + /// + /// Specifies spacing behavior between text characters. + /// + [SvgAttribute("letter-spacing")] + public virtual SvgUnit LetterSpacing + { + get { return this._letterSpacing; } + set { this._letterSpacing = value; this.IsPathDirty = true; } + } + + /// + /// Specifies spacing behavior between words. + /// + [SvgAttribute("word-spacing")] + public virtual SvgUnit WordSpacing + { + get { return this._wordSpacing; } + set { this._wordSpacing = value; this.IsPathDirty = true; } + } + + /// + /// Indicates which font family is to be used to render the text. + /// + [SvgAttribute("font-family")] + public virtual string FontFamily + { + get { return this._fontFamily ?? DefaultFontFamily; } + set + { + this._fontFamily = ValidateFontFamily(value); + this.IsPathDirty = true; + } + } + + /// + /// Refers to the size of the font from baseline to baseline when multiple lines of text are set solid in a multiline layout environment. + /// + [SvgAttribute("font-size")] + public virtual SvgUnit FontSize + { + get { return this._fontSize; } + set { this._fontSize = value; this.IsPathDirty = true; } + } + + + /// + /// Refers to the boldness of the font. + /// + [SvgAttribute("font-weight")] + public virtual SvgFontWeight FontWeight + { + get { return this._fontWeight; } + set { this._fontWeight = value; this.IsPathDirty = true; } + } + + + /// + /// Set all font information. + /// + [SvgAttribute("font")] + public virtual 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 px or 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; + } + } + + /// + /// Gets or sets the fill. + /// + /// + /// Unlike other s, has a default fill of black rather than transparent. + /// + /// The fill. + public override SvgPaintServer Fill + { + get { return (this.Attributes["fill"] == null) ? new SvgColourServer(Color.Black) : (SvgPaintServer)this.Attributes["fill"]; } + set { this.Attributes["fill"] = value; } + } + + /// + /// Returns a that represents the current . + /// + /// + /// A that represents the current . + /// + public override string ToString() + { + return this.Text; + } + + /// + /// Gets or sets a value to determine if anti-aliasing should occur when the element is being rendered. + /// + /// + protected override bool RequiresSmoothRendering + { + get { return true; } + } + + /// + /// Gets the bounds of the element. + /// + /// The bounds. + public override System.Drawing.RectangleF Bounds + { + get { return this.Path.GetBounds(); } + } + + static private RectangleF MeasureString(SvgRenderer renderer, string text, Font font) + { + GraphicsPath p = new GraphicsPath(); + p.AddString(text, font.FontFamily, 0, font.Size, new PointF(0.0f, 0.0f), StringFormat.GenericTypographic); + + p.Transform(renderer.Transform); + return p.GetBounds(); + } + + 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; + } + + + /// + /// Renders the and contents to the specified object. + /// + /// The object to render to. + /// Necessary to make sure that any internal tspan elements get rendered as well + protected override void Render(SvgRenderer renderer) + { + if ((this.Path != null) && this.Visible && this.Displayable) + { + this.PushTransforms(renderer); + this.SetClip(renderer); + + // If this element needs smoothing enabled turn anti-aliasing on + if (this.RequiresSmoothRendering) + { + renderer.SmoothingMode = SmoothingMode.AntiAlias; + } + + this.RenderFill(renderer); + this.RenderStroke(renderer); + this.RenderChildren(renderer); + + // Reset the smoothing mode + if (this.RequiresSmoothRendering && renderer.SmoothingMode == SmoothingMode.AntiAlias) + { + renderer.SmoothingMode = SmoothingMode.Default; + } + + this.ResetClip(renderer); + this.PopTransforms(renderer); + } + } + + private GraphicsPath _path; + + protected class NodeBounds + { + public float xOffset { get; set; } + public SizeF Bounds { get; set; } + public ISvgNode Node { get; set; } + } + protected class BoundsData + { + private List _nodes = new List(); + public IList Nodes + { + get { return _nodes; } + } + public SizeF Bounds { get; set; } + } + protected BoundsData GetTextBounds() + { + var font = GetFont(); + SvgTextBase innerText; + SizeF stringBounds; + float totalHeight = 0; + float totalWidth = 0; + + var result = new BoundsData(); + var nodes = (from n in this.Nodes + where (n is SvgContentNode || n is SvgTextBase) && !string.IsNullOrEmpty(n.Content) + select n).ToList(); + ISvgNode node; + for (var i = 0; i < nodes.Count; i++) + { + node = nodes[i]; + lock (_stringMeasure) + { + innerText = node as SvgTextBase; + if (innerText == null) + { + stringBounds = _stringMeasure.MeasureString(PrepareText(node.Content, + i > 0 && nodes[i - 1] is SvgTextBase, + i < nodes.Count - 1 && nodes[i + 1] is SvgTextBase), font); + } + else + { + stringBounds = innerText.GetTextBounds().Bounds; + } + result.Nodes.Add(new NodeBounds() { Bounds = stringBounds, Node = node, xOffset = totalWidth }); + totalHeight = Math.Max(totalHeight, stringBounds.Height); + totalWidth += stringBounds.Width; + } + } + result.Bounds = new SizeF(totalWidth, totalHeight); + return result; + } + + protected float _calcX = 0; + protected float _calcY = 0; + + /// + /// Gets the for this element. + /// + /// + public override System.Drawing.Drawing2D.GraphicsPath Path + { + get + { + // Make sure the path is always null if there is no text + //if there is a TSpan inside of this text element then path should not be null (even if this text is empty!) + if ((string.IsNullOrEmpty(this.Text) || this.Text.Trim().Length < 1) && this.Children.Where(x => x is SvgTextSpan).Select(x => x as SvgTextSpan).Count() == 0) + return _path = null; + //NOT SURE WHAT THIS IS ABOUT - Path gets created again anyway - WTF? + // When an empty string is passed to GraphicsPath, it rises an InvalidArgumentException... + + if (_path == null || this.IsPathDirty) + { + var font = GetFont(); + SvgTextBase innerText; + //RectangleF bounds; + float x = (_x == SvgUnit.Empty || _x == SvgUnit.None ? _calcX : _x.ToDeviceValue(this)) + _dx.ToDeviceValue(this); + float y = (_y == SvgUnit.Empty || _y == SvgUnit.None ? _calcY : _y.ToDeviceValue(this, true)) + _dy.ToDeviceValue(this, true); + + _path = new GraphicsPath(); + _path.StartFigure(); + + // Measure the overall bounds of all the text + var boundsData = GetTextBounds(); + + // Determine the location of the start point + switch (this.TextAnchor) + { + case SvgTextAnchor.Middle: + x -= (boundsData.Bounds.Width / 2); + break; + case SvgTextAnchor.End: + x -= boundsData.Bounds.Width; + break; + } + + NodeBounds data; + for (var i = 0; i < boundsData.Nodes.Count; i++) + { + data = boundsData.Nodes[i]; + innerText = data.Node as SvgTextBase; + if (innerText == null) + { + // Minus FontSize because the x/y coords mark the bottom left, not bottom top. + DrawString(_path, x + data.xOffset, y - boundsData.Bounds.Height, font, + PrepareText(data.Node.Content, i > 0 && boundsData.Nodes[i-1].Node is SvgTextBase, + i < boundsData.Nodes.Count - 1 && boundsData.Nodes[i + 1].Node is SvgTextBase)); + } + else + { + innerText._calcX = x + data.xOffset; + innerText._calcY = y; + } + } + + _path.CloseFigure(); + this.IsPathDirty = false; + } + return _path; + } + protected set + { + _path = value; + } + } + + /// + /// Prepare the text according to the whitespace handling rules. SVG Spec. + /// + /// Text to be prepared + /// Prepared text + protected string PrepareText(string value, bool leadingSpace, bool trailingSpace) + { + if (_space == XmlSpaceHandling.preserve) + { + return (leadingSpace ? " " : "") + value.Replace('\t', ' ').Replace("\r\n", " ").Replace('\r', ' ').Replace('\n', ' ') + (trailingSpace ? " " : ""); + } + else + { + return (leadingSpace ? " " : "") + value.Replace("\r", "").Replace("\n", "").Replace('\t', ' ').Trim().Replace(" ", " ") + (trailingSpace ? " " : ""); + } + } + /// + /// Get the font information based on data stored with the text object or inherited from the parent. + /// + /// + internal Font GetFont() + { + var parent = this.Parent as SvgTextBase; + Font parentFont = null; + if (parent != null) parentFont = parent.GetFont(); + + float fontSize = this.FontSize.ToDeviceValue(this); + if (fontSize == 0.0f) + { + fontSize = (parentFont == null ? 1.0f : parentFont.Size); + fontSize = (fontSize == 0.0f ? 1.0f : fontSize); + } + var fontWeight = ((_fontWeight == SvgFontWeight.inherit && parentFont != null && parentFont.Bold) || _fontWeight == SvgFontWeight.bold ? + FontStyle.Bold : FontStyle.Regular); + var family = _fontFamily ?? (parentFont == null ? DefaultFontFamily : parentFont.FontFamily.Name); + return new Font(family, fontSize, fontWeight, GraphicsUnit.Pixel); + } + + /// + /// Draws a string on a path at a specified location and with a specified font. + /// + internal void DrawString(GraphicsPath path, float x, float y, Font font, string text) + { + PointF location = new PointF(x, y); + + // No way to do letter-spacing or word-spacing, so do manually + if (this.LetterSpacing.Value > 0.0f || this.WordSpacing.Value > 0.0f) + { + // Cut up into words, or just leave as required + string[] words = (this.WordSpacing.Value > 0.0f) ? text.Split(' ') : new string[] { text }; + float wordSpacing = this.WordSpacing.ToDeviceValue(this); + float letterSpacing = this.LetterSpacing.ToDeviceValue(this); + float start = this.X.ToDeviceValue(this); + + foreach (string word in words) + { + // Only do if there is line spacing, just write the word otherwise + if (this.LetterSpacing.Value > 0.0f) + { + char[] characters = word.ToCharArray(); + foreach (char currentCharacter in characters) + { + path.AddString(currentCharacter.ToString(), font.FontFamily, (int)font.Style, font.Size, location, StringFormat.GenericTypographic); + location = new PointF(path.GetBounds().Width + start + letterSpacing, location.Y); + } + } + else + { + path.AddString(word, font.FontFamily, (int)font.Style, font.Size, location, StringFormat.GenericTypographic); + } + + // Move the location of the word to be written along + location = new PointF(path.GetBounds().Width + start + wordSpacing, location.Y); + } + } + else + { + if (!string.IsNullOrEmpty(text)) + { + path.AddString(text, font.FontFamily, (int)font.Style, font.Size, location, StringFormat.GenericTypographic); + } + } + } + + [SvgAttribute("onchange")] + public event EventHandler Change; + + //change + protected void OnChange(string newString, string sessionID) + { + RaiseChange(this, new StringArg {s = newString, SessionID = sessionID}); + } + + protected void RaiseChange(object sender, StringArg s) + { + var handler = Change; + if (handler != null) + { + handler(sender, s); + } + } + +#if Net4 + public override void RegisterEvents(ISvgEventCaller caller) + { + //register basic events + base.RegisterEvents(caller); + + //add change event for text + caller.RegisterAction(this.ID + "/onchange", OnChange); + } + + public override void UnregisterEvents(ISvgEventCaller caller) + { + //unregister base events + base.UnregisterEvents(caller); + + //unregister change event + caller.UnregisterAction(this.ID + "/onchange"); + + } +#endif + } +} diff --git a/Source/Text/SvgTextSpan.cs b/Source/Text/SvgTextSpan.cs index 629e12fc3cac0ecbfd011be9d27d2d7c2db227f1..3f49f780c7ea908522e062c355f1a455d577951b 100644 --- a/Source/Text/SvgTextSpan.cs +++ b/Source/Text/SvgTextSpan.cs @@ -8,69 +8,8 @@ using System.Text; namespace Svg { [SvgElement("tspan")] - public class SvgTextSpan : SvgElement + public class SvgTextSpan : SvgTextBase { - private SvgUnit _x; - private SvgUnit _y; - private SvgUnit _dx; - private SvgUnit _dy; - - /// - /// Gets or sets the X. - /// - /// The X. - [SvgAttribute("x")] - public SvgUnit X - { - get { return this._x; } - set { this._x = value; } - } - - /// - /// Gets or sets the X. - /// - /// The X. - [SvgAttribute("y")] - public SvgUnit Y - { - get { return this._y; } - set { this._y = value; } - } - - - /// - /// Gets or sets the deltaX from the containing text. - /// - /// The dX. - [SvgAttribute("dx")] - public SvgUnit DX - { - get { return this._dx; } - set { this._dx = value; } - } - - /// - /// Gets or sets the deltaY from the containing text. - /// - /// The dY. - [SvgAttribute("dy")] - public SvgUnit DY - { - get { return this._dy; } - set { this._dy = value; } - } - - - /// - /// Gets or sets the text to be rendered. - /// - public virtual string Text - { - get { return base.Content; } - set { base.Content = value; this.Content = value; } - } - - public override SvgElement DeepCopy() { @@ -82,8 +21,8 @@ namespace Svg var newObj = base.DeepCopy() as SvgTextSpan; newObj.X = this.X; newObj.Y = this.Y; - newObj.DX = this.DX; - newObj.DY = this.DY; + newObj.Dx = this.Dx; + newObj.Dy = this.Dy; newObj.Text = this.Text; return newObj;