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 { /// /// The element defines a graphics element consisting of text. /// [SvgElement("text")] public class SvgText : 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; 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); } /// /// Initializes a new instance of the class. /// /// The text. public SvgText(string text) : this() { 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 (string.IsNullOrWhiteSpace(this.Text)) 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. 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) { 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) { var handler = Change; if (handler != null) { handler(sender, s); } } 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"); } 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; } } }