using System;
using System.Collections.Generic;
using System.Text;
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 _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);
}
///
/// 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
{
this._x = value;
this.IsPathDirty = true;
OnAttributeChanged(new AttributeEventArgs{ Attribute = "x", Value = value });
}
}
///
/// Gets or sets the Y.
///
/// The Y.
[SvgAttribute("y")]
public virtual SvgUnit Y
{
get { return this._y; }
set
{
this._y = value;
this.IsPathDirty = true;
OnAttributeChanged(new AttributeEventArgs{ Attribute = "y", 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 { this._font = value; 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.IsNullOrEmpty(this.Text))
// _path = null;
//NOT SURE WHAT THIS IS ABOUT - Path gets created again anyway - WTF?
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, SvgUnit.Empty, SvgUnit.Empty, 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;
}
}
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.
foreach (var f in fontParts.Where(f => families.Any(family => family.Name == f)))
{
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);
}
}
}
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;
}
}
}