Commit ceb78a3f authored by Eric Domke's avatar Eric Domke
Browse files

Iteration 1 of tspan functionality

parent f10b0336
...@@ -9,7 +9,7 @@ namespace Svg.Pathing ...@@ -9,7 +9,7 @@ namespace Svg.Pathing
public override void AddToPath(System.Drawing.Drawing2D.GraphicsPath graphicsPath) 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. // 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; int i = graphicsPath.PathTypes.Length - 1;
while (i >= 0 && graphicsPath.PathTypes[i] > 0) i--; while (i >= 0 && graphicsPath.PathTypes[i] > 0) i--;
......
...@@ -136,6 +136,7 @@ ...@@ -136,6 +136,7 @@
<Compile Include="Filter Effects\feGaussianBlur\SvgGaussianBlur.cs" /> <Compile Include="Filter Effects\feGaussianBlur\SvgGaussianBlur.cs" />
<Compile Include="Filter Effects\feMerge\SvgMerge.cs" /> <Compile Include="Filter Effects\feMerge\SvgMerge.cs" />
<Compile Include="Painting\EnumConverters.cs" /> <Compile Include="Painting\EnumConverters.cs" />
<Compile Include="SvgContentNode.cs" />
<Compile Include="SvgDefinitionDefaults.cs" /> <Compile Include="SvgDefinitionDefaults.cs" />
<Compile Include="NonSvgElement.cs" /> <Compile Include="NonSvgElement.cs" />
<Compile Include="SvgUnknownElement.cs" /> <Compile Include="SvgUnknownElement.cs" />
...@@ -183,6 +184,7 @@ ...@@ -183,6 +184,7 @@
<Compile Include="DataTypes\SvgUnitConverter.cs" /> <Compile Include="DataTypes\SvgUnitConverter.cs" />
<Compile Include="SvgTextReader.cs" /> <Compile Include="SvgTextReader.cs" />
<Compile Include="Text\SvgText.cs" /> <Compile Include="Text\SvgText.cs" />
<Compile Include="Text\SvgTextBase.cs" />
<Compile Include="Text\SvgTextAnchor.cs" /> <Compile Include="Text\SvgTextAnchor.cs" />
<Compile Include="Text\SvgTextSpan.cs" /> <Compile Include="Text\SvgTextSpan.cs" />
<Compile Include="Transforms\ISvgTransformable.cs" /> <Compile Include="Transforms\ISvgTransformable.cs" />
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Svg
{
public class SvgContentNode : ISvgNode
{
public string Content { get; set; }
}
}
...@@ -8,6 +8,7 @@ using System.Drawing.Text; ...@@ -8,6 +8,7 @@ using System.Drawing.Text;
using System.IO; using System.IO;
using System.Text; using System.Text;
using System.Xml; using System.Xml;
using System.Linq;
namespace Svg namespace Svg
{ {
...@@ -184,7 +185,6 @@ namespace Svg ...@@ -184,7 +185,6 @@ namespace Svg
using (var reader = new SvgTextReader(stream, entities)) using (var reader = new SvgTextReader(stream, entities))
{ {
var elementStack = new Stack<SvgElement>(); var elementStack = new Stack<SvgElement>();
var value = new StringBuilder();
bool elementEmpty; bool elementEmpty;
SvgElement element = null; SvgElement element = null;
SvgElement parent; SvgElement parent;
...@@ -218,7 +218,10 @@ namespace Svg ...@@ -218,7 +218,10 @@ namespace Svg
{ {
parent = elementStack.Peek(); parent = elementStack.Peek();
if (parent != null && element != null) if (parent != null && element != null)
{
parent.Children.Add(element); parent.Children.Add(element);
parent.Nodes.Add(element);
}
} }
// Push element into stack // Push element into stack
...@@ -236,21 +239,24 @@ namespace Svg ...@@ -236,21 +239,24 @@ namespace Svg
// Pop the element out of the stack // Pop the element out of the stack
element = elementStack.Pop(); element = elementStack.Pop();
if (value.Length > 0 && element != null) if (element.Nodes.OfType<SvgContentNode>().Any())
{ {
element.Content = value.ToString(); element.Content = (from e in element.Nodes select e.Content).Aggregate((p, c) => p + c);
}
// Reset content value for new element else
value.Length = 0; {
element.Nodes.Clear(); // No sense wasting the space where it isn't needed
} }
break; break;
case XmlNodeType.CDATA: case XmlNodeType.CDATA:
case XmlNodeType.Text: case XmlNodeType.Text:
value.Append(reader.Value); element = elementStack.Peek();
element.Nodes.Add(new SvgContentNode() { Content = reader.Value });
break; break;
case XmlNodeType.EntityReference: case XmlNodeType.EntityReference:
reader.ResolveEntity(); reader.ResolveEntity();
value.Append(reader.Value); element = elementStack.Peek();
element.Nodes.Add(new SvgContentNode() { Content = reader.Value });
break; break;
} }
} }
......
...@@ -15,7 +15,7 @@ namespace Svg ...@@ -15,7 +15,7 @@ namespace Svg
/// <summary> /// <summary>
/// The base class of which all SVG elements are derived from. /// The base class of which all SVG elements are derived from.
/// </summary> /// </summary>
public abstract class SvgElement : ISvgElement, ISvgTransformable, ICloneable public abstract class SvgElement : ISvgElement, ISvgTransformable, ICloneable, ISvgNode
{ {
//optimization //optimization
protected class PropertyAttributeTuple protected class PropertyAttributeTuple
...@@ -42,6 +42,7 @@ namespace Svg ...@@ -42,6 +42,7 @@ namespace Svg
private static readonly object _loadEventKey = new object(); private static readonly object _loadEventKey = new object();
private Matrix _graphicsMatrix; private Matrix _graphicsMatrix;
private SvgCustomAttributeCollection _customAttributes; private SvgCustomAttributeCollection _customAttributes;
private List<ISvgNode> _nodes = new List<ISvgNode>();
/// <summary> /// <summary>
/// Gets the name of the element. /// Gets the name of the element.
...@@ -117,6 +118,11 @@ namespace Svg ...@@ -117,6 +118,11 @@ namespace Svg
get { return this._children; } get { return this._children; }
} }
public IList<ISvgNode> Nodes
{
get { return this._nodes; }
}
public IEnumerable<SvgElement> Descendants() public IEnumerable<SvgElement> Descendants()
{ {
return this.AsEnumerable().Descendants(); return this.AsEnumerable().Descendants();
...@@ -611,8 +617,8 @@ namespace Svg ...@@ -611,8 +617,8 @@ namespace Svg
childPath = (GraphicsPath)childPath.Clone(); childPath = (GraphicsPath)childPath.Clone();
if(child.Transforms != null) if(child.Transforms != null)
childPath.Transform(child.Transforms.GetMatrix()); childPath.Transform(child.Transforms.GetMatrix());
path.AddPath(childPath, false); if (childPath.PointCount > 0) path.AddPath(childPath, false);
} }
} }
} }
...@@ -921,7 +927,6 @@ namespace Svg ...@@ -921,7 +927,6 @@ namespace Svg
} }
#endregion graphical EVENTS #endregion graphical EVENTS
} }
public class SVGArg : EventArgs public class SVGArg : EventArgs
...@@ -1035,10 +1040,16 @@ namespace Svg ...@@ -1035,10 +1040,16 @@ namespace Svg
public bool CtrlKey; public bool CtrlKey;
} }
public interface ISvgNode
{
string Content { get; }
}
internal interface ISvgElement internal interface ISvgElement
{ {
SvgElement Parent {get;} SvgElement Parent {get;}
SvgElementCollection Children { get; } SvgElementCollection Children { get; }
IList<ISvgNode> Nodes { get; }
void Render(SvgRenderer renderer); void Render(SvgRenderer renderer);
} }
......
...@@ -157,6 +157,7 @@ namespace Svg ...@@ -157,6 +157,7 @@ namespace Svg
StringFormat format = StringFormat.GenericTypographic; StringFormat format = StringFormat.GenericTypographic;
format.SetMeasurableCharacterRanges(new CharacterRange[]{new CharacterRange(0, text.Length)}); 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); Region[] r = this._innerGraphics.MeasureCharacterRanges(text, font, new Rectangle(0, 0, 1000, 1000), format);
RectangleF rect = r[0].GetBounds(this._innerGraphics); RectangleF rect = r[0].GetBounds(this._innerGraphics);
......
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Text; using System.Text;
using System.Text.RegularExpressions;
using System.ComponentModel;
using System.Drawing; using System.Drawing;
using System.Drawing.Drawing2D; using System.Drawing.Drawing2D;
using System.Drawing.Text;
using Svg.DataTypes; using Svg.DataTypes;
using System.Linq;
namespace Svg namespace Svg
{ {
...@@ -15,43 +12,12 @@ namespace Svg ...@@ -15,43 +12,12 @@ namespace Svg
/// The <see cref="SvgText"/> element defines a graphics element consisting of text. /// The <see cref="SvgText"/> element defines a graphics element consisting of text.
/// </summary> /// </summary>
[SvgElement("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";
/// <summary> /// <summary>
/// Initializes the <see cref="SvgText"/> class. /// Initializes the <see cref="SvgText"/> class.
/// </summary> /// </summary>
static SvgText() public SvgText() : base() {}
{
Bitmap bitmap = new Bitmap(1, 1);
_stringMeasure = SvgRenderer.FromImage(bitmap);
_stringMeasure.TextRenderingHint = TextRenderingHint.AntiAlias;
}
/// <summary>
/// Initializes a new instance of the <see cref="SvgText"/> class.
/// </summary>
public SvgText()
{
this._fontFamily = DefaultFontFamily;
this._fontSize = new SvgUnit(0.0f);
this._dy = new SvgUnit(0.0f);
this._dx = new SvgUnit(0.0f);
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="SvgText"/> class. /// Initializes a new instance of the <see cref="SvgText"/> class.
...@@ -61,446 +27,25 @@ namespace Svg ...@@ -61,446 +27,25 @@ namespace Svg
{ {
this.Text = text; this.Text = text;
} }
/// <summary> public override SvgElement DeepCopy()
/// Gets or sets the text to be rendered.
/// </summary>
public virtual string Text
{
get { return base.Content; }
set { base.Content = value; this.IsPathDirty = true; this.Content = value; }
}
/// <summary>
/// Gets or sets the text anchor.
/// </summary>
/// <value>The text anchor.</value>
[SvgAttribute("text-anchor")]
public virtual SvgTextAnchor TextAnchor
{
get { return this._textAnchor; }
set { this._textAnchor = value; this.IsPathDirty = true; }
}
/// <summary>
/// Gets or sets the X.
/// </summary>
/// <value>The X.</value>
[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 });
}
}
}
/// <summary>
/// Gets or sets the dX.
/// </summary>
/// <value>The dX.</value>
[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 });
}
}
}
/// <summary>
/// Gets or sets the Y.
/// </summary>
/// <value>The Y.</value>
[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 });
}
}
}
/// <summary>
/// Gets or sets the dY.
/// </summary>
/// <value>The dY.</value>
[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 });
}
}
}
/// <summary>
/// Specifies spacing behavior between text characters.
/// </summary>
[SvgAttribute("letter-spacing")]
public virtual SvgUnit LetterSpacing
{
get { return this._letterSpacing; }
set { this._letterSpacing = value; this.IsPathDirty = true; }
}
/// <summary>
/// Specifies spacing behavior between words.
/// </summary>
[SvgAttribute("word-spacing")]
public virtual SvgUnit WordSpacing
{
get { return this._wordSpacing; }
set { this._wordSpacing = value; this.IsPathDirty = true; }
}
/// <summary>
/// Indicates which font family is to be used to render the text.
/// </summary>
[SvgAttribute("font-family")]
public virtual string FontFamily
{
get { return this._fontFamily; }
set
{
this._fontFamily = ValidateFontFamily(value);
this.IsPathDirty = true;
}
}
/// <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._fontSize; }
set { this._fontSize = value; this.IsPathDirty = true; }
}
/// <summary>
/// Refers to the boldness of the font.
/// </summary>
[SvgAttribute("font-weight")]
public virtual SvgFontWeight FontWeight
{
get { return this._fontWeight; }
set { this._fontWeight = value; this.IsPathDirty = true; }
}
/// <summary>
/// Set all font information.
/// </summary>
[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 <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>
/// Gets or sets the fill.
/// </summary>
/// <remarks>
/// <para>Unlike other <see cref="SvgGraphicsElement"/>s, <see cref="SvgText"/> has a default fill of black rather than transparent.</para>
/// </remarks>
/// <value>The fill.</value>
public override SvgPaintServer Fill
{
get { return (this.Attributes["fill"] == null) ? new SvgColourServer(Color.Black) : (SvgPaintServer)this.Attributes["fill"]; }
set { this.Attributes["fill"] = value; }
}
/// <summary>
/// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
/// </summary>
/// <returns>
/// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
/// </returns>
public override string ToString()
{
return this.Text;
}
/// <summary>
/// Gets or sets a value to determine if anti-aliasing should occur when the element is being rendered.
/// </summary>
/// <value></value>
protected override bool RequiresSmoothRendering
{
get { return true; }
}
/// <summary>
/// Gets the bounds of the element.
/// </summary>
/// <value>The bounds.</value>
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();
}
/// <summary>
/// Gets the <see cref="GraphicsPath"/> for this element.
/// </summary>
/// <value></value>
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)
{ {
// Split font family list on "," and then trim start and end spaces and quotes. return DeepCopy<SvgText>();
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;
} }
private void DrawString(GraphicsPath path, SvgUnit x, SvgUnit y, SvgUnit dx, SvgUnit dy, Font font, float fontSize, string text) public override SvgElement DeepCopy<T>()
{
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<StringArg> 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; var newObj = base.DeepCopy<T>() as SvgText;
if (handler != null) newObj.TextAnchor = this.TextAnchor;
{ newObj.WordSpacing = this.WordSpacing;
handler(sender, s); 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<string, string>(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<SvgText>();
}
public override SvgElement DeepCopy<T>()
{
var newObj = base.DeepCopy<T>() 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;
}
} }
} }
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;
/// <summary>
/// Initializes the <see cref="SvgTextBase"/> class.
/// </summary>
static SvgTextBase()
{
Bitmap bitmap = new Bitmap(1, 1);
_stringMeasure = SvgRenderer.FromImage(bitmap);
_stringMeasure.TextRenderingHint = TextRenderingHint.AntiAlias;
}
/// <summary>
/// Initializes a new instance of the <see cref="SvgTextBase"/> class.
/// </summary>
public SvgTextBase()
{
this._fontSize = new SvgUnit(0.0f);
this._dy = new SvgUnit(0.0f);
this._dx = new SvgUnit(0.0f);
}
/// <summary>
/// Gets or sets the text to be rendered.
/// </summary>
public virtual string Text
{
get { return base.Content; }
set { base.Content = value; this.IsPathDirty = true; this.Content = value; }
}
/// <summary>
/// Gets or sets the text anchor.
/// </summary>
/// <value>The text anchor.</value>
[SvgAttribute("text-anchor")]
public virtual SvgTextAnchor TextAnchor
{
get { return this._textAnchor; }
set { this._textAnchor = value; this.IsPathDirty = true; }
}
/// <summary>
/// Gets or sets the X.
/// </summary>
/// <value>The X.</value>
[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 });
}
}
}
/// <summary>
/// Gets or sets the dX.
/// </summary>
/// <value>The dX.</value>
[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 });
}
}
}
/// <summary>
/// Gets or sets the Y.
/// </summary>
/// <value>The Y.</value>
[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 });
}
}
}
/// <summary>
/// Gets or sets the dY.
/// </summary>
/// <value>The dY.</value>
[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 });
}
}
}
/// <summary>
/// Specifies spacing behavior between text characters.
/// </summary>
[SvgAttribute("letter-spacing")]
public virtual SvgUnit LetterSpacing
{
get { return this._letterSpacing; }
set { this._letterSpacing = value; this.IsPathDirty = true; }
}
/// <summary>
/// Specifies spacing behavior between words.
/// </summary>
[SvgAttribute("word-spacing")]
public virtual SvgUnit WordSpacing
{
get { return this._wordSpacing; }
set { this._wordSpacing = value; this.IsPathDirty = true; }
}
/// <summary>
/// Indicates which font family is to be used to render the text.
/// </summary>
[SvgAttribute("font-family")]
public virtual string FontFamily
{
get { return this._fontFamily ?? DefaultFontFamily; }
set
{
this._fontFamily = ValidateFontFamily(value);
this.IsPathDirty = true;
}
}
/// <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._fontSize; }
set { this._fontSize = value; this.IsPathDirty = true; }
}
/// <summary>
/// Refers to the boldness of the font.
/// </summary>
[SvgAttribute("font-weight")]
public virtual SvgFontWeight FontWeight
{
get { return this._fontWeight; }
set { this._fontWeight = value; this.IsPathDirty = true; }
}
/// <summary>
/// Set all font information.
/// </summary>
[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 <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>
/// Gets or sets the fill.
/// </summary>
/// <remarks>
/// <para>Unlike other <see cref="SvgGraphicsElement"/>s, <see cref="SvgText"/> has a default fill of black rather than transparent.</para>
/// </remarks>
/// <value>The fill.</value>
public override SvgPaintServer Fill
{
get { return (this.Attributes["fill"] == null) ? new SvgColourServer(Color.Black) : (SvgPaintServer)this.Attributes["fill"]; }
set { this.Attributes["fill"] = value; }
}
/// <summary>
/// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
/// </summary>
/// <returns>
/// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
/// </returns>
public override string ToString()
{
return this.Text;
}
/// <summary>
/// Gets or sets a value to determine if anti-aliasing should occur when the element is being rendered.
/// </summary>
/// <value></value>
protected override bool RequiresSmoothRendering
{
get { return true; }
}
/// <summary>
/// Gets the bounds of the element.
/// </summary>
/// <value>The bounds.</value>
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;
}
/// <summary>
/// Renders the <see cref="SvgElement"/> and contents to the specified <see cref="Graphics"/> object.
/// </summary>
/// <param name="renderer">The <see cref="SvgRenderer"/> object to render to.</param>
/// <remarks>Necessary to make sure that any internal tspan elements get rendered as well</remarks>
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<NodeBounds> _nodes = new List<NodeBounds>();
public IList<NodeBounds> 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;
/// <summary>
/// Gets the <see cref="GraphicsPath"/> for this element.
/// </summary>
/// <value></value>
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;
}
}
/// <summary>
/// Prepare the text according to the whitespace handling rules. <see href="http://www.w3.org/TR/SVG/text.html">SVG Spec</see>.
/// </summary>
/// <param name="value">Text to be prepared</param>
/// <returns>Prepared text</returns>
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 ? " " : "");
}
}
/// <summary>
/// Get the font information based on data stored with the text object or inherited from the parent.
/// </summary>
/// <returns></returns>
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);
}
/// <summary>
/// Draws a string on a path at a specified location and with a specified font.
/// </summary>
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<StringArg> 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<string, string>(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
}
}
...@@ -8,69 +8,8 @@ using System.Text; ...@@ -8,69 +8,8 @@ using System.Text;
namespace Svg namespace Svg
{ {
[SvgElement("tspan")] [SvgElement("tspan")]
public class SvgTextSpan : SvgElement public class SvgTextSpan : SvgTextBase
{ {
private SvgUnit _x;
private SvgUnit _y;
private SvgUnit _dx;
private SvgUnit _dy;
/// <summary>
/// Gets or sets the X.
/// </summary>
/// <value>The X.</value>
[SvgAttribute("x")]
public SvgUnit X
{
get { return this._x; }
set { this._x = value; }
}
/// <summary>
/// Gets or sets the X.
/// </summary>
/// <value>The X.</value>
[SvgAttribute("y")]
public SvgUnit Y
{
get { return this._y; }
set { this._y = value; }
}
/// <summary>
/// Gets or sets the deltaX from the containing text.
/// </summary>
/// <value>The dX.</value>
[SvgAttribute("dx")]
public SvgUnit DX
{
get { return this._dx; }
set { this._dx = value; }
}
/// <summary>
/// Gets or sets the deltaY from the containing text.
/// </summary>
/// <value>The dY.</value>
[SvgAttribute("dy")]
public SvgUnit DY
{
get { return this._dy; }
set { this._dy = value; }
}
/// <summary>
/// Gets or sets the text to be rendered.
/// </summary>
public virtual string Text
{
get { return base.Content; }
set { base.Content = value; this.Content = value; }
}
public override SvgElement DeepCopy() public override SvgElement DeepCopy()
{ {
...@@ -82,8 +21,8 @@ namespace Svg ...@@ -82,8 +21,8 @@ namespace Svg
var newObj = base.DeepCopy<T>() as SvgTextSpan; var newObj = base.DeepCopy<T>() as SvgTextSpan;
newObj.X = this.X; newObj.X = this.X;
newObj.Y = this.Y; newObj.Y = this.Y;
newObj.DX = this.DX; newObj.Dx = this.Dx;
newObj.DY = this.DY; newObj.Dy = this.Dy;
newObj.Text = this.Text; newObj.Text = this.Text;
return newObj; return newObj;
......
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