Commit 32e70005 authored by peracto's avatar peracto Committed by mrbean-bremen
Browse files

Use element applies referenced viewBox scaling, added FontManager (#414)

* use element applies referenced viewBox scaling
* created FontManager
parent f7c5fb06
...@@ -16,3 +16,4 @@ Tests/**/*.suo ...@@ -16,3 +16,4 @@ Tests/**/*.suo
*.trx *.trx
Source/TestResults/ Source/TestResults/
Source/.vs Source/.vs
/Source/Svg.sln.DotSettings.user
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D; using System.Drawing.Drawing2D;
namespace Svg namespace Svg
...@@ -79,6 +80,21 @@ namespace Svg ...@@ -79,6 +80,21 @@ namespace Svg
set { this.Attributes["y"] = value; } set { this.Attributes["y"] = value; }
} }
[SvgAttribute("width")]
public virtual SvgUnit Width
{
get { return this.Attributes.GetAttribute<SvgUnit>("width"); }
set { this.Attributes["width"] = value; }
}
[SvgAttribute("height")]
public virtual SvgUnit Height
{
get { return this.Attributes.GetAttribute<SvgUnit>("height"); }
set { this.Attributes["height"] = value; }
}
/// <summary> /// <summary>
/// Applies the required transforms to <see cref="ISvgRenderer"/>. /// Applies the required transforms to <see cref="ISvgRenderer"/>.
/// </summary> /// </summary>
...@@ -99,6 +115,8 @@ namespace Svg ...@@ -99,6 +115,8 @@ namespace Svg
{ {
this.X = 0; this.X = 0;
this.Y = 0; this.Y = 0;
this.Width = 0;
this.Height = 0;
} }
public override System.Drawing.Drawing2D.GraphicsPath Path(ISvgRenderer renderer) public override System.Drawing.Drawing2D.GraphicsPath Path(ISvgRenderer renderer)
...@@ -107,15 +125,33 @@ namespace Svg ...@@ -107,15 +125,33 @@ namespace Svg
return (element != null && !this.HasRecursiveReference()) ? element.Path(renderer) : null; return (element != null && !this.HasRecursiveReference()) ? element.Path(renderer) : null;
} }
/// <summary>
/// Gets an <see cref="SvgPoint"/> representing the top left point of the rectangle.
/// </summary>
public SvgPoint Location
{
get { return new SvgPoint(X, Y); }
}
/// <summary>
/// Gets the bounds of the element.
/// </summary>
/// <value>The bounds.</value>
public override System.Drawing.RectangleF Bounds public override System.Drawing.RectangleF Bounds
{ {
get get
{ {
var ew = this.Width.ToDeviceValue(null, UnitRenderingType.Horizontal, this);
var eh = this.Height.ToDeviceValue(null, UnitRenderingType.Vertical, this);
if (ew > 0 && eh > 0)
return TransformedBounds(new RectangleF(this.Location.ToDeviceValue(null, this),
new SizeF(ew, eh)));
var element = this.OwnerDocument.IdManager.GetElementById(this.ReferencedElement) as SvgVisualElement; var element = this.OwnerDocument.IdManager.GetElementById(this.ReferencedElement) as SvgVisualElement;
if (element != null) if (element != null)
{ {
return element.Bounds; return element.Bounds;
} }
return new System.Drawing.RectangleF(); return new System.Drawing.RectangleF();
} }
} }
...@@ -131,6 +167,19 @@ namespace Svg ...@@ -131,6 +167,19 @@ namespace Svg
var element = this.OwnerDocument.IdManager.GetElementById(this.ReferencedElement) as SvgVisualElement; var element = this.OwnerDocument.IdManager.GetElementById(this.ReferencedElement) as SvgVisualElement;
if (element != null) if (element != null)
{ {
var ew = Width.ToDeviceValue(renderer, UnitRenderingType.Horizontal, this);
var eh = Height.ToDeviceValue(renderer, UnitRenderingType.Vertical, this);
if (ew > 0 && eh > 0)
{
var viewBox = element.Attributes.GetAttribute<SvgViewBox>("viewBox");
if (viewBox!=SvgViewBox.Empty && Math.Abs(ew - viewBox.Width) > float.Epsilon && Math.Abs(eh - viewBox.Height) > float.Epsilon)
{
var sw = ew / viewBox.Width;
var sh = eh / viewBox.Height;
renderer.ScaleTransform(sw, sh, MatrixOrder.Prepend);
}
}
var origParent = element.Parent; var origParent = element.Parent;
element._parent = this; element._parent = this;
// as the new parent may have other styles that are inherited, // as the new parent may have other styles that are inherited,
......
...@@ -122,6 +122,7 @@ ...@@ -122,6 +122,7 @@
<Compile Include="Rendering\ISvgRenderer.cs" /> <Compile Include="Rendering\ISvgRenderer.cs" />
<Compile Include="Rendering\SvgRendering.cs" /> <Compile Include="Rendering\SvgRendering.cs" />
<Compile Include="SvgElementStyle.cs" /> <Compile Include="SvgElementStyle.cs" />
<Compile Include="SvgFontManager.cs" />
<Compile Include="SvgNodeReader.cs" /> <Compile Include="SvgNodeReader.cs" />
<Compile Include="Css\CssQuery.cs" /> <Compile Include="Css\CssQuery.cs" />
<Compile Include="Css\SvgElementOps.cs" /> <Compile Include="Css\SvgElementOps.cs" />
......
...@@ -434,8 +434,6 @@ namespace Svg ...@@ -434,8 +434,6 @@ namespace Svg
{ {
// Split font family list on "," and then trim start and end spaces and quotes. // Split font family list on "," and then trim start and end spaces and quotes.
var fontParts = (fontFamilyList ?? string.Empty).Split(new[] { ',' }).Select(fontName => fontName.Trim(new[] { '"', ' ', '\'' })); var fontParts = (fontFamilyList ?? string.Empty).Split(new[] { ',' }).Select(fontName => fontName.Trim(new[] { '"', ' ', '\'' }));
var families = System.Drawing.FontFamily.Families;
Func<FontFamily, bool> getFamily;
FontFamily family; FontFamily family;
IEnumerable<SvgFontFace> sFaces; IEnumerable<SvgFontFace> sFaces;
...@@ -444,13 +442,10 @@ namespace Svg ...@@ -444,13 +442,10 @@ namespace Svg
foreach (var f in fontParts) foreach (var f in fontParts)
{ {
if (doc != null && doc.FontDefns().TryGetValue(f, out sFaces)) return sFaces; if (doc != null && doc.FontDefns().TryGetValue(f, out sFaces)) return sFaces;
family = SvgFontManager.FindFont(f);
getFamily = new Func<FontFamily, bool>(ff => string.Equals(ff.Name, f, StringComparison.OrdinalIgnoreCase));
family = families.FirstOrDefault(getFamily);
if (family != null) return family; if (family != null) return family;
family = PrivateFonts.Families.FirstOrDefault(getFamily); family = PrivateFonts.Families.FirstOrDefault(ff => string.Equals(ff.Name, f, StringComparison.OrdinalIgnoreCase));
if (family != null) return family; if (family != null) return family;
switch (f.ToLower()) switch (f.ToLower())
{ {
case "serif": case "serif":
...@@ -466,4 +461,4 @@ namespace Svg ...@@ -466,4 +461,4 @@ namespace Svg
return System.Drawing.FontFamily.GenericSansSerif; return System.Drawing.FontFamily.GenericSansSerif;
} }
} }
} }
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Text;
using System.IO;
using System.Linq;
namespace Svg
{
/// <summary>
/// Manages access to <see cref="SystemFonts"/> and any privately loaded fonts.
/// When a font is requested in the render process, if the font is not found as an embedded SvgFont, the render
/// process will SvgFontManager.FindFont method.
/// </summary>
public static class SvgFontManager
{
private static readonly Dictionary<string, FontFamily> SystemFonts;
public static Func<string, FontFamily> FontLoaderCallback;
static SvgFontManager()
{
SystemFonts = FontFamily.Families.ToDictionary(ff => ff.Name.ToLower());
}
/// <summary>
/// Loads a font from the given path.
/// </summary>
/// <param name="path">A <see cref="string"/> containing the full path to the font file.</param>
/// <returns>An <see cref="FontFamily"/> of the loaded font.</returns>
public static FontFamily LoadFontFamily(string path)
{
var pfc = new PrivateFontCollection();
var fp = Path.GetFullPath(path);
pfc.AddFontFile(fp);
return pfc.Families.Length == 0 ? null : pfc.Families[0];
}
/// <summary>
/// This method searches a dictionary of fonts (pre loaded with the system fonts). If a
/// font can't be found and a callback has been provided - then the callback should perform
/// any validation and return a font (or null if not found/error).
/// Where a font can't be located it is the responsibility of the caller to perform any
/// exception handling.
/// </summary>
/// <param name="name">A <see cref="string"/> containing the FamilyName of the font.</param>
/// <returns>An <see cref="FontFamily"/> of the loaded font or null is not located.</returns>
public static FontFamily FindFont(string name)
{
if (name == null) return null;
FontFamily ff = null;
if (SystemFonts.TryGetValue(name.ToLower(), out ff)) return ff;
if (FontLoaderCallback == null) return null;
var ff2 = FontLoaderCallback(name);
SystemFonts.Add(name.ToLower(), ff2);
return ff2;
}
}
}
\ No newline at end of file
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.ComponentModel; using System.ComponentModel;
using System.Drawing; using System.Drawing;
using System.Drawing.Drawing2D; using System.Drawing.Drawing2D;
using System.Drawing.Text; using System.Drawing.Text;
using Svg.DataTypes; using Svg.DataTypes;
using System.Linq; using System.Linq;
using System.Globalization; using System.Globalization;
namespace Svg namespace Svg
{ {
public abstract class SvgTextBase : SvgVisualElement public abstract class SvgTextBase : SvgVisualElement
{ {
[CLSCompliant(false)] protected SvgUnitCollection _x = new SvgUnitCollection(); [CLSCompliant(false)] protected SvgUnitCollection _x = new SvgUnitCollection();
[CLSCompliant(false)] protected SvgUnitCollection _y = new SvgUnitCollection(); [CLSCompliant(false)] protected SvgUnitCollection _y = new SvgUnitCollection();
[CLSCompliant(false)] protected SvgUnitCollection _dy = new SvgUnitCollection(); [CLSCompliant(false)] protected SvgUnitCollection _dy = new SvgUnitCollection();
[CLSCompliant(false)] protected SvgUnitCollection _dx = new SvgUnitCollection(); [CLSCompliant(false)] protected SvgUnitCollection _dx = new SvgUnitCollection();
private string _rotate; private string _rotate;
private List<float> _rotations = new List<float>(); private List<float> _rotations = new List<float>();
/// <summary> /// <summary>
/// Gets or sets the text to be rendered. /// Gets or sets the text to be rendered.
/// </summary> /// </summary>
public virtual string Text public virtual string Text
{ {
get { return base.Content; } get { return base.Content; }
set { set {
Nodes.Clear(); Nodes.Clear();
Children.Clear(); Children.Clear();
if(value != null) if(value != null)
{ {
Nodes.Add(new SvgContentNode { Content = value }); Nodes.Add(new SvgContentNode { Content = value });
} }
this.IsPathDirty = true; this.IsPathDirty = true;
Content = value; Content = value;
} }
} }
public override XmlSpaceHandling SpaceHandling public override XmlSpaceHandling SpaceHandling
{ {
get { return base.SpaceHandling; } get { return base.SpaceHandling; }
set { base.SpaceHandling = value; this.IsPathDirty = true; } set { base.SpaceHandling = value; this.IsPathDirty = true; }
} }
/// <summary> /// <summary>
/// Gets or sets the X. /// Gets or sets the X.
/// </summary> /// </summary>
/// <value>The X.</value> /// <value>The X.</value>
[SvgAttribute("x")] [SvgAttribute("x")]
public virtual SvgUnitCollection X public virtual SvgUnitCollection X
{ {
get { return this._x; } get { return this._x; }
set set
{ {
if (_x != value) if (_x != value)
{ {
this._x = value; this._x = value;
this.IsPathDirty = true; this.IsPathDirty = true;
OnAttributeChanged(new AttributeEventArgs { Attribute = "x", Value = value }); OnAttributeChanged(new AttributeEventArgs { Attribute = "x", Value = value });
} }
} }
} }
/// <summary> /// <summary>
/// Gets or sets the dX. /// Gets or sets the dX.
/// </summary> /// </summary>
/// <value>The dX.</value> /// <value>The dX.</value>
[SvgAttribute("dx")] [SvgAttribute("dx")]
public virtual SvgUnitCollection Dx public virtual SvgUnitCollection Dx
{ {
get { return this._dx; } get { return this._dx; }
set set
{ {
if (_dx != value) if (_dx != value)
{ {
this._dx = value; this._dx = value;
this.IsPathDirty = true; this.IsPathDirty = true;
OnAttributeChanged(new AttributeEventArgs { Attribute = "dx", Value = value }); OnAttributeChanged(new AttributeEventArgs { Attribute = "dx", Value = value });
} }
} }
} }
/// <summary> /// <summary>
/// Gets or sets the Y. /// Gets or sets the Y.
/// </summary> /// </summary>
/// <value>The Y.</value> /// <value>The Y.</value>
[SvgAttribute("y")] [SvgAttribute("y")]
public virtual SvgUnitCollection Y public virtual SvgUnitCollection Y
{ {
get { return this._y; } get { return this._y; }
set set
{ {
if (_y != value) if (_y != value)
{ {
this._y = value; this._y = value;
this.IsPathDirty = true; this.IsPathDirty = true;
OnAttributeChanged(new AttributeEventArgs { Attribute = "y", Value = value }); OnAttributeChanged(new AttributeEventArgs { Attribute = "y", Value = value });
} }
} }
} }
/// <summary> /// <summary>
/// Gets or sets the dY. /// Gets or sets the dY.
/// </summary> /// </summary>
/// <value>The dY.</value> /// <value>The dY.</value>
[SvgAttribute("dy")] [SvgAttribute("dy")]
public virtual SvgUnitCollection Dy public virtual SvgUnitCollection Dy
{ {
get { return this._dy; } get { return this._dy; }
set set
{ {
if (_dy != value) if (_dy != value)
{ {
this._dy = value; this._dy = value;
this.IsPathDirty = true; this.IsPathDirty = true;
OnAttributeChanged(new AttributeEventArgs { Attribute = "dy", Value = value }); OnAttributeChanged(new AttributeEventArgs { Attribute = "dy", Value = value });
} }
} }
} }
/// <summary> /// <summary>
/// Gets or sets the rotate. /// Gets or sets the rotate.
/// </summary> /// </summary>
/// <value>The rotate.</value> /// <value>The rotate.</value>
[SvgAttribute("rotate")] [SvgAttribute("rotate")]
public virtual string Rotate public virtual string Rotate
{ {
get { return this._rotate; } get { return this._rotate; }
set set
{ {
if (_rotate != value) if (_rotate != value)
{ {
this._rotate = value; this._rotate = value;
this._rotations.Clear(); this._rotations.Clear();
this._rotations.AddRange(from r in _rotate.Split(new char[] { ',', ' ', '\r', '\n', '\t' }, StringSplitOptions.RemoveEmptyEntries) select float.Parse(r)); this._rotations.AddRange(from r in _rotate.Split(new char[] { ',', ' ', '\r', '\n', '\t' }, StringSplitOptions.RemoveEmptyEntries) select float.Parse(r));
this.IsPathDirty = true; this.IsPathDirty = true;
OnAttributeChanged(new AttributeEventArgs { Attribute = "rotate", Value = value }); OnAttributeChanged(new AttributeEventArgs { Attribute = "rotate", Value = value });
} }
} }
} }
/// <summary> /// <summary>
/// The pre-calculated length of the text /// The pre-calculated length of the text
/// </summary> /// </summary>
[SvgAttribute("textLength", true)] [SvgAttribute("textLength", true)]
public virtual SvgUnit TextLength public virtual SvgUnit TextLength
{ {
get { return (this.Attributes["textLength"] == null ? SvgUnit.None : (SvgUnit)this.Attributes["textLength"]); } get { return (this.Attributes["textLength"] == null ? SvgUnit.None : (SvgUnit)this.Attributes["textLength"]); }
set { this.Attributes["textLength"] = value; this.IsPathDirty = true; } set { this.Attributes["textLength"] = value; this.IsPathDirty = true; }
} }
/// <summary> /// <summary>
/// Gets or sets the text anchor. /// Gets or sets the text anchor.
/// </summary> /// </summary>
/// <value>The text anchor.</value> /// <value>The text anchor.</value>
[SvgAttribute("lengthAdjust", true)] [SvgAttribute("lengthAdjust", true)]
public virtual SvgTextLengthAdjust LengthAdjust public virtual SvgTextLengthAdjust LengthAdjust
{ {
get { return (this.Attributes["lengthAdjust"] == null) ? SvgTextLengthAdjust.Spacing : (SvgTextLengthAdjust)this.Attributes["lengthAdjust"]; } get { return (this.Attributes["lengthAdjust"] == null) ? SvgTextLengthAdjust.Spacing : (SvgTextLengthAdjust)this.Attributes["lengthAdjust"]; }
set { this.Attributes["lengthAdjust"] = value; this.IsPathDirty = true; } set { this.Attributes["lengthAdjust"] = value; this.IsPathDirty = true; }
} }
/// <summary> /// <summary>
/// Specifies spacing behavior between text characters. /// Specifies spacing behavior between text characters.
/// </summary> /// </summary>
[SvgAttribute("letter-spacing", true)] [SvgAttribute("letter-spacing", true)]
public virtual SvgUnit LetterSpacing public virtual SvgUnit LetterSpacing
{ {
get { return (this.Attributes["letter-spacing"] == null ? SvgUnit.None : (SvgUnit)this.Attributes["letter-spacing"]); } get { return (this.Attributes["letter-spacing"] == null ? SvgUnit.None : (SvgUnit)this.Attributes["letter-spacing"]); }
set { this.Attributes["letter-spacing"] = value; this.IsPathDirty = true; } set { this.Attributes["letter-spacing"] = value; this.IsPathDirty = true; }
} }
/// <summary> /// <summary>
/// Specifies spacing behavior between words. /// Specifies spacing behavior between words.
/// </summary> /// </summary>
[SvgAttribute("word-spacing", true)] [SvgAttribute("word-spacing", true)]
public virtual SvgUnit WordSpacing public virtual SvgUnit WordSpacing
{ {
get { return (this.Attributes["word-spacing"] == null ? SvgUnit.None : (SvgUnit)this.Attributes["word-spacing"]); } get { return (this.Attributes["word-spacing"] == null ? SvgUnit.None : (SvgUnit)this.Attributes["word-spacing"]); }
set { this.Attributes["word-spacing"] = value; this.IsPathDirty = true; } set { this.Attributes["word-spacing"] = value; this.IsPathDirty = true; }
} }
/// <summary> /// <summary>
/// Gets or sets the fill. /// Gets or sets the fill.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// <para>Unlike other <see cref="SvgVisualElement"/>s, <see cref="SvgText"/> has a default fill of black rather than transparent.</para> /// <para>Unlike other <see cref="SvgVisualElement"/>s, <see cref="SvgText"/> has a default fill of black rather than transparent.</para>
/// </remarks> /// </remarks>
/// <value>The fill.</value> /// <value>The fill.</value>
public override SvgPaintServer Fill public override SvgPaintServer Fill
{ {
get { return (this.Attributes["fill"] == null) ? new SvgColourServer(System.Drawing.Color.Black) : (SvgPaintServer)this.Attributes["fill"]; } get { return (this.Attributes["fill"] == null) ? new SvgColourServer(System.Drawing.Color.Black) : (SvgPaintServer)this.Attributes["fill"]; }
set { this.Attributes["fill"] = value; } set { this.Attributes["fill"] = value; }
} }
/// <summary> /// <summary>
/// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>. /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
/// </summary> /// </summary>
/// <returns> /// <returns>
/// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>. /// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
/// </returns> /// </returns>
public override string ToString() public override string ToString()
{ {
return this.Text; return this.Text;
} }
/// <summary> /// <summary>
/// Gets the bounds of the element. /// Gets the bounds of the element.
/// </summary> /// </summary>
/// <value>The bounds.</value> /// <value>The bounds.</value>
public override System.Drawing.RectangleF Bounds public override System.Drawing.RectangleF Bounds
{ {
get get
{ {
var path = this.Path(null); var path = this.Path(null);
foreach (var elem in this.Children.OfType<SvgVisualElement>()) foreach (var elem in this.Children.OfType<SvgVisualElement>())
{ {
path.AddPath(elem.Path(null), false); //When empty Text span, don't add path
} var span = elem as SvgTextSpan;
if (span != null && span.Text == null)
continue;
path.AddPath(elem.Path(null), false);
}
if (Transforms != null && Transforms.Count > 0) if (Transforms != null && Transforms.Count > 0)
{ {
path.Transform(Transforms.GetMatrix()); path.Transform(Transforms.GetMatrix());
} }
return path.GetBounds(); return path.GetBounds();
} }
} }
/// <summary> /// <summary>
/// Renders the <see cref="SvgElement"/> and contents to the specified <see cref="Graphics"/> object. /// Renders the <see cref="SvgElement"/> and contents to the specified <see cref="Graphics"/> object.
/// </summary> /// </summary>
/// <param name="renderer">The <see cref="ISvgRenderer"/> object to render to.</param> /// <param name="renderer">The <see cref="ISvgRenderer"/> object to render to.</param>
/// <remarks>Necessary to make sure that any internal tspan elements get rendered as well</remarks> /// <remarks>Necessary to make sure that any internal tspan elements get rendered as well</remarks>
protected override void Render(ISvgRenderer renderer) protected override void Render(ISvgRenderer renderer)
{ {
if ((this.Path(renderer) != null) && this.Visible && this.Displayable) if ((this.Path(renderer) != null) && this.Visible && this.Displayable)
{ {
this.PushTransforms(renderer); this.PushTransforms(renderer);
this.SetClip(renderer); this.SetClip(renderer);
// If this element needs smoothing enabled turn anti-aliasing on // If this element needs smoothing enabled turn anti-aliasing on
if (this.RequiresSmoothRendering) if (this.RequiresSmoothRendering)
{ {
renderer.SmoothingMode = SmoothingMode.AntiAlias; renderer.SmoothingMode = SmoothingMode.AntiAlias;
} }
this.RenderFill(renderer); this.RenderFill(renderer);
this.RenderStroke(renderer); this.RenderStroke(renderer);
this.RenderChildren(renderer); this.RenderChildren(renderer);
// Reset the smoothing mode // Reset the smoothing mode
if (this.RequiresSmoothRendering && renderer.SmoothingMode == SmoothingMode.AntiAlias) if (this.RequiresSmoothRendering && renderer.SmoothingMode == SmoothingMode.AntiAlias)
{ {
renderer.SmoothingMode = SmoothingMode.Default; renderer.SmoothingMode = SmoothingMode.Default;
} }
this.ResetClip(renderer); this.ResetClip(renderer);
this.PopTransforms(renderer); this.PopTransforms(renderer);
} }
} }
internal virtual IEnumerable<ISvgNode> GetContentNodes() internal virtual IEnumerable<ISvgNode> GetContentNodes()
{ {
return (this.Nodes == null || this.Nodes.Count < 1 ? this.Children.OfType<ISvgNode>().Where(o => !(o is ISvgDescriptiveElement)) : this.Nodes); return (this.Nodes == null || this.Nodes.Count < 1 ? this.Children.OfType<ISvgNode>().Where(o => !(o is ISvgDescriptiveElement)) : this.Nodes);
} }
protected virtual GraphicsPath GetBaselinePath(ISvgRenderer renderer) protected virtual GraphicsPath GetBaselinePath(ISvgRenderer renderer)
{ {
return null; return null;
} }
protected virtual float GetAuthorPathLength() protected virtual float GetAuthorPathLength()
{ {
return 0; return 0;
} }
private GraphicsPath _path; private GraphicsPath _path;
/// <summary> /// <summary>
/// Gets the <see cref="GraphicsPath"/> for this element. /// Gets the <see cref="GraphicsPath"/> for this element.
/// </summary> /// </summary>
/// <value></value> /// <value></value>
public override GraphicsPath Path(ISvgRenderer renderer) public override GraphicsPath Path(ISvgRenderer renderer)
{ {
//if there is a TSpan inside of this text element then path should not be null (even if this text is empty!) //if there is a TSpan inside of this text element then path should not be null (even if this text is empty!)
var nodes = GetContentNodes().Where(x => x is SvgContentNode && var nodes = GetContentNodes().Where(x => x is SvgContentNode &&
string.IsNullOrEmpty(x.Content.Trim(new[] { '\r', '\n', '\t' }))); string.IsNullOrEmpty(x.Content.Trim(new[] { '\r', '\n', '\t' })));
if (_path == null || IsPathDirty || nodes.Count() == 1) if (_path == null || IsPathDirty || nodes.Count() == 1)
{ {
renderer = (renderer ?? SvgRenderer.FromNull()); renderer = (renderer ?? SvgRenderer.FromNull());
SetPath(new TextDrawingState(renderer, this)); SetPath(new TextDrawingState(renderer, this));
} }
return _path; return _path;
} }
private void SetPath(TextDrawingState state) private void SetPath(TextDrawingState state)
{ {
SetPath(state, true); SetPath(state, true);
} }
/// <summary> /// <summary>
/// Sets the path on this element and all child elements. Uses the state /// Sets the path on this element and all child elements. Uses the state
/// object to track the state of the drawing /// object to track the state of the drawing
/// </summary> /// </summary>
/// <param name="state">State of the drawing operation</param> /// <param name="state">State of the drawing operation</param>
/// <param name="doMeasurements">If true, calculate and apply text length adjustments.</param> /// <param name="doMeasurements">If true, calculate and apply text length adjustments.</param>
private void SetPath(TextDrawingState state, bool doMeasurements) private void SetPath(TextDrawingState state, bool doMeasurements)
{ {
TextDrawingState origState = null; TextDrawingState origState = null;
bool alignOnBaseline = state.BaselinePath != null && (this.TextAnchor == SvgTextAnchor.Middle || this.TextAnchor == SvgTextAnchor.End); bool alignOnBaseline = state.BaselinePath != null && (this.TextAnchor == SvgTextAnchor.Middle || this.TextAnchor == SvgTextAnchor.End);
if (doMeasurements) if (doMeasurements)
{ {
if (this.TextLength != SvgUnit.None) if (this.TextLength != SvgUnit.None)
{ {
origState = state.Clone(); origState = state.Clone();
} }
else if (alignOnBaseline) else if (alignOnBaseline)
{ {
origState = state.Clone(); origState = state.Clone();
state.BaselinePath = null; state.BaselinePath = null;
} }
} }
foreach (var node in GetContentNodes()) foreach (var node in GetContentNodes())
{ {
SvgTextBase textNode = node as SvgTextBase; SvgTextBase textNode = node as SvgTextBase;
if (textNode == null) if (textNode == null)
{ {
if (!string.IsNullOrEmpty(node.Content)) state.DrawString(PrepareText(node.Content)); if (!string.IsNullOrEmpty(node.Content)) state.DrawString(PrepareText(node.Content));
} }
else else
{ {
TextDrawingState newState= new TextDrawingState(state, textNode); TextDrawingState newState= new TextDrawingState(state, textNode);
textNode.SetPath(newState); textNode.SetPath(newState);
state.NumChars += newState.NumChars; state.NumChars += newState.NumChars;
state.Current = newState.Current; state.Current = newState.Current;
} }
} }
var path = state.GetPath() ?? new GraphicsPath(); var path = state.GetPath() ?? new GraphicsPath();
// Apply any text length adjustments // Apply any text length adjustments
if (doMeasurements) if (doMeasurements)
{ {
if (this.TextLength != SvgUnit.None) if (this.TextLength != SvgUnit.None)
{ {
var specLength = this.TextLength.ToDeviceValue(state.Renderer, UnitRenderingType.Horizontal, this); var specLength = this.TextLength.ToDeviceValue(state.Renderer, UnitRenderingType.Horizontal, this);
var actLength = state.TextBounds.Width; var actLength = state.TextBounds.Width;
var diff = (actLength - specLength); var diff = (actLength - specLength);
if (Math.Abs(diff) > 1.5) if (Math.Abs(diff) > 1.5)
{ {
if (this.LengthAdjust == SvgTextLengthAdjust.Spacing) if (this.LengthAdjust == SvgTextLengthAdjust.Spacing)
{ {
if (this.X.Count < 2) if (this.X.Count < 2)
{ {
origState.LetterSpacingAdjust = -1 * diff / (state.NumChars - origState.NumChars - 1); origState.LetterSpacingAdjust = -1 * diff / (state.NumChars - origState.NumChars - 1);
SetPath(origState, false); SetPath(origState, false);
return; return;
} }
} }
else else
{ {
using (var matrix = new Matrix()) using (var matrix = new Matrix())
{ {
matrix.Translate(-1 * state.TextBounds.X, 0, MatrixOrder.Append); matrix.Translate(-1 * state.TextBounds.X, 0, MatrixOrder.Append);
matrix.Scale(specLength / actLength, 1, MatrixOrder.Append); matrix.Scale(specLength / actLength, 1, MatrixOrder.Append);
matrix.Translate(state.TextBounds.X, 0, MatrixOrder.Append); matrix.Translate(state.TextBounds.X, 0, MatrixOrder.Append);
path.Transform(matrix); path.Transform(matrix);
} }
} }
} }
} }
else if (alignOnBaseline) else if (alignOnBaseline)
{ {
var bounds = path.GetBounds(); var bounds = path.GetBounds();
if (this.TextAnchor == SvgTextAnchor.Middle) if (this.TextAnchor == SvgTextAnchor.Middle)
{ {
origState.StartOffsetAdjust = -1 * bounds.Width / 2; origState.StartOffsetAdjust = -1 * bounds.Width / 2;
} }
else else
{ {
origState.StartOffsetAdjust = -1 * bounds.Width; origState.StartOffsetAdjust = -1 * bounds.Width;
} }
SetPath(origState, false); SetPath(origState, false);
return; return;
} }
} }
_path = path; _path = path;
this.IsPathDirty = false; this.IsPathDirty = false;
} }
private static readonly Regex MultipleSpaces = new Regex(@" {2,}", RegexOptions.Compiled); private static readonly Regex MultipleSpaces = new Regex(@" {2,}", RegexOptions.Compiled);
/// <summary> /// <summary>
/// Prepare the text according to the whitespace handling rules and text transformations. <see href="http://www.w3.org/TR/SVG/text.html">SVG Spec</see>. /// Prepare the text according to the whitespace handling rules and text transformations. <see href="http://www.w3.org/TR/SVG/text.html">SVG Spec</see>.
/// </summary> /// </summary>
/// <param name="value">Text to be prepared</param> /// <param name="value">Text to be prepared</param>
/// <returns>Prepared text</returns> /// <returns>Prepared text</returns>
protected string PrepareText(string value) protected string PrepareText(string value)
{ {
value = ApplyTransformation(value); value = ApplyTransformation(value);
if (this.SpaceHandling == XmlSpaceHandling.preserve) if (this.SpaceHandling == XmlSpaceHandling.preserve)
{ {
return value.Replace('\t', ' ').Replace("\r\n", " ").Replace('\r', ' ').Replace('\n', ' '); return value.Replace('\t', ' ').Replace("\r\n", " ").Replace('\r', ' ').Replace('\n', ' ');
} }
else else
{ {
var convValue = MultipleSpaces.Replace(value.Replace("\r", "").Replace("\n", "").Replace('\t', ' '), " "); var convValue = MultipleSpaces.Replace(value.Replace("\r", "").Replace("\n", "").Replace('\t', ' '), " ");
return convValue; return convValue;
} }
} }
private string ApplyTransformation(string value) private string ApplyTransformation(string value)
{ {
switch (this.TextTransformation) switch (this.TextTransformation)
...@@ -431,513 +435,513 @@ namespace Svg ...@@ -431,513 +435,513 @@ namespace Svg
} }
return value; return value;
} }
[SvgAttribute("onchange")] [SvgAttribute("onchange")]
public event EventHandler<StringArg> Change; public event EventHandler<StringArg> Change;
//change //change
protected void OnChange(string newString, string sessionID) protected void OnChange(string newString, string sessionID)
{ {
RaiseChange(this, new StringArg { s = newString, SessionID = sessionID }); RaiseChange(this, new StringArg { s = newString, SessionID = sessionID });
} }
protected void RaiseChange(object sender, StringArg s) protected void RaiseChange(object sender, StringArg s)
{ {
var handler = Change; var handler = Change;
if (handler != null) if (handler != null)
{ {
handler(sender, s); handler(sender, s);
} }
} }
//private static GraphicsPath GetPath(string text, Font font) //private static GraphicsPath GetPath(string text, Font font)
//{ //{
// var fontMetrics = (from c in text.Distinct() // var fontMetrics = (from c in text.Distinct()
// select new { Char = c, Metrics = Metrics(c, font) }). // select new { Char = c, Metrics = Metrics(c, font) }).
// ToDictionary(c => c.Char, c=> c.Metrics); // ToDictionary(c => c.Char, c=> c.Metrics);
// // Measure each character and check the metrics against the overall metrics of rendering // // Measure each character and check the metrics against the overall metrics of rendering
// // an entire word with kerning. // // an entire word with kerning.
//} //}
//private static RectangleF Metrics(char c, Font font) //private static RectangleF Metrics(char c, Font font)
//{ //{
// var path = new GraphicsPath(); // var path = new GraphicsPath();
// path.AddString(c.ToString(), font.FontFamily, (int)font.Style, font.Size, new Point(0, 0), StringFormat.GenericTypographic); // path.AddString(c.ToString(), font.FontFamily, (int)font.Style, font.Size, new Point(0, 0), StringFormat.GenericTypographic);
// return path.GetBounds(); // return path.GetBounds();
//} //}
#if Net4 #if Net4
public override void RegisterEvents(ISvgEventCaller caller) public override void RegisterEvents(ISvgEventCaller caller)
{ {
//register basic events //register basic events
base.RegisterEvents(caller); base.RegisterEvents(caller);
//add change event for text //add change event for text
caller.RegisterAction<string, string>(this.ID + "/onchange", OnChange); caller.RegisterAction<string, string>(this.ID + "/onchange", OnChange);
} }
public override void UnregisterEvents(ISvgEventCaller caller) public override void UnregisterEvents(ISvgEventCaller caller)
{ {
//unregister base events //unregister base events
base.UnregisterEvents(caller); base.UnregisterEvents(caller);
//unregister change event //unregister change event
caller.UnregisterAction(this.ID + "/onchange"); caller.UnregisterAction(this.ID + "/onchange");
} }
#endif #endif
private class FontBoundable : ISvgBoundable private class FontBoundable : ISvgBoundable
{ {
private IFontDefn _font; private IFontDefn _font;
private float _width = 1; private float _width = 1;
public FontBoundable(IFontDefn font) public FontBoundable(IFontDefn font)
{ {
_font = font; _font = font;
} }
public FontBoundable(IFontDefn font, float width) public FontBoundable(IFontDefn font, float width)
{ {
_font = font; _font = font;
_width = width; _width = width;
} }
public PointF Location public PointF Location
{ {
get { return PointF.Empty; } get { return PointF.Empty; }
} }
public SizeF Size public SizeF Size
{ {
get { return new SizeF(_width, _font.Size); } get { return new SizeF(_width, _font.Size); }
} }
public RectangleF Bounds public RectangleF Bounds
{ {
get { return new RectangleF(this.Location, this.Size); } get { return new RectangleF(this.Location, this.Size); }
} }
} }
private class TextDrawingState private class TextDrawingState
{ {
private float _xAnchor = float.MinValue; private float _xAnchor = float.MinValue;
private IList<GraphicsPath> _anchoredPaths = new List<GraphicsPath>(); private IList<GraphicsPath> _anchoredPaths = new List<GraphicsPath>();
private GraphicsPath _currPath = null; private GraphicsPath _currPath = null;
private GraphicsPath _finalPath = null; private GraphicsPath _finalPath = null;
private float _authorPathLength = 0; private float _authorPathLength = 0;
public GraphicsPath BaselinePath { get; set; } public GraphicsPath BaselinePath { get; set; }
public PointF Current { get; set; } public PointF Current { get; set; }
public RectangleF TextBounds { get; set; } public RectangleF TextBounds { get; set; }
public SvgTextBase Element { get; set; } public SvgTextBase Element { get; set; }
public float LetterSpacingAdjust { get; set; } public float LetterSpacingAdjust { get; set; }
public int NumChars { get; set; } public int NumChars { get; set; }
public TextDrawingState Parent { get; set; } public TextDrawingState Parent { get; set; }
public ISvgRenderer Renderer { get; set; } public ISvgRenderer Renderer { get; set; }
public float StartOffsetAdjust { get; set; } public float StartOffsetAdjust { get; set; }
private TextDrawingState() { } private TextDrawingState() { }
public TextDrawingState(ISvgRenderer renderer, SvgTextBase element) public TextDrawingState(ISvgRenderer renderer, SvgTextBase element)
{ {
this.Element = element; this.Element = element;
this.Renderer = renderer; this.Renderer = renderer;
this.Current = PointF.Empty; this.Current = PointF.Empty;
this.TextBounds = RectangleF.Empty; this.TextBounds = RectangleF.Empty;
_xAnchor = 0; _xAnchor = 0;
this.BaselinePath = element.GetBaselinePath(renderer); this.BaselinePath = element.GetBaselinePath(renderer);
_authorPathLength = element.GetAuthorPathLength(); _authorPathLength = element.GetAuthorPathLength();
} }
public TextDrawingState(TextDrawingState parent, SvgTextBase element) public TextDrawingState(TextDrawingState parent, SvgTextBase element)
{ {
this.Element = element; this.Element = element;
this.Renderer = parent.Renderer; this.Renderer = parent.Renderer;
this.Parent = parent; this.Parent = parent;
this.Current = parent.Current; this.Current = parent.Current;
this.TextBounds = parent.TextBounds; this.TextBounds = parent.TextBounds;
this.BaselinePath = element.GetBaselinePath(parent.Renderer) ?? parent.BaselinePath; this.BaselinePath = element.GetBaselinePath(parent.Renderer) ?? parent.BaselinePath;
var currPathLength = element.GetAuthorPathLength(); var currPathLength = element.GetAuthorPathLength();
_authorPathLength = currPathLength == 0 ? parent._authorPathLength : currPathLength; _authorPathLength = currPathLength == 0 ? parent._authorPathLength : currPathLength;
} }
public GraphicsPath GetPath() public GraphicsPath GetPath()
{ {
FlushPath(); FlushPath();
return _finalPath; return _finalPath;
} }
public TextDrawingState Clone() public TextDrawingState Clone()
{ {
var result = new TextDrawingState(); var result = new TextDrawingState();
result._anchoredPaths = this._anchoredPaths.ToList(); result._anchoredPaths = this._anchoredPaths.ToList();
result.BaselinePath = this.BaselinePath; result.BaselinePath = this.BaselinePath;
result._xAnchor = this._xAnchor; result._xAnchor = this._xAnchor;
result.Current = this.Current; result.Current = this.Current;
result.TextBounds = this.TextBounds; result.TextBounds = this.TextBounds;
result.Element = this.Element; result.Element = this.Element;
result.NumChars = this.NumChars; result.NumChars = this.NumChars;
result.Parent = this.Parent; result.Parent = this.Parent;
result.Renderer = this.Renderer; result.Renderer = this.Renderer;
return result; return result;
} }
public void DrawString(string value) public void DrawString(string value)
{ {
// Get any defined anchors // Get any defined anchors
var xAnchors = GetValues(value.Length, e => e._x, UnitRenderingType.HorizontalOffset); var xAnchors = GetValues(value.Length, e => e._x, UnitRenderingType.HorizontalOffset);
var yAnchors = GetValues(value.Length, e => e._y, UnitRenderingType.VerticalOffset); var yAnchors = GetValues(value.Length, e => e._y, UnitRenderingType.VerticalOffset);
using (var font = this.Element.GetFont(this.Renderer)) using (var font = this.Element.GetFont(this.Renderer))
{ {
var fontBaselineHeight = font.Ascent(this.Renderer); var fontBaselineHeight = font.Ascent(this.Renderer);
PathStatistics pathStats = null; PathStatistics pathStats = null;
var pathScale = 1.0; var pathScale = 1.0;
if (BaselinePath != null) if (BaselinePath != null)
{ {
pathStats = new PathStatistics(BaselinePath.PathData); pathStats = new PathStatistics(BaselinePath.PathData);
if (_authorPathLength > 0) pathScale = _authorPathLength / pathStats.TotalLength; if (_authorPathLength > 0) pathScale = _authorPathLength / pathStats.TotalLength;
} }
// Get all of the offsets (explicit and defined by spacing) // Get all of the offsets (explicit and defined by spacing)
IList<float> xOffsets; IList<float> xOffsets;
IList<float> yOffsets; IList<float> yOffsets;
IList<float> rotations; IList<float> rotations;
float baselineShift = 0.0f; float baselineShift = 0.0f;
try try
{ {
this.Renderer.SetBoundable(new FontBoundable(font, (float)(pathStats == null ? 1 : pathStats.TotalLength))); this.Renderer.SetBoundable(new FontBoundable(font, (float)(pathStats == null ? 1 : pathStats.TotalLength)));
xOffsets = GetValues(value.Length, e => e._dx, UnitRenderingType.Horizontal); xOffsets = GetValues(value.Length, e => e._dx, UnitRenderingType.Horizontal);
yOffsets = GetValues(value.Length, e => e._dy, UnitRenderingType.Vertical); yOffsets = GetValues(value.Length, e => e._dy, UnitRenderingType.Vertical);
if (StartOffsetAdjust != 0.0f) if (StartOffsetAdjust != 0.0f)
{ {
if (xOffsets.Count < 1) if (xOffsets.Count < 1)
{ {
xOffsets.Add(StartOffsetAdjust); xOffsets.Add(StartOffsetAdjust);
} }
else else
{ {
xOffsets[0] += StartOffsetAdjust; xOffsets[0] += StartOffsetAdjust;
} }
} }
if (this.Element.LetterSpacing.Value != 0.0f || this.Element.WordSpacing.Value != 0.0f || this.LetterSpacingAdjust != 0.0f) if (this.Element.LetterSpacing.Value != 0.0f || this.Element.WordSpacing.Value != 0.0f || this.LetterSpacingAdjust != 0.0f)
{ {
var spacing = this.Element.LetterSpacing.ToDeviceValue(this.Renderer, UnitRenderingType.Horizontal, this.Element) + this.LetterSpacingAdjust; var spacing = this.Element.LetterSpacing.ToDeviceValue(this.Renderer, UnitRenderingType.Horizontal, this.Element) + this.LetterSpacingAdjust;
var wordSpacing = this.Element.WordSpacing.ToDeviceValue(this.Renderer, UnitRenderingType.Horizontal, this.Element); var wordSpacing = this.Element.WordSpacing.ToDeviceValue(this.Renderer, UnitRenderingType.Horizontal, this.Element);
if (this.Parent == null && this.NumChars == 0 && xOffsets.Count < 1) xOffsets.Add(0); if (this.Parent == null && this.NumChars == 0 && xOffsets.Count < 1) xOffsets.Add(0);
for (int i = (this.Parent == null && this.NumChars == 0 ? 1 : 0); i < value.Length; i++) for (int i = (this.Parent == null && this.NumChars == 0 ? 1 : 0); i < value.Length; i++)
{ {
if (i >= xOffsets.Count) if (i >= xOffsets.Count)
{ {
xOffsets.Add(spacing + (char.IsWhiteSpace(value[i]) ? wordSpacing : 0)); xOffsets.Add(spacing + (char.IsWhiteSpace(value[i]) ? wordSpacing : 0));
} }
else else
{ {
xOffsets[i] += spacing + (char.IsWhiteSpace(value[i]) ? wordSpacing : 0); xOffsets[i] += spacing + (char.IsWhiteSpace(value[i]) ? wordSpacing : 0);
} }
} }
} }
rotations = GetValues(value.Length, e => e._rotations); rotations = GetValues(value.Length, e => e._rotations);
// Calculate Y-offset due to baseline shift. Don't inherit the value so that it is not accumulated multiple times. // Calculate Y-offset due to baseline shift. Don't inherit the value so that it is not accumulated multiple times.
var baselineShiftText = this.Element.Attributes.GetAttribute<string>("baseline-shift"); var baselineShiftText = this.Element.Attributes.GetAttribute<string>("baseline-shift");
switch (baselineShiftText) switch (baselineShiftText)
{ {
case null: case null:
case "": case "":
case "baseline": case "baseline":
case "inherit": case "inherit":
// do nothing // do nothing
break; break;
case "sub": case "sub":
baselineShift = new SvgUnit(SvgUnitType.Ex, 1).ToDeviceValue(this.Renderer, UnitRenderingType.Vertical, this.Element); baselineShift = new SvgUnit(SvgUnitType.Ex, 1).ToDeviceValue(this.Renderer, UnitRenderingType.Vertical, this.Element);
break; break;
case "super": case "super":
baselineShift = -1 * new SvgUnit(SvgUnitType.Ex, 1).ToDeviceValue(this.Renderer, UnitRenderingType.Vertical, this.Element); baselineShift = -1 * new SvgUnit(SvgUnitType.Ex, 1).ToDeviceValue(this.Renderer, UnitRenderingType.Vertical, this.Element);
break; break;
default: default:
var convert = new SvgUnitConverter(); var convert = new SvgUnitConverter();
var shiftUnit = (SvgUnit)convert.ConvertFromInvariantString(baselineShiftText); var shiftUnit = (SvgUnit)convert.ConvertFromInvariantString(baselineShiftText);
baselineShift = -1 * shiftUnit.ToDeviceValue(this.Renderer, UnitRenderingType.Vertical, this.Element); baselineShift = -1 * shiftUnit.ToDeviceValue(this.Renderer, UnitRenderingType.Vertical, this.Element);
break; break;
} }
if (baselineShift != 0.0f) if (baselineShift != 0.0f)
{ {
if (yOffsets.Any()) if (yOffsets.Any())
{ {
yOffsets[0] += baselineShift; yOffsets[0] += baselineShift;
} }
else else
{ {
yOffsets.Add(baselineShift); yOffsets.Add(baselineShift);
} }
} }
} }
finally finally
{ {
this.Renderer.PopBoundable(); this.Renderer.PopBoundable();
} }
var xTextStart = Current.X; var xTextStart = Current.X;
// NOTE: Assuming a horizontal left-to-right font // NOTE: Assuming a horizontal left-to-right font
// Render absolutely positioned items in the horizontal direction // Render absolutely positioned items in the horizontal direction
var yPos = Current.Y; var yPos = Current.Y;
for (int i = 0; i < xAnchors.Count - 1; i++) for (int i = 0; i < xAnchors.Count - 1; i++)
{ {
FlushPath(); FlushPath();
_xAnchor = xAnchors[i] + (xOffsets.Count > i ? xOffsets[i] : 0); _xAnchor = xAnchors[i] + (xOffsets.Count > i ? xOffsets[i] : 0);
EnsurePath(); EnsurePath();
yPos = (yAnchors.Count > i ? yAnchors[i] : yPos) + (yOffsets.Count > i ? yOffsets[i] : 0); yPos = (yAnchors.Count > i ? yAnchors[i] : yPos) + (yOffsets.Count > i ? yOffsets[i] : 0);
xTextStart = xTextStart.Equals(Current.X) ? _xAnchor : xTextStart; xTextStart = xTextStart.Equals(Current.X) ? _xAnchor : xTextStart;
DrawStringOnCurrPath(value[i].ToString(), font, new PointF(_xAnchor, yPos), DrawStringOnCurrPath(value[i].ToString(), font, new PointF(_xAnchor, yPos),
fontBaselineHeight, (rotations.Count > i ? rotations[i] : rotations.LastOrDefault())); fontBaselineHeight, (rotations.Count > i ? rotations[i] : rotations.LastOrDefault()));
} }
// Render any remaining characters // Render any remaining characters
var renderChar = 0; var renderChar = 0;
var xPos = this.Current.X; var xPos = this.Current.X;
if (xAnchors.Any()) if (xAnchors.Any())
{ {
FlushPath(); FlushPath();
renderChar = xAnchors.Count - 1; renderChar = xAnchors.Count - 1;
xPos = xAnchors.Last(); xPos = xAnchors.Last();
_xAnchor = xPos; _xAnchor = xPos;
} }
EnsurePath(); EnsurePath();
// Render individual characters as necessary // Render individual characters as necessary
var lastIndividualChar = renderChar + Math.Max(Math.Max(Math.Max(Math.Max(xOffsets.Count, yOffsets.Count), yAnchors.Count), rotations.Count) - renderChar - 1, 0); var lastIndividualChar = renderChar + Math.Max(Math.Max(Math.Max(Math.Max(xOffsets.Count, yOffsets.Count), yAnchors.Count), rotations.Count) - renderChar - 1, 0);
if (rotations.LastOrDefault() != 0.0f || pathStats != null) lastIndividualChar = value.Length; if (rotations.LastOrDefault() != 0.0f || pathStats != null) lastIndividualChar = value.Length;
if (lastIndividualChar > renderChar) if (lastIndividualChar > renderChar)
{ {
var charBounds = font.MeasureCharacters(this.Renderer, value.Substring(renderChar, Math.Min(lastIndividualChar + 1, value.Length) - renderChar)); var charBounds = font.MeasureCharacters(this.Renderer, value.Substring(renderChar, Math.Min(lastIndividualChar + 1, value.Length) - renderChar));
PointF pathPoint; PointF pathPoint;
float rotation; float rotation;
float halfWidth; float halfWidth;
for (int i = renderChar; i < lastIndividualChar; i++) for (int i = renderChar; i < lastIndividualChar; i++)
{ {
xPos += (float)pathScale * (xOffsets.Count > i ? xOffsets[i] : 0) + (charBounds[i - renderChar].X - (i == renderChar ? 0 : charBounds[i - renderChar - 1].X)); xPos += (float)pathScale * (xOffsets.Count > i ? xOffsets[i] : 0) + (charBounds[i - renderChar].X - (i == renderChar ? 0 : charBounds[i - renderChar - 1].X));
yPos = (yAnchors.Count > i ? yAnchors[i] : yPos) + (yOffsets.Count > i ? yOffsets[i] : 0); yPos = (yAnchors.Count > i ? yAnchors[i] : yPos) + (yOffsets.Count > i ? yOffsets[i] : 0);
if (pathStats == null) if (pathStats == null)
{ {
xTextStart = xTextStart.Equals(Current.X) ? xPos : xTextStart; xTextStart = xTextStart.Equals(Current.X) ? xPos : xTextStart;
DrawStringOnCurrPath(value[i].ToString(), font, new PointF(xPos, yPos), DrawStringOnCurrPath(value[i].ToString(), font, new PointF(xPos, yPos),
fontBaselineHeight, (rotations.Count > i ? rotations[i] : rotations.LastOrDefault())); fontBaselineHeight, (rotations.Count > i ? rotations[i] : rotations.LastOrDefault()));
} }
else else
{ {
xPos = Math.Max(xPos, 0); xPos = Math.Max(xPos, 0);
halfWidth = charBounds[i - renderChar].Width / 2; halfWidth = charBounds[i - renderChar].Width / 2;
if (pathStats.OffsetOnPath(xPos + halfWidth)) if (pathStats.OffsetOnPath(xPos + halfWidth))
{ {
pathStats.LocationAngleAtOffset(xPos + halfWidth, out pathPoint, out rotation); pathStats.LocationAngleAtOffset(xPos + halfWidth, out pathPoint, out rotation);
pathPoint = new PointF((float)(pathPoint.X - halfWidth * Math.Cos(rotation * Math.PI / 180) - (float)pathScale * yPos * Math.Sin(rotation * Math.PI / 180)), pathPoint = new PointF((float)(pathPoint.X - halfWidth * Math.Cos(rotation * Math.PI / 180) - (float)pathScale * yPos * Math.Sin(rotation * Math.PI / 180)),
(float)(pathPoint.Y - halfWidth * Math.Sin(rotation * Math.PI / 180) + (float)pathScale * yPos * Math.Cos(rotation * Math.PI / 180))); (float)(pathPoint.Y - halfWidth * Math.Sin(rotation * Math.PI / 180) + (float)pathScale * yPos * Math.Cos(rotation * Math.PI / 180)));
xTextStart = xTextStart.Equals(Current.X) ? pathPoint.X : xTextStart; xTextStart = xTextStart.Equals(Current.X) ? pathPoint.X : xTextStart;
DrawStringOnCurrPath(value[i].ToString(), font, pathPoint, fontBaselineHeight, rotation); DrawStringOnCurrPath(value[i].ToString(), font, pathPoint, fontBaselineHeight, rotation);
} }
} }
} }
// Add the kerning to the next character // Add the kerning to the next character
if (lastIndividualChar < value.Length) if (lastIndividualChar < value.Length)
{ {
xPos += charBounds[charBounds.Count - 1].X - charBounds[charBounds.Count - 2].X; xPos += charBounds[charBounds.Count - 1].X - charBounds[charBounds.Count - 2].X;
} }
else else
{ {
xPos += charBounds.Last().Width; xPos += charBounds.Last().Width;
} }
} }
// Render the string normally // Render the string normally
if (lastIndividualChar < value.Length) if (lastIndividualChar < value.Length)
{ {
xPos += (xOffsets.Count > lastIndividualChar ? xOffsets[lastIndividualChar] : 0); xPos += (xOffsets.Count > lastIndividualChar ? xOffsets[lastIndividualChar] : 0);
yPos = (yAnchors.Count > lastIndividualChar ? yAnchors[lastIndividualChar] : yPos) + yPos = (yAnchors.Count > lastIndividualChar ? yAnchors[lastIndividualChar] : yPos) +
(yOffsets.Count > lastIndividualChar ? yOffsets[lastIndividualChar] : 0); (yOffsets.Count > lastIndividualChar ? yOffsets[lastIndividualChar] : 0);
xTextStart = xTextStart.Equals(Current.X) ? xPos : xTextStart; xTextStart = xTextStart.Equals(Current.X) ? xPos : xTextStart;
DrawStringOnCurrPath(value.Substring(lastIndividualChar), font, new PointF(xPos, yPos), DrawStringOnCurrPath(value.Substring(lastIndividualChar), font, new PointF(xPos, yPos),
fontBaselineHeight, rotations.LastOrDefault()); fontBaselineHeight, rotations.LastOrDefault());
var bounds = font.MeasureString(this.Renderer, value.Substring(lastIndividualChar)); var bounds = font.MeasureString(this.Renderer, value.Substring(lastIndividualChar));
xPos += bounds.Width; xPos += bounds.Width;
} }
NumChars += value.Length; NumChars += value.Length;
// Undo any baseline shift. This is not persisted, unlike normal vertical offsets. // Undo any baseline shift. This is not persisted, unlike normal vertical offsets.
this.Current = new PointF(xPos, yPos - baselineShift); this.Current = new PointF(xPos, yPos - baselineShift);
this.TextBounds = new RectangleF(xTextStart, 0, this.Current.X - xTextStart, 0); this.TextBounds = new RectangleF(xTextStart, 0, this.Current.X - xTextStart, 0);
} }
} }
private void DrawStringOnCurrPath(string value, IFontDefn font, PointF location, float fontBaselineHeight, float rotation) private void DrawStringOnCurrPath(string value, IFontDefn font, PointF location, float fontBaselineHeight, float rotation)
{ {
var drawPath = _currPath; var drawPath = _currPath;
if (rotation != 0.0f) drawPath = new GraphicsPath(); if (rotation != 0.0f) drawPath = new GraphicsPath();
font.AddStringToPath(this.Renderer, drawPath, value, new PointF(location.X, location.Y - fontBaselineHeight)); font.AddStringToPath(this.Renderer, drawPath, value, new PointF(location.X, location.Y - fontBaselineHeight));
if (rotation != 0.0f && drawPath.PointCount > 0) if (rotation != 0.0f && drawPath.PointCount > 0)
{ {
using (var matrix = new Matrix()) using (var matrix = new Matrix())
{ {
matrix.Translate(-1 * location.X, -1 * location.Y, MatrixOrder.Append); matrix.Translate(-1 * location.X, -1 * location.Y, MatrixOrder.Append);
matrix.Rotate(rotation, MatrixOrder.Append); matrix.Rotate(rotation, MatrixOrder.Append);
matrix.Translate(location.X, location.Y, MatrixOrder.Append); matrix.Translate(location.X, location.Y, MatrixOrder.Append);
drawPath.Transform(matrix); drawPath.Transform(matrix);
_currPath.AddPath(drawPath, false); _currPath.AddPath(drawPath, false);
} }
} }
} }
private void EnsurePath() private void EnsurePath()
{ {
if (_currPath == null) if (_currPath == null)
{ {
_currPath = new GraphicsPath(); _currPath = new GraphicsPath();
_currPath.StartFigure(); _currPath.StartFigure();
var currState = this; var currState = this;
while (currState != null && currState._xAnchor <= float.MinValue) while (currState != null && currState._xAnchor <= float.MinValue)
{ {
currState = currState.Parent; currState = currState.Parent;
} }
currState._anchoredPaths.Add(_currPath); currState._anchoredPaths.Add(_currPath);
} }
} }
private void FlushPath() private void FlushPath()
{ {
if (_currPath != null) if (_currPath != null)
{ {
_currPath.CloseFigure(); _currPath.CloseFigure();
// Abort on empty paths (e.g. rendering a space) // Abort on empty paths (e.g. rendering a space)
if (_currPath.PointCount < 1) if (_currPath.PointCount < 1)
{ {
_anchoredPaths.Clear(); _anchoredPaths.Clear();
_xAnchor = float.MinValue; _xAnchor = float.MinValue;
_currPath = null; _currPath = null;
return; return;
} }
if (_xAnchor > float.MinValue) if (_xAnchor > float.MinValue)
{ {
float minX = float.MaxValue; float minX = float.MaxValue;
float maxX = float.MinValue; float maxX = float.MinValue;
RectangleF bounds; RectangleF bounds;
foreach (var path in _anchoredPaths) foreach (var path in _anchoredPaths)
{ {
bounds = path.GetBounds(); bounds = path.GetBounds();
if (bounds.Left < minX) minX = bounds.Left; if (bounds.Left < minX) minX = bounds.Left;
if (bounds.Right > maxX) maxX = bounds.Right; if (bounds.Right > maxX) maxX = bounds.Right;
} }
var xOffset = 0f; //_xAnchor - minX; var xOffset = 0f; //_xAnchor - minX;
switch (Element.TextAnchor) switch (Element.TextAnchor)
{ {
case SvgTextAnchor.Middle: case SvgTextAnchor.Middle:
if (_anchoredPaths.Count() == 1) xOffset -= this.TextBounds.Width / 2; if (_anchoredPaths.Count() == 1) xOffset -= this.TextBounds.Width / 2;
else xOffset -= (maxX - minX) / 2; else xOffset -= (maxX - minX) / 2;
break; break;
case SvgTextAnchor.End: case SvgTextAnchor.End:
if (_anchoredPaths.Count() == 1) xOffset -= this.TextBounds.Width; if (_anchoredPaths.Count() == 1) xOffset -= this.TextBounds.Width;
else xOffset -= (maxX - minX); else xOffset -= (maxX - minX);
break; break;
} }
if (xOffset != 0) if (xOffset != 0)
{ {
using (var matrix = new Matrix()) using (var matrix = new Matrix())
{ {
matrix.Translate(xOffset, 0); matrix.Translate(xOffset, 0);
foreach (var path in _anchoredPaths) foreach (var path in _anchoredPaths)
{ {
path.Transform(matrix); path.Transform(matrix);
} }
} }
} }
_anchoredPaths.Clear(); _anchoredPaths.Clear();
_xAnchor = float.MinValue; _xAnchor = float.MinValue;
} }
if (_finalPath == null) if (_finalPath == null)
{ {
_finalPath = _currPath; _finalPath = _currPath;
} }
else else
{ {
_finalPath.AddPath(_currPath, false); _finalPath.AddPath(_currPath, false);
} }
_currPath = null; _currPath = null;
} }
} }
private IList<float> GetValues(int maxCount, Func<SvgTextBase, IEnumerable<float>> listGetter) private IList<float> GetValues(int maxCount, Func<SvgTextBase, IEnumerable<float>> listGetter)
{ {
var currState = this; var currState = this;
int charCount = 0; int charCount = 0;
var results = new List<float>(); var results = new List<float>();
int resultCount = 0; int resultCount = 0;
while (currState != null) while (currState != null)
{ {
charCount += currState.NumChars; charCount += currState.NumChars;
results.AddRange(listGetter.Invoke(currState.Element).Skip(charCount).Take(maxCount)); results.AddRange(listGetter.Invoke(currState.Element).Skip(charCount).Take(maxCount));
if (results.Count > resultCount) if (results.Count > resultCount)
{ {
maxCount -= results.Count - resultCount; maxCount -= results.Count - resultCount;
charCount += results.Count - resultCount; charCount += results.Count - resultCount;
resultCount = results.Count; resultCount = results.Count;
} }
if (maxCount < 1) return results; if (maxCount < 1) return results;
currState = currState.Parent; currState = currState.Parent;
} }
return results; return results;
} }
private IList<float> GetValues(int maxCount, Func<SvgTextBase, IEnumerable<SvgUnit>> listGetter, UnitRenderingType renderingType) private IList<float> GetValues(int maxCount, Func<SvgTextBase, IEnumerable<SvgUnit>> listGetter, UnitRenderingType renderingType)
{ {
var currState = this; var currState = this;
int charCount = 0; int charCount = 0;
var results = new List<float>(); var results = new List<float>();
int resultCount = 0; int resultCount = 0;
while (currState != null) while (currState != null)
{ {
charCount += currState.NumChars; charCount += currState.NumChars;
results.AddRange(listGetter.Invoke(currState.Element).Skip(charCount).Take(maxCount).Select(p => p.ToDeviceValue(currState.Renderer, renderingType, currState.Element))); results.AddRange(listGetter.Invoke(currState.Element).Skip(charCount).Take(maxCount).Select(p => p.ToDeviceValue(currState.Renderer, renderingType, currState.Element)));
if (results.Count > resultCount) if (results.Count > resultCount)
{ {
maxCount -= results.Count - resultCount; maxCount -= results.Count - resultCount;
charCount += results.Count - resultCount; charCount += results.Count - resultCount;
resultCount = results.Count; resultCount = results.Count;
} }
if (maxCount < 1) return results; if (maxCount < 1) return results;
currState = currState.Parent; currState = currState.Parent;
} }
return results; return results;
} }
} }
/// <summary>Empty text elements are not legal - only write this element if it has children.</summary> /// <summary>Empty text elements are not legal - only write this element if it has children.</summary>
public override bool ShouldWriteElement() public override bool ShouldWriteElement()
{ {
return (this.HasChildren() || this.Nodes.Count > 0); return (this.HasChildren() || this.Nodes.Count > 0);
} }
} }
} }
...@@ -199,5 +199,6 @@ __issue-385-01_Test_text-anchor-middle ...@@ -199,5 +199,6 @@ __issue-385-01_Test_text-anchor-middle
__issue-398-01 __issue-398-01
__pull_request-373-01 __pull_request-373-01
__pull_request-374-01 __pull_request-374-01
__pull_request-414-01
__Telefunken_FuBK_test_pattern __Telefunken_FuBK_test_pattern
__title __title
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="flag-icon-css-eu-small" viewBox="0 0 640 480">
<defs>
<symbol id="s" viewBox="0 0 640 480">
<image href="./__issue-323-02.svg" width="640" height="480" />
</symbol>
</defs>
<use x="0" y="0" href="#s" width="320" height="240" />
<image x="0" y="240" width="320" height="240" href="./__issue-323-02.svg" />
<rect x="0" y="0" width="320" height="240" fill="red" opacity="0.25" />
</svg>
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