From 46e375e4132f10f53c23698cab18889a2e92cf72 Mon Sep 17 00:00:00 2001 From: Eric Domke Date: Sun, 17 Aug 2014 22:28:20 -0400 Subject: [PATCH] Bug Fixes - Fixing path parsing algorithm to deal with nuanced arc cases and hopefully improve performance - Attempts at better memory management - Working toward getting symbols to render correctly --- Samples/SVGViewer/DebugRenderer.cs | 115 +++ Samples/SVGViewer/SVGViewer.csproj | 1 + Samples/SVGViewer/SvgViewer.cs | 2 + Source/Basic Shapes/SvgImage.cs | 27 +- Source/Css/SvgElementOps.cs | 8 +- Source/DataTypes/SvgUnit.cs | 39 +- Source/DataTypes/SvgViewBox.cs | 6 +- Source/Document Structure/SvgSymbol.cs | 6 + Source/Document Structure/SvgUse.cs | 32 +- .../feColourMatrix/SvgColourMatrix.cs | 20 +- Source/Painting/SvgMarker.cs | 84 +- Source/Painting/SvgRadialGradientServer.cs | 32 +- Source/Paths/SvgPathBuilder.cs | 783 +++++++++++++++--- Source/Rendering/ISvgRenderer.cs | 3 - Source/Rendering/SvgRenderer.cs | 15 - Source/SvgDocument.cs | 9 +- Source/SvgElement.cs | 6 +- Source/SvgElementFactory.cs | 13 +- Source/Text/GdiFontDefn.cs | 5 + Source/Text/IFontDefn.cs | 2 +- Source/Text/SvgFontDefn.cs | 21 +- Source/Text/SvgTextBase.cs | 322 +++---- Source/Transforms/SvgScale.cs | 2 +- Tests/SvgW3CTestRunner/View.cs | 1 + 24 files changed, 1124 insertions(+), 430 deletions(-) create mode 100644 Samples/SVGViewer/DebugRenderer.cs diff --git a/Samples/SVGViewer/DebugRenderer.cs b/Samples/SVGViewer/DebugRenderer.cs new file mode 100644 index 0000000..48bbe33 --- /dev/null +++ b/Samples/SVGViewer/DebugRenderer.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Svg; +using System.Drawing.Drawing2D; +using System.Drawing; + +namespace SVGViewer +{ + class DebugRenderer : ISvgRenderer + { + private Region _clip = new Region(); + private Matrix _transform = new Matrix(); + private Stack _boundables = new Stack(); + + public void SetBoundable(ISvgBoundable boundable) + { + _boundables.Push(boundable); + } + public ISvgBoundable GetBoundable() + { + return _boundables.Peek(); + } + public ISvgBoundable PopBoundable() + { + return _boundables.Pop(); + } + + + public float DpiY + { + get { return 96; } + } + + public void DrawImage(Image image, RectangleF destRect, RectangleF srcRect, GraphicsUnit graphicsUnit) + { + + } + public void DrawImageUnscaled(Image image, Point location) + { + + } + public void DrawPath(Pen pen, GraphicsPath path) + { + var newPath = (GraphicsPath)path.Clone(); + newPath.Transform(_transform); + + } + public void FillPath(Brush brush, GraphicsPath path) + { + var newPath = (GraphicsPath)path.Clone(); + newPath.Transform(_transform); + } + public Region GetClip() + { + return _clip; + } + public void RotateTransform(float fAngle, MatrixOrder order = MatrixOrder.Append) + { + _transform.Rotate(fAngle, order); + } + public void ScaleTransform(float sx, float sy, MatrixOrder order = MatrixOrder.Append) + { + _transform.Scale(sx, sy, order); + } + public void SetClip(Region region, CombineMode combineMode = CombineMode.Replace) + { + switch (combineMode) + { + case CombineMode.Intersect: + _clip.Intersect(region); + break; + case CombineMode.Complement: + _clip.Complement(region); + break; + case CombineMode.Exclude: + _clip.Exclude(region); + break; + case CombineMode.Union: + _clip.Union(region); + break; + case CombineMode.Xor: + _clip.Xor(region); + break; + default: + _clip = region; + break; + } + } + public void TranslateTransform(float dx, float dy, MatrixOrder order = MatrixOrder.Append) + { + _transform.Translate(dx, dy, order); + } + + + + public SmoothingMode SmoothingMode + { + get { return SmoothingMode.Default; } + set { /* Do Nothing */ } + } + + public Matrix Transform + { + get { return _transform; } + set { _transform = value; } + } + + public void Dispose() + { + + } + } +} diff --git a/Samples/SVGViewer/SVGViewer.csproj b/Samples/SVGViewer/SVGViewer.csproj index 3fe0abc..6a0bd09 100644 --- a/Samples/SVGViewer/SVGViewer.csproj +++ b/Samples/SVGViewer/SVGViewer.csproj @@ -93,6 +93,7 @@ + Form diff --git a/Samples/SVGViewer/SvgViewer.cs b/Samples/SVGViewer/SvgViewer.cs index 5cf67fa..ab5d75f 100644 --- a/Samples/SVGViewer/SvgViewer.cs +++ b/Samples/SVGViewer/SvgViewer.cs @@ -43,6 +43,8 @@ namespace SVGViewer private void RenderSvg(SvgDocument svgDoc) { + var render = new DebugRenderer(); + svgDoc.Draw(render); svgImage.Image = svgDoc.Draw(); } } diff --git a/Source/Basic Shapes/SvgImage.cs b/Source/Basic Shapes/SvgImage.cs index d6b21a9..aa69f1d 100644 --- a/Source/Basic Shapes/SvgImage.cs +++ b/Source/Basic Shapes/SvgImage.cs @@ -205,8 +205,10 @@ namespace Svg // we're assuming base64, as ascii encoding would be *highly* unsusual for images // also assuming it's png or jpeg mimetype byte[] imageBytes = Convert.FromBase64String(uriString.Substring(dataIdx)); - Image image = Image.FromStream(new MemoryStream(imageBytes)); - return image; + using (var stream = new MemoryStream(imageBytes)) + { + return Image.FromStream(stream); + } } if (!uri.IsAbsoluteUri) @@ -219,16 +221,19 @@ namespace Svg using (WebResponse webResponse = httpRequest.GetResponse()) { - MemoryStream ms = BufferToMemoryStream(webResponse.GetResponseStream()); - if (uri.LocalPath.EndsWith(".svg", StringComparison.InvariantCultureIgnoreCase)) - { - var doc = SvgDocument.Open(ms); - doc.BaseUri = uri; - return doc.Draw(); - } - else + using (var stream = webResponse.GetResponseStream()) { - return Bitmap.FromStream(ms); + stream.Position = 0; + if (uri.LocalPath.EndsWith(".svg", StringComparison.InvariantCultureIgnoreCase)) + { + var doc = SvgDocument.Open(stream); + doc.BaseUri = uri; + return doc.Draw(); + } + else + { + return Bitmap.FromStream(stream); + } } } } diff --git a/Source/Css/SvgElementOps.cs b/Source/Css/SvgElementOps.cs index 8928d4b..bf9b2be 100644 --- a/Source/Css/SvgElementOps.cs +++ b/Source/Css/SvgElementOps.cs @@ -10,8 +10,12 @@ namespace Svg.Css { public Selector Type(NamespacePrefix prefix, string name) { - var type = SvgElementFactory.AvailableElements.SingleOrDefault(e => e.ElementName == name); - return nodes => nodes.Where(n => n.GetType() == type.ElementType); + SvgElementFactory.ElementInfo type = null; + if (SvgElementFactory.AvailableElements.TryGetValue(name, out type)) + { + return nodes => nodes.Where(n => n.GetType() == type.ElementType); + } + return nodes => Enumerable.Empty(); } public Selector Universal(NamespacePrefix prefix) diff --git a/Source/DataTypes/SvgUnit.cs b/Source/DataTypes/SvgUnit.cs index 0130f24..524b1df 100644 --- a/Source/DataTypes/SvgUnit.cs +++ b/Source/DataTypes/SvgUnit.cs @@ -107,34 +107,37 @@ namespace Svg } float points; - IFontDefn currFont; switch (type) { case SvgUnitType.Em: - currFont = GetFont(renderer, owner); - if (currFont == null) + using (var currFont = GetFont(renderer, owner)) { - points = (float)(value * 9); - _deviceValue = (points / 72.0f) * ppi; - } - else - { - _deviceValue = value * (currFont.SizeInPoints / 72.0f) * ppi; + if (currFont == null) + { + points = (float)(value * 9); + _deviceValue = (points / 72.0f) * ppi; + } + else + { + _deviceValue = value * (currFont.SizeInPoints / 72.0f) * ppi; + } } break; case SvgUnitType.Ex: - currFont = GetFont(renderer, owner); - if (currFont == null) - { - points = (float)(value * 9); - _deviceValue = (points * 0.5f / 72.0f) * ppi; - } - else + using (var currFont = GetFont(renderer, owner)) { - _deviceValue = value * 0.5f * (currFont.SizeInPoints / 72.0f) * ppi; + if (currFont == null) + { + points = (float)(value * 9); + _deviceValue = (points * 0.5f / 72.0f) * ppi; + } + else + { + _deviceValue = value * 0.5f * (currFont.SizeInPoints / 72.0f) * ppi; + } + break; } - break; case SvgUnitType.Centimeter: _deviceValue = (float)((value / cmInInch) * ppi); break; diff --git a/Source/DataTypes/SvgViewBox.cs b/Source/DataTypes/SvgViewBox.cs index 62d56ab..7795fd9 100644 --- a/Source/DataTypes/SvgViewBox.cs +++ b/Source/DataTypes/SvgViewBox.cs @@ -133,8 +133,8 @@ namespace Svg var fScaleX = width / this.Width; var fScaleY = height / this.Height; //(this.MinY < 0 ? -1 : 1) * - var fMinX = this.MinX; - var fMinY = this.MinY; + var fMinX = -this.MinX; + var fMinY = -this.MinY; if (aspectRatio == null) aspectRatio = new SvgAspectRatio(SvgPreserveAspectRatio.xMidYMid, false); if (aspectRatio.Align != SvgPreserveAspectRatio.none) @@ -197,7 +197,7 @@ namespace Svg renderer.SetClip(new Region(new RectangleF(x, y, width, height)), CombineMode.Intersect); renderer.ScaleTransform(fScaleX, fScaleY, MatrixOrder.Prepend); renderer.TranslateTransform(x, y); - renderer.TranslateTransform(-fMinX, -fMinY); + renderer.TranslateTransform(fMinX, fMinY); } } diff --git a/Source/Document Structure/SvgSymbol.cs b/Source/Document Structure/SvgSymbol.cs index f0ebd2f..97661f9 100644 --- a/Source/Document Structure/SvgSymbol.cs +++ b/Source/Document Structure/SvgSymbol.cs @@ -91,6 +91,12 @@ namespace Svg.Document_Structure return true; } + // Only render if the parent is set to a Use element + protected override void Render(ISvgRenderer renderer) + { + if (_parent is SvgUse) base.Render(renderer); + } + public override SvgElement DeepCopy() { return DeepCopy(); diff --git a/Source/Document Structure/SvgUse.cs b/Source/Document Structure/SvgUse.cs index ba2b6ab..5987487 100644 --- a/Source/Document Structure/SvgUse.cs +++ b/Source/Document Structure/SvgUse.cs @@ -41,7 +41,7 @@ namespace Svg protected internal override bool PushTransforms(ISvgRenderer renderer) { if (!base.PushTransforms(renderer)) return false; - renderer.TranslateTransform(this.X.ToDeviceValue(renderer, UnitRenderingType.Horizontal, this), + renderer.TranslateTransform(this.X.ToDeviceValue(renderer, UnitRenderingType.Horizontal, this), this.Y.ToDeviceValue(renderer, UnitRenderingType.Vertical, this)); return true; } @@ -70,20 +70,22 @@ namespace Svg protected override void Render(ISvgRenderer renderer) { - if (!Visible || !Displayable) - return; - - this.PushTransforms(renderer); - - SvgVisualElement element = (SvgVisualElement)this.OwnerDocument.IdManager.GetElementById(this.ReferencedElement); - // For the time of rendering we want the referenced element to inherit - // this elements transforms - SvgElement parent = element._parent; - element._parent = this; - element.RenderElement(renderer); - element._parent = parent; - - this.PopTransforms(renderer); + if (this.Visible && this.Displayable && this.PushTransforms(renderer)) + { + this.SetClip(renderer); + + var element = (SvgVisualElement)this.OwnerDocument.IdManager.GetElementById(this.ReferencedElement); + if (element != null) + { + var origParent = element.Parent; + element._parent = this; + element.RenderElement(renderer); + element._parent = origParent; + } + + this.ResetClip(renderer); + this.PopTransforms(renderer); + } } diff --git a/Source/Filter Effects/feColourMatrix/SvgColourMatrix.cs b/Source/Filter Effects/feColourMatrix/SvgColourMatrix.cs index e577ef3..705780c 100644 --- a/Source/Filter Effects/feColourMatrix/SvgColourMatrix.cs +++ b/Source/Filter Effects/feColourMatrix/SvgColourMatrix.cs @@ -86,17 +86,19 @@ namespace Svg.FilterEffects } var colorMatrix = new ColorMatrix(colorMatrixElements); - var imageAttrs = new ImageAttributes(); - imageAttrs.SetColorMatrix(colorMatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap); - - var result = new Bitmap(inputImage.Width, inputImage.Height); - using (var g = Graphics.FromImage(result)) + using (var imageAttrs = new ImageAttributes()) { - g.DrawImage(inputImage, new Rectangle(0, 0, inputImage.Width, inputImage.Height), - 0, 0, inputImage.Width, inputImage.Height, GraphicsUnit.Pixel, imageAttrs); - g.Flush(); + imageAttrs.SetColorMatrix(colorMatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap); + + var result = new Bitmap(inputImage.Width, inputImage.Height); + using (var g = Graphics.FromImage(result)) + { + g.DrawImage(inputImage, new Rectangle(0, 0, inputImage.Width, inputImage.Height), + 0, 0, inputImage.Width, inputImage.Height, GraphicsUnit.Pixel, imageAttrs); + g.Flush(); + } + buffer[this.Result] = result; } - buffer[this.Result] = result; } diff --git a/Source/Painting/SvgMarker.cs b/Source/Painting/SvgMarker.cs index a959e3d..0876f5f 100644 --- a/Source/Painting/SvgMarker.cs +++ b/Source/Painting/SvgMarker.cs @@ -170,45 +170,47 @@ namespace Svg /// private void RenderPart2(float fAngle, ISvgRenderer pRenderer, SvgPath pOwner, PointF pMarkerPoint) { - Pen pRenderPen = CreatePen(pOwner, pRenderer); - - GraphicsPath markerPath = GetClone(pOwner); - - Matrix transMatrix = new Matrix(); - transMatrix.Translate(pMarkerPoint.X, pMarkerPoint.Y); - if (Orient.IsAuto) - transMatrix.Rotate(fAngle); - else - transMatrix.Rotate(Orient.Angle); - switch (MarkerUnits) - { - case SvgMarkerUnits.strokeWidth: - transMatrix.Translate(AdjustForViewBoxWidth(-RefX.ToDeviceValue(pRenderer, UnitRenderingType.Horizontal, this) * - pOwner.StrokeWidth.ToDeviceValue(pRenderer, UnitRenderingType.Other, this)), - AdjustForViewBoxHeight(-RefY.ToDeviceValue(pRenderer, UnitRenderingType.Vertical, this) * - pOwner.StrokeWidth.ToDeviceValue(pRenderer, UnitRenderingType.Other, this))); - break; - case SvgMarkerUnits.userSpaceOnUse: - transMatrix.Translate(-RefX.ToDeviceValue(pRenderer, UnitRenderingType.Horizontal, this), - -RefY.ToDeviceValue(pRenderer, UnitRenderingType.Vertical, this)); - break; - } - markerPath.Transform(transMatrix); - pRenderer.DrawPath(pRenderPen, markerPath); - - SvgPaintServer pFill = Fill; - SvgFillRule pFillRule = FillRule; // TODO: What do we use the fill rule for? - float fOpacity = FillOpacity; - - if (pFill != null) + using (var pRenderPen = CreatePen(pOwner, pRenderer)) { - Brush pBrush = pFill.GetBrush(this, pRenderer, fOpacity); - pRenderer.FillPath(pBrush, markerPath); - pBrush.Dispose(); + using (var markerPath = GetClone(pOwner)) + { + using (var transMatrix = new Matrix()) + { + transMatrix.Translate(pMarkerPoint.X, pMarkerPoint.Y); + if (Orient.IsAuto) + transMatrix.Rotate(fAngle); + else + transMatrix.Rotate(Orient.Angle); + switch (MarkerUnits) + { + case SvgMarkerUnits.strokeWidth: + transMatrix.Translate(AdjustForViewBoxWidth(-RefX.ToDeviceValue(pRenderer, UnitRenderingType.Horizontal, this) * + pOwner.StrokeWidth.ToDeviceValue(pRenderer, UnitRenderingType.Other, this)), + AdjustForViewBoxHeight(-RefY.ToDeviceValue(pRenderer, UnitRenderingType.Vertical, this) * + pOwner.StrokeWidth.ToDeviceValue(pRenderer, UnitRenderingType.Other, this))); + break; + case SvgMarkerUnits.userSpaceOnUse: + transMatrix.Translate(-RefX.ToDeviceValue(pRenderer, UnitRenderingType.Horizontal, this), + -RefY.ToDeviceValue(pRenderer, UnitRenderingType.Vertical, this)); + break; + } + markerPath.Transform(transMatrix); + pRenderer.DrawPath(pRenderPen, markerPath); + + SvgPaintServer pFill = Fill; + SvgFillRule pFillRule = FillRule; // TODO: What do we use the fill rule for? + float fOpacity = FillOpacity; + + if (pFill != null) + { + using (var pBrush = pFill.GetBrush(this, pRenderer, fOpacity)) + { + pRenderer.FillPath(pBrush, markerPath); + } + } + } + } } - pRenderPen.Dispose(); - markerPath.Dispose(); - transMatrix.Dispose(); } /// @@ -240,9 +242,11 @@ namespace Svg switch (MarkerUnits) { case SvgMarkerUnits.strokeWidth: - Matrix transMatrix = new Matrix(); - transMatrix.Scale(AdjustForViewBoxWidth(pPath.StrokeWidth), AdjustForViewBoxHeight(pPath.StrokeWidth)); - pRet.Transform(transMatrix); + using (var transMatrix = new Matrix()) + { + transMatrix.Scale(AdjustForViewBoxWidth(pPath.StrokeWidth), AdjustForViewBoxHeight(pPath.StrokeWidth)); + pRet.Transform(transMatrix); + } break; case SvgMarkerUnits.userSpaceOnUse: break; diff --git a/Source/Painting/SvgRadialGradientServer.cs b/Source/Painting/SvgRadialGradientServer.cs index 240f87e..f6a98bc 100644 --- a/Source/Painting/SvgRadialGradientServer.cs +++ b/Source/Painting/SvgRadialGradientServer.cs @@ -128,11 +128,13 @@ namespace Svg // Transform the path based on the scaling var gradBounds = path.GetBounds(); var transCenter = new PointF(gradBounds.Left + gradBounds.Width / 2, gradBounds.Top + gradBounds.Height / 2); - var scaleMat = new Matrix(); - scaleMat.Translate(-1 * transCenter.X, -1 * transCenter.Y, MatrixOrder.Append); - scaleMat.Scale(scale, scale, MatrixOrder.Append); - scaleMat.Translate(transCenter.X, transCenter.Y, MatrixOrder.Append); - path.Transform(scaleMat); + using (var scaleMat = new Matrix()) + { + scaleMat.Translate(-1 * transCenter.X, -1 * transCenter.Y, MatrixOrder.Append); + scaleMat.Scale(scale, scale, MatrixOrder.Append); + scaleMat.Translate(transCenter.X, transCenter.Y, MatrixOrder.Append); + path.Transform(scaleMat); + } // calculate the brush var brush = new PathGradientBrush(path); @@ -167,16 +169,18 @@ namespace Svg }; var pathBounds = path.GetBounds(); var pathCenter = new PointF(pathBounds.X + pathBounds.Width / 2, pathBounds.Y + pathBounds.Height / 2); - var transform = new Matrix(); - transform.Translate(-1 * pathCenter.X, -1 * pathCenter.Y, MatrixOrder.Append); - transform.Scale(.95f, .95f, MatrixOrder.Append); - transform.Translate(pathCenter.X, pathCenter.Y, MatrixOrder.Append); - - var boundsTest = RectangleF.Inflate(bounds, 0, 0); - while (!(path.IsVisible(points[0]) && path.IsVisible(points[1]) && - path.IsVisible(points[2]) && path.IsVisible(points[3]))) + using (var transform = new Matrix()) { - transform.TransformPoints(points); + transform.Translate(-1 * pathCenter.X, -1 * pathCenter.Y, MatrixOrder.Append); + transform.Scale(.95f, .95f, MatrixOrder.Append); + transform.Translate(pathCenter.X, pathCenter.Y, MatrixOrder.Append); + + var boundsTest = RectangleF.Inflate(bounds, 0, 0); + while (!(path.IsVisible(points[0]) && path.IsVisible(points[1]) && + path.IsVisible(points[2]) && path.IsVisible(points[3]))) + { + transform.TransformPoints(points); + } } return bounds.Height / (points[2].Y - points[1].Y); } diff --git a/Source/Paths/SvgPathBuilder.cs b/Source/Paths/SvgPathBuilder.cs index 740e4f5..2bd64fe 100644 --- a/Source/Paths/SvgPathBuilder.cs +++ b/Source/Paths/SvgPathBuilder.cs @@ -12,14 +12,14 @@ using Svg.Pathing; namespace Svg { - public static class PointFExtensions - { - public static string ToSvgString(this PointF p) - { - return p.X.ToString() + " " + p.Y.ToString(); - } - } - + public static class PointFExtensions + { + public static string ToSvgString(this PointF p) + { + return p.X.ToString() + " " + p.Y.ToString(); + } + } + public class SvgPathBuilder : TypeConverter { /// @@ -37,18 +37,16 @@ namespace Svg try { - List coords; char command; bool isRelative; foreach (var commandSet in SplitCommands(path.TrimEnd(null))) { - coords = new List(ParseCoordinates(commandSet.Trim())); command = commandSet[0]; isRelative = char.IsLower(command); // http://www.w3.org/TR/SVG11/paths.html#PathDataGeneralInformation - CreatePathSegment(command, segments, coords, isRelative); + CreatePathSegment(command, segments, new CoordinateParser(commandSet.Trim()), isRelative); } } catch (Exception exc) @@ -59,111 +57,124 @@ namespace Svg return segments; } - public static void CreatePathSegment(char command, SvgPathSegmentList segments, List coords, bool isRelative) + private static void CreatePathSegment(char command, SvgPathSegmentList segments, CoordinateParser parser, bool isRelative) { - - switch (command) - { - case 'm': // relative moveto - case 'M': // moveto - segments.Add( - new SvgMoveToSegment(ToAbsolute(coords[0], coords[1], segments, isRelative))); - for (var i = 2; i < coords.Count; i += 2) - { - segments.Add(new SvgLineSegment(segments.Last.End, - ToAbsolute(coords[i], coords[i + 1], segments, isRelative))); - } - break; - case 'a': - case 'A': - SvgArcSize size; - SvgArcSweep sweep; + var coords = new float[6]; - for (var i = 0; i < coords.Count; i += 7) - { - size = (coords[i + 3] != 0.0f) ? SvgArcSize.Large : SvgArcSize.Small; - sweep = (coords[i + 4] != 0.0f) ? SvgArcSweep.Positive : SvgArcSweep.Negative; + switch (command) + { + case 'm': // relative moveto + case 'M': // moveto + if (parser.TryGetFloat(out coords[0]) && parser.TryGetFloat(out coords[1])) + { + segments.Add(new SvgMoveToSegment(ToAbsolute(coords[0], coords[1], segments, isRelative))); + } - // A|a rx ry x-axis-rotation large-arc-flag sweep-flag x y - segments.Add(new SvgArcSegment(segments.Last.End, coords[i], coords[i + 1], coords[i + 2], - size, sweep, ToAbsolute(coords[i + 5], coords[i + 6], segments, isRelative))); - } - break; - case 'l': // relative lineto - case 'L': // lineto - for (var i = 0; i < coords.Count; i += 2) - { - segments.Add(new SvgLineSegment(segments.Last.End, - ToAbsolute(coords[i], coords[i + 1], segments, isRelative))); - } - break; - case 'H': // horizontal lineto - case 'h': // relative horizontal lineto - foreach (var value in coords) - segments.Add(new SvgLineSegment(segments.Last.End, - ToAbsolute(value, segments.Last.End.Y, segments, isRelative, false))); - break; - case 'V': // vertical lineto - case 'v': // relative vertical lineto - foreach (var value in coords) - segments.Add(new SvgLineSegment(segments.Last.End, - ToAbsolute(segments.Last.End.X, value, segments, false, isRelative))); - break; - case 'Q': // curveto - case 'q': // relative curveto - for (var i = 0; i < coords.Count; i += 4) - { - segments.Add(new SvgQuadraticCurveSegment(segments.Last.End, - ToAbsolute(coords[i], coords[i + 1], segments, isRelative), - ToAbsolute(coords[i + 2], coords[i + 3], segments, isRelative))); - } - break; - case 'T': // shorthand/smooth curveto - case 't': // relative shorthand/smooth curveto - for (var i = 0; i < coords.Count; i += 2) - { - var lastQuadCurve = segments.Last as SvgQuadraticCurveSegment; + while (parser.TryGetFloat(out coords[0]) && parser.TryGetFloat(out coords[1])) + { + segments.Add(new SvgLineSegment(segments.Last.End, + ToAbsolute(coords[0], coords[1], segments, isRelative))); + } + break; + case 'a': + case 'A': + bool size; + bool sweep; + + while (parser.TryGetFloat(out coords[0]) && parser.TryGetFloat(out coords[1]) && + parser.TryGetFloat(out coords[2]) && parser.TryGetBool(out size) && + parser.TryGetBool(out sweep) && parser.TryGetFloat(out coords[3]) && + parser.TryGetFloat(out coords[4])) + { + // A|a rx ry x-axis-rotation large-arc-flag sweep-flag x y + segments.Add(new SvgArcSegment(segments.Last.End, coords[0], coords[1], coords[2], + (size ? SvgArcSize.Large : SvgArcSize.Small), + (sweep ? SvgArcSweep.Positive : SvgArcSweep.Negative), + ToAbsolute(coords[3], coords[4], segments, isRelative))); + } + break; + case 'l': // relative lineto + case 'L': // lineto + while (parser.TryGetFloat(out coords[0]) && parser.TryGetFloat(out coords[1])) + { + segments.Add(new SvgLineSegment(segments.Last.End, + ToAbsolute(coords[0], coords[1], segments, isRelative))); + } + break; + case 'H': // horizontal lineto + case 'h': // relative horizontal lineto + while (parser.TryGetFloat(out coords[0])) + { + segments.Add(new SvgLineSegment(segments.Last.End, + ToAbsolute(coords[0], segments.Last.End.Y, segments, isRelative, false))); + } + break; + case 'V': // vertical lineto + case 'v': // relative vertical lineto + while (parser.TryGetFloat(out coords[0])) + { + segments.Add(new SvgLineSegment(segments.Last.End, + ToAbsolute(segments.Last.End.X, coords[0], segments, false, isRelative))); + } + break; + case 'Q': // curveto + case 'q': // relative curveto + while (parser.TryGetFloat(out coords[0]) && parser.TryGetFloat(out coords[1]) && + parser.TryGetFloat(out coords[2]) && parser.TryGetFloat(out coords[3])) + { + segments.Add(new SvgQuadraticCurveSegment(segments.Last.End, + ToAbsolute(coords[0], coords[1], segments, isRelative), + ToAbsolute(coords[2], coords[3], segments, isRelative))); + } + break; + case 'T': // shorthand/smooth curveto + case 't': // relative shorthand/smooth curveto + while (parser.TryGetFloat(out coords[0]) && parser.TryGetFloat(out coords[1])) + { + var lastQuadCurve = segments.Last as SvgQuadraticCurveSegment; - var controlPoint = lastQuadCurve != null - ? Reflect(lastQuadCurve.ControlPoint, segments.Last.End) - : segments.Last.End; + var controlPoint = lastQuadCurve != null + ? Reflect(lastQuadCurve.ControlPoint, segments.Last.End) + : segments.Last.End; - segments.Add(new SvgQuadraticCurveSegment(segments.Last.End, controlPoint, - ToAbsolute(coords[i], coords[i + 1], segments, isRelative))); - } - break; - case 'C': // curveto - case 'c': // relative curveto - for (var i = 0; i < coords.Count; i += 6) - { - segments.Add(new SvgCubicCurveSegment(segments.Last.End, - ToAbsolute(coords[i], coords[i + 1], segments, isRelative), - ToAbsolute(coords[i + 2], coords[i + 3], segments, isRelative), - ToAbsolute(coords[i + 4], coords[i + 5], segments, isRelative))); - } - break; - case 'S': // shorthand/smooth curveto - case 's': // relative shorthand/smooth curveto - - for (var i = 0; i < coords.Count; i += 4) - { - var lastCubicCurve = segments.Last as SvgCubicCurveSegment; + segments.Add(new SvgQuadraticCurveSegment(segments.Last.End, controlPoint, + ToAbsolute(coords[0], coords[1], segments, isRelative))); + } + break; + case 'C': // curveto + case 'c': // relative curveto + while (parser.TryGetFloat(out coords[0]) && parser.TryGetFloat(out coords[1]) && + parser.TryGetFloat(out coords[2]) && parser.TryGetFloat(out coords[3]) && + parser.TryGetFloat(out coords[4]) && parser.TryGetFloat(out coords[5])) + { + segments.Add(new SvgCubicCurveSegment(segments.Last.End, + ToAbsolute(coords[0], coords[1], segments, isRelative), + ToAbsolute(coords[2], coords[3], segments, isRelative), + ToAbsolute(coords[4], coords[5], segments, isRelative))); + } + break; + case 'S': // shorthand/smooth curveto + case 's': // relative shorthand/smooth curveto + while (parser.TryGetFloat(out coords[0]) && parser.TryGetFloat(out coords[1]) && + parser.TryGetFloat(out coords[2]) && parser.TryGetFloat(out coords[3])) + { + var lastCubicCurve = segments.Last as SvgCubicCurveSegment; - var controlPoint = lastCubicCurve != null - ? Reflect(lastCubicCurve.SecondControlPoint, segments.Last.End) - : segments.Last.End; + var controlPoint = lastCubicCurve != null + ? Reflect(lastCubicCurve.SecondControlPoint, segments.Last.End) + : segments.Last.End; - segments.Add(new SvgCubicCurveSegment(segments.Last.End, controlPoint, - ToAbsolute(coords[i], coords[i + 1], segments, isRelative), - ToAbsolute(coords[i + 2], coords[i + 3], segments, isRelative))); - } - break; - case 'Z': // closepath - case 'z': // relative closepath - segments.Add(new SvgClosePathSegment()); - break; + segments.Add(new SvgCubicCurveSegment(segments.Last.End, controlPoint, + ToAbsolute(coords[0], coords[1], segments, isRelative), + ToAbsolute(coords[2], coords[3], segments, isRelative))); } + break; + case 'Z': // closepath + case 'z': // relative closepath + segments.Add(new SvgClosePathSegment()); + break; + } } private static PointF Reflect(PointF point, PointF mirror) @@ -246,7 +257,7 @@ namespace Svg for (var i = 0; i < path.Length; i++) { string command; - if (char.IsLetter(path[i]) && path[i] != 'e') //e is used in scientific notiation. but not svg path + if (char.IsLetter(path[i]) && path[i] != 'e') //e is used in scientific notiation. but not svg path { command = path.Substring(commandStart, i - commandStart).Trim(); commandStart = i; @@ -273,18 +284,540 @@ namespace Svg } } - private static IEnumerable ParseCoordinates(string coords) + private enum NumState + { + invalid, + separator, + prefix, + integer, + decPlace, + fraction, + exponent, + expPrefix, + expValue + } + + private class CoordinateParser { - var parts = Regex.Split(coords.Remove(0, 1), @"[\s,]|(?=(?= _coords.Length) + { + result = float.MinValue; + return MarkState(false); + } + else + { + result = float.Parse(_coords.Substring(_pos, _coords.Length - _pos), NumberStyles.Float, CultureInfo.InvariantCulture); + _pos = _coords.Length; + return MarkState(true); + } + } + + private static bool IsCoordSeparator(char value) + { + switch (value) + { + case ' ': + case '\t': + case '\n': + case '\r': + case ',': + return true; + } + return false; } } + //private static IEnumerable ParseCoordinates(string coords) + //{ + // if (string.IsNullOrEmpty(coords) || coords.Length < 2) yield break; + + // var pos = 0; + // var currState = NumState.separator; + // var newState = NumState.separator; + + // for (int i = 1; i < coords.Length; i++) + // { + // switch (currState) + // { + // case NumState.separator: + // if (char.IsNumber(coords[i])) + // { + // newState = NumState.integer; + // } + // else if (IsCoordSeparator(coords[i])) + // { + // newState = NumState.separator; + // } + // else + // { + // switch (coords[i]) + // { + // case '.': + // newState = NumState.decPlace; + // break; + // case '+': + // case '-': + // newState = NumState.prefix; + // break; + // default: + // newState = NumState.invalid; + // break; + // } + // } + // break; + // case NumState.prefix: + // if (char.IsNumber(coords[i])) + // { + // newState = NumState.integer; + // } + // else if (coords[i] == '.') + // { + // newState = NumState.decPlace; + // } + // else + // { + // newState = NumState.invalid; + // } + // break; + // case NumState.integer: + // if (char.IsNumber(coords[i])) + // { + // newState = NumState.integer; + // } + // else if (IsCoordSeparator(coords[i])) + // { + // newState = NumState.separator; + // } + // else + // { + // switch (coords[i]) + // { + // case '.': + // newState = NumState.decPlace; + // break; + // case 'e': + // newState = NumState.exponent; + // break; + // case '+': + // case '-': + // newState = NumState.prefix; + // break; + // default: + // newState = NumState.invalid; + // break; + // } + // } + // break; + // case NumState.decPlace: + // if (char.IsNumber(coords[i])) + // { + // newState = NumState.fraction; + // } + // else if (IsCoordSeparator(coords[i])) + // { + // newState = NumState.separator; + // } + // else + // { + // switch (coords[i]) + // { + // case 'e': + // newState = NumState.exponent; + // break; + // case '+': + // case '-': + // newState = NumState.prefix; + // break; + // default: + // newState = NumState.invalid; + // break; + // } + // } + // break; + // case NumState.fraction: + // if (char.IsNumber(coords[i])) + // { + // newState = NumState.fraction; + // } + // else if (IsCoordSeparator(coords[i])) + // { + // newState = NumState.separator; + // } + // else + // { + // switch (coords[i]) + // { + // case '.': + // newState = NumState.decPlace; + // break; + // case 'e': + // newState = NumState.exponent; + // break; + // case '+': + // case '-': + // newState = NumState.prefix; + // break; + // default: + // newState = NumState.invalid; + // break; + // } + // } + // break; + // case NumState.exponent: + // if (char.IsNumber(coords[i])) + // { + // newState = NumState.expValue; + // } + // else if (IsCoordSeparator(coords[i])) + // { + // newState = NumState.invalid; + // } + // else + // { + // switch (coords[i]) + // { + // case '+': + // case '-': + // newState = NumState.expPrefix; + // break; + // default: + // newState = NumState.invalid; + // break; + // } + // } + // break; + // case NumState.expPrefix: + // if (char.IsNumber(coords[i])) + // { + // newState = NumState.expValue; + // } + // else + // { + // newState = NumState.invalid; + // } + // break; + // case NumState.expValue: + // if (char.IsNumber(coords[i])) + // { + // newState = NumState.expValue; + // } + // else if (IsCoordSeparator(coords[i])) + // { + // newState = NumState.separator; + // } + // else + // { + // switch (coords[i]) + // { + // case '.': + // newState = NumState.decPlace; + // break; + // case '+': + // case '-': + // newState = NumState.prefix; + // break; + // default: + // newState = NumState.invalid; + // break; + // } + // } + // break; + // } + + // if (newState < currState) + // { + // yield return float.Parse(coords.Substring(pos, i - pos), NumberStyles.Float, CultureInfo.InvariantCulture); + // pos = i; + // } + // else if (newState != currState && currState == NumState.separator) + // { + // pos = i; + // } + + // if (newState == NumState.invalid) yield break; + // currState = newState; + // } + + // if (currState != NumState.separator) + // { + // yield return float.Parse(coords.Substring(pos, coords.Length - pos), NumberStyles.Float, CultureInfo.InvariantCulture); + // } + //} + public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { if (value is string) @@ -294,27 +827,27 @@ namespace Svg return base.ConvertFrom(context, culture, value); } - - public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) - { - if (destinationType == typeof(string)) + + public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) + { + if (destinationType == typeof(string)) { var paths = value as SvgPathSegmentList; if (paths != null) { - var curretCulture = CultureInfo.CurrentCulture; - Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; - var s = string.Join(" ", paths.Select(p => p.ToString()).ToArray()); - Thread.CurrentThread.CurrentCulture = curretCulture; + var curretCulture = CultureInfo.CurrentCulture; + Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; + var s = string.Join(" ", paths.Select(p => p.ToString()).ToArray()); + Thread.CurrentThread.CurrentCulture = curretCulture; return s; } } - return base.ConvertTo(context, culture, value, destinationType); - } - - public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) + return base.ConvertTo(context, culture, value, destinationType); + } + + public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) { if (destinationType == typeof(string)) { diff --git a/Source/Rendering/ISvgRenderer.cs b/Source/Rendering/ISvgRenderer.cs index 0488abd..b291f47 100644 --- a/Source/Rendering/ISvgRenderer.cs +++ b/Source/Rendering/ISvgRenderer.cs @@ -12,11 +12,8 @@ namespace Svg void DrawImageUnscaled(Image image, Point location); void DrawPath(Pen pen, GraphicsPath path); void FillPath(Brush brush, GraphicsPath path); - float FontBaselineOffset(IFontDefn font); ISvgBoundable GetBoundable(); Region GetClip(); - IList MeasureCharacters(string text, IFontDefn font); - SizeF MeasureString(string text, IFontDefn font); ISvgBoundable PopBoundable(); void RotateTransform(float fAngle, MatrixOrder order = MatrixOrder.Append); void ScaleTransform(float sx, float sy, MatrixOrder order = MatrixOrder.Append); diff --git a/Source/Rendering/SvgRenderer.cs b/Source/Rendering/SvgRenderer.cs index 2c03b01..6d5fbde 100644 --- a/Source/Rendering/SvgRenderer.cs +++ b/Source/Rendering/SvgRenderer.cs @@ -98,21 +98,6 @@ namespace Svg this._innerGraphics.Dispose(); } - public float FontBaselineOffset(IFontDefn font) - { - return font.Ascent(this); - } - - public IList MeasureCharacters(string text, IFontDefn font) - { - return font.MeasureCharacters(this, text); - } - - public SizeF MeasureString(string text, IFontDefn font) - { - return font.MeasureString(this, text); - } - Graphics IGraphicsProvider.GetGraphics() { return _innerGraphics; diff --git a/Source/SvgDocument.cs b/Source/SvgDocument.cs index 14f1ef8..1d3d0b2 100644 --- a/Source/SvgDocument.cs +++ b/Source/SvgDocument.cs @@ -171,9 +171,12 @@ namespace Svg throw new FileNotFoundException("The specified document cannot be found.", path); } - var doc = Open(File.OpenRead(path), entities); - doc.BaseUri = new Uri(System.IO.Path.GetFullPath(path)); - return doc; + using (var stream = File.OpenRead(path)) + { + var doc = Open(stream, entities); + doc.BaseUri = new Uri(System.IO.Path.GetFullPath(path)); + return doc; + } } /// diff --git a/Source/SvgElement.cs b/Source/SvgElement.cs index d808b9c..dc802ea 100644 --- a/Source/SvgElement.cs +++ b/Source/SvgElement.cs @@ -254,6 +254,8 @@ namespace Svg get { return this._customAttributes; } } + private static readonly Matrix _zeroMatrix = new Matrix(0, 0, 0, 0, 0, 0); + /// /// Applies the required transforms to . /// @@ -268,9 +270,9 @@ namespace Svg { return true; } - if (this.Transforms.Count == 1 && this.Transforms[0].Matrix.Equals(new Matrix(0, 0, 0, 0, 0, 0))) return false; + if (this.Transforms.Count == 1 && this.Transforms[0].Matrix.Equals(_zeroMatrix)) return false; - Matrix transformMatrix = renderer.Transform; + Matrix transformMatrix = renderer.Transform.Clone(); foreach (SvgTransform transformation in this.Transforms) { diff --git a/Source/SvgElementFactory.cs b/Source/SvgElementFactory.cs index aaef9dd..9def526 100644 --- a/Source/SvgElementFactory.cs +++ b/Source/SvgElementFactory.cs @@ -14,13 +14,13 @@ namespace Svg /// internal class SvgElementFactory { - private static List availableElements; + private static Dictionary availableElements; private static Parser cssParser = new Parser(); /// /// Gets a list of available types that can be used when creating an . /// - public static List AvailableElements + public static Dictionary AvailableElements { get { @@ -31,7 +31,10 @@ namespace Svg && t.IsSubclassOf(typeof(SvgElement)) select new ElementInfo { ElementName = ((SvgElementAttribute)t.GetCustomAttributes(typeof(SvgElementAttribute), true)[0]).ElementName, ElementType = t }; - availableElements = svgTypes.ToList(); + availableElements = (from t in svgTypes + where t.ElementName != "svg" + group t by t.ElementName into types + select types).ToDictionary(e => e.Key, e => e.SingleOrDefault()); } return availableElements; @@ -91,8 +94,8 @@ namespace Svg } else { - ElementInfo validType = AvailableElements.SingleOrDefault(e => e.ElementName == elementName); - if (validType != null) + ElementInfo validType = null; + if (AvailableElements.TryGetValue(elementName, out validType)) { createdElement = (SvgElement) Activator.CreateInstance(validType.ElementType); } diff --git a/Source/Text/GdiFontDefn.cs b/Source/Text/GdiFontDefn.cs index ef8d99d..8fc8122 100644 --- a/Source/Text/GdiFontDefn.cs +++ b/Source/Text/GdiFontDefn.cs @@ -86,5 +86,10 @@ namespace Svg return provider.GetGraphics(); } } + + public void Dispose() + { + _font.Dispose(); + } } } diff --git a/Source/Text/IFontDefn.cs b/Source/Text/IFontDefn.cs index 3b53896..a462547 100644 --- a/Source/Text/IFontDefn.cs +++ b/Source/Text/IFontDefn.cs @@ -7,7 +7,7 @@ using System.Drawing.Drawing2D; namespace Svg { - public interface IFontDefn + public interface IFontDefn : IDisposable { float Size { get; } float SizeInPoints { get; } diff --git a/Source/Text/SvgFontDefn.cs b/Source/Text/SvgFontDefn.cs index d26f3b6..688f5a7 100644 --- a/Source/Text/SvgFontDefn.cs +++ b/Source/Text/SvgFontDefn.cs @@ -45,14 +45,14 @@ namespace Svg public IList MeasureCharacters(ISvgRenderer renderer, string text) { var result = new List(); - GetPath(renderer, text, result, false); + using (var path = GetPath(renderer, text, result, false)) { } return result; } public System.Drawing.SizeF MeasureString(ISvgRenderer renderer, string text) { var result = new List(); - GetPath(renderer, text, result, true); + using (var path = GetPath(renderer, text, result, true)) { } var nonEmpty = result.Where(r => r != RectangleF.Empty); if (!nonEmpty.Any()) return SizeF.Empty; return new SizeF(nonEmpty.Last().Right - nonEmpty.First().Left, Ascent(renderer)); @@ -63,10 +63,12 @@ namespace Svg var textPath = GetPath(renderer, text, null, false); if (textPath.PointCount > 0) { - var translate = new Matrix(); - translate.Translate(location.X, location.Y); - textPath.Transform(translate); - path.AddPath(textPath, false); + using (var translate = new Matrix()) + { + translate.Translate(location.X, location.Y); + textPath.Transform(translate); + path.AddPath(textPath, false); + } } } @@ -99,6 +101,7 @@ namespace Svg scaleMatrix.Scale(_emScale, -1 * _emScale, MatrixOrder.Append); scaleMatrix.Translate(xPos, ascent, MatrixOrder.Append); path.Transform(scaleMatrix); + scaleMatrix.Dispose(); bounds = path.GetBounds(); if (ranges != null) @@ -126,5 +129,11 @@ namespace Svg if (_glyphs == null) _glyphs = _font.Descendants().OfType().ToDictionary(g => g.Unicode ?? g.GlyphName ?? g.ID); if (_kerning == null) _kerning = _font.Descendants().OfType().ToDictionary(k => k.Glyph1 + "|" + k.Glyph2); } + + public void Dispose() + { + _glyphs = null; + _kerning = null; + } } } diff --git a/Source/Text/SvgTextBase.cs b/Source/Text/SvgTextBase.cs index 7e90db6..4a648af 100644 --- a/Source/Text/SvgTextBase.cs +++ b/Source/Text/SvgTextBase.cs @@ -376,11 +376,13 @@ namespace Svg } 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); + using (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); + } } } } @@ -575,186 +577,188 @@ namespace Svg // 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) + using (var font = this.Element.GetFont(this.Renderer)) { - pathStats = new PathStatistics(BaselinePath.PathData); - if (_authorPathLength > 0) pathScale = _authorPathLength / pathStats.TotalLength; - } - - // Get all of the offsets (explicit and defined by spacing) - IList xOffsets; - IList yOffsets; - IList 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) + var fontBaselineHeight = font.Ascent(this.Renderer); + PathStatistics pathStats = null; + var pathScale = 1.0; + if (BaselinePath != null) { - if (xOffsets.Count < 1) - { - xOffsets.Add(StartOffsetAdjust); - } - else - { - xOffsets[0] += StartOffsetAdjust; - } + pathStats = new PathStatistics(BaselinePath.PathData); + if (_authorPathLength > 0) pathScale = _authorPathLength / pathStats.TotalLength; } - if (this.Element.LetterSpacing.Value != 0.0f || this.Element.WordSpacing.Value != 0.0f || this.LetterSpacingAdjust != 0.0f) + // Get all of the offsets (explicit and defined by spacing) + IList xOffsets; + IList yOffsets; + IList rotations; + float baselineShift = 0.0f; + + try { - 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++) + 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 (i >= xOffsets.Count) + if (xOffsets.Count < 1) { - xOffsets.Add(spacing + (char.IsWhiteSpace(value[i]) ? wordSpacing : 0)); + xOffsets.Add(StartOffsetAdjust); } else { - xOffsets[i] += spacing + (char.IsWhiteSpace(value[i]) ? wordSpacing : 0); + xOffsets[0] += StartOffsetAdjust; } } - } - rotations = GetValues(value.Length, e => e._rotations); + 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); + } + } + } - // 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("baseline-shift"); + rotations = GetValues(value.Length, e => e._rotations); - 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; - } + // 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("baseline-shift"); - if (baselineShift != 0.0f) - { - if (yOffsets.Any()) + switch (baselineShiftText) { - yOffsets[0] += baselineShift; + 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; } - else + + if (baselineShift != 0.0f) { - yOffsets.Add(baselineShift); + if (yOffsets.Any()) + { + yOffsets[0] += baselineShift; + } + else + { + yOffsets.Add(baselineShift); + } } } - } - finally - { - this.Renderer.PopBoundable(); - } + 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); + // 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())); - } + 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 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 = 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++) + // 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) { - 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) + 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++) { - DrawStringOnCurrPath(value[i].ToString(), font, new PointF(xPos, yPos), - fontBaselineHeight, (rotations.Count > i ? rotations[i] : rotations.LastOrDefault())); + 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 = 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); - } + xPos += charBounds.Last().Width; } } - // Add the kerning to the next character - if (lastIndividualChar < value.Length) - { - xPos += charBounds[charBounds.Count - 1].X - charBounds[charBounds.Count - 2].X; - } - else + // Render the string normally + if (lastIndividualChar < value.Length) { - xPos += charBounds.Last().Width; + 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; } - } - - // 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 = 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); + NumChars += value.Length; + // Undo any baseline shift. This is not persisted, unlike normal vertical offsets. + this.Current = new PointF(xPos, yPos - baselineShift); + } } private void DrawStringOnCurrPath(string value, IFontDefn font, PointF location, float fontBaselineHeight, float rotation) @@ -764,12 +768,14 @@ namespace Svg font.AddStringToPath(this.Renderer, drawPath, value, new PointF(location.X, location.Y - fontBaselineHeight)); if (rotation != 0.0f && drawPath.PointCount > 0) { - 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); + using (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); + } } } @@ -830,11 +836,13 @@ namespace Svg if (xOffset != 0) { - var matrix = new Matrix(); - matrix.Translate(xOffset, 0); - foreach (var path in _anchoredPaths) + using (var matrix = new Matrix()) { - path.Transform(matrix); + matrix.Translate(xOffset, 0); + foreach (var path in _anchoredPaths) + { + path.Transform(matrix); + } } } diff --git a/Source/Transforms/SvgScale.cs b/Source/Transforms/SvgScale.cs index b036929..b14fef3 100644 --- a/Source/Transforms/SvgScale.cs +++ b/Source/Transforms/SvgScale.cs @@ -27,7 +27,7 @@ namespace Svg.Transforms { get { - System.Drawing.Drawing2D.Matrix matrix = new System.Drawing.Drawing2D.Matrix(); + var matrix = new System.Drawing.Drawing2D.Matrix(); matrix.Scale(this.X, this.Y); return matrix; } diff --git a/Tests/SvgW3CTestRunner/View.cs b/Tests/SvgW3CTestRunner/View.cs index c8faec4..f34a3a9 100644 --- a/Tests/SvgW3CTestRunner/View.cs +++ b/Tests/SvgW3CTestRunner/View.cs @@ -32,6 +32,7 @@ namespace SvgW3CTestRunner var fileName = lstFiles.SelectedItem.ToString(); try { + Debug.Print(fileName); var doc = SvgDocument.Open(_svgBasePath + fileName); if (fileName.StartsWith("__")) { -- GitLab