Commit 7c70bd11 authored by Eric Domke's avatar Eric Domke
Browse files

Text on a Path & SVG Fonts

- Extraction interface for SvgRenderer
- Initial support for Text on a Path
- Initial support for Svg Fonts
- Support for symbol element
- Minor bug fixes with image pattern rendering
- Additional support for Text whitespace modes
parent 1a00f391
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Svg
{
[SvgElement("missing-glyph")]
public class SvgMissingGlyph : SvgGlyph
{
[SvgAttribute("glyph-name")]
public override string GlyphName
{
get { return this.Attributes["glyph-name"] as string ?? "__MISSING_GLYPH__"; }
set { this.Attributes["glyph-name"] = value; }
}
}
}
...@@ -11,33 +11,14 @@ using System.Linq; ...@@ -11,33 +11,14 @@ using System.Linq;
namespace Svg namespace Svg
{ {
public enum XmlSpaceHandling
{
@default,
preserve
}
public abstract class SvgTextBase : SvgVisualElement public abstract class SvgTextBase : SvgVisualElement
{ {
private SvgUnitCollection _x = new SvgUnitCollection(); protected SvgUnitCollection _x = new SvgUnitCollection();
private SvgUnitCollection _y = new SvgUnitCollection(); protected SvgUnitCollection _y = new SvgUnitCollection();
private SvgUnitCollection _dy = new SvgUnitCollection(); protected SvgUnitCollection _dy = new SvgUnitCollection();
private SvgUnitCollection _dx = new SvgUnitCollection(); protected SvgUnitCollection _dx = new SvgUnitCollection();
private SvgUnit _letterSpacing; private string _rotate;
private SvgUnit _wordSpacing; private List<float> _rotations = new List<float>();
private static readonly SvgRenderer _stringMeasure;
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> /// <summary>
/// Gets or sets the text to be rendered. /// Gets or sets the text to be rendered.
...@@ -66,6 +47,12 @@ namespace Svg ...@@ -66,6 +47,12 @@ namespace Svg
set { this.Attributes["baseline-shift"] = value; this.IsPathDirty = true; } set { this.Attributes["baseline-shift"] = value; this.IsPathDirty = true; }
} }
public override XmlSpaceHandling SpaceHandling
{
get { return base.SpaceHandling; }
set { base.SpaceHandling = value; this.IsPathDirty = true; }
}
/// <summary> /// <summary>
/// Gets or sets the X. /// Gets or sets the X.
/// </summary> /// </summary>
...@@ -142,14 +129,56 @@ namespace Svg ...@@ -142,14 +129,56 @@ namespace Svg
} }
} }
/// <summary>
/// Gets or sets the rotate.
/// </summary>
/// <value>The rotate.</value>
[SvgAttribute("rotate")]
public virtual string Rotate
{
get { return this._rotate; }
set
{
if (_rotate != value)
{
this._rotate = value;
this._rotations.Clear();
this._rotations.AddRange(from r in _rotate.Split(new char[] {',', ' ', '\r', '\n', '\t'}, StringSplitOptions.RemoveEmptyEntries) select float.Parse(r));
this.IsPathDirty = true;
OnAttributeChanged(new AttributeEventArgs { Attribute = "rotate", Value = value });
}
}
}
/// <summary>
/// The pre-calculated length of the text
/// </summary>
[SvgAttribute("textLength")]
public virtual SvgUnit TextLength
{
get { return (this.Attributes["textLength"] == null ? SvgUnit.None : (SvgUnit)this.Attributes["textLength"]); }
set { this.Attributes["textLength"] = value; this.IsPathDirty = true; }
}
/// <summary>
/// Gets or sets the text anchor.
/// </summary>
/// <value>The text anchor.</value>
[SvgAttribute("lengthAdjust")]
public virtual SvgTextLengthAdjust LengthAdjust
{
get { return (this.Attributes["lengthAdjust"] == null) ? SvgTextLengthAdjust.spacing : (SvgTextLengthAdjust)this.Attributes["lengthAdjust"]; }
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")] [SvgAttribute("letter-spacing")]
public virtual SvgUnit LetterSpacing public virtual SvgUnit LetterSpacing
{ {
get { return this._letterSpacing; } get { return (this.Attributes["letter-spacing"] == null ? SvgUnit.None : (SvgUnit)this.Attributes["letter-spacing"]); }
set { this._letterSpacing = value; this.IsPathDirty = true; } set { this.Attributes["letter-spacing"] = value; this.IsPathDirty = true; }
} }
/// <summary> /// <summary>
...@@ -158,8 +187,8 @@ namespace Svg ...@@ -158,8 +187,8 @@ namespace Svg
[SvgAttribute("word-spacing")] [SvgAttribute("word-spacing")]
public virtual SvgUnit WordSpacing public virtual SvgUnit WordSpacing
{ {
get { return this._wordSpacing; } get { return (this.Attributes["word-spacing"] == null ? SvgUnit.None : (SvgUnit)this.Attributes["word-spacing"]); }
set { this._wordSpacing = value; this.IsPathDirty = true; } set { this.Attributes["word-spacing"] = value; this.IsPathDirty = true; }
} }
/// <summary> /// <summary>
...@@ -215,9 +244,9 @@ namespace Svg ...@@ -215,9 +244,9 @@ namespace Svg
/// <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="SvgRenderer"/> 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(SvgRenderer 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)
{ {
...@@ -245,142 +274,364 @@ namespace Svg ...@@ -245,142 +274,364 @@ namespace Svg
} }
} }
internal virtual IEnumerable<ISvgNode> GetContentNodes()
{
return (this.Nodes == null || this.Nodes.Count < 1 ? this.Children.OfType<ISvgNode>() : this.Nodes);
}
protected virtual GraphicsPath GetBaselinePath(ISvgRenderer renderer)
{
return null;
}
protected virtual float GetAuthorPathLength()
{
return 0;
}
private GraphicsPath _path; private GraphicsPath _path;
protected class NodeBounds /// <summary>
/// Gets the <see cref="GraphicsPath"/> for this element.
/// </summary>
/// <value></value>
public override System.Drawing.Drawing2D.GraphicsPath Path(ISvgRenderer renderer)
{ {
public float xOffset { get; set; } // Make sure the path is always null if there is no text
public SizeF Bounds { get; set; } //if there is a TSpan inside of this text element then path should not be null (even if this text is empty!)
public ISvgNode Node { get; set; } var nodes = this.GetContentNodes().ToList();
if (nodes.Count < 1) return _path = null;
if (nodes.Count == 1 && nodes[0] is SvgContentNode &&
(string.IsNullOrEmpty(nodes[0].Content) || nodes[0].Content.Trim().Length < 1)) return _path = null;
if (_path == null || this.IsPathDirty)
{
renderer = (renderer ?? SvgRenderer.FromNull());
this.SetPath(new TextDrawingState(renderer, this));
}
return _path;
} }
protected class BoundsData
private void SetPath(TextDrawingState state)
{
SetPath(state, true);
}
/// <summary>
/// Sets the path on this element and all child elements. Uses the state
/// object to track the state of the drawing
/// </summary>
/// <param name="state">State of the drawing operation</param>
private void SetPath(TextDrawingState state, bool doMeasurements)
{
SvgTextBase inner;
TextDrawingState newState;
TextDrawingState origState = null;
bool alignOnBaseline = state.BaselinePath != null && (this.TextAnchor == SvgTextAnchor.Middle || this.TextAnchor == SvgTextAnchor.End);
if (doMeasurements)
{ {
private List<NodeBounds> _nodes = new List<NodeBounds>(); if (this.TextLength != SvgUnit.None)
public IList<NodeBounds> Nodes {
origState = state.Clone();
}
else if (alignOnBaseline)
{ {
get { return _nodes; } origState = state.Clone();
state.BaselinePath = null;
} }
public SizeF Bounds { get; set; }
} }
protected BoundsData GetTextBounds(SvgRenderer renderer)
foreach (var node in GetContentNodes())
{
inner = node as SvgTextBase;
if (inner == null)
{
if (!string.IsNullOrEmpty(node.Content)) state.DrawString(PrepareText(node.Content));
}
else
{ {
var font = GetFont(renderer); newState = new TextDrawingState(state, inner);
SvgTextBase innerText; inner.SetPath(newState);
SizeF stringBounds; state.NumChars += newState.NumChars;
float totalHeight = 0; state.Current = newState.Current;
float totalWidth = 0; }
}
var result = new BoundsData(); var path = state.GetPath() ?? new GraphicsPath();
var nodes = (from n in this.Nodes
where (n is SvgContentNode || n is SvgTextBase) && !string.IsNullOrEmpty(n.Content)
select n).ToList();
// Individual character spacing // Apply any text length adjustments
if (nodes.FirstOrDefault() is SvgContentNode && _x.Count > 1) if (doMeasurements)
{
if (this.TextLength != SvgUnit.None)
{
var bounds = path.GetBounds();
var specLength = this.TextLength.ToDeviceValue(state.Renderer, UnitRenderingType.Horizontal, this);
var actLength = bounds.Width;
var diff = (actLength - specLength);
if (Math.Abs(diff) > 1.5)
{
if (this.LengthAdjust == SvgTextLengthAdjust.spacing)
{ {
string ch; origState.LetterSpacingAdjust = -1 * diff / (state.NumChars - origState.NumChars - 1);
var content = nodes.First() as SvgContentNode; SetPath(origState, false);
nodes.RemoveAt(0); return;
int posCount = Math.Min(content.Content.Length, _x.Count); }
var text = PrepareText(content.Content, false, (nodes.Count > 1 && nodes[1] is SvgTextBase)); else
{
var matrix = new Matrix();
matrix.Translate(-1 * bounds.X, 0, MatrixOrder.Append);
matrix.Scale(specLength / actLength, 1, MatrixOrder.Append);
matrix.Translate(bounds.X, 0, MatrixOrder.Append);
path.Transform(matrix);
}
}
}
else if (alignOnBaseline)
{
var bounds = path.GetBounds();
if (this.TextAnchor == SvgTextAnchor.Middle)
{
origState.StartOffsetAdjust = -1 * bounds.Width / 2;
}
else
{
origState.StartOffsetAdjust = -1 * bounds.Width;
}
SetPath(origState, false);
return;
}
}
for (var i = 0; i < posCount; i++)
_path = path;
this.IsPathDirty = false;
}
private static readonly Regex MultipleSpaces = new Regex(@" {2,}", RegexOptions.Compiled);
/// <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)
{
if (this.SpaceHandling == XmlSpaceHandling.preserve)
{ {
ch = (i == posCount - 1 ? text.Substring(i) : text.Substring(i, 1)); return value.Replace('\t', ' ').Replace("\r\n", " ").Replace('\r', ' ').Replace('\n', ' ');
stringBounds = _stringMeasure.MeasureString(ch, font); }
totalHeight = Math.Max(totalHeight, stringBounds.Height); else
result.Nodes.Add(new NodeBounds()
{ {
Bounds = stringBounds, var convValue = MultipleSpaces.Replace(value.Replace("\r", "").Replace("\n", "").Replace('\t', ' '), " ");
Node = new SvgContentNode() { Content = ch }, return convValue;
xOffset = (i == 0 ? 0 : _x[i].ToDeviceValue(renderer, UnitRenderingType.Horizontal, this) -
_x[0].ToDeviceValue(renderer, UnitRenderingType.Horizontal, this))
});
} }
} }
// Calculate the bounds of the text [SvgAttribute("onchange")]
ISvgNode node; public event EventHandler<StringArg> Change;
var accumulateDims = true;
for (var i = 0; i < nodes.Count; i++) //change
protected void OnChange(string newString, string sessionID)
{ {
node = nodes[i]; RaiseChange(this, new StringArg { s = newString, SessionID = sessionID });
lock (_stringMeasure) }
protected void RaiseChange(object sender, StringArg s)
{ {
innerText = node as SvgTextBase; var handler = Change;
if (innerText == null) if (handler != null)
{ {
stringBounds = _stringMeasure.MeasureString(PrepareText(node.Content, handler(sender, s);
i > 0 && nodes[i - 1] is SvgTextBase,
i < nodes.Count - 1 && nodes[i + 1] is SvgTextBase), font);
result.Nodes.Add(new NodeBounds() { Bounds = stringBounds, Node = node, xOffset = totalWidth });
} }
else }
//private static GraphicsPath GetPath(string text, Font font)
//{
// var fontMetrics = (from c in text.Distinct()
// select new { Char = c, Metrics = Metrics(c, font) }).
// ToDictionary(c => c.Char, c=> c.Metrics);
// // Measure each character and check the metrics against the overall metrics of rendering
// // an entire word with kerning.
//}
//private static RectangleF Metrics(char c, Font font)
//{
// var path = new GraphicsPath();
// path.AddString(c.ToString(), font.FontFamily, (int)font.Style, font.Size, new Point(0, 0), StringFormat.GenericTypographic);
// return path.GetBounds();
//}
#if Net4
public override void RegisterEvents(ISvgEventCaller caller)
{ {
stringBounds = innerText.GetTextBounds(renderer).Bounds; //register basic events
result.Nodes.Add(new NodeBounds() { Bounds = stringBounds, Node = node, xOffset = totalWidth }); base.RegisterEvents(caller);
accumulateDims = accumulateDims && SvgUnitCollection.IsNullOrEmpty(innerText.X) && SvgUnitCollection.IsNullOrEmpty(innerText.Y);
if (accumulateDims && innerText.Dx.Count == 1) totalWidth += innerText.Dx[0].ToDeviceValue(renderer, UnitRenderingType.Horizontal, this); //add change event for text
caller.RegisterAction<string, string>(this.ID + "/onchange", OnChange);
} }
if (accumulateDims) public override void UnregisterEvents(ISvgEventCaller caller)
{ {
totalHeight = Math.Max(totalHeight, stringBounds.Height); //unregister base events
totalWidth += stringBounds.Width; base.UnregisterEvents(caller);
//unregister change event
caller.UnregisterAction(this.ID + "/onchange");
} }
#endif
private class FontBoundable : ISvgBoundable
{
private IFontDefn _font;
private float _width = 1;
public FontBoundable(IFontDefn font)
{
_font = font;
} }
public FontBoundable(IFontDefn font, float width)
{
_font = font;
_width = width;
} }
result.Bounds = new SizeF(totalWidth, totalHeight);
return result; public PointF Location
{
get { return PointF.Empty; }
} }
protected float _calcX = 0; public SizeF Size
protected float _calcY = 0; {
get { return new SizeF(_width, _font.Size); }
}
/// <summary> public RectangleF Bounds
/// Gets the <see cref="GraphicsPath"/> for this element.
/// </summary>
/// <value></value>
public override System.Drawing.Drawing2D.GraphicsPath Path(SvgRenderer renderer)
{ {
// Make sure the path is always null if there is no text get { return new RectangleF(this.Location, this.Size); }
//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) private class TextDrawingState
{ {
renderer = (renderer ?? SvgRenderer.FromNull()); private float _xAnchor = float.MinValue;
// Measure the overall bounds of all the text private IList<GraphicsPath> _anchoredPaths = new List<GraphicsPath>();
var boundsData = GetTextBounds(renderer); private GraphicsPath _currPath = null;
private GraphicsPath _finalPath = null;
private float _authorPathLength = 0;
var font = GetFont(renderer); public GraphicsPath BaselinePath { get; set; }
SvgTextBase innerText; public PointF Current { get; set; }
float x = (_x.Count < 1 ? _calcX : _x[0].ToDeviceValue(renderer, UnitRenderingType.HorizontalOffset, this)) + public SvgTextBase Element { get; set; }
(_dx.Count < 1 ? 0 : _dx[0].ToDeviceValue(renderer, UnitRenderingType.Horizontal, this)); public float LetterSpacingAdjust { get; set; }
float y = (_y.Count < 1 ? _calcY : _y[0].ToDeviceValue(renderer, UnitRenderingType.VerticalOffset, this)) + public int NumChars { get; set; }
(_dy.Count < 1 ? 0 : _dy[0].ToDeviceValue(renderer, UnitRenderingType.Vertical, this)); public TextDrawingState Parent { get; set; }
public ISvgRenderer Renderer { get; set; }
public float StartOffsetAdjust { get; set; }
_path = new GraphicsPath(); private TextDrawingState() { }
_path.StartFigure(); public TextDrawingState(ISvgRenderer renderer, SvgTextBase element)
{
this.Element = element;
this.Renderer = renderer;
this.Current = PointF.Empty;
_xAnchor = 0;
this.BaselinePath = element.GetBaselinePath(renderer);
_authorPathLength = element.GetAuthorPathLength();
}
public TextDrawingState(TextDrawingState parent, SvgTextBase element)
{
this.Element = element;
this.Renderer = parent.Renderer;
this.Parent = parent;
this.Current = parent.Current;
this.BaselinePath = element.GetBaselinePath(parent.Renderer) ?? parent.BaselinePath;
var currPathLength = element.GetAuthorPathLength();
_authorPathLength = currPathLength == 0 ? parent._authorPathLength : currPathLength;
}
// Determine the location of the start point public GraphicsPath GetPath()
switch (this.TextAnchor)
{ {
case SvgTextAnchor.Middle: FlushPath();
x -= (boundsData.Bounds.Width / 2); return _finalPath;
break; }
case SvgTextAnchor.End:
x -= boundsData.Bounds.Width; public TextDrawingState Clone()
break; {
var result = new TextDrawingState();
result._anchoredPaths = this._anchoredPaths.ToList();
result.BaselinePath = this.BaselinePath;
result._xAnchor = this._xAnchor;
result.Current = this.Current;
result.Element = this.Element;
result.NumChars = this.NumChars;
result.Parent = this.Parent;
result.Renderer = this.Renderer;
return result;
} }
public void DrawString(string value)
{
// Get any defined anchors
var xAnchors = GetValues(value.Length, e => e._x, UnitRenderingType.HorizontalOffset);
var yAnchors = GetValues(value.Length, e => e._y, UnitRenderingType.VerticalOffset);
var font = this.Element.GetFont(this.Renderer);
var fontBaselineHeight = this.Renderer.FontBaselineOffset(font);
PathStatistics pathStats = null;
var pathScale = 1.0;
if (BaselinePath != null)
{
pathStats = new PathStatistics(BaselinePath.PathData);
if (_authorPathLength > 0) pathScale = _authorPathLength / pathStats.TotalLength;
}
// Get all of the offsets (explicit and defined by spacing)
IList<float> xOffsets;
IList<float> yOffsets;
IList<float> rotations;
float baselineShift = 0.0f;
try try
{ {
renderer.Boundable(new FontBoundable(font)); this.Renderer.SetBoundable(new FontBoundable(font, (float)(pathStats == null ? 1 : pathStats.TotalLength)));
switch (this.BaselineShift) xOffsets = GetValues(value.Length, e => e._dx, UnitRenderingType.Horizontal);
yOffsets = GetValues(value.Length, e => e._dy, UnitRenderingType.Vertical);
if (StartOffsetAdjust != 0.0f)
{
if (xOffsets.Count < 1)
{
xOffsets.Add(StartOffsetAdjust);
}
else
{
xOffsets[0] += StartOffsetAdjust;
}
}
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 wordSpacing = this.Element.WordSpacing.ToDeviceValue(this.Renderer, UnitRenderingType.Horizontal, this.Element);
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++)
{
if (i >= xOffsets.Count)
{
xOffsets.Add(spacing + (char.IsWhiteSpace(value[i]) ? wordSpacing : 0));
}
else
{
xOffsets[i] += spacing + (char.IsWhiteSpace(value[i]) ? wordSpacing : 0);
}
}
}
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.
var baselineShiftText = this.Element.Attributes.GetAttribute<string>("baseline-shift");
switch (baselineShiftText)
{ {
case null: case null:
case "": case "":
...@@ -389,180 +640,273 @@ namespace Svg ...@@ -389,180 +640,273 @@ namespace Svg
// do nothing // do nothing
break; break;
case "sub": case "sub":
y += new SvgUnit(SvgUnitType.Ex, 1).ToDeviceValue(renderer, UnitRenderingType.Vertical, this); baselineShift = new SvgUnit(SvgUnitType.Ex, 1).ToDeviceValue(this.Renderer, UnitRenderingType.Vertical, this.Element);
break; break;
case "super": case "super":
y -= new SvgUnit(SvgUnitType.Ex, 1).ToDeviceValue(renderer, UnitRenderingType.Vertical, this); 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 shift = (SvgUnit)convert.ConvertFromInvariantString(this.BaselineShift); var shiftUnit = (SvgUnit)convert.ConvertFromInvariantString(baselineShiftText);
y -= shift.ToDeviceValue(renderer, UnitRenderingType.Vertical, this); baselineShift = -1 * shiftUnit.ToDeviceValue(this.Renderer, UnitRenderingType.Vertical, this.Element);
break; break;
} }
}
finally
{
renderer.PopBoundable();
}
NodeBounds data; if (baselineShift != 0.0f)
var yCummOffset = 0.0f;
for (var i = 0; i < boundsData.Nodes.Count; i++)
{ {
data = boundsData.Nodes[i]; if (yOffsets.Any())
innerText = data.Node as SvgTextBase;
if (innerText == null)
{ {
// Minus FontSize because the x/y coords mark the bottom left, not bottom top. yOffsets[0] += baselineShift;
DrawString(renderer, _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 else
{ {
innerText._calcX = x + data.xOffset; yOffsets.Add(baselineShift);
innerText._calcY = y + yCummOffset;
if (innerText.Dy.Count == 1) yCummOffset += innerText.Dy[0].ToDeviceValue(renderer, UnitRenderingType.Vertical, this);
} }
} }
}
finally
{
this.Renderer.PopBoundable();
}
_path.CloseFigure(); // NOTE: Assuming a horizontal left-to-right font
this.IsPathDirty = false; // Render absolutely positioned items in the horizontal direction
var yPos = Current.Y;
for (int i = 0; i < xAnchors.Count - 1; i++)
{
FlushPath();
_xAnchor = xAnchors[i] + (xOffsets.Count > i ? xOffsets[i] : 0);
EnsurePath();
yPos = (yAnchors.Count > i ? yAnchors[i] : yPos) + (yOffsets.Count > i ? yOffsets[i] : 0);
DrawStringOnCurrPath(value[i].ToString(), font, new PointF(_xAnchor, yPos),
fontBaselineHeight, (rotations.Count > i ? rotations[i] : rotations.LastOrDefault()));
} }
return _path;
// Render any remaining characters
var renderChar = 0;
var xPos = this.Current.X;
if (xAnchors.Any())
{
FlushPath();
renderChar = xAnchors.Count - 1;
xPos = xAnchors.Last();
_xAnchor = xPos;
} }
EnsurePath();
private static readonly Regex MultipleSpaces = new Regex(@" {2,}", RegexOptions.Compiled);
/// <summary> // Render individual characters as necessary
/// Prepare the text according to the whitespace handling rules. <see href="http://www.w3.org/TR/SVG/text.html">SVG Spec</see>. var lastIndividualChar = renderChar + Math.Max(Math.Max(Math.Max(Math.Max(xOffsets.Count, yOffsets.Count), yAnchors.Count), rotations.Count) - renderChar - 1, 0);
/// </summary> if (rotations.LastOrDefault() != 0.0f || pathStats != null) lastIndividualChar = value.Length;
/// <param name="value">Text to be prepared</param> if (lastIndividualChar > renderChar)
/// <returns>Prepared text</returns>
protected string PrepareText(string value, bool leadingSpace, bool trailingSpace)
{ {
if (_space == XmlSpaceHandling.preserve) var charBounds = this.Renderer.MeasureCharacters(value.Substring(renderChar, Math.Min(lastIndividualChar + 1, value.Length) - renderChar), font);
PointF pathPoint;
float rotation;
float halfWidth;
for (int i = renderChar; i < lastIndividualChar; i++)
{ {
return value.Replace('\t', ' ').Replace("\r\n", " ").Replace('\r', ' ').Replace('\n', ' '); 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);
if (pathStats == null)
{
DrawStringOnCurrPath(value[i].ToString(), font, new PointF(xPos, yPos),
fontBaselineHeight, (rotations.Count > i ? rotations[i] : rotations.LastOrDefault()));
} }
else else
{ {
var convValue = MultipleSpaces.Replace(value.Replace("\r", "").Replace("\n", "").Replace('\t', ' '), " "); xPos = Math.Max(xPos, 0);
//if (!leadingSpace) convValue = convValue.TrimStart(); halfWidth = charBounds[i-renderChar].Width / 2;
//if (!trailingSpace) convValue = convValue.TrimEnd(); if (pathStats.OffsetOnPath(xPos + halfWidth))
return convValue; {
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)),
(float)(pathPoint.Y - halfWidth * Math.Sin(rotation * Math.PI / 180) + (float)pathScale * yPos * Math.Cos(rotation * Math.PI / 180)));
DrawStringOnCurrPath(value[i].ToString(), font, pathPoint, fontBaselineHeight, rotation);
}
} }
} }
/// <summary> // Add the kerning to the next character
/// Draws a string on a path at a specified location and with a specified font. if (lastIndividualChar < value.Length)
/// </summary>
internal void DrawString(SvgRenderer renderer, GraphicsPath path, float x, float y, Font font, string text)
{ {
PointF location = new PointF(x, y); xPos += charBounds[charBounds.Count - 1].X - charBounds[charBounds.Count - 2].X;
}
// No way to do letter-spacing or word-spacing, so do manually else
if (this.LetterSpacing.Value > 0.0f || this.WordSpacing.Value > 0.0f)
{ {
// Cut up into words, or just leave as required xPos += charBounds.Last().Width;
string[] words = (this.WordSpacing.Value > 0.0f) ? text.Split(' ') : new string[] { text }; }
float wordSpacing = this.WordSpacing.ToDeviceValue(renderer, UnitRenderingType.Horizontal, this); }
float letterSpacing = this.LetterSpacing.ToDeviceValue(renderer, UnitRenderingType.Horizontal, this);
float start = x;
foreach (string word in words) // Render the string normally
{ if (lastIndividualChar < value.Length)
// 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); xPos += (xOffsets.Count > lastIndividualChar ? xOffsets[lastIndividualChar] : 0);
location = new PointF(path.GetBounds().Width + start + letterSpacing, location.Y); yPos = (yAnchors.Count > lastIndividualChar ? yAnchors[lastIndividualChar] : yPos) +
(yOffsets.Count > lastIndividualChar ? yOffsets[lastIndividualChar] : 0);
DrawStringOnCurrPath(value.Substring(lastIndividualChar), font, new PointF(xPos, yPos),
fontBaselineHeight, rotations.LastOrDefault());
var bounds = this.Renderer.MeasureString(value.Substring(lastIndividualChar), font);
xPos += bounds.Width;
} }
NumChars += value.Length;
// Undo any baseline shift. This is not persisted, unlike normal vertical offsets.
this.Current = new PointF(xPos, yPos - baselineShift);
} }
else
private void DrawStringOnCurrPath(string value, IFontDefn font, PointF location, float fontBaselineHeight, float rotation)
{
var drawPath = _currPath;
if (rotation != 0.0f) drawPath = new GraphicsPath();
font.AddStringToPath(this.Renderer, drawPath, value, new PointF(location.X, location.Y - fontBaselineHeight));
if (rotation != 0.0f && drawPath.PointCount > 0)
{ {
path.AddString(word, font.FontFamily, (int)font.Style, font.Size, location, StringFormat.GenericTypographic); var matrix = new Matrix();
matrix.Translate(-1 * location.X, -1 * location.Y, MatrixOrder.Append);
matrix.Rotate(rotation, MatrixOrder.Append);
matrix.Translate(location.X, location.Y, MatrixOrder.Append);
drawPath.Transform(matrix);
_currPath.AddPath(drawPath, false);
} }
// Move the location of the word to be written along
location = new PointF(path.GetBounds().Width + start + wordSpacing, location.Y);
} }
}
else private void EnsurePath()
{
if (_currPath == null)
{ {
if (!string.IsNullOrEmpty(text)) _currPath = new GraphicsPath();
_currPath.StartFigure();
var currState = this;
while (currState != null && currState._xAnchor <= float.MinValue)
{ {
path.AddString(text, font.FontFamily, (int)font.Style, font.Size, location, StringFormat.GenericTypographic); currState = currState.Parent;
} }
currState._anchoredPaths.Add(_currPath);
} }
} }
[SvgAttribute("onchange")] private void FlushPath()
public event EventHandler<StringArg> Change; {
if (_currPath != null)
{
_currPath.CloseFigure();
//change // Abort on empty paths (e.g. rendering a space)
protected void OnChange(string newString, string sessionID) if (_currPath.PointCount < 1)
{ {
RaiseChange(this, new StringArg { s = newString, SessionID = sessionID }); _anchoredPaths.Clear();
_xAnchor = float.MinValue;
_currPath = null;
return;
} }
protected void RaiseChange(object sender, StringArg s) if (_xAnchor > float.MinValue)
{ {
var handler = Change; float minX = float.MaxValue;
if (handler != null) float maxX = float.MinValue;
RectangleF bounds;
foreach (var path in _anchoredPaths)
{ {
handler(sender, s); bounds = path.GetBounds();
if (bounds.Left < minX) minX = bounds.Left;
if (bounds.Right > maxX) maxX = bounds.Right;
} }
var xOffset = _xAnchor - minX;
switch (Element.TextAnchor)
{
case SvgTextAnchor.Middle:
xOffset -= (maxX - minX) / 2;
break;
case SvgTextAnchor.End:
xOffset -= (maxX - minX);
break;
} }
#if Net4 if (xOffset != 0)
public override void RegisterEvents(ISvgEventCaller caller)
{ {
//register basic events var matrix = new Matrix();
base.RegisterEvents(caller); matrix.Translate(xOffset, 0);
foreach (var path in _anchoredPaths)
{
path.Transform(matrix);
}
}
_anchoredPaths.Clear();
_xAnchor = float.MinValue;
//add change event for text
caller.RegisterAction<string, string>(this.ID + "/onchange", OnChange);
} }
public override void UnregisterEvents(ISvgEventCaller caller) if (_finalPath == null)
{ {
//unregister base events _finalPath = _currPath;
base.UnregisterEvents(caller); }
else
//unregister change event {
caller.UnregisterAction(this.ID + "/onchange"); _finalPath.AddPath(_currPath, false);
}
_currPath = null;
}
} }
#endif
private class FontBoundable : ISvgBoundable private IList<float> GetValues(int maxCount, Func<SvgTextBase, IEnumerable<float>> listGetter)
{ {
private Font _font; var currState = this;
int charCount = 0;
var results = new List<float>();
int resultCount = 0;
public FontBoundable(Font font) while (currState != null)
{ {
_font = font; charCount += currState.NumChars;
results.AddRange(listGetter.Invoke(currState.Element).Skip(charCount).Take(maxCount));
if (results.Count > resultCount)
{
maxCount -= results.Count - resultCount;
charCount += results.Count - resultCount;
resultCount = results.Count;
} }
public PointF Location if (maxCount < 1) return results;
{
get { return PointF.Empty; } currState = currState.Parent;
} }
public SizeF Size return results;
{
get { return new SizeF(1, _font.Size); }
} }
private IList<float> GetValues(int maxCount, Func<SvgTextBase, IEnumerable<SvgUnit>> listGetter, UnitRenderingType renderingType)
{
var currState = this;
int charCount = 0;
var results = new List<float>();
int resultCount = 0;
public RectangleF Bounds while (currState != null)
{ {
get { return new RectangleF(this.Location, this.Size); } charCount += currState.NumChars;
results.AddRange(listGetter.Invoke(currState.Element).Skip(charCount).Take(maxCount).Select(p => p.ToDeviceValue(currState.Renderer, renderingType, currState.Element)));
if (results.Count > resultCount)
{
maxCount -= results.Count - resultCount;
charCount += results.Count - resultCount;
resultCount = results.Count;
} }
if (maxCount < 1) return results;
currState = currState.Parent;
} }
return results;
}
}
} }
} }
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Diagnostics;
namespace Svg
{
/// <summary>
/// The <see cref="SvgText"/> element defines a graphics element consisting of text.
/// </summary>
[SvgElement("textPath")]
public class SvgTextPath : SvgTextBase
{
private Uri _referencedPath;
public override SvgUnitCollection Dx
{
get { return null; }
set { /* do nothing */ }
}
[SvgAttribute("startOffset")]
public virtual SvgUnit StartOffset
{
get { return (_dx.Count < 1 ? SvgUnit.None : _dx[0]); }
set
{
if (_dx.Count < 1)
{
_dx.Add(value);
}
else
{
_dx[0] = value;
}
}
}
[SvgAttribute("method")]
public virtual SvgTextPathMethod Method
{
get { return (this.Attributes["method"] == null ? SvgTextPathMethod.align : (SvgTextPathMethod)this.Attributes["method"]); }
set { this.Attributes["method"] = value; }
}
[SvgAttribute("spacing")]
public virtual SvgTextPathSpacing Spacing
{
get { return (this.Attributes["spacing"] == null ? SvgTextPathSpacing.exact : (SvgTextPathSpacing)this.Attributes["spacing"]); }
set { this.Attributes["spacing"] = value; }
}
[SvgAttribute("href", SvgAttributeAttribute.XLinkNamespace)]
public virtual Uri ReferencedPath
{
get { return this._referencedPath; }
set { this._referencedPath = value; }
}
protected override GraphicsPath GetBaselinePath(ISvgRenderer renderer)
{
var path = this.OwnerDocument.IdManager.GetElementById(this.ReferencedPath) as SvgVisualElement;
if (path == null) return null;
var pathData = (GraphicsPath)path.Path(renderer).Clone();
if (path.Transforms.Count > 0)
{
Matrix transformMatrix = new Matrix(1, 0, 0, 1, 0, 0);
foreach (var transformation in path.Transforms)
{
transformMatrix.Multiply(transformation.Matrix);
}
pathData.Transform(transformMatrix);
}
return pathData;
}
protected override float GetAuthorPathLength()
{
var path = this.OwnerDocument.IdManager.GetElementById(this.ReferencedPath) as SvgPath;
if (path == null) return 0;
return path.PathLength;
}
public override SvgElement DeepCopy()
{
return base.DeepCopy<SvgTextPath>();
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Svg
{
[SvgElement("tref")]
public class SvgTextRef : SvgTextBase
{
private Uri _referencedElement;
[SvgAttribute("href", SvgAttributeAttribute.XLinkNamespace)]
public virtual Uri ReferencedElement
{
get { return this._referencedElement; }
set { this._referencedElement = value; }
}
internal override IEnumerable<ISvgNode> GetContentNodes()
{
var refText = this.OwnerDocument.IdManager.GetElementById(this.ReferencedElement) as SvgTextBase;
if (refText == null)
{
return base.GetContentNodes();
}
else
{
return refText.GetContentNodes();
}
}
public override SvgElement DeepCopy()
{
return DeepCopy<SvgTextRef>();
}
public override SvgElement DeepCopy<T>()
{
var newObj = base.DeepCopy<T>() as SvgTextRef;
newObj.X = this.X;
newObj.Y = this.Y;
newObj.Dx = this.Dx;
newObj.Dy = this.Dy;
newObj.Text = this.Text;
newObj.ReferencedElement = this.ReferencedElement;
return newObj;
}
}
}
...@@ -18,14 +18,14 @@ namespace Svg ...@@ -18,14 +18,14 @@ namespace Svg
/// </summary> /// </summary>
SvgTransformCollection Transforms { get; set; } SvgTransformCollection Transforms { get; set; }
/// <summary> /// <summary>
/// Applies the required transforms to <see cref="SvgRenderer"/>. /// Applies the required transforms to <see cref="ISvgRenderer"/>.
/// </summary> /// </summary>
/// <param name="renderer">The <see cref="SvgRenderer"/> to be transformed.</param> /// <param name="renderer">The <see cref="ISvgRenderer"/> to be transformed.</param>
void PushTransforms(SvgRenderer renderer); void PushTransforms(ISvgRenderer renderer);
/// <summary> /// <summary>
/// Removes any previously applied transforms from the specified <see cref="SvgRenderer"/>. /// Removes any previously applied transforms from the specified <see cref="ISvgRenderer"/>.
/// </summary> /// </summary>
/// <param name="renderer">The <see cref="SvgRenderer"/> that should have transforms removed.</param> /// <param name="renderer">The <see cref="ISvgRenderer"/> that should have transforms removed.</param>
void PopTransforms(SvgRenderer renderer); void PopTransforms(ISvgRenderer renderer);
} }
} }
\ No newline at end of file
...@@ -5,6 +5,7 @@ using System.Windows.Forms; ...@@ -5,6 +5,7 @@ using System.Windows.Forms;
using System.Drawing; using System.Drawing;
using System.IO; using System.IO;
using Svg; using Svg;
using System.Diagnostics;
namespace SvgW3CTestRunner namespace SvgW3CTestRunner
{ {
......
<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" version="1.1" width="600" height="507">
<desc>Created with Highcharts 3.0.2</desc>
<defs>
<pattern id="highcharts-pattern-3" patternUnits="userSpaceOnUse" width="25" height="13">
<rect x="0" y="0" width="25" height="13" fill="#ff6600"/>
<image preserveAspectRatio="none" x="0" y="0" width="25" height="13" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAANCAYAAABcrsXuAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAZdEVYdFNvZnR3YXJlAEFkb2JlIEltYWdlUmVhZHlxyWU8AAADJGlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS4zLWMwMTEgNjYuMTQ1NjYxLCAyMDEyLzAyLzA2LTE0OjU2OjI3ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M2IChNYWNpbnRvc2gpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjAyMDc5RTREQ0EwQzExRTI4ODVCRUJEOEE1RkNENjA0IiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjAyMDc5RTRFQ0EwQzExRTI4ODVCRUJEOEE1RkNENjA0Ij4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6MDIwNzlFNEJDQTBDMTFFMjg4NUJFQkQ4QTVGQ0Q2MDQiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6MDIwNzlFNENDQTBDMTFFMjg4NUJFQkQ4QTVGQ0Q2MDQiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7yExAoAAABNUlEQVQ4T5WTMUqEMRBGf0QEQRsLtbHaztpOtrW111q8gL32XsAL2GuppYsgaGchgiBewMYL+L1sZpmZPxF88DbJ5MuSzSaDmMkNOo5leSW/a8vYQ551l3KJQgfmyAyrfDgY38hruVZbxq3crWR+hUKCGnNkAuzwQfod2m6o937xnWRDBn1qo1PYka/yrIzGUGeeXOZCPsutKn1qgV35IY/LqA/z5MhnTuV7lX5gKr/kQRlF8vEAOfKsyxxWR3zKvXk3cCR/apshz7rmFyZKZlK6ETv//dq2/ifWNY/GYUcYsJvk385fb2JbvsjzMopQY47MAn+v//Mm1uW9tOtq15oacwu699rRexNgG+TR2kMOm9mUT3J0rzuQI886jx1161iHN3ky7wZ4C4+1zZBnXevSJIbJLyqPQIBXtVxmAAAAAElFTkSuQmCC"/>
</pattern>
</defs>
<rect fill="url(#highcharts-pattern-3)" x="140.5" y="49.5" width="259" height="387" stroke="#FFFFFF" stroke-width="1" rx="0" ry="0"/>
</svg>
\ No newline at end of file
<svg height="400" width="346" version="1.1" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0,0)" xmlns:xlink="http://www.w3.org/1999/xlink" width="346" height="400" xmlns="http://www.w3.org/2000/svg" version="1.1"><desc>Created with Highcharts 3.0.1</desc><defs><clipPath id="highcharts-3"><rect fill="none" x="0" y="0" width="301" height="201" /></clipPath></defs><rect fill="rgb(255, 255, 255)" stroke="#000" stroke-width="1" x="0.5" y="0.5" width="344" height="398" rx="15" ry="15" /><g class="highcharts-grid" /><g class="highcharts-grid" ><path opacity="1" fill="none" stroke="#000" stroke-width="1" d="M 184.5 40 L 184.5 341" /><path opacity="1" fill="none" stroke="#000" stroke-width="1" d="M 234.5 40 L 234.5 341" /><path opacity="1" fill="none" stroke="#000" stroke-width="1" d="M 285.5 40 L 285.5 341" /><path opacity="1" fill="none" stroke="#000" stroke-width="1" d="M 335.5 40 L 335.5 341" /><path opacity="1" fill="none" stroke="#000" stroke-width="1" d="M 134.5 40 L 134.5 341" /></g><g class="highcharts-axis" ><path opacity="1" fill="none" stroke="#000" stroke-width="1" d="M 134 115.5 L 129 115.5" /><path opacity="1" fill="none" stroke="#000" stroke-width="1" d="M 134 153.5 L 129 153.5" /><path opacity="1" fill="none" stroke="#000" stroke-width="1" d="M 134 191.5 L 129 191.5" /><path opacity="1" fill="none" stroke="#000" stroke-width="1" d="M 134 228.5 L 129 228.5" /><path opacity="1" fill="none" stroke="#000" stroke-width="1" d="M 134 266.5 L 129 266.5" /><path opacity="1" fill="none" stroke="#000" stroke-width="1" d="M 134 303.5 L 129 303.5" /><path opacity="1" fill="none" stroke="#000" stroke-width="1" d="M 134 340.5 L 129 340.5" /><path opacity="1" fill="none" stroke="#000" stroke-width="1" d="M 134 78.5 L 129 78.5" /><path fill="none" stroke="#000" stroke-width="1" d="M 134 40.5 L 129 40.5" /><text visibility="visible" style="font-weight:bold;font-size:13px;font:bold 13px verdana, sans-serif; color: rgb(0, 0, 0); font-size-adjust: none; font-stretch: normal; fill: #000;" text-anchor="middle" transform="rotate(270 25.5 190.5)" x="25.5" y="190.5" ><tspan x="25.5">Destination</tspan></text><path visibility="visible" fill="none" stroke="#000" stroke-width="1" d="M 134.5 40 L 134.5 341" /></g><g class="highcharts-axis" ><text visibility="visible" style="font-weight:bold;font-size:13px;font:bold 13px verdana, sans-serif; color: rgb(0, 0, 0); font-size-adjust: none; font-stretch: normal; fill: #000;" text-anchor="middle" x="235" y="381.1952" ><tspan x="235">Count</tspan></text></g><g class="highcharts-series-group" ><g class="highcharts-series highcharts-tracker" visibility="visible" clip-path="url(#highcharts-3)" transform="translate(336 341) rotate(90) scale(-1 1) scale(1)" width="202" height="301"><rect fill="#b19c67" stroke="#000" stroke-width="1" x="273.5" y="28.5" width="18" height="173" rx="0" ry="0" /><rect fill="#b19c67" stroke="#000" stroke-width="1" x="235.5" y="186.5" width="18" height="15" rx="0" ry="0" /><rect fill="#b19c67" stroke="#000" stroke-width="1" x="197.5" y="192.5" width="18" height="9" rx="0" ry="0" /><rect fill="#b19c67" stroke="#000" stroke-width="1" x="160.5" y="195.5" width="18" height="6" rx="0" ry="0" /><rect fill="#b19c67" stroke="#000" stroke-width="1" x="122.5" y="195.5" width="18" height="6" rx="0" ry="0" /><rect fill="#b19c67" stroke="#000" stroke-width="1" x="85.5" y="196.5" width="18" height="5" rx="0" ry="0" /><rect fill="#b19c67" stroke="#000" stroke-width="1" x="47.5" y="197.5" width="18" height="4" rx="0" ry="0" /><rect fill="#b19c67" stroke="#000" stroke-width="1" x="9.5" y="201.5" width="18" height="0" rx="0" ry="0" /></g><g class="highcharts-markers" visibility="visible" clip-path="none" transform="translate(336 341) rotate(90) scale(-1 1) scale(1)" width="202" height="301" /></g><text class="highcharts-title" style="font-weight:bold;font-size:16px;font:bold 16px lucida grande, lucida sans unicode, verdana, arial, helvetica, sans-serif; color: rgb(0, 0, 0); font-size-adjust: none; font-stretch: normal; fill: #000;" text-anchor="middle" x="173" y="25" ><tspan x="173">Destination Summary</tspan></text><g class="highcharts-data-labels highcharts-tracker" visibility="visible" transform="translate(134 40) scale(1)" ><g visibility="inherit" style="cursor: default; align: left;" transform="translate(174 9)" ><text style="color: black; line-height: 14px; font-family: arial; font-size: 11px; fill: black;" x="3" y="15" ><tspan x="3">858</tspan></text></g><g visibility="inherit" style="cursor: default; align: left;" transform="translate(16 47)" ><text style="color: black; line-height: 14px; font-family: arial; font-size: 11px; fill: black;" x="3" y="15" ><tspan x="3">77</tspan></text></g><g visibility="inherit" style="cursor: default; align: left;" transform="translate(10 85)" ><text style="color: black; line-height: 14px; font-family: arial; font-size: 11px; fill: black;" x="3" y="15" ><tspan x="3">46</tspan></text></g><g visibility="inherit" style="cursor: default; align: left;" transform="translate(7 122)" ><text style="color: black; line-height: 14px; font-family: arial; font-size: 11px; fill: black;" x="3" y="15" ><tspan x="3">33</tspan></text></g><g visibility="inherit" style="cursor: default; align: left;" transform="translate(7 160)" ><text style="color: black; line-height: 14px; font-family: arial; font-size: 11px; fill: black;" x="3" y="15" ><tspan x="3">31</tspan></text></g><g visibility="inherit" style="cursor: default; align: left;" transform="translate(6 197)" ><text style="color: black; line-height: 14px; font-family: arial; font-size: 11px; fill: black;" x="3" y="15" ><tspan x="3">29</tspan></text></g><g visibility="inherit" style="cursor: default; align: left;" transform="translate(5 235)" ><text style="color: black; line-height: 14px; font-family: arial; font-size: 11px; fill: black;" x="3" y="15" ><tspan x="3">22</tspan></text></g><g visibility="inherit" style="cursor: default; align: left;" transform="translate(1 273)" ><text style="color: black; line-height: 14px; font-family: arial; font-size: 11px; fill: black;" x="3" y="15" ><tspan x="3">1</tspan></text></g></g><g class="highcharts-axis-labels" ><text style="color: rgb(0, 0, 0); line-height: 14px; font-family: arial; font-size: 11px; font-weight: bold; cursor: default; fill: #000;" opacity="1" text-anchor="end" x="126" y="65.0875"><tspan x="126">Unknown</tspan></text><text style="color: rgb(0, 0, 0); line-height: 14px; font-family: arial; font-size: 11px; font-weight: bold; cursor: default; fill: #000;" opacity="1" text-anchor="end" x="126" y="102.7125"><tspan x="126">Hospital 2</tspan></text><text style="color: rgb(0, 0, 0); line-height: 14px; font-family: arial; font-size: 11px; font-weight: bold; cursor: default; fill: #000;" opacity="1" text-anchor="end" x="126" y="140.3375"><tspan x="126">Clinic A</tspan></text><text style="color: rgb(0, 0, 0); line-height: 14px; font-family: arial; font-size: 11px; font-weight: bold; cursor: default; fill: #000;" opacity="1" text-anchor="end" x="126" y="177.9625"><tspan x="126">Abacus</tspan></text><text style="color: rgb(0, 0, 0); line-height: 14px; font-family: arial; font-size: 11px; font-weight: bold; cursor: default; fill: #000;" opacity="1" text-anchor="end" x="126" y="215.5875"><tspan x="126">Hospital 3</tspan></text><text style="color: rgb(0, 0, 0); line-height: 14px; font-family: arial; font-size: 11px; font-weight: bold; cursor: default; fill: #000;" opacity="1" text-anchor="end" x="126" y="253.2125"><tspan x="126">Hospital 1</tspan></text><text style="color: rgb(0, 0, 0); line-height: 14px; font-family: arial; font-size: 11px; font-weight: bold; cursor: default; fill: #000;" opacity="1" text-anchor="end" x="126" y="283.8375"><tspan x="126">ABCD Regional</tspan><tspan x="126" dy="14px">Med Ctr</tspan></text><text style="color: rgb(0, 0, 0); line-height: 14px; font-family: arial; font-size: 11px; font-weight: bold; cursor: default; fill: #000;" opacity="1" text-anchor="end" x="126" y="328.4625"><tspan x="126">Hospital 5</tspan></text></g><g class="highcharts-axis-labels" ><text style="color: rgb(0, 0, 0); line-height: 14px; font-family: arial; font-size: 11px; font-weight: bold; cursor: default; fill: #000;" opacity="1" text-anchor="middle" transform="rotate(-30 134 355)" x="134" y="355"><tspan x="134">0</tspan></text><text style="color: rgb(0, 0, 0); line-height: 14px; font-family: arial; font-size: 11px; font-weight: bold; cursor: default; fill: #000;" opacity="1" text-anchor="middle" transform="rotate(-30 336 355)" x="336" y="355"><tspan x="336">1000</tspan></text><text style="color: rgb(0, 0, 0); line-height: 14px; font-family: arial; font-size: 11px; font-weight: bold; cursor: default; fill: #000;" opacity="1" text-anchor="middle" transform="rotate(-30 184.5 355)" x="184.5" y="355"><tspan x="184.5">250</tspan></text><text style="color: rgb(0, 0, 0); line-height: 14px; font-family: arial; font-size: 11px; font-weight: bold; cursor: default; fill: #000;" opacity="1" text-anchor="middle" transform="rotate(-30 235 355)" x="235" y="355"><tspan x="235">500</tspan></text><text style="color: rgb(0, 0, 0); line-height: 14px; font-family: arial; font-size: 11px; font-weight: bold; cursor: default; fill: #000;" opacity="1" text-anchor="middle" transform="rotate(-30 285.5 355)" x="285.5" y="355"><tspan x="285.5">750</tspan></text></g></g></svg>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 17.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="64px" height="64px" viewBox="0 0 64 64" enable-background="new 0 0 64 64" xml:space="preserve">
<symbol id="Square_64_x2A_64" viewBox="-32 -32 64 64">
<g id="Document_3_">
<polygon opacity="0" fill="#ED1C24" points="32,-32 -32,-32 -32,32 32,32 "/>
</g>
</symbol>
<symbol id="Truck" viewBox="0 -64 64 64">
<use xlink:href="#Square_64_x2A_64" width="64" height="64" x="-32" y="-32" transform="matrix(1 0 0 1 32 -32)" overflow="visible"/>
<polygon opacity="0.2" points="59.3,-53.9 6,-53.9 7.2,-48.9 57.1,-48.9 "/>
<polygon fill="#474747" points="54.2,-46.9 8.2,-46.9 8.2,-43.9 54.2,-43.9 "/>
<path fill="#A3A3A3" d="M17.5-30.9v-13h-8v-2H5.2l0,2.6c0,2.1,4.9,11.4,6.6,12.4H17.5z"/>
<path fill="#6D6D6D" d="M17.5-34.9v-13h-8v-2h-4v6.6c0,2.1,2.7,7.4,4.5,8.4H17.5z"/>
<polygon fill="#6D6D6D" points="53.2,-48.9 8.2,-48.9 8.2,-46.9 53.2,-46.9 "/>
<polygon fill="#474747" points="53.2,-48.9 22.3,-48.9 20.3,-46.8 53.2,-46.8 "/>
<path fill="#474747" d="M14.8-48.9c0,1.9-1.6,3.5-3.5,3.5S7.8-47,7.8-48.9H14.8z"/>
<path fill="none" stroke="#6D6D6D" stroke-miterlimit="10" d="M43.9-48.9c0,1.9-1.6,3.5-3.5,3.5s-3.5-1.6-3.5-3.5H43.9z"/>
<path fill="none" stroke="#6D6D6D" stroke-miterlimit="10" d="M50.5-48.9c0,1.9-1.6,3.5-3.5,3.5s-3.5-1.6-3.5-3.5H50.5z"/>
<path fill="#E8E8E8" stroke="#000000" stroke-width="1.2142" stroke-miterlimit="10" d="M13.8-48.9c0,1.4-1.1,2.5-2.5,2.5
s-2.5-1.1-2.5-2.5c0-1.4,1.1-2.5,2.5-2.5S13.8-50.3,13.8-48.9z"/>
<path fill="#E8E8E8" stroke="#000000" stroke-width="1.2142" stroke-miterlimit="10" d="M42.9-48.9c0,1.4-1.1,2.5-2.5,2.5
c-1.4,0-2.5-1.1-2.5-2.5c0-1.4,1.1-2.5,2.5-2.5C41.8-51.4,42.9-50.3,42.9-48.9z"/>
<path fill="#E8E8E8" stroke="#000000" stroke-width="1.2142" stroke-miterlimit="10" d="M49.5-48.9c0,1.4-1.1,2.5-2.5,2.5
c-1.4,0-2.5-1.1-2.5-2.5c0-1.4,1.1-2.5,2.5-2.5C48.4-51.4,49.5-50.3,49.5-48.9z"/>
<polygon fill="#ACD860" points="18.2,-23.9 54,-23.9 56.2,-26.9 18.2,-26.9 "/>
<polygon fill="#789B42" points="18.2,-26.9 18.2,-44.9 56.2,-44.9 56.2,-26.9 "/>
<polygon fill="#C9F772" points="56.2,-27.4 18.2,-27.4 18.2,-26.4 55.8,-26.4 56.2,-26.9 "/>
<path fill="#939393" d="M10.5-48.9c0,0.4,0.4,0.8,0.8,0.8s0.8-0.4,0.8-0.8s-0.4-0.8-0.8-0.8S10.5-49.4,10.5-48.9z"/>
<path fill="#939393" d="M39.6-48.9c0,0.4,0.4,0.8,0.8,0.8c0.4,0,0.8-0.4,0.8-0.8s-0.4-0.8-0.8-0.8C40-49.7,39.6-49.4,39.6-48.9z"/>
<path fill="#939393" d="M46.2-48.9c0,0.4,0.4,0.8,0.8,0.8c0.4,0,0.8-0.4,0.8-0.8s-0.4-0.8-0.8-0.8C46.6-49.7,46.2-49.4,46.2-48.9z"
/>
<path fill="#E8E8E8" d="M10.2-35.9c-0.7,0-2.9-3-3.2-5c2.9,0,4.5,0,4.5,0v5H10.2z"/>
<polygon fill="#E8E8E8" points="15.5,-40.9 12.5,-40.9 12.5,-35.9 15.5,-35.9 "/>
<line fill="none" stroke="#474747" stroke-width="2" stroke-miterlimit="10" x1="4.5" y1="-48.9" x2="8.5" y2="-48.9"/>
<polygon fill="#474747" points="4.2,-47.9 5.5,-44.8 5.5,-47.9 "/>
</symbol>
<use xlink:href="#Truck" width="64" height="64" id="XMLID_30_" y="-64" transform="matrix(1 0 0 -1 0 6.510417e-04)" overflow="visible"/>
</svg>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 17.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="64px" height="64px" viewBox="0 0 64 64" enable-background="new 0 0 64 64" xml:space="preserve">
<symbol id="Person" viewBox="0 -64 64 64">
<!--<use xlink:href="#Square_64_x2A_64" width="64" height="64" x="-32" y="-32" transform="matrix(1 0 0 1 32 -32)" overflow="visible"/>-->
<path opacity="0.2" d="M15.4-51c0,3.2,7.4,5.8,16.4,5.8s16.4-2.6,16.4-5.8c0-3.2-7.4-5.8-16.4-5.8S15.4-54.3,15.4-51z"/>
<path id="XMLID_35_" fill="#3287A5" d="M48.8-50.1L48.8-50.1c-1-3.2-9.5-4.9-16.6-4.9s-15.5,1.6-16.6,4.8l0,0
c-0.6,1.9,0.3,4,2.1,4.9l6,2.9c0.7,0.4,1.2,1.1,1.2,1.9v2.6c0,1.2,0.9,2.1,2.1,2.1h10.1c1.2,0,2.1-0.9,2.1-2.1v-2.6
c0-0.8,0.5-1.6,1.2-1.9l6-2.9C48.5-46.1,49.4-48.2,48.8-50.1z"/>
<g>
<path fill="#9B836B" d="M40.4-42.2c0.5-0.3,0.2-1.6-0.7-3.1c0,0-3.4-5.1-7.7-5.1c-4.3,0-7.6,4.9-7.6,4.9c-1,1.4-1.2,2.8-0.5,3.2
s1.2,2,1.2,3.6s1.4,3,3.1,3h8.1c1.7,0,3.1-1.4,3.1-3S39.9-42,40.4-42.2z"/>
</g>
<path fill="#CCAB87" d="M44.9-29.1c-0.2-1.7-1.2-3-2.4-3.2c-1.6-5.5-5.6-9.4-10.3-9.4c-4.7,0-8.6,3.9-10.2,9.4c0,0,0,0-0.1,0
c-1.3-0.2-2.6,1.3-2.9,3.2c-0.2,2,0.6,3.7,2,3.9c0.1,0,0.2,0,0.3,0c0.6,7.5,5.3,13.2,11,13.2c5.7,0,10.4-5.8,11-13.3
C44.4-25.7,45.1-27.3,44.9-29.1z"/>
<path fill="#493223" d="M42.9-28.3c0,0-1,5.6-2.1,7.3c-1.1,1.7-8-1.7-13-2.2c-5-0.6-7-1.3-6.4-4c-1.3,4.8-1.3,14.5,2.6,17.5
c3.9,3,11.1,0,13.5,0c4.7,0,7.2-5,7.4-7.2C45.2-19.2,45.1-26,42.9-28.3z"/>
<g>
<path fill="#9B836B" d="M21.3-28.5c0,0-0.1,0.1-0.1,0.4C21.1-28,21-27.6,21-27.1c0,0.5,0.1,1.1,0.4,1.7c0.3,0.6,1,1.2,1.8,1.4
c0.8,0.3,1.6,0.4,2.4,0.6c0.8,0.2,1.7,0.4,2.6,0.7c0.9,0.3,1.8,0.6,2.6,1.1c0.4,0.2,0.8,0.5,1.2,0.7c0.4,0.3,1,0.6,1.5,0.8
c1.1,0.4,2.2,0.7,3.3,0.7c1.1,0.1,2.3,0,3.4-0.5c0.6-0.2,1.1-0.5,1.6-1c0.2-0.2,0.5-0.6,0.6-0.7c0.1-0.2,0.3-0.6,0.4-0.8
c0.4-1.1,0.3-2,0.3-2.8C43-26.7,43-27.9,43-28.8c0-0.4,0-0.8,0-1c0-0.2,0-0.4,0-0.4s-0.1,0.5-0.2,1.4c-0.1,0.4-0.2,1-0.3,1.6
c-0.1,0.6-0.2,1.3-0.5,2c-0.2,0.7-0.5,1.5-1,2c-0.1,0.2-0.2,0.2-0.3,0.3c-0.2,0.2-0.2,0.2-0.4,0.2c-0.2,0.1-0.5,0.2-0.9,0.3
c-0.7,0.1-1.5,0.2-2.4,0.2c-0.9,0-1.8-0.1-2.7-0.3c-0.5-0.1-0.8-0.2-1.3-0.4c-0.5-0.2-1-0.3-1.5-0.4c-1-0.3-2-0.4-3-0.6
c-0.9-0.1-1.9-0.3-2.7-0.4c-0.8-0.2-1.7-0.2-2.3-0.5c-0.6-0.3-1.1-0.6-1.5-1c-0.3-0.4-0.5-0.9-0.6-1.3
C21.3-28,21.3-28.5,21.3-28.5L21.3-28.5z"/>
</g>
<g>
<path fill="#2D1F17" d="M21.3-26.5c0,0-0.1,0.1-0.1,0.3c-0.1,0.2-0.2,0.6-0.1,1.1c0.1,0.5,0.4,1,0.9,1.5c0.5,0.5,1.2,0.7,2,0.9
c0.7,0.2,1.5,0.3,2.3,0.6c0.8,0.2,1.7,0.4,2.5,0.8c0.9,0.3,1.7,0.7,2.5,1.1c0.4,0.2,0.8,0.5,1.2,0.7c0.5,0.3,0.9,0.5,1.4,0.7
c1,0.4,2,0.8,3,1c1.1,0.2,2.2,0.3,3.4-0.1c0.6-0.2,1.2-0.5,1.7-1.1l0.2-0.3l0.1-0.1l0.2-0.3c0.2-0.3,0.2-0.5,0.3-0.8
c0.3-1,0.2-1.9,0.2-2.7C43-24.8,43-26,43-26.9c0-0.4,0-0.8,0-1c0-0.2,0-0.4,0-0.4s-0.1,0.5-0.2,1.3c-0.2,0.8-0.3,2.1-0.7,3.5
c-0.2,0.7-0.5,1.4-0.9,2c-0.1,0.1-0.2,0.3-0.3,0.3l-0.2,0.2l-0.1,0.1c0-0.1,0,0,0,0c-0.2,0.1-0.4,0.1-0.7,0.1
c-0.6,0-1.4,0-2.3-0.2c-0.9-0.1-1.7-0.3-2.6-0.6c-0.4-0.1-0.9-0.2-1.3-0.4c-0.4-0.2-1-0.3-1.5-0.5c-1-0.3-2-0.5-2.9-0.6
c-1.9-0.3-3.6-0.5-5-0.8c-0.7-0.1-1.3-0.4-1.7-0.6c-0.5-0.3-0.8-0.6-0.9-1c-0.2-0.4-0.2-0.7-0.2-0.9
C21.3-26.4,21.3-26.5,21.3-26.5L21.3-26.5z"/>
</g>
<g>
<path fill="#2D1F17" d="M37-10c0,0,0.3-0.4,0.8-1.1c0.4-0.7,1-1.7,1.4-2.7c0.2-0.5,0.4-1,0.6-1.5c0.1-0.3,0.1-0.5,0.2-0.7
c0.1-0.2,0.1-0.5,0.2-0.7c0.1-0.9,0.2-1.4,0.2-1.4l-1.2,0c0,0,0,0.5,0,1.3c0,0.2,0,0.4-0.1,0.6c0,0.2-0.1,0.5-0.1,0.7
c-0.1,0.5-0.2,1-0.3,1.5c-0.3,1-0.7,2.1-1,2.8C37.2-10.5,37-10,37-10L37-10z"/>
</g>
<path fill="#CCAB87" d="M41.4-42.7c-1.3-4.5-5.1-7.7-9.4-7.7c-4.3,0-7.8,3.1-9.2,7.5l1.5,0.7c3.5-3.2,9.9-7.2,15.5,0.3L41.4-42.7z"
/>
<path fill="none" stroke="#43B7D6" stroke-miterlimit="10" d="M22.3-43.1c0,0,2.9-7.3,10.2-7.3s8.8,7.7,8.8,7.7"/>
</symbol>
<symbol id="Square_64_x2A_64" viewBox="-32 -32 64 64">
<g id="Document_3_">
<polygon opacity="0" fill="#ED1C24" points="32,-32 -32,-32 -32,32 32,32 "/>
</g>
</symbol>
<use xlink:href="#Person" width="64" height="64" y="-64" transform="matrix(1 0 0 -1 0 6.510417e-04)" overflow="visible"/>
</svg>
\ No newline at end of file
...@@ -63,26 +63,26 @@ ...@@ -63,26 +63,26 @@
<g id="text-on-path-01"> <g id="text-on-path-01">
<use xlink:href="#Path1" fill="none" stroke="blue" /> <use xlink:href="#Path1" fill="none" stroke="blue" />
<text font-size="36" fill="black" > <text font-size="36" fill="black" >
<textPath xlink:href="#Path1"><tspan dx="-240">Positive offset Negative offset</tspan></textPath> <textPath xlink:href="#Path1"><tspan dx="-240">Negative offset</tspan></textPath>
</text> </text>
</g> </g>
<g id="text-on-path-02"> <g id="text-on-path-02">
<use xlink:href="#Path2" fill="none" stroke="blue" /> <use xlink:href="#Path2" fill="none" stroke="blue" />
<text font-size="36" fill="black" > <text font-size="36" fill="black" >
<textPath xlink:href="#Path2" startOffset="-240">Positive offset Negative offset</textPath> <textPath xlink:href="#Path2" startOffset="-240">Negative offset</textPath>
</text> </text>
</g> </g>
<g transform="translate(0 150)"> <g transform="translate(0 150)">
<g id="text-on-path-03"> <g id="text-on-path-03">
<use xlink:href="#Path1" fill="none" stroke="blue" /> <use xlink:href="#Path1" fill="none" stroke="blue" />
<text font-size="36" fill="black" > <text font-size="36" fill="black" >
<textPath xlink:href="#Path1"><tspan dx="60">Positive offset Negative offset</tspan></textPath> <textPath xlink:href="#Path1"><tspan dx="60">Positive offset</tspan></textPath>
</text> </text>
</g> </g>
<g id="text-on-path-04"> <g id="text-on-path-04">
<use xlink:href="#Path2" fill="none" stroke="blue" /> <use xlink:href="#Path2" fill="none" stroke="blue" />
<text font-size="36" fill="black" > <text font-size="36" fill="black" >
<textPath xlink:href="#Path2" startOffset="60">Positive offset Negative offset</textPath> <textPath xlink:href="#Path2" startOffset="60">Positive offset</textPath>
</text> </text>
</g> </g>
</g> </g>
......
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