Commit 1585c700 authored by Ritch Melton's avatar Ritch Melton
Browse files

merged

parents cc4f2940 1818255b
......@@ -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,69 +57,80 @@ 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)
{
var coords = new float[6];
switch (command)
{
case 'm': // relative moveto
case 'M': // moveto
segments.Add(
new SvgMoveToSegment(ToAbsolute(coords[0], coords[1], segments, isRelative)));
if (parser.TryGetFloat(out coords[0]) && parser.TryGetFloat(out coords[1]))
{
segments.Add(new SvgMoveToSegment(ToAbsolute(coords[0], coords[1], segments, isRelative)));
}
for (var i = 2; i < coords.Count; i += 2)
while (parser.TryGetFloat(out coords[0]) && parser.TryGetFloat(out coords[1]))
{
segments.Add(new SvgLineSegment(segments.Last.End,
ToAbsolute(coords[i], coords[i + 1], segments, isRelative)));
ToAbsolute(coords[0], coords[1], segments, isRelative)));
}
break;
case 'a':
case 'A':
SvgArcSize size;
SvgArcSweep sweep;
bool size;
bool sweep;
for (var i = 0; i < coords.Count; i += 7)
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]))
{
size = (coords[i + 3] != 0.0f) ? SvgArcSize.Large : SvgArcSize.Small;
sweep = (coords[i + 4] != 0.0f) ? SvgArcSweep.Positive : SvgArcSweep.Negative;
// 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)));
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
for (var i = 0; i < coords.Count; i += 2)
while (parser.TryGetFloat(out coords[0]) && parser.TryGetFloat(out coords[1]))
{
segments.Add(new SvgLineSegment(segments.Last.End,
ToAbsolute(coords[i], coords[i + 1], segments, isRelative)));
ToAbsolute(coords[0], coords[1], segments, isRelative)));
}
break;
case 'H': // horizontal lineto
case 'h': // relative horizontal lineto
foreach (var value in coords)
while (parser.TryGetFloat(out coords[0]))
{
segments.Add(new SvgLineSegment(segments.Last.End,
ToAbsolute(value, segments.Last.End.Y, segments, isRelative, false)));
ToAbsolute(coords[0], segments.Last.End.Y, segments, isRelative, false)));
}
break;
case 'V': // vertical lineto
case 'v': // relative vertical lineto
foreach (var value in coords)
while (parser.TryGetFloat(out coords[0]))
{
segments.Add(new SvgLineSegment(segments.Last.End,
ToAbsolute(segments.Last.End.X, value, segments, false, isRelative)));
ToAbsolute(segments.Last.End.X, coords[0], segments, false, isRelative)));
}
break;
case 'Q': // curveto
case 'q': // relative curveto
for (var i = 0; i < coords.Count; i += 4)
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[i], coords[i + 1], segments, isRelative),
ToAbsolute(coords[i + 2], coords[i + 3], segments, isRelative)));
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
for (var i = 0; i < coords.Count; i += 2)
while (parser.TryGetFloat(out coords[0]) && parser.TryGetFloat(out coords[1]))
{
var lastQuadCurve = segments.Last as SvgQuadraticCurveSegment;
......@@ -130,23 +139,25 @@ namespace Svg
: segments.Last.End;
segments.Add(new SvgQuadraticCurveSegment(segments.Last.End, controlPoint,
ToAbsolute(coords[i], coords[i + 1], segments, isRelative)));
ToAbsolute(coords[0], coords[1], segments, isRelative)));
}
break;
case 'C': // 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]) &&
parser.TryGetFloat(out coords[4]) && parser.TryGetFloat(out coords[5]))
{
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)));
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
for (var i = 0; i < coords.Count; i += 4)
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;
......@@ -155,8 +166,8 @@ namespace Svg
: 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)));
ToAbsolute(coords[0], coords[1], segments, isRelative),
ToAbsolute(coords[2], coords[3], segments, isRelative)));
}
break;
case 'Z': // closepath
......@@ -273,17 +284,224 @@ namespace Svg
}
}
private static IEnumerable<float> ParseCoordinates(string coords)
{
var parts = Regex.Split(coords.Remove(0, 1), @"[\s,]|(?=(?<!e)-)", RegexOptions.Compiled);
for (int i = 0; i < parts.Length; i++)
{
if (!String.IsNullOrEmpty(parts[i]))
yield return float.Parse(parts[i].Trim(), NumberStyles.Float, CultureInfo.InvariantCulture);
}
}
//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)
{
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
namespace Svg
{
public interface IGraphicsProvider
{
Graphics GetGraphics();
}
}
using System;
using System.Drawing.Drawing2D;
using System.Drawing;
using System.Collections.Generic;
namespace Svg
{
public interface ISvgRenderer : IDisposable
{
float DpiY { get; }
void DrawImage(Image image, RectangleF destRect, RectangleF srcRect, GraphicsUnit graphicsUnit);
void DrawImageUnscaled(Image image, Point location);
void DrawPath(Pen pen, GraphicsPath path);
void FillPath(Brush brush, GraphicsPath path);
ISvgBoundable GetBoundable();
Region GetClip();
ISvgBoundable PopBoundable();
void RotateTransform(float fAngle, MatrixOrder order = MatrixOrder.Append);
void ScaleTransform(float sx, float sy, MatrixOrder order = MatrixOrder.Append);
void SetBoundable(ISvgBoundable boundable);
void SetClip(Region region, CombineMode combineMode = CombineMode.Replace);
SmoothingMode SmoothingMode { get; set; }
Matrix Transform { get; set; }
void TranslateTransform(float dx, float dy, MatrixOrder order = MatrixOrder.Append);
}
}
......@@ -11,16 +11,16 @@ namespace Svg
/// <summary>
/// Convenience wrapper around a graphics object
/// </summary>
public sealed class SvgRenderer : IDisposable
public sealed class SvgRenderer : IDisposable, IGraphicsProvider, ISvgRenderer
{
private Graphics _innerGraphics;
private Stack<ISvgBoundable> _boundables = new Stack<ISvgBoundable>();
public void Boundable(ISvgBoundable boundable)
public void SetBoundable(ISvgBoundable boundable)
{
_boundables.Push(boundable);
}
public ISvgBoundable Boundable()
public ISvgBoundable GetBoundable()
{
return _boundables.Peek();
}
......@@ -29,107 +29,57 @@ namespace Svg
return _boundables.Pop();
}
/// <summary>
/// Initializes a new instance of the <see cref="SvgRenderer"/> class.
/// </summary>
private SvgRenderer()
{
}
public Region Clip
public float DpiY
{
get { return this._innerGraphics.Clip; }
set { this._innerGraphics.Clip = value; }
get { return _innerGraphics.DpiY; }
}
/// <summary>
/// Creates a new <see cref="SvgRenderer"/> from the specified <see cref="Image"/>.
/// Initializes a new instance of the <see cref="ISvgRenderer"/> class.
/// </summary>
/// <param name="image"><see cref="Image"/> from which to create the new <see cref="SvgRenderer"/>.</param>
public static SvgRenderer FromImage(Image image)
private SvgRenderer(Graphics graphics)
{
SvgRenderer renderer = new SvgRenderer();
renderer._innerGraphics = Graphics.FromImage(image);
return renderer;
}
/// <summary>
/// Creates a new <see cref="SvgRenderer"/> from the specified <see cref="Graphics"/>.
/// </summary>
/// <param name="graphics">The <see cref="Graphics"/> to create the renderer from.</param>
public static SvgRenderer FromGraphics(Graphics graphics)
{
SvgRenderer renderer = new SvgRenderer();
renderer._innerGraphics = graphics;
return renderer;
}
public static SvgRenderer FromNull()
{
SvgRenderer renderer = new SvgRenderer();
var img = new Bitmap(1, 1);
renderer._innerGraphics = Graphics.FromImage(img);
return renderer;
}
public void DrawImageUnscaled(Image image, Point location)
{
this._innerGraphics.DrawImageUnscaled(image, location);
this._innerGraphics = graphics;
}
public void DrawImage(Image image, RectangleF destRect, RectangleF srcRect, GraphicsUnit graphicsUnit)
{
_innerGraphics.DrawImage(image, destRect, srcRect, graphicsUnit);
}
public void AddClip(Region region)
public void DrawImageUnscaled(Image image, Point location)
{
this._innerGraphics.SetClip(region, CombineMode.Intersect);
this._innerGraphics.DrawImageUnscaled(image, location);
}
public void SetClip(Region region)
public void DrawPath(Pen pen, GraphicsPath path)
{
this._innerGraphics.SetClip(region, CombineMode.Replace);
this._innerGraphics.DrawPath(pen, path);
}
public void FillPath(Brush brush, GraphicsPath path)
{
this._innerGraphics.FillPath(brush, path);
}
public void DrawPath(Pen pen, GraphicsPath path)
public Region GetClip()
{
this._innerGraphics.DrawPath(pen, path);
return this._innerGraphics.Clip;
}
public void RotateTransform(float fAngle, MatrixOrder order)
public void RotateTransform(float fAngle, MatrixOrder order = MatrixOrder.Append)
{
this._innerGraphics.RotateTransform(fAngle, order);
}
public void RotateTransform(float fAngle)
public void ScaleTransform(float sx, float sy, MatrixOrder order = MatrixOrder.Append)
{
this.RotateTransform(fAngle, MatrixOrder.Append);
this._innerGraphics.ScaleTransform(sx, sy, order);
}
public void TranslateTransform(float dx, float dy, MatrixOrder order)
public void SetClip(Region region, CombineMode combineMode = CombineMode.Replace)
{
this._innerGraphics.TranslateTransform(dx, dy, order);
this._innerGraphics.SetClip(region, combineMode);
}
public void TranslateTransform(float dx, float dy)
public void TranslateTransform(float dx, float dy, MatrixOrder order = MatrixOrder.Append)
{
this.TranslateTransform(dx, dy, MatrixOrder.Append);
this._innerGraphics.TranslateTransform(dx, dy, order);
}
public void ScaleTransform(float sx, float sy, MatrixOrder order)
{
this._innerGraphics.ScaleTransform(sx, sy, order);
}
public void ScaleTransform(float sx, float sy)
{
this.ScaleTransform(sx, sy, MatrixOrder.Append);
}
public SmoothingMode SmoothingMode
{
......@@ -137,61 +87,49 @@ namespace Svg
set { this._innerGraphics.SmoothingMode = value; }
}
public PixelOffsetMode PixelOffsetMode
{
get { return this._innerGraphics.PixelOffsetMode; }
set { this._innerGraphics.PixelOffsetMode = value; }
}
public CompositingQuality CompositingQuality
{
get { return this._innerGraphics.CompositingQuality; }
set { this._innerGraphics.CompositingQuality = value; }
}
public TextRenderingHint TextRenderingHint
public Matrix Transform
{
get { return this._innerGraphics.TextRenderingHint; }
set { this._innerGraphics.TextRenderingHint = value; }
get { return this._innerGraphics.Transform; }
set { this._innerGraphics.Transform = value; }
}
public int TextContrast
public void Dispose()
{
get { return this._innerGraphics.TextContrast; }
set { this._innerGraphics.TextContrast = value; }
this._innerGraphics.Dispose();
}
public Matrix Transform
Graphics IGraphicsProvider.GetGraphics()
{
get { return this._innerGraphics.Transform; }
set { this._innerGraphics.Transform = value; }
return _innerGraphics;
}
public void Save()
/// <summary>
/// Creates a new <see cref="ISvgRenderer"/> from the specified <see cref="Image"/>.
/// </summary>
/// <param name="image"><see cref="Image"/> from which to create the new <see cref="ISvgRenderer"/>.</param>
public static ISvgRenderer FromImage(Image image)
{
this._innerGraphics.Save();
var g = Graphics.FromImage(image);
g.TextRenderingHint = TextRenderingHint.AntiAlias;
g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.Half;
g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
g.TextContrast = 1;
return new SvgRenderer(g);
}
public void Dispose()
/// <summary>
/// Creates a new <see cref="ISvgRenderer"/> from the specified <see cref="Graphics"/>.
/// </summary>
/// <param name="graphics">The <see cref="Graphics"/> to create the renderer from.</param>
public static ISvgRenderer FromGraphics(Graphics graphics)
{
this._innerGraphics.Dispose();
return new SvgRenderer(graphics);
}
public SizeF MeasureString(string text, Font font)
public static ISvgRenderer FromNull()
{
var ff = font.FontFamily;
//Baseline calculation to match http://bobpowell.net/formattingtext.aspx
float ascent = ff.GetCellAscent(font.Style);
float baselineOffset = font.SizeInPoints / ff.GetEmHeight(font.Style) * ascent;
float baselineOffsetPixels = this._innerGraphics.DpiY / 72f * baselineOffset;
StringFormat format = StringFormat.GenericTypographic;
format.SetMeasurableCharacterRanges(new CharacterRange[]{new CharacterRange(0, text.Length)});
format.FormatFlags |= StringFormatFlags.MeasureTrailingSpaces;
Region[] r = this._innerGraphics.MeasureCharacterRanges(text, font, new Rectangle(0, 0, 1000, 1000), format);
RectangleF rect = r[0].GetBounds(this._innerGraphics);
return new SizeF(rect.Width, baselineOffsetPixels);
var img = new Bitmap(1, 1);
return SvgRenderer.FromImage(img);
}
}
}
\ No newline at end of file
......@@ -100,8 +100,20 @@
<Compile Include="Clipping and Masking\SvgClipPath.cs" />
<Compile Include="Clipping and Masking\SvgMask.cs" />
<Compile Include="DataTypes\ISvgSupportsCoordinateUnits.cs" />
<Compile Include="DataTypes\SvgPointCollection.cs" />
<Compile Include="DataTypes\SvgTextDecoration.cs" />
<Compile Include="DataTypes\SvgTextLengthAdjust.cs" />
<Compile Include="DataTypes\SvgTextPathMethod.cs" />
<Compile Include="DataTypes\SvgTextPathSpacing.cs" />
<Compile Include="DataTypes\XmlSpaceHandling.cs" />
<Compile Include="Document Structure\SvgSymbol.cs" />
<Compile Include="Filter Effects\ImageBuffer.cs" />
<Compile Include="Painting\GenericBoundable.cs" />
<Compile Include="Painting\SvgFallbackPaintServer .cs" />
<Compile Include="Paths\CoordinateParser.cs" />
<Compile Include="Rendering\IGraphicsProvider.cs" />
<Compile Include="Rendering\ISvgRenderer.cs" />
<Compile Include="SvgElementStyle.cs" />
<Compile Include="SvgNodeReader.cs" />
<Compile Include="Css\CssQuery.cs" />
<Compile Include="Css\SvgElementOps.cs" />
......@@ -234,7 +246,7 @@
<Compile Include="SvgUnknownElement.cs" />
<Compile Include="SvgElementAttribute.cs" />
<Compile Include="SvgExtentions.cs" />
<Compile Include="SvgRenderer.cs" />
<Compile Include="Rendering\SvgRenderer.cs" />
<Compile Include="Painting\SvgColourConverter.cs" />
<Compile Include="Painting\SvgGradientSpreadMethod.cs" />
<Compile Include="SvgDtdResolver.cs" />
......@@ -275,10 +287,24 @@
<Compile Include="DataTypes\SvgUnit.cs" />
<Compile Include="DataTypes\SvgUnitConverter.cs" />
<Compile Include="SvgTextReader.cs" />
<Compile Include="Text\SvgFontFaceUri.cs" />
<Compile Include="Text\FontFamily.cs" />
<Compile Include="Text\GdiFontDefn.cs" />
<Compile Include="Text\IFontDefn.cs" />
<Compile Include="Text\SvgFont.cs" />
<Compile Include="Text\SvgFontDefn.cs" />
<Compile Include="Text\SvgFontFace.cs" />
<Compile Include="Text\SvgFontFaceSrc.cs" />
<Compile Include="Text\SvgGlyph.cs" />
<Compile Include="Text\SvgKern.cs" />
<Compile Include="Text\SvgMissingGlyph.cs" />
<Compile Include="Text\SvgText.cs" />
<Compile Include="Text\SvgTextBase.cs" />
<Compile Include="Text\SvgTextAnchor.cs" />
<Compile Include="Text\SvgTextPath.cs" />
<Compile Include="Text\SvgTextSpan.cs" />
<Compile Include="Text\SvgTextRef.cs" />
<Compile Include="Text\PathStatistics.cs" />
<Compile Include="Transforms\ISvgTransformable.cs" />
<Compile Include="Transforms\SvgMatrix.cs" />
<Compile Include="Transforms\SvgRotate.cs" />
......
......@@ -15,13 +15,14 @@ namespace Svg
/// <summary>
/// Gets a <see cref="string"/> containing the XLink namespace (http://www.w3.org/1999/xlink).
/// </summary>
public const string SVG_NAMESPACE = "http://www.w3.org/2000/svg";
public const string SvgNamespace = "http://www.w3.org/2000/svg";
public const string XLinkPrefix = "xlink";
public const string XLinkNamespace = "http://www.w3.org/1999/xlink";
public const string XmlNamespace = "http://www.w3.org/XML/1998/namespace";
public static readonly List<KeyValuePair<string, string>> Namespaces = new List<KeyValuePair<string, string>>()
{
new KeyValuePair<string, string>("", SVG_NAMESPACE),
new KeyValuePair<string, string>("", SvgNamespace),
new KeyValuePair<string, string>(XLinkPrefix, XLinkNamespace)
};
private string _name;
......@@ -55,7 +56,7 @@ namespace Svg
{
get
{
if (_namespace == SVG_NAMESPACE)
if (_namespace == SvgNamespace)
return _name;
return Namespaces.First(x => x.Value == _namespace).Key + ":" + _name;
}
......@@ -93,7 +94,7 @@ namespace Svg
internal SvgAttributeAttribute(string name)
{
this._name = name;
this._namespace = SVG_NAMESPACE;
this._namespace = SvgNamespace;
}
/// <summary>
......
......@@ -90,7 +90,10 @@ namespace Svg
(value is SvgTextAnchor && (SvgTextAnchor)value == SvgTextAnchor.inherit) ||
(value is SvgFontVariant && (SvgFontVariant)value == SvgFontVariant.inherit) ||
(value is SvgTextDecoration && (SvgTextDecoration)value == SvgTextDecoration.inherit) ||
(value == "inherit")
(value is XmlSpaceHandling && (XmlSpaceHandling)value == XmlSpaceHandling.inherit) ||
(value is SvgOverflow && (SvgOverflow)value == SvgOverflow.inherit) ||
(value == SvgColourServer.Inherit) ||
(value is string && (string)value == "inherit")
);
}
......
......@@ -22,6 +22,18 @@ namespace Svg
public static readonly int PointsPerInch = 96;
private SvgElementIdManager _idManager;
private Dictionary<string, IEnumerable<SvgFontFace>> _fontDefns = null;
internal Dictionary<string, IEnumerable<SvgFontFace>> FontDefns()
{
if (_fontDefns == null)
{
_fontDefns = (from f in Descendants().OfType<SvgFontFace>()
group f by f.FontFamily into family
select family).ToDictionary(f => f.Key, f => (IEnumerable<SvgFontFace>)f);
}
return _fontDefns;
}
/// <summary>
/// Initializes a new instance of the <see cref="SvgDocument"/> class.
/// </summary>
......@@ -64,6 +76,11 @@ namespace Svg
/// </summary>
public int Ppi { get; set; }
/// <summary>
/// Gets or sets an external Cascading Style Sheet (CSS)
/// </summary>
public string ExternalCSSHref { get; set; }
#region ITypeDescriptorContext Members
IContainer ITypeDescriptorContext.Container
......@@ -159,10 +176,13 @@ namespace Svg
throw new FileNotFoundException("The specified document cannot be found.", path);
}
var doc = Open<T>(File.OpenRead(path), entities);
using (var stream = File.OpenRead(path))
{
var doc = Open<T>(stream, entities);
doc.BaseUri = new Uri(System.IO.Path.GetFullPath(path));
return doc;
}
}
/// <summary>
/// Attempts to open an SVG document from the specified <see cref="Stream"/>.
......@@ -379,18 +399,18 @@ namespace Svg
}
/// <summary>
/// Renders the <see cref="SvgDocument"/> to the specified <see cref="SvgRenderer"/>.
/// Renders the <see cref="SvgDocument"/> to the specified <see cref="ISvgRenderer"/>.
/// </summary>
/// <param name="renderer">The <see cref="SvgRenderer"/> to render the document with.</param>
/// <param name="renderer">The <see cref="ISvgRenderer"/> to render the document with.</param>
/// <exception cref="ArgumentNullException">The <paramref name="renderer"/> parameter cannot be <c>null</c>.</exception>
public void Draw(SvgRenderer renderer)
public void Draw(ISvgRenderer renderer)
{
if (renderer == null)
{
throw new ArgumentNullException("renderer");
}
renderer.Boundable(this);
renderer.SetBoundable(this);
this.Render(renderer);
}
......@@ -407,7 +427,7 @@ namespace Svg
}
var renderer = SvgRenderer.FromGraphics(graphics);
renderer.Boundable(this);
renderer.SetBoundable(this);
this.Render(renderer);
}
......@@ -420,7 +440,7 @@ namespace Svg
//Trace.TraceInformation("Begin Render");
var size = GetDimensions();
var bitmap = new Bitmap((int)Math.Ceiling(size.Width), (int)Math.Ceiling(size.Height));
var bitmap = new Bitmap((int)Math.Round(size.Width), (int)Math.Round(size.Height));
// bitmap.SetResolution(300, 300);
try
{
......@@ -447,12 +467,8 @@ namespace Svg
{
using (var renderer = SvgRenderer.FromImage(bitmap))
{
renderer.Boundable(new GenericBoundable(0, 0, bitmap.Width, bitmap.Height));
renderer.TextRenderingHint = TextRenderingHint.AntiAlias;
renderer.TextContrast = 1;
renderer.PixelOffsetMode = PixelOffsetMode.Half;
renderer.SetBoundable(new GenericBoundable(0, 0, bitmap.Width, bitmap.Height));
this.Render(renderer);
renderer.Save();
}
}
catch
......@@ -471,6 +487,9 @@ namespace Svg
xmlWriter.WriteDocType("svg", "-//W3C//DTD SVG 1.1//EN", "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd", null);
if (!String.IsNullOrEmpty(this.ExternalCSSHref))
xmlWriter.WriteProcessingInstruction("xml-stylesheet", String.Format("type=\"text/css\" href=\"{0}\"", this.ExternalCSSHref));
this.WriteElement(xmlWriter);
xmlWriter.Flush();
......
......@@ -15,8 +15,11 @@ namespace Svg
/// <summary>
/// The base class of which all SVG elements are derived from.
/// </summary>
public abstract class SvgElement : ISvgElement, ISvgTransformable, ICloneable, ISvgNode
public abstract partial class SvgElement : ISvgElement, ISvgTransformable, ICloneable, ISvgNode
{
internal const int StyleSpecificity_PresAttribute = 0;
internal const int StyleSpecificity_InlineStyle = 1 << 16;
//optimization
protected class PropertyAttributeTuple
{
......@@ -48,6 +51,7 @@ namespace Svg
private Dictionary<string, SortedDictionary<int, string>> _styles = new Dictionary<string, SortedDictionary<int, string>>();
public void AddStyle(string name, string value, int specificity)
{
SortedDictionary<int, string> rules;
......@@ -68,6 +72,32 @@ namespace Svg
_styles = null;
}
public bool ContainsAttribute(string name)
{
SortedDictionary<int, string> rules;
return (this.Attributes.ContainsKey(name) || this.CustomAttributes.ContainsKey(name) ||
(_styles.TryGetValue(name, out rules) && (rules.ContainsKey(StyleSpecificity_InlineStyle) || rules.ContainsKey(StyleSpecificity_PresAttribute))));
}
public bool TryGetAttribute(string name, out string value)
{
object objValue;
if (this.Attributes.TryGetValue(name, out objValue))
{
value = objValue.ToString();
return true;
}
if (this.CustomAttributes.TryGetValue(name, out value)) return true;
SortedDictionary<int, string> rules;
if (_styles.TryGetValue(name, out rules))
{
// Get staged styles that are
if (rules.TryGetValue(StyleSpecificity_InlineStyle, out value)) return true;
if (rules.TryGetValue(StyleSpecificity_PresAttribute, out value)) return true;
}
return false;
}
/// <summary>
/// Gets the name of the element.
/// </summary>
......@@ -254,23 +284,25 @@ 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="SvgRenderer"/>.
/// Applies the required transforms to <see cref="ISvgRenderer"/>.
/// </summary>
/// <param name="renderer">The <see cref="SvgRenderer"/> to be transformed.</param>
protected internal virtual bool PushTransforms(SvgRenderer renderer)
/// <param name="renderer">The <see cref="ISvgRenderer"/> to be transformed.</param>
protected internal virtual bool PushTransforms(ISvgRenderer renderer)
{
_graphicsMatrix = renderer.Transform;
_graphicsClip = renderer.Clip;
_graphicsClip = renderer.GetClip();
// Return if there are no transforms
if (this.Transforms == null || this.Transforms.Count == 0)
{
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)
{
......@@ -283,10 +315,10 @@ namespace Svg
}
/// <summary>
/// Removes any previously applied transforms from the specified <see cref="SvgRenderer"/>.
/// Removes any previously applied transforms from the specified <see cref="ISvgRenderer"/>.
/// </summary>
/// <param name="renderer">The <see cref="SvgRenderer"/> that should have transforms removed.</param>
protected internal virtual void PopTransforms(SvgRenderer renderer)
/// <param name="renderer">The <see cref="ISvgRenderer"/> that should have transforms removed.</param>
protected internal virtual void PopTransforms(ISvgRenderer renderer)
{
renderer.Transform = _graphicsMatrix;
_graphicsMatrix = null;
......@@ -295,19 +327,19 @@ namespace Svg
}
/// <summary>
/// Applies the required transforms to <see cref="SvgRenderer"/>.
/// Applies the required transforms to <see cref="ISvgRenderer"/>.
/// </summary>
/// <param name="renderer">The <see cref="SvgRenderer"/> to be transformed.</param>
void ISvgTransformable.PushTransforms(SvgRenderer renderer)
/// <param name="renderer">The <see cref="ISvgRenderer"/> to be transformed.</param>
void ISvgTransformable.PushTransforms(ISvgRenderer renderer)
{
this.PushTransforms(renderer);
}
/// <summary>
/// Removes any previously applied transforms from the specified <see cref="SvgRenderer"/>.
/// Removes any previously applied transforms from the specified <see cref="ISvgRenderer"/>.
/// </summary>
/// <param name="renderer">The <see cref="SvgRenderer"/> that should have transforms removed.</param>
void ISvgTransformable.PopTransforms(SvgRenderer renderer)
/// <param name="renderer">The <see cref="ISvgRenderer"/> that should have transforms removed.</param>
void ISvgTransformable.PopTransforms(ISvgRenderer renderer)
{
this.PopTransforms(renderer);
}
......@@ -344,6 +376,17 @@ namespace Svg
}
}
/// <summary>
/// Gets or sets the text anchor.
/// </summary>
/// <value>The text anchor.</value>
[SvgAttribute("space", SvgAttributeAttribute.XmlNamespace)]
public virtual XmlSpaceHandling SpaceHandling
{
get { return (this.Attributes["space"] == null) ? XmlSpaceHandling.inherit : (XmlSpaceHandling)this.Attributes["space"]; }
set { this.Attributes["space"] = value; }
}
public void SetAndForceUniqueID(string value, bool autoForceUniqueID = true, Action<SvgElement, string, string> logElementOldIDNewID = null)
{
// Don't do anything if it hasn't changed
......@@ -469,10 +512,10 @@ namespace Svg
/// <summary>
/// Renders this element to the <see cref="SvgRenderer"/>.
/// Renders this element to the <see cref="ISvgRenderer"/>.
/// </summary>
/// <param name="renderer">The <see cref="SvgRenderer"/> that the element should use to render itself.</param>
public void RenderElement(SvgRenderer renderer)
/// <param name="renderer">The <see cref="ISvgRenderer"/> that the element should use to render itself.</param>
public void RenderElement(ISvgRenderer renderer)
{
this.Render(renderer);
}
......@@ -632,10 +675,10 @@ namespace Svg
}
/// <summary>
/// Renders the <see cref="SvgElement"/> and contents to the specified <see cref="SvgRenderer"/> object.
/// Renders the <see cref="SvgElement"/> and contents to the specified <see cref="ISvgRenderer"/> object.
/// </summary>
/// <param name="renderer">The <see cref="SvgRenderer"/> object to render to.</param>
protected virtual void Render(SvgRenderer renderer)
/// <param name="renderer">The <see cref="ISvgRenderer"/> object to render to.</param>
protected virtual void Render(ISvgRenderer renderer)
{
this.PushTransforms(renderer);
this.RenderChildren(renderer);
......@@ -645,8 +688,8 @@ namespace Svg
/// <summary>
/// Renders the children of this <see cref="SvgElement"/>.
/// </summary>
/// <param name="renderer">The <see cref="SvgRenderer"/> to render the child <see cref="SvgElement"/>s to.</param>
protected virtual void RenderChildren(SvgRenderer renderer)
/// <param name="renderer">The <see cref="ISvgRenderer"/> to render the child <see cref="SvgElement"/>s to.</param>
protected virtual void RenderChildren(ISvgRenderer renderer)
{
foreach (SvgElement element in this.Children)
{
......@@ -655,10 +698,10 @@ namespace Svg
}
/// <summary>
/// Renders the <see cref="SvgElement"/> and contents to the specified <see cref="SvgRenderer"/> object.
/// Renders the <see cref="SvgElement"/> and contents to the specified <see cref="ISvgRenderer"/> object.
/// </summary>
/// <param name="renderer">The <see cref="SvgRenderer"/> object to render to.</param>
void ISvgElement.Render(SvgRenderer renderer)
/// <param name="renderer">The <see cref="ISvgRenderer"/> object to render to.</param>
void ISvgElement.Render(ISvgRenderer renderer)
{
this.Render(renderer);
}
......@@ -698,7 +741,7 @@ namespace Svg
/// </summary>
/// <param name="elem"></param>
/// <param name="path"></param>
protected GraphicsPath GetPaths(SvgElement elem, SvgRenderer renderer)
protected GraphicsPath GetPaths(SvgElement elem, ISvgRenderer renderer)
{
var ret = new GraphicsPath();
......@@ -1117,6 +1160,6 @@ namespace Svg
SvgElementCollection Children { get; }
IList<ISvgNode> Nodes { get; }
void Render(SvgRenderer renderer);
void Render(ISvgRenderer renderer);
}
}
\ No newline at end of file
......@@ -14,14 +14,13 @@ namespace Svg
/// </summary>
internal class SvgElementFactory
{
private static List<ElementInfo> availableElements;
private const string svgNS = "http://www.w3.org/2000/svg";
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
{
......@@ -32,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;
......@@ -84,7 +86,7 @@ namespace Svg
//Trace.TraceInformation("Begin CreateElement: {0}", elementName);
if (elementNS == svgNS)
if (elementNS == SvgAttributeAttribute.SvgNamespace || string.IsNullOrEmpty(elementNS))
{
if (elementName == "svg")
{
......@@ -92,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);
}
......@@ -137,13 +139,13 @@ namespace Svg
{
foreach (var decl in rule.Declarations)
{
element.AddStyle(decl.Name, decl.Term.ToString(), 1 << 16);
element.AddStyle(decl.Name, decl.Term.ToString(), SvgElement.StyleSpecificity_InlineStyle);
}
}
}
else if (IsStyleAttribute(reader.LocalName))
{
element.AddStyle(reader.LocalName, reader.Value, 2 << 16);
element.AddStyle(reader.LocalName, reader.Value, SvgElement.StyleSpecificity_PresAttribute);
}
else
{
......
......@@ -3,6 +3,8 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Net;
using System.IO;
namespace Svg
{
......@@ -39,6 +41,30 @@ namespace Svg
public virtual SvgElement GetElementById(Uri uri)
{
if (uri.ToString().StartsWith("url(")) uri = new Uri(uri.ToString().Substring(4).TrimEnd(')'), UriKind.Relative);
if (!uri.IsAbsoluteUri && this._document.BaseUri != null && !uri.ToString().StartsWith("#"))
{
var fullUri = new Uri(this._document.BaseUri, uri);
var hash = fullUri.OriginalString.Substring(fullUri.OriginalString.LastIndexOf('#'));
SvgDocument doc;
switch (fullUri.Scheme.ToLowerInvariant())
{
case "file":
doc = SvgDocument.Open<SvgDocument>(fullUri.LocalPath.Substring(0, fullUri.LocalPath.Length - hash.Length));
return doc.IdManager.GetElementById(hash);
case "http":
case "https":
var httpRequest = WebRequest.Create(uri);
using (WebResponse webResponse = httpRequest.GetResponse())
{
doc = SvgDocument.Open<SvgDocument>(webResponse.GetResponseStream());
return doc.IdManager.GetElementById(hash);
}
default:
throw new NotSupportedException();
}
}
return this.GetElementById(uri.ToString());
}
......
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text;
using System.Reflection;
using System.ComponentModel;
using Svg.DataTypes;
using System.Text.RegularExpressions;
using System.Linq;
namespace Svg
{
public partial class SvgElement
{
private bool _dirty;
/// <summary>
/// Gets or sets a value indicating whether this element's <see cref="Path"/> is dirty.
/// </summary>
/// <value>
/// <c>true</c> if the path is dirty; otherwise, <c>false</c>.
/// </value>
protected virtual bool IsPathDirty
{
get { return this._dirty; }
set { this._dirty = value; }
}
private static float FixOpacityValue(float value)
{
const float max = 1.0f;
const float min = 0.0f;
return Math.Min(Math.Max(value, min), max);
}
/// <summary>
/// Gets or sets the fill <see cref="SvgPaintServer"/> of this element.
/// </summary>
[SvgAttribute("fill")]
public virtual SvgPaintServer Fill
{
get { return (this.Attributes["fill"] == null) ? SvgColourServer.NotSet : (SvgPaintServer)this.Attributes["fill"]; }
set { this.Attributes["fill"] = value; }
}
/// <summary>
/// Gets or sets the <see cref="SvgPaintServer"/> to be used when rendering a stroke around this element.
/// </summary>
[SvgAttribute("stroke")]
public virtual SvgPaintServer Stroke
{
get { return (this.Attributes["stroke"] == null) ? null : (SvgPaintServer)this.Attributes["stroke"]; }
set { this.Attributes["stroke"] = value; }
}
[SvgAttribute("fill-rule")]
public virtual SvgFillRule FillRule
{
get { return (this.Attributes["fill-rule"] == null) ? SvgFillRule.NonZero : (SvgFillRule)this.Attributes["fill-rule"]; }
set { this.Attributes["fill-rule"] = value; }
}
/// <summary>
/// Gets or sets the opacity of this element's <see cref="Fill"/>.
/// </summary>
[SvgAttribute("fill-opacity")]
public virtual float FillOpacity
{
get { return (this.Attributes["fill-opacity"] == null) ? this.Opacity : (float)this.Attributes["fill-opacity"]; }
set { this.Attributes["fill-opacity"] = FixOpacityValue(value); }
}
/// <summary>
/// Gets or sets the width of the stroke (if the <see cref="Stroke"/> property has a valid value specified.
/// </summary>
[SvgAttribute("stroke-width")]
public virtual SvgUnit StrokeWidth
{
get { return (this.Attributes["stroke-width"] == null) ? new SvgUnit(1.0f) : (SvgUnit)this.Attributes["stroke-width"]; }
set { this.Attributes["stroke-width"] = value; }
}
[SvgAttribute("stroke-linecap")]
public virtual SvgStrokeLineCap StrokeLineCap
{
get { return (this.Attributes["stroke-linecap"] == null) ? SvgStrokeLineCap.Butt : (SvgStrokeLineCap)this.Attributes["stroke-linecap"]; }
set { this.Attributes["stroke-linecap"] = value; }
}
[SvgAttribute("stroke-linejoin")]
public virtual SvgStrokeLineJoin StrokeLineJoin
{
get { return (this.Attributes["stroke-linejoin"] == null) ? SvgStrokeLineJoin.Miter : (SvgStrokeLineJoin)this.Attributes["stroke-linejoin"]; }
set { this.Attributes["stroke-linejoin"] = value; }
}
[SvgAttribute("stroke-miterlimit")]
public virtual float StrokeMiterLimit
{
get { return (this.Attributes["stroke-miterlimit"] == null) ? 4f : (float)this.Attributes["stroke-miterlimit"]; }
set { this.Attributes["stroke-miterlimit"] = value; }
}
[SvgAttribute("stroke-dasharray")]
public virtual SvgUnitCollection StrokeDashArray
{
get { return this.Attributes["stroke-dasharray"] as SvgUnitCollection; }
set { this.Attributes["stroke-dasharray"] = value; }
}
[SvgAttribute("stroke-dashoffset")]
public virtual SvgUnit StrokeDashOffset
{
get { return (this.Attributes["stroke-dashoffset"] == null) ? SvgUnit.Empty : (SvgUnit)this.Attributes["stroke-dashoffset"]; }
set { this.Attributes["stroke-dashoffset"] = value; }
}
/// <summary>
/// Gets or sets the opacity of the stroke, if the <see cref="Stroke"/> property has been specified. 1.0 is fully opaque; 0.0 is transparent.
/// </summary>
[SvgAttribute("stroke-opacity")]
public virtual float StrokeOpacity
{
get { return (this.Attributes["stroke-opacity"] == null) ? this.Opacity : (float)this.Attributes["stroke-opacity"]; }
set { this.Attributes["stroke-opacity"] = FixOpacityValue(value); }
}
/// <summary>
/// Gets or sets the colour of the gradient stop.
/// </summary>
/// <remarks>Apparently this can be set on non-sensical elements. Don't ask; just check the tests.</remarks>
[SvgAttribute("stop-color")]
[TypeConverter(typeof(SvgPaintServerFactory))]
public SvgPaintServer StopColor
{
get { return this.Attributes["stop-color"] as SvgPaintServer; }
set { this.Attributes["stop-color"] = value; }
}
/// <summary>
/// Gets or sets the opacity of the element. 1.0 is fully opaque; 0.0 is transparent.
/// </summary>
[SvgAttribute("opacity")]
public virtual float Opacity
{
get { return (this.Attributes["opacity"] == null) ? 1.0f : (float)this.Attributes["opacity"]; }
set { this.Attributes["opacity"] = FixOpacityValue(value); }
}
/// <summary>
/// Indicates which font family is to be used to render the text.
/// </summary>
[SvgAttribute("font-family")]
public virtual string FontFamily
{
get { return this.Attributes["font-family"] as string; }
set { this.Attributes["font-family"] = value; this.IsPathDirty = true; }
}
/// <summary>
/// Refers to the size of the font from baseline to baseline when multiple lines of text are set solid in a multiline layout environment.
/// </summary>
[SvgAttribute("font-size")]
public virtual SvgUnit FontSize
{
get { return (this.Attributes["font-size"] == null) ? SvgUnit.Empty : (SvgUnit)this.Attributes["font-size"]; }
set { this.Attributes["font-size"] = value; this.IsPathDirty = true; }
}
/// <summary>
/// Refers to the style of the font.
/// </summary>
[SvgAttribute("font-style")]
public virtual SvgFontStyle FontStyle
{
get { return (this.Attributes["font-style"] == null) ? SvgFontStyle.inherit : (SvgFontStyle)this.Attributes["font-style"]; }
set { this.Attributes["font-style"] = value; this.IsPathDirty = true; }
}
/// <summary>
/// Refers to the varient of the font.
/// </summary>
[SvgAttribute("font-variant")]
public virtual SvgFontVariant FontVariant
{
get { return (this.Attributes["font-variant"] == null) ? SvgFontVariant.inherit : (SvgFontVariant)this.Attributes["font-variant"]; }
set { this.Attributes["font-variant"] = value; this.IsPathDirty = true; }
}
/// <summary>
/// Refers to the boldness of the font.
/// </summary>
[SvgAttribute("text-decoration")]
public virtual SvgTextDecoration TextDecoration
{
get { return (this.Attributes["text-decoration"] == null) ? SvgTextDecoration.inherit : (SvgTextDecoration)this.Attributes["text-decoration"]; }
set { this.Attributes["text-decoration"] = value; this.IsPathDirty = true; }
}
/// <summary>
/// Refers to the boldness of the font.
/// </summary>
[SvgAttribute("font-weight")]
public virtual SvgFontWeight FontWeight
{
get { return (this.Attributes["font-weight"] == null) ? SvgFontWeight.inherit : (SvgFontWeight)this.Attributes["font-weight"]; }
set { this.Attributes["font-weight"] = value; this.IsPathDirty = true; }
}
private enum FontParseState
{
fontStyle,
fontVariant,
fontWeight,
fontSize,
fontFamilyNext,
fontFamilyCurr
}
/// <summary>
/// Set all font information.
/// </summary>
[SvgAttribute("font")]
public virtual string Font
{
get { return (this.Attributes["font"] == null ? "" : this.Attributes["font"] as string); }
set
{
var state = FontParseState.fontStyle;
var parts = value.Split(' ');
SvgFontStyle fontStyle;
SvgFontVariant fontVariant;
SvgFontWeight fontWeight;
SvgUnit fontSize;
bool success;
string[] sizes;
string part;
for (int i = 0; i < parts.Length; i++)
{
part = parts[i];
success = false;
while (!success)
{
switch (state)
{
case FontParseState.fontStyle:
success = Enums.TryParse<SvgFontStyle>(part, out fontStyle);
if (success) this.FontStyle = fontStyle;
state++;
break;
case FontParseState.fontVariant:
success = Enums.TryParse<SvgFontVariant>(part, out fontVariant);
if (success) this.FontVariant = fontVariant;
state++;
break;
case FontParseState.fontWeight:
success = Enums.TryParse<SvgFontWeight>(part, out fontWeight);
if (success) this.FontWeight = fontWeight;
state++;
break;
case FontParseState.fontSize:
sizes = part.Split('/');
try
{
fontSize = (SvgUnit)(new SvgUnitConverter().ConvertFromInvariantString(sizes[0]));
success = true;
this.FontSize = fontSize;
}
catch { }
state++;
break;
case FontParseState.fontFamilyNext:
state++;
success = true;
break;
}
}
switch (state)
{
case FontParseState.fontFamilyNext:
this.FontFamily = string.Join(" ", parts, i + 1, parts.Length - (i + 1));
i = int.MaxValue - 2;
break;
case FontParseState.fontFamilyCurr:
this.FontFamily = string.Join(" ", parts, i, parts.Length - (i));
i = int.MaxValue - 2;
break;
}
}
this.Attributes["font"] = value;
this.IsPathDirty = true;
}
}
private const string DefaultFontFamily = "Times New Roman";
/// <summary>
/// Get the font information based on data stored with the text object or inherited from the parent.
/// </summary>
/// <returns></returns>
internal IFontDefn GetFont(ISvgRenderer renderer)
{
// Get the font-size
float fontSize;
var fontSizeUnit = this.FontSize;
if (fontSizeUnit == SvgUnit.None)
{
fontSize = 1.0f;
}
else
{
fontSize = fontSizeUnit.ToDeviceValue(renderer, UnitRenderingType.Vertical, this);
}
var family = ValidateFontFamily(this.FontFamily, this.OwnerDocument);
var sFaces = family as IEnumerable<SvgFontFace>;
if (sFaces == null)
{
var fontStyle = System.Drawing.FontStyle.Regular;
// Get the font-weight
switch (this.FontWeight)
{
case SvgFontWeight.bold:
case SvgFontWeight.bolder:
case SvgFontWeight.w600:
case SvgFontWeight.w700:
case SvgFontWeight.w800:
case SvgFontWeight.w900:
fontStyle |= System.Drawing.FontStyle.Bold;
break;
}
// Get the font-style
switch (this.FontStyle)
{
case SvgFontStyle.italic:
case SvgFontStyle.oblique:
fontStyle |= System.Drawing.FontStyle.Italic;
break;
}
// Get the text-decoration
switch (this.TextDecoration)
{
case SvgTextDecoration.lineThrough:
fontStyle |= System.Drawing.FontStyle.Strikeout;
break;
case SvgTextDecoration.underline:
fontStyle |= System.Drawing.FontStyle.Underline;
break;
}
var ff = family as FontFamily;
if (!ff.IsStyleAvailable(fontStyle))
{
// Do Something
}
// Get the font-family
return new GdiFontDefn(new System.Drawing.Font(ff, fontSize, fontStyle, System.Drawing.GraphicsUnit.Pixel));
}
else
{
var font = sFaces.First().Parent as SvgFont;
if (font == null)
{
var uri = sFaces.First().Descendants().OfType<SvgFontFaceUri>().First().ReferencedElement;
font = OwnerDocument.IdManager.GetElementById(uri) as SvgFont;
}
return new SvgFontDefn(font, fontSize, OwnerDocument.Ppi);
}
}
public static object ValidateFontFamily(string fontFamilyList, SvgDocument doc)
{
// Split font family list on "," and then trim start and end spaces and quotes.
var fontParts = (fontFamilyList ?? "").Split(new[] { ',' }).Select(fontName => fontName.Trim(new[] { '"', ' ', '\'' }));
var families = System.Drawing.FontFamily.Families;
FontFamily family;
IEnumerable<SvgFontFace> sFaces;
// Find a the first font that exists in the list of installed font families.
//styles from IE get sent through as lowercase.
foreach (var f in fontParts)
{
if (doc.FontDefns().TryGetValue(f, out sFaces)) return sFaces;
family = families.FirstOrDefault(ff => ff.Name.ToLower() == f.ToLower());
if (family != null) return family;
switch (f)
{
case "serif":
return System.Drawing.FontFamily.GenericSerif;
case "sans-serif":
return System.Drawing.FontFamily.GenericSansSerif;
case "monospace":
return System.Drawing.FontFamily.GenericMonospace;
}
}
// No valid font family found from the list requested.
return System.Drawing.FontFamily.GenericSansSerif;
}
}
}
......@@ -123,7 +123,6 @@ namespace Svg
{
const string entityText = "<!ENTITY";
string[] entities = this.Value.Split(new string[]{entityText}, StringSplitOptions.None);
string[] parts = null;
string name = null;
string value = null;
int quoteIndex;
......
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace Svg.Text
{
/// <summary>
/// http://stackoverflow.com/questions/3633000/net-enumerate-winforms-font-styles
/// </summary>
public class FontFamily
{
#region InstalledFont Parameters
string _fontName = string.Empty;
string _fontSubFamily = string.Empty;
string _fontPath = string.Empty;
#endregion
#region InstalledFont Constructor
private FontFamily(string fontName, string fontSubFamily, string fontPath)
{
_fontName = fontName;
_fontSubFamily = fontSubFamily;
_fontPath = fontPath;
}
#endregion
#region InstalledFont Properties
public string FontName { get { return _fontName; } set { _fontName = value; } }
public string FontSubFamily { get { return _fontSubFamily; } set { _fontSubFamily = value; } }
public string FontPath { get { return _fontPath; } set { _fontPath = value; } }
#endregion
static public FontFamily FromPath(string fontFilePath)
{
string FontName = string.Empty;
string FontSubFamily = string.Empty;
string encStr = "UTF-8";
string strRet = string.Empty;
using (FileStream fs = new FileStream(fontFilePath, FileMode.Open, FileAccess.Read))
{
TT_OFFSET_TABLE ttOffsetTable = new TT_OFFSET_TABLE()
{
uMajorVersion = ReadUShort(fs),
uMinorVersion = ReadUShort(fs),
uNumOfTables = ReadUShort(fs),
uSearchRange = ReadUShort(fs),
uEntrySelector = ReadUShort(fs),
uRangeShift = ReadUShort(fs),
};
TT_TABLE_DIRECTORY tblDir = new TT_TABLE_DIRECTORY();
bool found = false;
for (int i = 0; i <= ttOffsetTable.uNumOfTables; i++)
{
tblDir = new TT_TABLE_DIRECTORY();
tblDir.Initialize();
fs.Read(tblDir.szTag, 0, tblDir.szTag.Length);
tblDir.uCheckSum = ReadULong(fs);
tblDir.uOffset = ReadULong(fs);
tblDir.uLength = ReadULong(fs);
Encoding enc = Encoding.GetEncoding(encStr);
string s = enc.GetString(tblDir.szTag);
if (s.CompareTo("name") == 0)
{
found = true;
break;
}
}
if (!found) return null;
fs.Seek(tblDir.uOffset, SeekOrigin.Begin);
TT_NAME_TABLE_HEADER ttNTHeader = new TT_NAME_TABLE_HEADER
{
uFSelector = ReadUShort(fs),
uNRCount = ReadUShort(fs),
uStorageOffset = ReadUShort(fs)
};
TT_NAME_RECORD ttRecord = new TT_NAME_RECORD();
for (int j = 0; j <= ttNTHeader.uNRCount; j++)
{
ttRecord = new TT_NAME_RECORD()
{
uPlatformID = ReadUShort(fs),
uEncodingID = ReadUShort(fs),
uLanguageID = ReadUShort(fs),
uNameID = ReadUShort(fs),
uStringLength = ReadUShort(fs),
uStringOffset = ReadUShort(fs)
};
if (ttRecord.uNameID > 2) { break; }
long nPos = fs.Position;
fs.Seek(tblDir.uOffset + ttRecord.uStringOffset + ttNTHeader.uStorageOffset, SeekOrigin.Begin);
byte[] buf = new byte[ttRecord.uStringLength];
fs.Read(buf, 0, ttRecord.uStringLength);
Encoding enc;
if (ttRecord.uEncodingID == 3 || ttRecord.uEncodingID == 1)
{
enc = Encoding.BigEndianUnicode;
}
else
{
enc = Encoding.UTF8;
}
strRet = enc.GetString(buf);
if (ttRecord.uNameID == 1) { FontName = strRet; }
if (ttRecord.uNameID == 2) { FontSubFamily = strRet; }
fs.Seek(nPos, SeekOrigin.Begin);
}
return new FontFamily(FontName, FontSubFamily, fontFilePath);
}
}
public struct TT_OFFSET_TABLE
{
public ushort uMajorVersion;
public ushort uMinorVersion;
public ushort uNumOfTables;
public ushort uSearchRange;
public ushort uEntrySelector;
public ushort uRangeShift;
}
public struct TT_TABLE_DIRECTORY
{
public byte[] szTag;
public UInt32 uCheckSum;
public UInt32 uOffset;
public UInt32 uLength;
public void Initialize()
{
szTag = new byte[4];
}
}
public struct TT_NAME_TABLE_HEADER
{
public ushort uFSelector;
public ushort uNRCount;
public ushort uStorageOffset;
}
public struct TT_NAME_RECORD
{
public ushort uPlatformID;
public ushort uEncodingID;
public ushort uLanguageID;
public ushort uNameID;
public ushort uStringLength;
public ushort uStringOffset;
}
static private UInt16 ReadChar(FileStream fs, int characters)
{
string[] s = new string[characters];
byte[] buf = new byte[Convert.ToByte(s.Length)];
buf = ReadAndSwap(fs, buf.Length);
return BitConverter.ToUInt16(buf, 0);
}
static private UInt16 ReadByte(FileStream fs)
{
byte[] buf = new byte[11];
buf = ReadAndSwap(fs, buf.Length);
return BitConverter.ToUInt16(buf, 0);
}
static private UInt16 ReadUShort(FileStream fs)
{
byte[] buf = new byte[2];
buf = ReadAndSwap(fs, buf.Length);
return BitConverter.ToUInt16(buf, 0);
}
static private UInt32 ReadULong(FileStream fs)
{
byte[] buf = new byte[4];
buf = ReadAndSwap(fs, buf.Length);
return BitConverter.ToUInt32(buf, 0);
}
static private byte[] ReadAndSwap(FileStream fs, int size)
{
byte[] buf = new byte[size];
fs.Read(buf, 0, buf.Length);
Array.Reverse(buf);
return buf;
}
}
class Program
{
//static void Main(string[] args)
//{
// System.Drawing.FontFamily fam;
// var allInstalledFonts = from e in Registry.LocalMachine.OpenSubKey("Software\\Microsoft\\Windows NT\\CurrentVersion\\Fonts", false).GetValueNames()
// select Registry.LocalMachine.OpenSubKey("Software\\Microsoft\\Windows NT\\CurrentVersion\\Fonts").GetValue(e);
// var ttfFonts = from e in allInstalledFonts.Where(e => (e.ToString().EndsWith(".ttf") || e.ToString().EndsWith(".otf"))) select e;
// var ttfFontsPaths = from e in ttfFonts.Select(e => (Path.GetPathRoot(e.ToString()) == "") ? Environment.GetFolderPath(Environment.SpecialFolder.Fonts) + "\\" + e.ToString() : e.ToString()) select e;
// var fonts = from e in ttfFontsPaths.Select(e => GetFontDetails(e.ToString())) select e;
// foreach (InstalledFont f in fonts)
// {
// if (f != null)
// Console.WriteLine("Name: " + f.FontName + ", SubFamily: " + f.FontSubFamily + ", Path: " + f.FontPath);
// }
// Console.ReadLine();
//}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Drawing2D;
namespace Svg
{
public class GdiFontDefn : IFontDefn
{
private Font _font;
public float Size
{
get { return _font.Size; }
}
public float SizeInPoints
{
get { return _font.SizeInPoints; }
}
public GdiFontDefn(Font font)
{
_font = font;
}
public void AddStringToPath(ISvgRenderer renderer, GraphicsPath path, string text, PointF location)
{
path.AddString(text, _font.FontFamily, (int)_font.Style, _font.Size, location, StringFormat.GenericTypographic);
}
//Baseline calculation to match http://bobpowell.net/formattingtext.aspx
public float Ascent(ISvgRenderer renderer)
{
var ff = _font.FontFamily;
float ascent = ff.GetCellAscent(_font.Style);
float baselineOffset = _font.SizeInPoints / ff.GetEmHeight(_font.Style) * ascent;
return renderer.DpiY / 72f * baselineOffset;
}
public IList<RectangleF> MeasureCharacters(ISvgRenderer renderer, string text)
{
var g = GetGraphics(renderer);
var regions = new List<RectangleF>();
StringFormat format;
for (int s = 0; s <= (text.Length - 1) / 32; s++)
{
format = StringFormat.GenericTypographic;
format.FormatFlags |= StringFormatFlags.MeasureTrailingSpaces;
format.SetMeasurableCharacterRanges((from r in Enumerable.Range(32 * s, Math.Min(32, text.Length - 32 * s))
select new CharacterRange(r, 1)).ToArray());
regions.AddRange(from r in g.MeasureCharacterRanges(text, _font, new Rectangle(0, 0, 1000, 1000), format)
select r.GetBounds(g));
}
return regions;
}
public SizeF MeasureString(ISvgRenderer renderer, string text)
{
var g = GetGraphics(renderer);
StringFormat format = StringFormat.GenericTypographic;
format.SetMeasurableCharacterRanges(new CharacterRange[] { new CharacterRange(0, text.Length) });
format.FormatFlags |= StringFormatFlags.MeasureTrailingSpaces;
Region[] r = g.MeasureCharacterRanges(text, _font, new Rectangle(0, 0, 1000, 1000), format);
RectangleF rect = r[0].GetBounds(g);
return new SizeF(rect.Width, Ascent(renderer));
}
private static Graphics _graphics;
private static Graphics GetGraphics(object renderer)
{
var provider = renderer as IGraphicsProvider;
if (provider == null)
{
if (_graphics == null)
{
var bmp = new Bitmap(1, 1);
_graphics = Graphics.FromImage(bmp);
}
return _graphics;
}
else
{
return provider.GetGraphics();
}
}
public void Dispose()
{
_font.Dispose();
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Drawing2D;
namespace Svg
{
public interface IFontDefn : IDisposable
{
float Size { get; }
float SizeInPoints { get; }
void AddStringToPath(ISvgRenderer renderer, GraphicsPath path, string text, PointF location);
float Ascent(ISvgRenderer renderer);
IList<RectangleF> MeasureCharacters(ISvgRenderer renderer, string text);
SizeF MeasureString(ISvgRenderer renderer, string text);
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing.Drawing2D;
using System.Drawing;
namespace Svg
{
public class PathStatistics
{
private const double GqBreak_TwoPoint = 0.57735026918962573;
private const double GqBreak_ThreePoint = 0.7745966692414834;
private const double GqBreak_FourPoint_01 = 0.33998104358485631;
private const double GqBreak_FourPoint_02 = 0.86113631159405257;
private const double GqWeight_FourPoint_01 = 0.65214515486254621;
private const double GqWeight_FourPoint_02 = 0.34785484513745385;
private PathData _data;
private double _totalLength;
private List<ISegment> _segments = new List<ISegment>();
public double TotalLength { get { return _totalLength; } }
public PathStatistics(PathData data)
{
_data = data;
int i = 1;
_totalLength = 0;
ISegment newSegment;
while (i < _data.Points.Length)
{
switch (_data.Types[i])
{
case 1:
newSegment = new LineSegment(_data.Points[i - 1], _data.Points[i]);
i++;
break;
case 3:
newSegment = new CubicBezierSegment(_data.Points[i - 1], _data.Points[i], _data.Points[i + 1], _data.Points[i + 2]);
i+= 3;
break;
default:
throw new NotSupportedException();
}
newSegment.StartOffset = _totalLength;
_segments.Add(newSegment);
_totalLength += newSegment.Length;
}
}
public void LocationAngleAtOffset(double offset, out PointF point, out float angle)
{
_segments[BinarySearchForSegment(offset, 0, _segments.Count - 1)].LocationAngleAtOffset(offset, out point, out angle);
}
public bool OffsetOnPath(double offset)
{
var seg = _segments[BinarySearchForSegment(offset, 0, _segments.Count - 1)];
offset -= seg.StartOffset;
return (offset >= 0 && offset <= seg.Length);
}
private int BinarySearchForSegment(double offset, int first, int last)
{
if (last == first)
{
return first;
}
else if ((last - first) == 1)
{
return (offset >= _segments[last].StartOffset ? last : first);
}
else
{
var mid = (last + first) / 2;
if (offset < _segments[mid].StartOffset)
{
return BinarySearchForSegment(offset, first, mid);
}
else
{
return BinarySearchForSegment(offset, mid, last);
}
}
}
private interface ISegment
{
double StartOffset { get; set; }
double Length { get; }
void LocationAngleAtOffset(double offset, out PointF point, out float rotation);
}
private class LineSegment : ISegment
{
private double _length;
private double _rotation;
private PointF _start;
private PointF _end;
public double StartOffset { get; set; }
public double Length { get { return _length; } }
public LineSegment(PointF start, PointF end)
{
_start = start;
_end = end;
_length = Math.Sqrt(Math.Pow(end.X - start.X, 2) + Math.Pow(end.Y - start.Y, 2));
_rotation = Math.Atan2(end.Y - start.Y, end.X - start.X) * 180 / Math.PI;
}
public void LocationAngleAtOffset(double offset, out PointF point, out float rotation)
{
offset -= StartOffset;
if (offset < 0 || offset > _length) throw new ArgumentOutOfRangeException();
point = new PointF((float)(_start.X + (offset / _length) * (_end.X - _start.X)),
(float)(_start.Y + (offset / _length) * (_end.Y - _start.Y)));
rotation = (float)_rotation;
}
}
private class CubicBezierSegment : ISegment
{
private PointF _p0;
private PointF _p1;
private PointF _p2;
private PointF _p3;
private double _length;
private Func<double, double> _integral;
private SortedList<double, double> _lengths = new SortedList<double, double>();
public double StartOffset { get; set; }
public double Length { get { return _length; } }
public CubicBezierSegment(PointF p0, PointF p1, PointF p2, PointF p3)
{
_p0 = p0;
_p1 = p1;
_p2 = p2;
_p3 = p3;
_integral = (t) => CubicBezierArcLengthIntegrand(_p0, _p1, _p2, _p3, t);
_length = GetLength(0, 1, 0.00000001f);
_lengths.Add(0, 0);
_lengths.Add(_length, 1);
}
private double GetLength(double left, double right, double epsilon)
{
var fullInt = GaussianQuadrature(_integral, left, right, 4);
return Subdivide(left, right, fullInt, 0, epsilon);
}
private double Subdivide(double left, double right, double fullInt, double totalLength, double epsilon)
{
var mid = (left + right) / 2;
var leftValue = GaussianQuadrature(_integral, left, mid, 4);
var rightValue = GaussianQuadrature(_integral, mid, right, 4);
if (Math.Abs(fullInt - (leftValue + rightValue)) > epsilon) {
var leftSub = Subdivide(left, mid, leftValue, totalLength, epsilon / 2.0);
totalLength += leftSub;
AddElementToTable(mid, totalLength);
return Subdivide(mid, right, rightValue, totalLength, epsilon / 2.0) + leftSub;
}
else
{
return leftValue + rightValue;
}
}
private void AddElementToTable(double position, double totalLength)
{
_lengths.Add(totalLength, position);
}
public void LocationAngleAtOffset(double offset, out PointF point, out float rotation)
{
offset -= StartOffset;
if (offset < 0 || offset > _length) throw new ArgumentOutOfRangeException();
var t = BinarySearchForParam(offset, 0, _lengths.Count - 1);
point = CubicBezierCurve(_p0, _p1, _p2, _p3, t);
var deriv = CubicBezierDerivative(_p0, _p1, _p2, _p3, t);
rotation = (float)(Math.Atan2(deriv.Y, deriv.X) * 180.0 / Math.PI);
}
private double BinarySearchForParam(double length, int first, int last)
{
if (last == first)
{
return _lengths.Values[last];
}
else if ((last - first) == 1)
{
return _lengths.Values[first] + (_lengths.Values[last] - _lengths.Values[first]) *
(length - _lengths.Keys[first]) / (_lengths.Keys[last] - _lengths.Keys[first]);
}
else
{
var mid = (last + first) / 2;
if (length < _lengths.Keys[mid])
{
return BinarySearchForParam(length, first, mid);
}
else
{
return BinarySearchForParam(length, mid, last);
}
}
}
/// <summary>
/// Evaluates the integral of the function over the integral using the specified number of points
/// </summary>
/// <param name="func"></param>
/// <param name="a"></param>
/// <param name="b"></param>
/// <param name="points"></param>
public static double GaussianQuadrature(Func<double, double> func, double a, double b, int points)
{
switch (points)
{
case 1:
return (b - a) * func.Invoke((a + b) / 2.0);
case 2:
return (b - a) / 2.0 * (func.Invoke((b - a) / 2.0 * -1 * GqBreak_TwoPoint + (a + b) / 2.0) +
func.Invoke((b - a) / 2.0 * GqBreak_TwoPoint + (a + b) / 2.0));
case 3:
return (b - a) / 2.0 * (5.0 / 9 * func.Invoke((b - a) / 2.0 * -1 * GqBreak_ThreePoint + (a + b) / 2.0) +
8.0 / 9 * func.Invoke((a + b) / 2.0) +
5.0 / 9 * func.Invoke((b - a) / 2.0 * GqBreak_ThreePoint + (a + b) / 2.0));
case 4:
return (b - a) / 2.0 * (GqWeight_FourPoint_01 * func.Invoke((b - a) / 2.0 * -1 * GqBreak_FourPoint_01 + (a + b) / 2.0) +
GqWeight_FourPoint_01 * func.Invoke((b - a) / 2.0 * GqBreak_FourPoint_01 + (a + b) / 2.0) +
GqWeight_FourPoint_02 * func.Invoke((b - a) / 2.0 * -1 * GqBreak_FourPoint_02 + (a + b) / 2.0) +
GqWeight_FourPoint_02 * func.Invoke((b - a) / 2.0 * GqBreak_FourPoint_02 + (a + b) / 2.0));
}
throw new NotSupportedException();
}
/// <remarks>http://en.wikipedia.org/wiki/B%C3%A9zier_curve</remarks>
private PointF CubicBezierCurve(PointF p0, PointF p1, PointF p2, PointF p3, double t)
{
return new PointF((float)(Math.Pow(1 - t, 3) * p0.X + 3 * Math.Pow(1 - t, 2) * t * p1.X +
3 * (1 - t) * Math.Pow(t, 2) * p2.X + Math.Pow(t, 3) * p3.X),
(float)(Math.Pow(1 - t, 3) * p0.Y + 3 * Math.Pow(1 - t, 2) * t * p1.Y +
3 * (1 - t) * Math.Pow(t, 2) * p2.Y + Math.Pow(t, 3) * p3.Y));
}
/// <remarks>http://www.cs.mtu.edu/~shene/COURSES/cs3621/NOTES/spline/Bezier/bezier-der.html</remarks>
private PointF CubicBezierDerivative(PointF p0, PointF p1, PointF p2, PointF p3, double t)
{
return new PointF((float)(3 * Math.Pow(1 - t, 2) * (p1.X - p0.X) + 6 * (1 - t) * t * (p2.X - p1.X) + 3 * Math.Pow(t, 2) * (p3.X - p2.X)),
(float)(3 * Math.Pow(1 - t, 2) * (p1.Y - p0.Y) + 6 * (1 - t) * t * (p2.Y - p1.Y) + 3 * Math.Pow(t, 2) * (p3.Y - p2.Y)));
}
private double CubicBezierArcLengthIntegrand(PointF p0, PointF p1, PointF p2, PointF p3, double t)
{
return Math.Sqrt(Math.Pow(3 * Math.Pow(1 - t, 2) * (p1.X - p0.X) + 6 * (1 - t) * t * (p2.X - p1.X) + 3 * Math.Pow(t, 2) * (p3.X - p2.X), 2) +
Math.Pow(3 * Math.Pow(1 - t, 2) * (p1.Y - p0.Y) + 6 * (1 - t) * t * (p2.Y - p1.Y) + 3 * Math.Pow(t, 2) * (p3.Y - p2.Y), 2));
}
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Svg
{
[SvgElement("font")]
public class SvgFont : SvgElement
{
[SvgAttribute("horiz-adv-x")]
public float HorizAdvX
{
get { return (this.Attributes["horiz-adv-x"] == null ? 0 : (float)this.Attributes["horiz-adv-x"]); }
set { this.Attributes["horiz-adv-x"] = value; }
}
[SvgAttribute("horiz-origin-x")]
public float HorizOriginX
{
get { return (this.Attributes["horiz-origin-x"] == null ? 0 : (float)this.Attributes["horiz-origin-x"]); }
set { this.Attributes["horiz-origin-x"] = value; }
}
[SvgAttribute("horiz-origin-y")]
public float HorizOriginY
{
get { return (this.Attributes["horiz-origin-y"] == null ? 0 : (float)this.Attributes["horiz-origin-y"]); }
set { this.Attributes["horiz-origin-y"] = value; }
}
[SvgAttribute("vert-adv-y")]
public float VertAdvY
{
get { return (this.Attributes["vert-adv-y"] == null ? this.Children.OfType<SvgFontFace>().First().UnitsPerEm : (float)this.Attributes["vert-adv-y"]); }
set { this.Attributes["vert-adv-y"] = value; }
}
[SvgAttribute("vert-origin-x")]
public float VertOriginX
{
get { return (this.Attributes["vert-origin-x"] == null ? this.HorizAdvX / 2 : (float)this.Attributes["vert-origin-x"]); }
set { this.Attributes["vert-origin-x"] = value; }
}
[SvgAttribute("vert-origin-y")]
public float VertOriginY
{
get { return (this.Attributes["vert-origin-y"] == null ?
(this.Children.OfType<SvgFontFace>().First().Attributes["ascent"] == null ? 0 : this.Children.OfType<SvgFontFace>().First().Ascent) :
(float)this.Attributes["vert-origin-y"]); }
set { this.Attributes["vert-origin-y"] = value; }
}
public override SvgElement DeepCopy()
{
return base.DeepCopy<SvgFont>();
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Drawing2D;
namespace Svg
{
public class SvgFontDefn : IFontDefn
{
private SvgFont _font;
private float _emScale;
private float _ppi;
private float _size;
private Dictionary<string, SvgGlyph> _glyphs;
private Dictionary<string, SvgKern> _kerning;
public float Size
{
get { return _size; }
}
public float SizeInPoints
{
get { return _size * 72.0f / _ppi; }
}
public SvgFontDefn (SvgFont font, float size, float ppi)
{
_font = font;
_size = size;
_ppi = ppi;
var face = _font.Children.OfType<SvgFontFace>().First();
_emScale = _size / face.UnitsPerEm;
}
public float Ascent(ISvgRenderer renderer)
{
float ascent = _font.Descendants().OfType<SvgFontFace>().First().Ascent;
float baselineOffset = this.SizeInPoints * (_emScale / _size) * ascent;
return renderer.DpiY / 72f * baselineOffset;
}
public IList<System.Drawing.RectangleF> MeasureCharacters(ISvgRenderer renderer, string text)
{
var result = new List<RectangleF>();
using (var path = GetPath(renderer, text, result, false)) { }
return result;
}
public System.Drawing.SizeF MeasureString(ISvgRenderer renderer, string text)
{
var result = new List<RectangleF>();
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));
}
public void AddStringToPath(ISvgRenderer renderer, GraphicsPath path, string text, PointF location)
{
var textPath = GetPath(renderer, text, null, false);
if (textPath.PointCount > 0)
{
using (var translate = new Matrix())
{
translate.Translate(location.X, location.Y);
textPath.Transform(translate);
path.AddPath(textPath, false);
}
}
}
private GraphicsPath GetPath(ISvgRenderer renderer, string text, IList<RectangleF> ranges, bool measureSpaces)
{
EnsureDictionaries();
RectangleF bounds;
SvgGlyph glyph;
SvgKern kern;
GraphicsPath path;
SvgGlyph prevGlyph = null;
Matrix scaleMatrix;
float xPos = 0;
var ascent = Ascent(renderer);
var result = new GraphicsPath();
if (string.IsNullOrEmpty(text)) return result;
for (int i = 0; i < text.Length; i++)
{
if (!_glyphs.TryGetValue(text.Substring(i, 1), out glyph)) glyph = _font.Descendants().OfType<SvgMissingGlyph>().First();
if (prevGlyph != null && _kerning.TryGetValue(prevGlyph.GlyphName + "|" + glyph.GlyphName, out kern))
{
xPos -= kern.Kerning * _emScale;
}
path = (GraphicsPath)glyph.Path(renderer).Clone();
scaleMatrix = new Matrix();
scaleMatrix.Scale(_emScale, -1 * _emScale, MatrixOrder.Append);
scaleMatrix.Translate(xPos, ascent, MatrixOrder.Append);
path.Transform(scaleMatrix);
scaleMatrix.Dispose();
bounds = path.GetBounds();
if (ranges != null)
{
if (measureSpaces && bounds == RectangleF.Empty)
{
ranges.Add(new RectangleF(xPos, 0, glyph.HorizAdvX * _emScale, ascent));
}
else
{
ranges.Add(bounds);
}
}
if (path.PointCount > 0) result.AddPath(path, false);
xPos += glyph.HorizAdvX * _emScale;
prevGlyph = glyph;
}
return result;
}
private void EnsureDictionaries()
{
if (_glyphs == null) _glyphs = _font.Descendants().OfType<SvgGlyph>().ToDictionary(g => g.Unicode ?? g.GlyphName ?? g.ID);
if (_kerning == null) _kerning = _font.Descendants().OfType<SvgKern>().ToDictionary(k => k.Glyph1 + "|" + k.Glyph2);
}
public void Dispose()
{
_glyphs = null;
_kerning = null;
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Svg
{
[SvgElement("font-face")]
public class SvgFontFace : SvgElement
{
[SvgAttribute("alphabetic")]
public float Alphabetic
{
get { return (this.Attributes["alphabetic"] == null ? 0 : (float)this.Attributes["alphabetic"]); }
set { this.Attributes["alphabetic"] = value; }
}
[SvgAttribute("ascent")]
public float Ascent
{
get { return (this.Attributes["ascent"] == null ? this.UnitsPerEm - ((SvgFont)this.Parent).VertOriginY : (float)this.Attributes["ascent"]); }
set { this.Attributes["ascent"] = value; }
}
[SvgAttribute("ascent-height")]
public float AscentHeight
{
get { return (this.Attributes["ascent-height"] == null ? this.Ascent : (float)this.Attributes["ascent-height"]); }
set { this.Attributes["ascent-height"] = value; }
}
[SvgAttribute("descent")]
public float Descent
{
get { return (this.Attributes["descent"] == null ? ((SvgFont)this.Parent).VertOriginY : (float)this.Attributes["descent"]); }
set { this.Attributes["descent"] = value; }
}
/// <summary>
/// Indicates which font family is to be used to render the text.
/// </summary>
[SvgAttribute("font-family")]
public virtual string FontFamily
{
get { return this.Attributes["font-family"] as string; }
set { this.Attributes["font-family"] = value; }
}
/// <summary>
/// Refers to the size of the font from baseline to baseline when multiple lines of text are set solid in a multiline layout environment.
/// </summary>
[SvgAttribute("font-size")]
public virtual SvgUnit FontSize
{
get { return (this.Attributes["font-size"] == null) ? SvgUnit.Empty : (SvgUnit)this.Attributes["font-size"]; }
set { this.Attributes["font-size"] = value; }
}
/// <summary>
/// Refers to the style of the font.
/// </summary>
[SvgAttribute("font-style")]
public virtual SvgFontStyle FontStyle
{
get { return (this.Attributes["font-style"] == null) ? SvgFontStyle.inherit : (SvgFontStyle)this.Attributes["font-style"]; }
set { this.Attributes["font-style"] = value; }
}
/// <summary>
/// Refers to the varient of the font.
/// </summary>
[SvgAttribute("font-variant")]
public virtual SvgFontVariant FontVariant
{
get { return (this.Attributes["font-variant"] == null) ? SvgFontVariant.inherit : (SvgFontVariant)this.Attributes["font-variant"]; }
set { this.Attributes["font-variant"] = value; }
}
/// <summary>
/// Refers to the boldness of the font.
/// </summary>
[SvgAttribute("font-weight")]
public virtual SvgFontWeight FontWeight
{
get { return (this.Attributes["font-weight"] == null) ? SvgFontWeight.inherit : (SvgFontWeight)this.Attributes["font-weight"]; }
set { this.Attributes["font-weight"] = value; }
}
[SvgAttribute("panose-1")]
public string Panose1
{
get { return this.Attributes["panose-1"] as string; }
set { this.Attributes["panose-1"] = value; }
}
[SvgAttribute("units-per-em")]
public float UnitsPerEm
{
get { return (this.Attributes["units-per-em"] == null ? 1000 : (float)this.Attributes["units-per-em"]); }
set { this.Attributes["units-per-em"] = value; }
}
[SvgAttribute("x-height")]
public float XHeight
{
get { return (this.Attributes["x-height"] == null ? float.MinValue : (float)this.Attributes["x-height"]); }
set { this.Attributes["x-height"] = value; }
}
public override SvgElement DeepCopy()
{
return base.DeepCopy<SvgFontFace>();
}
}
}
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