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);
using (var font = this.Element.GetFont(this.Renderer))
{
var fontBaselineHeight = font.Ascent(this.Renderer);
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
{
this.Renderer.SetBoundable(new FontBoundable(font, (float)(pathStats == null ? 1 : pathStats.TotalLength)));
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 "":
case "baseline":
case "inherit":
// do nothing
break;
case "sub":
baselineShift = new SvgUnit(SvgUnitType.Ex, 1).ToDeviceValue(this.Renderer, UnitRenderingType.Vertical, this.Element);
break;
case "super":
baselineShift = -1 * new SvgUnit(SvgUnitType.Ex, 1).ToDeviceValue(this.Renderer, UnitRenderingType.Vertical, this.Element);
break;
default:
var convert = new SvgUnitConverter();
var shiftUnit = (SvgUnit)convert.ConvertFromInvariantString(baselineShiftText);
baselineShift = -1 * shiftUnit.ToDeviceValue(this.Renderer, UnitRenderingType.Vertical, this.Element);
break;
}
if (baselineShift != 0.0f)
{
if (yOffsets.Any())
{
yOffsets[0] += baselineShift;
}
else
{
yOffsets.Add(baselineShift);
}
}
}
finally
{
this.Renderer.PopBoundable();
}
// NOTE: Assuming a horizontal left-to-right font
// 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()));
}
// 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();
// 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);
if (rotations.LastOrDefault() != 0.0f || pathStats != null) lastIndividualChar = value.Length;
if (lastIndividualChar > renderChar)
{
var charBounds = font.MeasureCharacters(this.Renderer, value.Substring(renderChar, Math.Min(lastIndividualChar + 1, value.Length) - renderChar));
PointF pathPoint;
float rotation;
float halfWidth;
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));
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
{
xPos = Math.Max(xPos, 0);
halfWidth = charBounds[i - renderChar].Width / 2;
if (pathStats.OffsetOnPath(xPos + halfWidth))
{
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);
}
}
}
// Add the kerning to the next character
if (lastIndividualChar < value.Length)
{
xPos += charBounds[charBounds.Count - 1].X - charBounds[charBounds.Count - 2].X;
}
else
{
xPos += charBounds.Last().Width;
}
}
// Render the string normally
if (lastIndividualChar < value.Length)
{
xPos += (xOffsets.Count > lastIndividualChar ? xOffsets[lastIndividualChar] : 0);
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 = font.MeasureString(this.Renderer, value.Substring(lastIndividualChar));
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);
}
}