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;