public static SvgUnitCollection Parse(ReadOnlySpan <char> points) { var units = new SvgUnitCollection(); var splitChars = SplitChars.AsSpan(); var parts = new StringSplitEnumerator(points, splitChars); foreach (var part in parts) { var newUnit = SvgUnitConverter.Parse(part.Value); if (!newUnit.IsNone) { units.Add(newUnit); } } return(units); }
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 = Element.BaselineShift.Trim().ToLower(); if (string.IsNullOrEmpty(baselineShiftText)) { baselineShiftText = "baseline"; } switch (baselineShiftText) { case "baseline": // do nothing break; case "sub": baselineShift = new SvgUnit(SvgUnitType.Ex, 1).ToDeviceValue(this.Renderer, UnitRenderingType.Vertical, this.Element); break; case "super": baselineShift = -1f * 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 = -1f * 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(); } var xTextStart = Current.X; // 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); xTextStart = xTextStart.Equals(Current.X) ? _xAnchor : xTextStart; 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) { xTextStart = xTextStart.Equals(Current.X) ? xPos : xTextStart; 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))); xTextStart = xTextStart.Equals(Current.X) ? pathPoint.X : xTextStart; 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); xTextStart = xTextStart.Equals(Current.X) ? xPos : xTextStart; 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); this.TextBounds = new RectangleF(xTextStart, 0, this.Current.X - xTextStart, 0); } }
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); } }
/// <summary> /// 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 //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) { renderer = (renderer ?? SvgRenderer.FromNull()); // Measure the overall bounds of all the text var boundsData = GetTextBounds(renderer); var font = GetFont(renderer); SvgTextBase innerText; float x = (_x.Count < 1 ? _calcX : _x[0].ToDeviceValue(renderer, UnitRenderingType.HorizontalOffset, this)) + (_dx.Count < 1 ? 0 : _dx[0].ToDeviceValue(renderer, UnitRenderingType.Horizontal, this)); float y = (_y.Count < 1 ? _calcY : _y[0].ToDeviceValue(renderer, UnitRenderingType.VerticalOffset, this)) + (_dy.Count < 1 ? 0 : _dy[0].ToDeviceValue(renderer, UnitRenderingType.Vertical, this)); _path = new GraphicsPath(); _path.StartFigure(); // Determine the location of the start point switch (this.TextAnchor) { case SvgTextAnchor.Middle: x -= (boundsData.Bounds.Width / 2); break; case SvgTextAnchor.End: x -= boundsData.Bounds.Width; break; } try { renderer.Boundable(new FontBoundable(font)); switch (this.BaselineShift) { case null: case "": case "baseline": case "inherit": // do nothing break; case "sub": y += new SvgUnit(SvgUnitType.Ex, 1).ToDeviceValue(renderer, UnitRenderingType.Vertical, this); break; case "super": y -= new SvgUnit(SvgUnitType.Ex, 1).ToDeviceValue(renderer, UnitRenderingType.Vertical, this); break; default: var convert = new SvgUnitConverter(); var shift = (SvgUnit)convert.ConvertFromInvariantString(this.BaselineShift); y -= shift.ToDeviceValue(renderer, UnitRenderingType.Vertical, this); break; } } finally { renderer.PopBoundable(); } NodeBounds data; var yCummOffset = 0.0f; for (var i = 0; i < boundsData.Nodes.Count; i++) { data = boundsData.Nodes[i]; innerText = data.Node as SvgTextBase; if (innerText == null) { // Minus FontSize because the x/y coords mark the bottom left, not bottom top. 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 { innerText._calcX = x + data.xOffset; innerText._calcY = y + yCummOffset; if (innerText.Dy.Count == 1) { yCummOffset += innerText.Dy[0].ToDeviceValue(renderer, UnitRenderingType.Vertical, this); } } } _path.CloseFigure(); this.IsPathDirty = false; } return(_path); }