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 @@ ...@@ -93,6 +93,7 @@
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="DebugRenderer.cs" />
<Compile Include="SvgViewer.cs"> <Compile Include="SvgViewer.cs">
<SubType>Form</SubType> <SubType>Form</SubType>
</Compile> </Compile>
......
...@@ -43,6 +43,8 @@ namespace SVGViewer ...@@ -43,6 +43,8 @@ namespace SVGViewer
private void RenderSvg(SvgDocument svgDoc) private void RenderSvg(SvgDocument svgDoc)
{ {
var render = new DebugRenderer();
svgDoc.Draw(render);
svgImage.Image = svgDoc.Draw(); svgImage.Image = svgDoc.Draw();
} }
} }
......
...@@ -205,8 +205,10 @@ namespace Svg ...@@ -205,8 +205,10 @@ namespace Svg
// we're assuming base64, as ascii encoding would be *highly* unsusual for images // we're assuming base64, as ascii encoding would be *highly* unsusual for images
// also assuming it's png or jpeg mimetype // also assuming it's png or jpeg mimetype
byte[] imageBytes = Convert.FromBase64String(uriString.Substring(dataIdx)); byte[] imageBytes = Convert.FromBase64String(uriString.Substring(dataIdx));
Image image = Image.FromStream(new MemoryStream(imageBytes)); using (var stream = new MemoryStream(imageBytes))
return image; {
return Image.FromStream(stream);
}
} }
if (!uri.IsAbsoluteUri) if (!uri.IsAbsoluteUri)
...@@ -219,16 +221,19 @@ namespace Svg ...@@ -219,16 +221,19 @@ namespace Svg
using (WebResponse webResponse = httpRequest.GetResponse()) using (WebResponse webResponse = httpRequest.GetResponse())
{ {
MemoryStream ms = BufferToMemoryStream(webResponse.GetResponseStream()); using (var stream = webResponse.GetResponseStream())
if (uri.LocalPath.EndsWith(".svg", StringComparison.InvariantCultureIgnoreCase))
{
var doc = SvgDocument.Open<SvgDocument>(ms);
doc.BaseUri = uri;
return doc.Draw();
}
else
{ {
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 ...@@ -10,8 +10,12 @@ namespace Svg.Css
{ {
public Selector<SvgElement> Type(NamespacePrefix prefix, string name) public Selector<SvgElement> Type(NamespacePrefix prefix, string name)
{ {
var type = SvgElementFactory.AvailableElements.SingleOrDefault(e => e.ElementName == name); SvgElementFactory.ElementInfo type = null;
return nodes => nodes.Where(n => n.GetType() == type.ElementType); 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) public Selector<SvgElement> Universal(NamespacePrefix prefix)
......
...@@ -107,34 +107,37 @@ namespace Svg ...@@ -107,34 +107,37 @@ namespace Svg
} }
float points; float points;
IFontDefn currFont;
switch (type) switch (type)
{ {
case SvgUnitType.Em: case SvgUnitType.Em:
currFont = GetFont(renderer, owner); using (var currFont = GetFont(renderer, owner))
if (currFont == null)
{ {
points = (float)(value * 9); if (currFont == null)
_deviceValue = (points / 72.0f) * ppi; {
} points = (float)(value * 9);
else _deviceValue = (points / 72.0f) * ppi;
{ }
_deviceValue = value * (currFont.SizeInPoints / 72.0f) * ppi; else
{
_deviceValue = value * (currFont.SizeInPoints / 72.0f) * ppi;
}
} }
break; break;
case SvgUnitType.Ex: case SvgUnitType.Ex:
currFont = GetFont(renderer, owner); using (var currFont = GetFont(renderer, owner))
if (currFont == null)
{
points = (float)(value * 9);
_deviceValue = (points * 0.5f / 72.0f) * ppi;
}
else
{ {
_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: case SvgUnitType.Centimeter:
_deviceValue = (float)((value / cmInInch) * ppi); _deviceValue = (float)((value / cmInInch) * ppi);
break; break;
......
...@@ -133,8 +133,8 @@ namespace Svg ...@@ -133,8 +133,8 @@ namespace Svg
var fScaleX = width / this.Width; var fScaleX = width / this.Width;
var fScaleY = height / this.Height; //(this.MinY < 0 ? -1 : 1) * var fScaleY = height / this.Height; //(this.MinY < 0 ? -1 : 1) *
var fMinX = this.MinX; var fMinX = -this.MinX;
var fMinY = this.MinY; var fMinY = -this.MinY;
if (aspectRatio == null) aspectRatio = new SvgAspectRatio(SvgPreserveAspectRatio.xMidYMid, false); if (aspectRatio == null) aspectRatio = new SvgAspectRatio(SvgPreserveAspectRatio.xMidYMid, false);
if (aspectRatio.Align != SvgPreserveAspectRatio.none) if (aspectRatio.Align != SvgPreserveAspectRatio.none)
...@@ -197,7 +197,7 @@ namespace Svg ...@@ -197,7 +197,7 @@ namespace Svg
renderer.SetClip(new Region(new RectangleF(x, y, width, height)), CombineMode.Intersect); renderer.SetClip(new Region(new RectangleF(x, y, width, height)), CombineMode.Intersect);
renderer.ScaleTransform(fScaleX, fScaleY, MatrixOrder.Prepend); renderer.ScaleTransform(fScaleX, fScaleY, MatrixOrder.Prepend);
renderer.TranslateTransform(x, y); renderer.TranslateTransform(x, y);
renderer.TranslateTransform(-fMinX, -fMinY); renderer.TranslateTransform(fMinX, fMinY);
} }
} }
......
...@@ -91,6 +91,12 @@ namespace Svg.Document_Structure ...@@ -91,6 +91,12 @@ namespace Svg.Document_Structure
return true; 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() public override SvgElement DeepCopy()
{ {
return DeepCopy<SvgSymbol>(); return DeepCopy<SvgSymbol>();
......
...@@ -41,7 +41,7 @@ namespace Svg ...@@ -41,7 +41,7 @@ namespace Svg
protected internal override bool PushTransforms(ISvgRenderer renderer) protected internal override bool PushTransforms(ISvgRenderer renderer)
{ {
if (!base.PushTransforms(renderer)) return false; 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)); this.Y.ToDeviceValue(renderer, UnitRenderingType.Vertical, this));
return true; return true;
} }
...@@ -70,20 +70,22 @@ namespace Svg ...@@ -70,20 +70,22 @@ namespace Svg
protected override void Render(ISvgRenderer renderer) protected override void Render(ISvgRenderer renderer)
{ {
if (!Visible || !Displayable) if (this.Visible && this.Displayable && this.PushTransforms(renderer))
return; {
this.SetClip(renderer);
this.PushTransforms(renderer);
var element = (SvgVisualElement)this.OwnerDocument.IdManager.GetElementById(this.ReferencedElement);
SvgVisualElement element = (SvgVisualElement)this.OwnerDocument.IdManager.GetElementById(this.ReferencedElement); if (element != null)
// For the time of rendering we want the referenced element to inherit {
// this elements transforms var origParent = element.Parent;
SvgElement parent = element._parent; element._parent = this;
element._parent = this; element.RenderElement(renderer);
element.RenderElement(renderer); element._parent = origParent;
element._parent = parent; }
this.PopTransforms(renderer); this.ResetClip(renderer);
this.PopTransforms(renderer);
}
} }
......
...@@ -86,17 +86,19 @@ namespace Svg.FilterEffects ...@@ -86,17 +86,19 @@ namespace Svg.FilterEffects
} }
var colorMatrix = new ColorMatrix(colorMatrixElements); var colorMatrix = new ColorMatrix(colorMatrixElements);
var imageAttrs = new ImageAttributes(); using (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))
{ {
g.DrawImage(inputImage, new Rectangle(0, 0, inputImage.Width, inputImage.Height), imageAttrs.SetColorMatrix(colorMatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
0, 0, inputImage.Width, inputImage.Height, GraphicsUnit.Pixel, imageAttrs);
g.Flush(); 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 ...@@ -170,45 +170,47 @@ namespace Svg
/// <param name="pMarkerPoint"></param> /// <param name="pMarkerPoint"></param>
private void RenderPart2(float fAngle, ISvgRenderer pRenderer, SvgPath pOwner, PointF pMarkerPoint) private void RenderPart2(float fAngle, ISvgRenderer pRenderer, SvgPath pOwner, PointF pMarkerPoint)
{ {
Pen pRenderPen = CreatePen(pOwner, pRenderer); using (var 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)
{ {
Brush pBrush = pFill.GetBrush(this, pRenderer, fOpacity); using (var markerPath = GetClone(pOwner))
pRenderer.FillPath(pBrush, markerPath); {
pBrush.Dispose(); 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> /// <summary>
...@@ -240,9 +242,11 @@ namespace Svg ...@@ -240,9 +242,11 @@ namespace Svg
switch (MarkerUnits) switch (MarkerUnits)
{ {
case SvgMarkerUnits.strokeWidth: case SvgMarkerUnits.strokeWidth:
Matrix transMatrix = new Matrix(); using (var transMatrix = new Matrix())
transMatrix.Scale(AdjustForViewBoxWidth(pPath.StrokeWidth), AdjustForViewBoxHeight(pPath.StrokeWidth)); {
pRet.Transform(transMatrix); transMatrix.Scale(AdjustForViewBoxWidth(pPath.StrokeWidth), AdjustForViewBoxHeight(pPath.StrokeWidth));
pRet.Transform(transMatrix);
}
break; break;
case SvgMarkerUnits.userSpaceOnUse: case SvgMarkerUnits.userSpaceOnUse:
break; break;
......
...@@ -128,11 +128,13 @@ namespace Svg ...@@ -128,11 +128,13 @@ namespace Svg
// Transform the path based on the scaling // Transform the path based on the scaling
var gradBounds = path.GetBounds(); var gradBounds = path.GetBounds();
var transCenter = new PointF(gradBounds.Left + gradBounds.Width / 2, gradBounds.Top + gradBounds.Height / 2); var transCenter = new PointF(gradBounds.Left + gradBounds.Width / 2, gradBounds.Top + gradBounds.Height / 2);
var scaleMat = new Matrix(); using (var scaleMat = new Matrix())
scaleMat.Translate(-1 * transCenter.X, -1 * transCenter.Y, MatrixOrder.Append); {
scaleMat.Scale(scale, scale, MatrixOrder.Append); scaleMat.Translate(-1 * transCenter.X, -1 * transCenter.Y, MatrixOrder.Append);
scaleMat.Translate(transCenter.X, transCenter.Y, MatrixOrder.Append); scaleMat.Scale(scale, scale, MatrixOrder.Append);
path.Transform(scaleMat); scaleMat.Translate(transCenter.X, transCenter.Y, MatrixOrder.Append);
path.Transform(scaleMat);
}
// calculate the brush // calculate the brush
var brush = new PathGradientBrush(path); var brush = new PathGradientBrush(path);
...@@ -167,16 +169,18 @@ namespace Svg ...@@ -167,16 +169,18 @@ namespace Svg
}; };
var pathBounds = path.GetBounds(); var pathBounds = path.GetBounds();
var pathCenter = new PointF(pathBounds.X + pathBounds.Width / 2, pathBounds.Y + pathBounds.Height / 2); var pathCenter = new PointF(pathBounds.X + pathBounds.Width / 2, pathBounds.Y + pathBounds.Height / 2);
var transform = new Matrix(); using (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])))
{ {
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); return bounds.Height / (points[2].Y - points[1].Y);
} }
......
...@@ -12,14 +12,14 @@ using Svg.Pathing; ...@@ -12,14 +12,14 @@ using Svg.Pathing;
namespace Svg namespace Svg
{ {
public static class PointFExtensions public static class PointFExtensions
{ {
public static string ToSvgString(this PointF p) public static string ToSvgString(this PointF p)
{ {
return p.X.ToString() + " " + p.Y.ToString(); return p.X.ToString() + " " + p.Y.ToString();
} }
} }
public class SvgPathBuilder : TypeConverter public class SvgPathBuilder : TypeConverter
{ {
/// <summary> /// <summary>
...@@ -37,18 +37,16 @@ namespace Svg ...@@ -37,18 +37,16 @@ namespace Svg
try try
{ {
List<float> coords;
char command; char command;
bool isRelative; bool isRelative;
foreach (var commandSet in SplitCommands(path.TrimEnd(null))) foreach (var commandSet in SplitCommands(path.TrimEnd(null)))
{ {
coords = new List<float>(ParseCoordinates(commandSet.Trim()));
command = commandSet[0]; command = commandSet[0];
isRelative = char.IsLower(command); isRelative = char.IsLower(command);
// http://www.w3.org/TR/SVG11/paths.html#PathDataGeneralInformation // 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) catch (Exception exc)
...@@ -59,111 +57,124 @@ namespace Svg ...@@ -59,111 +57,124 @@ namespace Svg
return segments; 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) var coords = new float[6];
{
segments.Add(new SvgLineSegment(segments.Last.End,
ToAbsolute(coords[i], coords[i + 1], segments, isRelative)));
}
break;
case 'a':
case 'A':
SvgArcSize size;
SvgArcSweep sweep;
for (var i = 0; i < coords.Count; i += 7) switch (command)
{ {
size = (coords[i + 3] != 0.0f) ? SvgArcSize.Large : SvgArcSize.Small; case 'm': // relative moveto
sweep = (coords[i + 4] != 0.0f) ? SvgArcSweep.Positive : SvgArcSweep.Negative; 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 while (parser.TryGetFloat(out coords[0]) && parser.TryGetFloat(out coords[1]))
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))); segments.Add(new SvgLineSegment(segments.Last.End,
} ToAbsolute(coords[0], coords[1], segments, isRelative)));
break; }
case 'l': // relative lineto break;
case 'L': // lineto case 'a':
for (var i = 0; i < coords.Count; i += 2) case 'A':
{ bool size;
segments.Add(new SvgLineSegment(segments.Last.End, bool sweep;
ToAbsolute(coords[i], coords[i + 1], segments, isRelative)));
} while (parser.TryGetFloat(out coords[0]) && parser.TryGetFloat(out coords[1]) &&
break; parser.TryGetFloat(out coords[2]) && parser.TryGetBool(out size) &&
case 'H': // horizontal lineto parser.TryGetBool(out sweep) && parser.TryGetFloat(out coords[3]) &&
case 'h': // relative horizontal lineto parser.TryGetFloat(out coords[4]))
foreach (var value in coords) {
segments.Add(new SvgLineSegment(segments.Last.End, // A|a rx ry x-axis-rotation large-arc-flag sweep-flag x y
ToAbsolute(value, segments.Last.End.Y, segments, isRelative, false))); segments.Add(new SvgArcSegment(segments.Last.End, coords[0], coords[1], coords[2],
break; (size ? SvgArcSize.Large : SvgArcSize.Small),
case 'V': // vertical lineto (sweep ? SvgArcSweep.Positive : SvgArcSweep.Negative),
case 'v': // relative vertical lineto ToAbsolute(coords[3], coords[4], segments, isRelative)));
foreach (var value in coords) }
segments.Add(new SvgLineSegment(segments.Last.End, break;
ToAbsolute(segments.Last.End.X, value, segments, false, isRelative))); case 'l': // relative lineto
break; case 'L': // lineto
case 'Q': // curveto while (parser.TryGetFloat(out coords[0]) && parser.TryGetFloat(out coords[1]))
case 'q': // relative curveto {
for (var i = 0; i < coords.Count; i += 4) segments.Add(new SvgLineSegment(segments.Last.End,
{ ToAbsolute(coords[0], coords[1], segments, isRelative)));
segments.Add(new SvgQuadraticCurveSegment(segments.Last.End, }
ToAbsolute(coords[i], coords[i + 1], segments, isRelative), break;
ToAbsolute(coords[i + 2], coords[i + 3], segments, isRelative))); case 'H': // horizontal lineto
} case 'h': // relative horizontal lineto
break; while (parser.TryGetFloat(out coords[0]))
case 'T': // shorthand/smooth curveto {
case 't': // relative shorthand/smooth curveto segments.Add(new SvgLineSegment(segments.Last.End,
for (var i = 0; i < coords.Count; i += 2) ToAbsolute(coords[0], segments.Last.End.Y, segments, isRelative, false)));
{ }
var lastQuadCurve = segments.Last as SvgQuadraticCurveSegment; 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 var controlPoint = lastQuadCurve != null
? Reflect(lastQuadCurve.ControlPoint, segments.Last.End) ? Reflect(lastQuadCurve.ControlPoint, segments.Last.End)
: segments.Last.End; : segments.Last.End;
segments.Add(new SvgQuadraticCurveSegment(segments.Last.End, controlPoint, segments.Add(new SvgQuadraticCurveSegment(segments.Last.End, controlPoint,
ToAbsolute(coords[i], coords[i + 1], segments, isRelative))); ToAbsolute(coords[0], coords[1], segments, isRelative)));
} }
break; break;
case 'C': // curveto case 'C': // curveto
case 'c': // relative curveto case 'c': // relative curveto
for (var i = 0; i < coords.Count; i += 6) while (parser.TryGetFloat(out coords[0]) && parser.TryGetFloat(out coords[1]) &&
{ parser.TryGetFloat(out coords[2]) && parser.TryGetFloat(out coords[3]) &&
segments.Add(new SvgCubicCurveSegment(segments.Last.End, parser.TryGetFloat(out coords[4]) && parser.TryGetFloat(out coords[5]))
ToAbsolute(coords[i], coords[i + 1], segments, isRelative), {
ToAbsolute(coords[i + 2], coords[i + 3], segments, isRelative), segments.Add(new SvgCubicCurveSegment(segments.Last.End,
ToAbsolute(coords[i + 4], coords[i + 5], segments, isRelative))); ToAbsolute(coords[0], coords[1], segments, isRelative),
} ToAbsolute(coords[2], coords[3], segments, isRelative),
break; ToAbsolute(coords[4], coords[5], segments, isRelative)));
case 'S': // shorthand/smooth curveto }
case 's': // relative shorthand/smooth curveto break;
case 'S': // shorthand/smooth curveto
for (var i = 0; i < coords.Count; i += 4) case 's': // relative shorthand/smooth curveto
{ while (parser.TryGetFloat(out coords[0]) && parser.TryGetFloat(out coords[1]) &&
var lastCubicCurve = segments.Last as SvgCubicCurveSegment; parser.TryGetFloat(out coords[2]) && parser.TryGetFloat(out coords[3]))
{
var lastCubicCurve = segments.Last as SvgCubicCurveSegment;
var controlPoint = lastCubicCurve != null var controlPoint = lastCubicCurve != null
? Reflect(lastCubicCurve.SecondControlPoint, segments.Last.End) ? Reflect(lastCubicCurve.SecondControlPoint, segments.Last.End)
: segments.Last.End; : segments.Last.End;
segments.Add(new SvgCubicCurveSegment(segments.Last.End, controlPoint, segments.Add(new SvgCubicCurveSegment(segments.Last.End, controlPoint,
ToAbsolute(coords[i], coords[i + 1], segments, isRelative), ToAbsolute(coords[0], coords[1], segments, isRelative),
ToAbsolute(coords[i + 2], coords[i + 3], segments, isRelative))); ToAbsolute(coords[2], coords[3], segments, isRelative)));
}
break;
case 'Z': // closepath
case 'z': // relative closepath
segments.Add(new SvgClosePathSegment());
break;
} }
break;
case 'Z': // closepath
case 'z': // relative closepath
segments.Add(new SvgClosePathSegment());
break;
}
} }
private static PointF Reflect(PointF point, PointF mirror) private static PointF Reflect(PointF point, PointF mirror)
...@@ -246,7 +257,7 @@ namespace Svg ...@@ -246,7 +257,7 @@ namespace Svg
for (var i = 0; i < path.Length; i++) for (var i = 0; i < path.Length; i++)
{ {
string command; 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(); command = path.Substring(commandStart, i - commandStart).Trim();
commandStart = i; commandStart = i;
...@@ -273,18 +284,540 @@ namespace Svg ...@@ -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])) _parseWorked = state;
yield return float.Parse(parts[i].Trim(), NumberStyles.Float, CultureInfo.InvariantCulture); 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) public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{ {
if (value is string) if (value is string)
...@@ -294,27 +827,27 @@ namespace Svg ...@@ -294,27 +827,27 @@ namespace Svg
return base.ConvertFrom(context, culture, value); return base.ConvertFrom(context, culture, value);
} }
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{ {
if (destinationType == typeof(string)) if (destinationType == typeof(string))
{ {
var paths = value as SvgPathSegmentList; var paths = value as SvgPathSegmentList;
if (paths != null) if (paths != null)
{ {
var curretCulture = CultureInfo.CurrentCulture; var curretCulture = CultureInfo.CurrentCulture;
Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
var s = string.Join(" ", paths.Select(p => p.ToString()).ToArray()); var s = string.Join(" ", paths.Select(p => p.ToString()).ToArray());
Thread.CurrentThread.CurrentCulture = curretCulture; Thread.CurrentThread.CurrentCulture = curretCulture;
return s; return s;
} }
} }
return base.ConvertTo(context, culture, value, destinationType); return base.ConvertTo(context, culture, value, destinationType);
} }
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{ {
if (destinationType == typeof(string)) if (destinationType == typeof(string))
{ {
......
...@@ -12,11 +12,8 @@ namespace Svg ...@@ -12,11 +12,8 @@ namespace Svg
void DrawImageUnscaled(Image image, Point location); void DrawImageUnscaled(Image image, Point location);
void DrawPath(Pen pen, GraphicsPath path); void DrawPath(Pen pen, GraphicsPath path);
void FillPath(Brush brush, GraphicsPath path); void FillPath(Brush brush, GraphicsPath path);
float FontBaselineOffset(IFontDefn font);
ISvgBoundable GetBoundable(); ISvgBoundable GetBoundable();
Region GetClip(); Region GetClip();
IList<RectangleF> MeasureCharacters(string text, IFontDefn font);
SizeF MeasureString(string text, IFontDefn font);
ISvgBoundable PopBoundable(); ISvgBoundable PopBoundable();
void RotateTransform(float fAngle, MatrixOrder order = MatrixOrder.Append); void RotateTransform(float fAngle, MatrixOrder order = MatrixOrder.Append);
void ScaleTransform(float sx, float sy, MatrixOrder order = MatrixOrder.Append); void ScaleTransform(float sx, float sy, MatrixOrder order = MatrixOrder.Append);
......
...@@ -98,21 +98,6 @@ namespace Svg ...@@ -98,21 +98,6 @@ namespace Svg
this._innerGraphics.Dispose(); 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() Graphics IGraphicsProvider.GetGraphics()
{ {
return _innerGraphics; return _innerGraphics;
......
...@@ -171,9 +171,12 @@ namespace Svg ...@@ -171,9 +171,12 @@ namespace Svg
throw new FileNotFoundException("The specified document cannot be found.", path); throw new FileNotFoundException("The specified document cannot be found.", path);
} }
var doc = Open<T>(File.OpenRead(path), entities); using (var stream = File.OpenRead(path))
doc.BaseUri = new Uri(System.IO.Path.GetFullPath(path)); {
return doc; var doc = Open<T>(stream, entities);
doc.BaseUri = new Uri(System.IO.Path.GetFullPath(path));
return doc;
}
} }
/// <summary> /// <summary>
......
...@@ -254,6 +254,8 @@ namespace Svg ...@@ -254,6 +254,8 @@ namespace Svg
get { return this._customAttributes; } get { return this._customAttributes; }
} }
private static readonly Matrix _zeroMatrix = new Matrix(0, 0, 0, 0, 0, 0);
/// <summary> /// <summary>
/// Applies the required transforms to <see cref="ISvgRenderer"/>. /// Applies the required transforms to <see cref="ISvgRenderer"/>.
/// </summary> /// </summary>
...@@ -268,9 +270,9 @@ namespace Svg ...@@ -268,9 +270,9 @@ namespace Svg
{ {
return true; 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) foreach (SvgTransform transformation in this.Transforms)
{ {
......
...@@ -14,13 +14,13 @@ namespace Svg ...@@ -14,13 +14,13 @@ namespace Svg
/// </summary> /// </summary>
internal class SvgElementFactory internal class SvgElementFactory
{ {
private static List<ElementInfo> availableElements; private static Dictionary<string, ElementInfo> availableElements;
private static Parser cssParser = new Parser(); private static Parser cssParser = new Parser();
/// <summary> /// <summary>
/// Gets a list of available types that can be used when creating an <see cref="SvgElement"/>. /// Gets a list of available types that can be used when creating an <see cref="SvgElement"/>.
/// </summary> /// </summary>
public static List<ElementInfo> AvailableElements public static Dictionary<string, ElementInfo> AvailableElements
{ {
get get
{ {
...@@ -31,7 +31,10 @@ namespace Svg ...@@ -31,7 +31,10 @@ namespace Svg
&& t.IsSubclassOf(typeof(SvgElement)) && t.IsSubclassOf(typeof(SvgElement))
select new ElementInfo { ElementName = ((SvgElementAttribute)t.GetCustomAttributes(typeof(SvgElementAttribute), true)[0]).ElementName, ElementType = t }; 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; return availableElements;
...@@ -91,8 +94,8 @@ namespace Svg ...@@ -91,8 +94,8 @@ namespace Svg
} }
else else
{ {
ElementInfo validType = AvailableElements.SingleOrDefault(e => e.ElementName == elementName); ElementInfo validType = null;
if (validType != null) if (AvailableElements.TryGetValue(elementName, out validType))
{ {
createdElement = (SvgElement) Activator.CreateInstance(validType.ElementType); createdElement = (SvgElement) Activator.CreateInstance(validType.ElementType);
} }
......
...@@ -86,5 +86,10 @@ namespace Svg ...@@ -86,5 +86,10 @@ namespace Svg
return provider.GetGraphics(); return provider.GetGraphics();
} }
} }
public void Dispose()
{
_font.Dispose();
}
} }
} }
...@@ -7,7 +7,7 @@ using System.Drawing.Drawing2D; ...@@ -7,7 +7,7 @@ using System.Drawing.Drawing2D;
namespace Svg namespace Svg
{ {
public interface IFontDefn public interface IFontDefn : IDisposable
{ {
float Size { get; } float Size { get; }
float SizeInPoints { 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