Commit 46e375e4 authored by Eric Domke's avatar Eric Domke
Browse files

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
parent 7c70bd11
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<ISvgBoundable> _boundables = new Stack<ISvgBoundable>();
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()
{
}
}
}
......@@ -93,6 +93,7 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="DebugRenderer.cs" />
<Compile Include="SvgViewer.cs">
<SubType>Form</SubType>
</Compile>
......
......@@ -43,6 +43,8 @@ namespace SVGViewer
private void RenderSvg(SvgDocument svgDoc)
{
var render = new DebugRenderer();
svgDoc.Draw(render);
svgImage.Image = svgDoc.Draw();
}
}
......
......@@ -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<SvgDocument>(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<SvgDocument>(stream);
doc.BaseUri = uri;
return doc.Draw();
}
else
{
return Bitmap.FromStream(stream);
}
}
}
}
......
......@@ -10,8 +10,12 @@ namespace Svg.Css
{
public Selector<SvgElement> 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<SvgElement>();
}
public Selector<SvgElement> Universal(NamespacePrefix prefix)
......
......@@ -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;
......
......@@ -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);
}
}
......
......@@ -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<SvgSymbol>();
......
......@@ -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);
}
}
......
......@@ -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;
}
......
......@@ -170,45 +170,47 @@ namespace Svg
/// <param name="pMarkerPoint"></param>
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();
}
/// <summary>
......@@ -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;
......
......@@ -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);
}
......
......@@ -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
{
/// <summary>
......@@ -37,18 +37,16 @@ namespace Svg
try
{
List<float> coords;
char command;
bool isRelative;
foreach (var commandSet in SplitCommands(path.TrimEnd(null)))
{
coords = new List<float>(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<float> 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<float> 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,]|(?=(?<!e)-)", RegexOptions.Compiled);
private string _coords;
private int _pos = 0;
private NumState _currState = NumState.separator;
private NumState _newState = NumState.separator;
private int i = 1;
private bool _parseWorked = true;
public CoordinateParser(string coords)
{
_coords = coords;
}
public bool HasMore { get { return _parseWorked; } }
for (int i = 0; i < parts.Length; i++)
private bool MarkState(bool state)
{
if (!String.IsNullOrEmpty(parts[i]))
yield return float.Parse(parts[i].Trim(), NumberStyles.Float, CultureInfo.InvariantCulture);
_parseWorked = state;
i++;
return state;
}
public bool TryGetBool(out bool result)
{
while (i < _coords.Length && _parseWorked)
{
switch (_currState)
{
case NumState.separator:
if (IsCoordSeparator(_coords[i]))
{
_newState = NumState.separator;
}
else if (_coords[i] == '0')
{
result = false;
_newState = NumState.separator;
_pos = i + 1;
return MarkState(true);
}
else if (_coords[i] == '1')
{
result = true;
_newState = NumState.separator;
_pos = i + 1;
return MarkState(true);
}
else
{
result = false;
return MarkState(false);
}
break;
default:
result = false;
return MarkState(false);
}
i++;
}
result = false;
return MarkState(false);
}
public bool TryGetFloat(out float result)
{
while (i < _coords.Length && _parseWorked)
{
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)
{
result = float.Parse(_coords.Substring(_pos, i - _pos), NumberStyles.Float, CultureInfo.InvariantCulture);
_pos = i;
_currState = _newState;
return MarkState(true);
}
else if (_newState != _currState && _currState == NumState.separator)
{
_pos = i;
}
if (_newState == NumState.invalid)
{
result = float.MinValue;
return MarkState(false);
}
_currState = _newState;
i++;
}
if (_currState == NumState.separator || !_parseWorked || _pos >= _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<float> 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))
{
......
......@@ -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<RectangleF> 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);
......
......@@ -98,21 +98,6 @@ namespace Svg
this._innerGraphics.Dispose();
}
public float FontBaselineOffset(IFontDefn font)
{
return font.Ascent(this);
}
public IList<RectangleF> 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;
......
......@@ -171,9 +171,12 @@ namespace Svg
throw new FileNotFoundException("The specified document cannot be found.", path);
}
var doc = Open<T>(File.OpenRead(path), entities);
doc.BaseUri = new Uri(System.IO.Path.GetFullPath(path));
return doc;
using (var stream = File.OpenRead(path))
{
var doc = Open<T>(stream, entities);
doc.BaseUri = new Uri(System.IO.Path.GetFullPath(path));
return doc;
}
}
/// <summary>
......
......@@ -254,6 +254,8 @@ namespace Svg
get { return this._customAttributes; }
}
private static readonly Matrix _zeroMatrix = new Matrix(0, 0, 0, 0, 0, 0);
/// <summary>
/// Applies the required transforms to <see cref="ISvgRenderer"/>.
/// </summary>
......@@ -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)
{
......
......@@ -14,13 +14,13 @@ namespace Svg
/// </summary>
internal class SvgElementFactory
{
private static List<ElementInfo> availableElements;
private static Dictionary<string, ElementInfo> availableElements;
private static Parser cssParser = new Parser();
/// <summary>
/// Gets a list of available types that can be used when creating an <see cref="SvgElement"/>.
/// </summary>
public static List<ElementInfo> AvailableElements
public static Dictionary<string, ElementInfo> 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);
}
......
......@@ -86,5 +86,10 @@ namespace Svg
return provider.GetGraphics();
}
}
public void Dispose()
{
_font.Dispose();
}
}
}
......@@ -7,7 +7,7 @@ using System.Drawing.Drawing2D;
namespace Svg
{
public interface IFontDefn
public interface IFontDefn : IDisposable
{
float Size { get; }
float SizeInPoints { get; }
......
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