diff --git a/Source/Basic Shapes/SvgCircle.cs b/Source/Basic Shapes/SvgCircle.cs index 93a4fcd989e174559ae5580a062fa6006fc4f334..d3fbb6df07b650c594177b9e05d4ed83109a6936 100644 --- a/Source/Basic Shapes/SvgCircle.cs +++ b/Source/Basic Shapes/SvgCircle.cs @@ -98,7 +98,7 @@ namespace Svg /// /// Gets the representing this element. /// - public override GraphicsPath Path(SvgRenderer renderer) + public override GraphicsPath Path(ISvgRenderer renderer) { if (this._path == null || this.IsPathDirty) { @@ -117,7 +117,7 @@ namespace Svg /// Renders the circle to the specified object. /// /// The graphics object. - protected override void Render(SvgRenderer renderer) + protected override void Render(ISvgRenderer renderer) { // Don't draw if there is no radius set if (this.Radius.Value > 0.0f) diff --git a/Source/Basic Shapes/SvgEllipse.cs b/Source/Basic Shapes/SvgEllipse.cs index 07f93ad223a317131915f98be9c40e55d1df5fb0..0ab1d8e03ea1c304de05f0389e92f762510f1de4 100644 --- a/Source/Basic Shapes/SvgEllipse.cs +++ b/Source/Basic Shapes/SvgEllipse.cs @@ -102,7 +102,7 @@ namespace Svg /// Gets the for this element. /// /// - public override GraphicsPath Path(SvgRenderer renderer) + public override GraphicsPath Path(ISvgRenderer renderer) { if (this._path == null || this.IsPathDirty) { @@ -122,7 +122,7 @@ namespace Svg /// Renders the and contents to the specified object. /// /// The object to render to. - protected override void Render(SvgRenderer renderer) + protected override void Render(ISvgRenderer renderer) { if (this._radiusX.Value > 0.0f && this._radiusY.Value > 0.0f) { diff --git a/Source/Basic Shapes/SvgImage.cs b/Source/Basic Shapes/SvgImage.cs index a8762c9b7eb02567ab528a65c92573204582c82d..d6b21a9962bfa1bbd16f340b9a18c1b27e791ac3 100644 --- a/Source/Basic Shapes/SvgImage.cs +++ b/Source/Basic Shapes/SvgImage.cs @@ -94,7 +94,7 @@ namespace Svg /// /// Gets the for this element. /// - public override GraphicsPath Path(SvgRenderer renderer) + public override GraphicsPath Path(ISvgRenderer renderer) { return null; } @@ -102,7 +102,7 @@ namespace Svg /// /// Renders the and contents to the specified object. /// - protected override void Render(SvgRenderer renderer) + protected override void Render(ISvgRenderer renderer) { if (!Visible || !Displayable) return; @@ -120,7 +120,7 @@ namespace Svg RectangleF destRect = destClip; this.PushTransforms(renderer); - renderer.AddClip(new Region(destClip)); + renderer.SetClip(new Region(destClip), CombineMode.Intersect); this.SetClip(renderer); if (AspectRatio != null && AspectRatio.Align != SvgPreserveAspectRatio.none) @@ -223,6 +223,7 @@ namespace Svg if (uri.LocalPath.EndsWith(".svg", StringComparison.InvariantCultureIgnoreCase)) { var doc = SvgDocument.Open(ms); + doc.BaseUri = uri; return doc.Draw(); } else diff --git a/Source/Basic Shapes/SvgLine.cs b/Source/Basic Shapes/SvgLine.cs index 05c12a055f76d9abbd07767d2e0522f6823b057d..2d66a999b5081f404cc80d806d2143a4b9cf971d 100644 --- a/Source/Basic Shapes/SvgLine.cs +++ b/Source/Basic Shapes/SvgLine.cs @@ -92,7 +92,7 @@ namespace Svg { } - public override System.Drawing.Drawing2D.GraphicsPath Path(SvgRenderer renderer) + public override System.Drawing.Drawing2D.GraphicsPath Path(ISvgRenderer renderer) { if (this._path == null || this.IsPathDirty) { diff --git a/Source/Basic Shapes/SvgPolygon.cs b/Source/Basic Shapes/SvgPolygon.cs index 5c6fc24cf18275b9ffa7943af9aa14c6afd34a60..b1c3e22dcd52cbf8b5e3a72f2b4724941885069b 100644 --- a/Source/Basic Shapes/SvgPolygon.cs +++ b/Source/Basic Shapes/SvgPolygon.cs @@ -32,7 +32,7 @@ namespace Svg get { return true; } } - public override GraphicsPath Path(SvgRenderer renderer) + public override GraphicsPath Path(ISvgRenderer renderer) { if (this._path == null || this.IsPathDirty) { diff --git a/Source/Basic Shapes/SvgPolyline.cs b/Source/Basic Shapes/SvgPolyline.cs index c499dd6c2987f8221ddd8d0579e417d368b3d9c7..2b262f7135298f6a8303f23d8e34b5886310564a 100644 --- a/Source/Basic Shapes/SvgPolyline.cs +++ b/Source/Basic Shapes/SvgPolyline.cs @@ -14,7 +14,7 @@ namespace Svg public class SvgPolyline : SvgPolygon { private GraphicsPath _Path; - public override GraphicsPath Path(SvgRenderer renderer) + public override GraphicsPath Path(ISvgRenderer renderer) { if (_Path == null || this.IsPathDirty) { diff --git a/Source/Basic Shapes/SvgRectangle.cs b/Source/Basic Shapes/SvgRectangle.cs index 717e6c68897d6e8d2677ec94a98df96a338a44ba..c9d79c3ea698e54bb13aca5c7ea3fb2b9ad6cf86 100644 --- a/Source/Basic Shapes/SvgRectangle.cs +++ b/Source/Basic Shapes/SvgRectangle.cs @@ -174,7 +174,7 @@ namespace Svg /// /// Gets the for this element. /// - public override GraphicsPath Path(SvgRenderer renderer) + public override GraphicsPath Path(ISvgRenderer renderer) { if (_path == null || IsPathDirty) { @@ -261,7 +261,7 @@ namespace Svg /// /// Renders the and contents to the specified object. /// - protected override void Render(SvgRenderer renderer) + protected override void Render(ISvgRenderer renderer) { if (Width.Value > 0.0f && Height.Value > 0.0f) { diff --git a/Source/Basic Shapes/SvgVisualElement.cs b/Source/Basic Shapes/SvgVisualElement.cs index 52169422564527a7f6a6abb18575e5fdc2efe4a9..99b14f31a118a144a0360f604996f90cc39879da 100644 --- a/Source/Basic Shapes/SvgVisualElement.cs +++ b/Source/Basic Shapes/SvgVisualElement.cs @@ -1,6 +1,7 @@ using System; using System.Drawing; using System.Drawing.Drawing2D; +using System.Diagnostics; namespace Svg { @@ -16,7 +17,7 @@ namespace Svg /// /// Gets the for this element. /// - public abstract GraphicsPath Path(SvgRenderer renderer); + public abstract GraphicsPath Path(ISvgRenderer renderer); PointF ISvgBoundable.Location { @@ -104,13 +105,13 @@ namespace Svg /// /// Renders the and contents to the specified object. /// - /// The object to render to. - protected override void Render(SvgRenderer renderer) + /// The object to render to. + protected override void Render(ISvgRenderer renderer) { this.Render(renderer, true); } - private void Render(SvgRenderer renderer, bool renderFilter) + private void Render(ISvgRenderer renderer, bool renderFilter) { if (this.Visible && this.Displayable && this.PushTransforms(renderer) && (!Renderable || this.Path(renderer) != null)) @@ -119,11 +120,20 @@ namespace Svg if (renderFilter && this.Filter != null) { - var filter = this.OwnerDocument.IdManager.GetElementById(this.Filter) as FilterEffects.SvgFilter; + var filterPath = this.Filter; + if (filterPath.ToString().StartsWith("url(")) + { + filterPath = new Uri(filterPath.ToString().Substring(4, filterPath.ToString().Length - 5), UriKind.RelativeOrAbsolute); + } + var filter = this.OwnerDocument.IdManager.GetElementById(filterPath) as FilterEffects.SvgFilter; if (filter != null) { this.PopTransforms(renderer); - filter.ApplyFilter(this, renderer, (r) => this.Render(r, false)); + try + { + filter.ApplyFilter(this, renderer, (r) => this.Render(r, false)); + } + catch (Exception ex) { Debug.Print(ex.ToString()); } renderNormal = false; } } @@ -163,10 +173,10 @@ namespace Svg } /// - /// Renders the fill of the to the specified + /// Renders the fill of the to the specified /// - /// The object to render to. - protected internal virtual void RenderFill(SvgRenderer renderer) + /// The object to render to. + protected internal virtual void RenderFill(ISvgRenderer renderer) { if (this.Fill != null) { @@ -182,12 +192,12 @@ namespace Svg } /// - /// Renders the stroke of the to the specified + /// Renders the stroke of the to the specified /// - /// The object to render to. - protected internal virtual void RenderStroke(SvgRenderer renderer) + /// The object to render to. + protected internal virtual void RenderStroke(ISvgRenderer renderer) { - if (this.Stroke != null) + if (this.Stroke != null && this.Stroke != SvgColourServer.None) { float strokeWidth = this.StrokeWidth.ToDeviceValue(renderer, UnitRenderingType.Other, this); using (var pen = new Pen(this.Stroke.GetBrush(this, renderer, Math.Min(Math.Max(this.StrokeOpacity * this.Opacity, 0), 1)), strokeWidth)) @@ -204,50 +214,50 @@ namespace Svg } /// - /// Sets the clipping region of the specified . + /// Sets the clipping region of the specified . /// - /// The to have its clipping region set. - protected internal virtual void SetClip(SvgRenderer renderer) + /// The to have its clipping region set. + protected internal virtual void SetClip(ISvgRenderer renderer) { if (this.ClipPath != null) { SvgClipPath clipPath = this.OwnerDocument.GetElementById(this.ClipPath.ToString()); - this._previousClip = renderer.Clip; + this._previousClip = renderer.GetClip(); if (clipPath != null) { - renderer.AddClip(clipPath.GetClipRegion(this)); + renderer.SetClip(clipPath.GetClipRegion(this), CombineMode.Intersect); } } } /// - /// Resets the clipping region of the specified back to where it was before the method was called. + /// Resets the clipping region of the specified back to where it was before the method was called. /// - /// The to have its clipping region reset. - protected internal virtual void ResetClip(SvgRenderer renderer) + /// The to have its clipping region reset. + protected internal virtual void ResetClip(ISvgRenderer renderer) { if (this._previousClip != null) { - renderer.Clip = this._previousClip; + renderer.SetClip(this._previousClip); this._previousClip = null; } } /// - /// Sets the clipping region of the specified . + /// Sets the clipping region of the specified . /// - /// The to have its clipping region set. - void ISvgClipable.SetClip(SvgRenderer renderer) + /// The to have its clipping region set. + void ISvgClipable.SetClip(ISvgRenderer renderer) { this.SetClip(renderer); } /// - /// Resets the clipping region of the specified back to where it was before the method was called. + /// Resets the clipping region of the specified back to where it was before the method was called. /// - /// The to have its clipping region reset. - void ISvgClipable.ResetClip(SvgRenderer renderer) + /// The to have its clipping region reset. + void ISvgClipable.ResetClip(ISvgRenderer renderer) { this.ResetClip(renderer); } diff --git a/Source/Basic Shapes/SvgVisualElementStyle.cs b/Source/Basic Shapes/SvgVisualElementStyle.cs index 6433cbb683c7375533668ca721f86a4dd4376339..f2e08df7d2f01f022b3f0ba0e9f07215b7cfb231 100644 --- a/Source/Basic Shapes/SvgVisualElementStyle.cs +++ b/Source/Basic Shapes/SvgVisualElementStyle.cs @@ -187,7 +187,7 @@ namespace Svg } /// - /// Refers to the boldness of the font. + /// Refers to the style of the font. /// [SvgAttribute("font-style")] public virtual SvgFontStyle FontStyle @@ -197,7 +197,7 @@ namespace Svg } /// - /// Refers to the boldness of the font. + /// Refers to the varient of the font. /// [SvgAttribute("font-variant")] public virtual SvgFontVariant FontVariant @@ -323,7 +323,7 @@ namespace Svg /// Get the font information based on data stored with the text object or inherited from the parent. /// /// - internal System.Drawing.Font GetFont(SvgRenderer renderer) + internal IFontDefn GetFont(ISvgRenderer renderer) { // Get the font-size float fontSize; @@ -337,62 +337,81 @@ namespace Svg fontSize = fontSizeUnit.ToDeviceValue(renderer, UnitRenderingType.Vertical, this); } - var fontStyle = System.Drawing.FontStyle.Regular; + var family = ValidateFontFamily(this.FontFamily, this.OwnerDocument); + var sFaces = family as IEnumerable; - // Get the font-weight - switch (this.FontWeight) + if (sFaces == null) { - case SvgFontWeight.bold: - case SvgFontWeight.bolder: - case SvgFontWeight.w600: - case SvgFontWeight.w700: - case SvgFontWeight.w800: - case SvgFontWeight.w900: - fontStyle |= System.Drawing.FontStyle.Bold; - break; - } + var fontStyle = System.Drawing.FontStyle.Regular; - // Get the font-style - switch (this.FontStyle) - { - case SvgFontStyle.italic: - case SvgFontStyle.oblique: - fontStyle |= System.Drawing.FontStyle.Italic; - break; - } + // 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 text-decoration - switch (this.TextDecoration) - { - case SvgTextDecoration.lineThrough: - fontStyle |= System.Drawing.FontStyle.Strikeout; - break; - case SvgTextDecoration.underline: - fontStyle |= System.Drawing.FontStyle.Underline; - break; - } + // Get the font-style + switch (this.FontStyle) + { + case SvgFontStyle.italic: + case SvgFontStyle.oblique: + fontStyle |= System.Drawing.FontStyle.Italic; + break; + } - var family = ValidateFontFamily(this.FontFamily); - if (!family.IsStyleAvailable(fontStyle)) + // 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 { - // Do Something + var font = sFaces.First().Parent as SvgFont; + if (font == null) + { + var uri = sFaces.First().Descendants().OfType().First().ReferencedElement; + font = OwnerDocument.IdManager.GetElementById(uri) as SvgFont; + } + return new SvgFontDefn(font, fontSize, OwnerDocument.Ppi); } - - // Get the font-family - return new System.Drawing.Font(family, fontSize, fontStyle, System.Drawing.GraphicsUnit.Pixel); } - private static FontFamily ValidateFontFamily(string fontFamilyList) + 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 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; diff --git a/Source/Clipping and Masking/ISvgClipable.cs b/Source/Clipping and Masking/ISvgClipable.cs index dc604fa3fe9f264a9b5d570b6d0e5c0db09e870e..2820ec1e51bf1c56bcb9b9807dd784f6c7c28e08 100644 --- a/Source/Clipping and Masking/ISvgClipable.cs +++ b/Source/Clipping and Masking/ISvgClipable.cs @@ -20,14 +20,14 @@ namespace Svg /// SvgClipRule ClipRule { get; set; } /// - /// Sets the clipping region of the specified . + /// Sets the clipping region of the specified . /// - /// The to have its clipping region set. - void SetClip(SvgRenderer renderer); + /// The to have its clipping region set. + void SetClip(ISvgRenderer renderer); /// - /// Resets the clipping region of the specified back to where it was before the method was called. + /// Resets the clipping region of the specified back to where it was before the method was called. /// - /// The to have its clipping region reset. - void ResetClip(SvgRenderer renderer); + /// The to have its clipping region reset. + void ResetClip(ISvgRenderer renderer); } } \ No newline at end of file diff --git a/Source/Clipping and Masking/SvgClipPath.cs b/Source/Clipping and Masking/SvgClipPath.cs index 72bc101dc0104a2025a2fb311d737082da09b2ba..feed3d38a78252d6a61e9eb25cb72ce3f6bfdd0e 100644 --- a/Source/Clipping and Masking/SvgClipPath.cs +++ b/Source/Clipping and Masking/SvgClipPath.cs @@ -75,7 +75,7 @@ namespace Svg } } - path.AddPath(childPath, false); + if (childPath.PointCount > 0) path.AddPath(childPath, false); } foreach (SvgElement child in element.Children) @@ -108,10 +108,10 @@ namespace Svg } /// - /// Renders the and contents to the specified object. + /// Renders the and contents to the specified object. /// - /// The object to render to. - protected override void Render(SvgRenderer renderer) + /// The object to render to. + protected override void Render(ISvgRenderer renderer) { // Do nothing } diff --git a/Source/DataTypes/SvgPoint.cs b/Source/DataTypes/SvgPoint.cs index e15358d16aa19592f762d1f9e356a2d774bbe52b..00d744565e52d7617dccb2552fa3e54ba5105ad7 100644 --- a/Source/DataTypes/SvgPoint.cs +++ b/Source/DataTypes/SvgPoint.cs @@ -23,7 +23,7 @@ namespace Svg set { this.y = value; } } - public PointF ToDeviceValue(SvgRenderer renderer, SvgElement owner) + public PointF ToDeviceValue(ISvgRenderer renderer, SvgElement owner) { return SvgUnit.GetDevicePoint(this.X, this.Y, renderer, owner); } diff --git a/Source/DataTypes/SvgTextLengthAdjust.cs b/Source/DataTypes/SvgTextLengthAdjust.cs new file mode 100644 index 0000000000000000000000000000000000000000..ab68d3ac4d6332a146debc1d371e752856aca8ac --- /dev/null +++ b/Source/DataTypes/SvgTextLengthAdjust.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Svg +{ + public enum SvgTextLengthAdjust + { + spacing, + spacingAndGlyphs + } +} diff --git a/Source/DataTypes/SvgTextPathMethod.cs b/Source/DataTypes/SvgTextPathMethod.cs new file mode 100644 index 0000000000000000000000000000000000000000..64aac329c3858057259d62c1ecbfe5f1cc78a1d8 --- /dev/null +++ b/Source/DataTypes/SvgTextPathMethod.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Svg +{ + public enum SvgTextPathMethod + { + align, + stretch + } +} diff --git a/Source/DataTypes/SvgTextPathSpacing.cs b/Source/DataTypes/SvgTextPathSpacing.cs new file mode 100644 index 0000000000000000000000000000000000000000..e53d8fa35d74e1e58f474a392ffc437fcd4124e6 --- /dev/null +++ b/Source/DataTypes/SvgTextPathSpacing.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Svg +{ + public enum SvgTextPathSpacing + { + exact, + auto + } +} diff --git a/Source/DataTypes/SvgUnit.cs b/Source/DataTypes/SvgUnit.cs index 9fb4f50c5a59de66ce0862f712ed79f93298cdc2..0130f247743b9315e9b2436d4c1287c377c7b616 100644 --- a/Source/DataTypes/SvgUnit.cs +++ b/Source/DataTypes/SvgUnit.cs @@ -64,7 +64,7 @@ namespace Svg /// /// The container element used as the basis for calculations /// The representation of the current unit in a device value (usually pixels). - public float ToDeviceValue(SvgRenderer renderer, UnitRenderingType renderType, SvgElement owner) + public float ToDeviceValue(ISvgRenderer renderer, UnitRenderingType renderType, SvgElement owner) { // If it's already been calculated if (this._deviceValue.HasValue) @@ -107,7 +107,7 @@ namespace Svg } float points; - Font currFont; + IFontDefn currFont; switch (type) { @@ -158,7 +158,7 @@ namespace Svg break; case SvgUnitType.Percentage: // Can't calculate if there is no style owner - var boundable = (renderer == null ? (owner == null ? null : owner.OwnerDocument) : renderer.Boundable()); + var boundable = (renderer == null ? (owner == null ? null : owner.OwnerDocument) : renderer.GetBoundable()); if (boundable == null) { _deviceValue = value; @@ -193,7 +193,7 @@ namespace Svg return this._deviceValue.Value; } - private Font GetFont(SvgRenderer renderer, SvgElement owner) + private IFontDefn GetFont(ISvgRenderer renderer, SvgElement owner) { if (owner == null) return null; @@ -335,18 +335,18 @@ namespace Svg this._deviceValue = null; } - public static System.Drawing.PointF GetDevicePoint(SvgUnit x, SvgUnit y, SvgRenderer renderer, SvgElement owner) + public static System.Drawing.PointF GetDevicePoint(SvgUnit x, SvgUnit y, ISvgRenderer renderer, SvgElement owner) { return new System.Drawing.PointF(x.ToDeviceValue(renderer, UnitRenderingType.Horizontal, owner), y.ToDeviceValue(renderer, UnitRenderingType.Vertical, owner)); } - public static System.Drawing.PointF GetDevicePointOffset(SvgUnit x, SvgUnit y, SvgRenderer renderer, SvgElement owner) + public static System.Drawing.PointF GetDevicePointOffset(SvgUnit x, SvgUnit y, ISvgRenderer renderer, SvgElement owner) { return new System.Drawing.PointF(x.ToDeviceValue(renderer, UnitRenderingType.HorizontalOffset, owner), y.ToDeviceValue(renderer, UnitRenderingType.VerticalOffset, owner)); } - public static System.Drawing.SizeF GetDeviceSize(SvgUnit width, SvgUnit height, SvgRenderer renderer, SvgElement owner) + public static System.Drawing.SizeF GetDeviceSize(SvgUnit width, SvgUnit height, ISvgRenderer renderer, SvgElement owner) { return new System.Drawing.SizeF(width.ToDeviceValue(renderer, UnitRenderingType.HorizontalOffset, owner), height.ToDeviceValue(renderer, UnitRenderingType.VerticalOffset, owner)); diff --git a/Source/DataTypes/SvgViewBox.cs b/Source/DataTypes/SvgViewBox.cs index 401140a31b3fa80c3ae1dea89b520ed9caf17435..62d56abd12d9870ec1f2555cbd9790102a861466 100644 --- a/Source/DataTypes/SvgViewBox.cs +++ b/Source/DataTypes/SvgViewBox.cs @@ -4,6 +4,7 @@ using System.ComponentModel; using System.Collections.Generic; using System.Text; using System.Globalization; +using System.Drawing.Drawing2D; namespace Svg { @@ -123,6 +124,81 @@ namespace Svg } #endregion + public void AddViewBoxTransform(SvgAspectRatio aspectRatio, ISvgRenderer renderer, SvgFragment frag) + { + if (this.Equals(SvgViewBox.Empty)) return; + + var width = (frag == null ? this.Width : frag.Width.ToDeviceValue(renderer, UnitRenderingType.Horizontal, frag)); + var height = (frag == null ? this.Height : frag.Height.ToDeviceValue(renderer, UnitRenderingType.Vertical, frag)); + + var fScaleX = width / this.Width; + var fScaleY = height / this.Height; //(this.MinY < 0 ? -1 : 1) * + var fMinX = this.MinX; + var fMinY = this.MinY; + + if (aspectRatio == null) aspectRatio = new SvgAspectRatio(SvgPreserveAspectRatio.xMidYMid, false); + if (aspectRatio.Align != SvgPreserveAspectRatio.none) + { + if (aspectRatio.Slice) + { + fScaleX = Math.Max(fScaleX, fScaleY); + fScaleY = Math.Max(fScaleX, fScaleY); + } + else + { + fScaleX = Math.Min(fScaleX, fScaleY); + fScaleY = Math.Min(fScaleX, fScaleY); + } + float fViewMidX = (this.Width / 2) * fScaleX; + float fViewMidY = (this.Height / 2) * fScaleY; + float fMidX = width / 2; + float fMidY = height / 2; + + switch (aspectRatio.Align) + { + case SvgPreserveAspectRatio.xMinYMin: + break; + case SvgPreserveAspectRatio.xMidYMin: + fMinX += fMidX - fViewMidX; + break; + case SvgPreserveAspectRatio.xMaxYMin: + fMinX += width - this.Width * fScaleX; + break; + case SvgPreserveAspectRatio.xMinYMid: + fMinY += fMidY - fViewMidY; + break; + case SvgPreserveAspectRatio.xMidYMid: + fMinX += fMidX - fViewMidX; + fMinY += fMidY - fViewMidY; + break; + case SvgPreserveAspectRatio.xMaxYMid: + fMinX += width - this.Width * fScaleX; + fMinY += fMidY - fViewMidY; + break; + case SvgPreserveAspectRatio.xMinYMax: + fMinY += height - this.Height * fScaleY; + break; + case SvgPreserveAspectRatio.xMidYMax: + fMinX += fMidX - fViewMidX; + fMinY += height - this.Height * fScaleY; + break; + case SvgPreserveAspectRatio.xMaxYMax: + fMinX += width - this.Width * fScaleX; + fMinY += height - this.Height * fScaleY; + break; + default: + break; + } + } + + var x = (frag == null ? 0 : frag.X.ToDeviceValue(renderer, UnitRenderingType.Horizontal, frag)); + var y = (frag == null ? 0 : frag.Y.ToDeviceValue(renderer, UnitRenderingType.Vertical, frag)); + + renderer.SetClip(new Region(new RectangleF(x, y, width, height)), CombineMode.Intersect); + renderer.ScaleTransform(fScaleX, fScaleY, MatrixOrder.Prepend); + renderer.TranslateTransform(x, y); + renderer.TranslateTransform(-fMinX, -fMinY); + } } internal class SvgViewBoxConverter : TypeConverter diff --git a/Source/DataTypes/XmlSpaceHandling.cs b/Source/DataTypes/XmlSpaceHandling.cs new file mode 100644 index 0000000000000000000000000000000000000000..1febaf87c9033297c46736ff7f7d4f7658065ac4 --- /dev/null +++ b/Source/DataTypes/XmlSpaceHandling.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Svg +{ + public enum XmlSpaceHandling + { + @default, + inherit, + preserve + } +} diff --git a/Source/Document Structure/SvgDefinitionList.cs b/Source/Document Structure/SvgDefinitionList.cs index 9342773ef06a40b4cb4b0557b87d5a441811b108..8f6d96636e952a655957cf16e6e8d47a75857db6 100644 --- a/Source/Document Structure/SvgDefinitionList.cs +++ b/Source/Document Structure/SvgDefinitionList.cs @@ -18,10 +18,10 @@ namespace Svg } /// - /// Renders the and contents to the specified object. + /// Renders the and contents to the specified object. /// - /// The object to render to. - protected override void Render(SvgRenderer renderer) + /// The object to render to. + protected override void Render(ISvgRenderer renderer) { // Do nothing. Children should NOT be rendered. } diff --git a/Source/Document Structure/SvgDocumentMetadata.cs b/Source/Document Structure/SvgDocumentMetadata.cs index a52cb8800c4bc452856354053d12d9dfa224ac0c..680097f1d560e06c903df669cf86e8fb54958af3 100644 --- a/Source/Document Structure/SvgDocumentMetadata.cs +++ b/Source/Document Structure/SvgDocumentMetadata.cs @@ -31,10 +31,10 @@ namespace Svg /// - /// Renders the and contents to the specified object. + /// Renders the and contents to the specified object. /// - /// The object to render to. - protected override void Render(SvgRenderer renderer) + /// The object to render to. + protected override void Render(ISvgRenderer renderer) { // Do nothing. Children should NOT be rendered. } diff --git a/Source/Document Structure/SvgFragment.cs b/Source/Document Structure/SvgFragment.cs index f23081f1a22960693540967e742d4ff4c8702ea7..36f151570dbcd4e4947a237d189eebe04148fd5c 100644 --- a/Source/Document Structure/SvgFragment.cs +++ b/Source/Document Structure/SvgFragment.cs @@ -148,86 +148,13 @@ namespace Svg } /// - /// Applies the required transforms to . + /// Applies the required transforms to . /// - /// The to be transformed. - protected internal override bool PushTransforms(SvgRenderer renderer) + /// The to be transformed. + protected internal override bool PushTransforms(ISvgRenderer renderer) { if (!base.PushTransforms(renderer)) return false; - - if (!this.ViewBox.Equals(SvgViewBox.Empty)) - { - var width = this.Width.ToDeviceValue(renderer, UnitRenderingType.Horizontal, this); - var height = this.Height.ToDeviceValue(renderer, UnitRenderingType.Vertical, this); - - var fScaleX = width / this.ViewBox.Width; - var fScaleY = height / this.ViewBox.Height; - var fMinX = -this.ViewBox.MinX; - var fMinY = -this.ViewBox.MinY; - - if (AspectRatio.Align != SvgPreserveAspectRatio.none) - { - if (AspectRatio.Slice) - { - fScaleX = Math.Max(fScaleX, fScaleY); - fScaleY = Math.Max(fScaleX, fScaleY); - } - else - { - fScaleX = Math.Min(fScaleX, fScaleY); - fScaleY = Math.Min(fScaleX, fScaleY); - } - float fViewMidX = (this.ViewBox.Width / 2) * fScaleX; - float fViewMidY = (this.ViewBox.Height / 2) * fScaleY; - float fMidX = width / 2; - float fMidY = height / 2; - - switch (AspectRatio.Align) - { - case SvgPreserveAspectRatio.xMinYMin: - break; - case SvgPreserveAspectRatio.xMidYMin: - fMinX += fMidX - fViewMidX; - break; - case SvgPreserveAspectRatio.xMaxYMin: - fMinX += width - this.ViewBox.Width * fScaleX; - break; - case SvgPreserveAspectRatio.xMinYMid: - fMinY += fMidY - fViewMidY; - break; - case SvgPreserveAspectRatio.xMidYMid: - fMinX += fMidX - fViewMidX; - fMinY += fMidY - fViewMidY; - break; - case SvgPreserveAspectRatio.xMaxYMid: - fMinX += width - this.ViewBox.Width * fScaleX; - fMinY += fMidY - fViewMidY; - break; - case SvgPreserveAspectRatio.xMinYMax: - fMinY += height - this.ViewBox.Height * fScaleY; - break; - case SvgPreserveAspectRatio.xMidYMax: - fMinX += fMidX - fViewMidX; - fMinY += height - this.ViewBox.Height * fScaleY; - break; - case SvgPreserveAspectRatio.xMaxYMax: - fMinX += width - this.ViewBox.Width * fScaleX; - fMinY += height - this.ViewBox.Height * fScaleY; - break; - default: - break; - } - } - - var x = _x.ToDeviceValue(renderer, UnitRenderingType.Horizontal, this); - var y = _y.ToDeviceValue(renderer, UnitRenderingType.Vertical, this); - - renderer.AddClip(new Region(new RectangleF(x, y, width, height))); - renderer.ScaleTransform(fScaleX, fScaleY, MatrixOrder.Prepend); - renderer.TranslateTransform(x,y); - renderer.TranslateTransform(fMinX, fMinY); - } - + this.ViewBox.AddViewBoxTransform(this.AspectRatio, renderer, this); return true; } diff --git a/Source/Document Structure/SvgGroup.cs b/Source/Document Structure/SvgGroup.cs index f4826f80236a00f5a679bf23bd62a999955327e4..63889c67a7d11e89695ca807492e6cb1f8c15e22 100644 --- a/Source/Document Structure/SvgGroup.cs +++ b/Source/Document Structure/SvgGroup.cs @@ -9,15 +9,11 @@ namespace Svg [SvgElement("g")] public class SvgGroup : SvgVisualElement { - public SvgGroup() - { - } - /// /// Gets the for this element. /// /// - public override System.Drawing.Drawing2D.GraphicsPath Path(SvgRenderer renderer) + public override System.Drawing.Drawing2D.GraphicsPath Path(ISvgRenderer renderer) { return GetPaths(this, renderer); } @@ -57,26 +53,7 @@ namespace Svg } protected override bool Renderable { get { return false; } } - - ///// - ///// Renders the and contents to the specified object. - ///// - ///// The object to render to. - //protected override void Render(SvgRenderer renderer) - //{ - // if (!Visible || !Displayable) - // return; - - // if (this.PushTransforms(renderer)) - // { - // this.SetClip(renderer); - // base.RenderChildren(renderer); - // this.ResetClip(renderer); - // this.PopTransforms(renderer); - // } - //} - - + public override SvgElement DeepCopy() { return DeepCopy(); diff --git a/Source/Document Structure/SvgSwitch.cs b/Source/Document Structure/SvgSwitch.cs index d3700306e19ce0de75605d3a590256f47cc50582..d07dabb00825c4ad9ca558ba3b140557ae1c0ec4 100644 --- a/Source/Document Structure/SvgSwitch.cs +++ b/Source/Document Structure/SvgSwitch.cs @@ -17,7 +17,7 @@ namespace Svg /// Gets the for this element. /// /// - public override System.Drawing.Drawing2D.GraphicsPath Path(SvgRenderer renderer) + public override System.Drawing.Drawing2D.GraphicsPath Path(ISvgRenderer renderer) { return GetPaths(this, renderer); } @@ -60,7 +60,7 @@ namespace Svg /// Renders the and contents to the specified object. /// /// The object to render to. - protected override void Render(SvgRenderer renderer) + protected override void Render(ISvgRenderer renderer) { if (!Visible || !Displayable) return; diff --git a/Source/Document Structure/SvgSymbol.cs b/Source/Document Structure/SvgSymbol.cs new file mode 100644 index 0000000000000000000000000000000000000000..f0ebd2f7ecc6f628d70d601f4ba2b0953c7c3787 --- /dev/null +++ b/Source/Document Structure/SvgSymbol.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Drawing; + +namespace Svg.Document_Structure +{ + /// + /// An element used to group SVG shapes. + /// + [SvgElement("symbol")] + public class SvgSymbol : SvgVisualElement + { + + /// + /// Gets or sets the viewport of the element. + /// + /// + [SvgAttribute("viewBox")] + public SvgViewBox ViewBox + { + get { return this.Attributes.GetAttribute("viewBox"); } + set { this.Attributes["viewBox"] = value; } + } + + /// + /// Gets or sets the aspect of the viewport. + /// + /// + [SvgAttribute("preserveAspectRatio")] + public SvgAspectRatio AspectRatio + { + get { return this.Attributes.GetAttribute("preserveAspectRatio"); } + set { this.Attributes["preserveAspectRatio"] = value; } + } + + /// + /// Gets the for this element. + /// + /// + public override System.Drawing.Drawing2D.GraphicsPath Path(ISvgRenderer renderer) + { + return GetPaths(this, renderer); + } + + /// + /// Gets the bounds of the element. + /// + /// The bounds. + public override System.Drawing.RectangleF Bounds + { + get + { + var r = new RectangleF(); + foreach (var c in this.Children) + { + if (c is SvgVisualElement) + { + // First it should check if rectangle is empty or it will return the wrong Bounds. + // This is because when the Rectangle is Empty, the Union method adds as if the first values where X=0, Y=0 + if (r.IsEmpty) + { + r = ((SvgVisualElement)c).Bounds; + } + else + { + var childBounds = ((SvgVisualElement)c).Bounds; + if (!childBounds.IsEmpty) + { + r = RectangleF.Union(r, childBounds); + } + } + } + } + + return r; + } + } + + protected override bool Renderable { get { return false; } } + + /// + /// Applies the required transforms to . + /// + /// The to be transformed. + protected internal override bool PushTransforms(ISvgRenderer renderer) + { + if (!base.PushTransforms(renderer)) return false; + this.ViewBox.AddViewBoxTransform(this.AspectRatio, renderer, null); + return true; + } + + public override SvgElement DeepCopy() + { + return DeepCopy(); + } + + public override SvgElement DeepCopy() + { + var newObj = base.DeepCopy() as SvgSymbol; + if (this.Fill != null) + newObj.Fill = this.Fill.DeepCopy() as SvgPaintServer; + return newObj; + } + } +} diff --git a/Source/Document Structure/SvgUse.cs b/Source/Document Structure/SvgUse.cs index 89dbc54767d5d597bb26c7fb7aace67559170b1d..ba2b6ab39294c5e68cd1bec5e6538980345c7223 100644 --- a/Source/Document Structure/SvgUse.cs +++ b/Source/Document Structure/SvgUse.cs @@ -35,10 +35,10 @@ namespace Svg } /// - /// Applies the required transforms to . + /// Applies the required transforms to . /// - /// The to be transformed. - protected internal override bool PushTransforms(SvgRenderer renderer) + /// The to be transformed. + protected internal override bool PushTransforms(ISvgRenderer renderer) { if (!base.PushTransforms(renderer)) return false; renderer.TranslateTransform(this.X.ToDeviceValue(renderer, UnitRenderingType.Horizontal, this), @@ -55,7 +55,7 @@ namespace Svg this.Y = 0; } - public override System.Drawing.Drawing2D.GraphicsPath Path(SvgRenderer renderer) + public override System.Drawing.Drawing2D.GraphicsPath Path(ISvgRenderer renderer) { SvgVisualElement element = (SvgVisualElement)this.OwnerDocument.IdManager.GetElementById(this.ReferencedElement); return (element != null) ? element.Path(renderer) : null; @@ -66,20 +66,9 @@ namespace Svg get { return new System.Drawing.RectangleF(); } } - // public override SvgElementCollection Children - // { - // get - // { - // SvgElement element = this.OwnerDocument.IdManager.GetElementById(this.ReferencedElement); - // SvgElementCollection elements = new SvgElementCollection(this, true); - // elements.Add(element); - // return elements; - // } - // } - protected override bool Renderable { get { return false; } } - protected override void Render(SvgRenderer renderer) + protected override void Render(ISvgRenderer renderer) { if (!Visible || !Displayable) return; diff --git a/Source/Extensibility/SvgForeignObject.cs b/Source/Extensibility/SvgForeignObject.cs index d61a95484130fd9b1bcd05edc2e4bd433e5f0eac..dd19031a3ca34b1ebba4fb948a04e3ed2202f92f 100644 --- a/Source/Extensibility/SvgForeignObject.cs +++ b/Source/Extensibility/SvgForeignObject.cs @@ -17,7 +17,7 @@ namespace Svg /// Gets the for this element. /// /// - public override System.Drawing.Drawing2D.GraphicsPath Path(SvgRenderer renderer) + public override System.Drawing.Drawing2D.GraphicsPath Path(ISvgRenderer renderer) { return GetPaths(this, renderer); } diff --git a/Source/Filter Effects/ImageBuffer.cs b/Source/Filter Effects/ImageBuffer.cs index 5a8ec9983c45876fed0125bb7cdfc0d8cb639817..11b721d240ab2ee09c0f371ec1ff9baa3a2a0bc0 100644 --- a/Source/Filter Effects/ImageBuffer.cs +++ b/Source/Filter Effects/ImageBuffer.cs @@ -14,8 +14,8 @@ namespace Svg.FilterEffects private Dictionary _images; private RectangleF _bounds; - private SvgRenderer _renderer; - private Action _renderMethod; + private ISvgRenderer _renderer; + private Action _renderMethod; private float _inflate; public Matrix Transform { get; set; } @@ -41,7 +41,7 @@ namespace Svg.FilterEffects } } - public ImageBuffer(RectangleF bounds, float inflate, SvgRenderer renderer, Action renderMethod) + public ImageBuffer(RectangleF bounds, float inflate, ISvgRenderer renderer, Action renderMethod) { _bounds = bounds; _inflate = inflate; @@ -124,7 +124,7 @@ namespace Svg.FilterEffects } private string ProcessKey(string key) { - if (string.IsNullOrEmpty(key)) return BufferKey; + if (string.IsNullOrEmpty(key)) return _images.ContainsKey(BufferKey) ? BufferKey : SvgFilterPrimitive.SourceGraphic; return key; } @@ -136,7 +136,7 @@ namespace Svg.FilterEffects (int)(_bounds.Height + 2 * _inflate * _bounds.Height + _bounds.Y)); using (var renderer = SvgRenderer.FromImage(graphic)) { - renderer.Boundable(_renderer.Boundable()); + renderer.SetBoundable(_renderer.GetBoundable()); var transform = new Matrix(); transform.Translate(_bounds.Width * _inflate, _bounds.Height * _inflate); renderer.Transform = transform; diff --git a/Source/Filter Effects/SvgFilter.cs b/Source/Filter Effects/SvgFilter.cs index 51c3cccba086edb5af855a8609ec86f2d9a1f59b..2bfc247325b66f4fc15a1b15d011a44db4edce61 100644 --- a/Source/Filter Effects/SvgFilter.cs +++ b/Source/Filter Effects/SvgFilter.cs @@ -80,10 +80,10 @@ namespace Svg.FilterEffects } /// - /// Renders the and contents to the specified object. + /// Renders the and contents to the specified object. /// - /// The object to render to. - protected override void Render(SvgRenderer renderer) + /// The object to render to. + protected override void Render(ISvgRenderer renderer) { base.RenderChildren(renderer); } @@ -109,7 +109,7 @@ namespace Svg.FilterEffects return transformMatrix; } - private RectangleF GetPathBounds(SvgVisualElement element, SvgRenderer renderer, Matrix transform) + private RectangleF GetPathBounds(SvgVisualElement element, ISvgRenderer renderer, Matrix transform) { var bounds = element.Path(renderer).GetBounds(); var pts = new PointF[] { bounds.Location, new PointF(bounds.Right, bounds.Bottom) }; @@ -119,7 +119,7 @@ namespace Svg.FilterEffects Math.Abs(pts[0].X - pts[1].X), Math.Abs(pts[0].Y - pts[1].Y)); } - public void ApplyFilter(SvgVisualElement element, SvgRenderer renderer, Action renderMethod) + public void ApplyFilter(SvgVisualElement element, ISvgRenderer renderer, Action renderMethod) { var inflate = 0.5f; var transform = GetTransform(element); @@ -139,11 +139,10 @@ namespace Svg.FilterEffects var bufferImg = buffer.Buffer; bufferImg.Save(@"C:\test.png"); var imgDraw = RectangleF.Inflate(bounds, inflate * bounds.Width, inflate * bounds.Height); - var prevClip = renderer.Clip; - renderer.Clip = new Region(imgDraw); + var prevClip = renderer.GetClip(); + renderer.SetClip(new Region(imgDraw)); renderer.DrawImage(bufferImg, imgDraw, new RectangleF(bounds.X, bounds.Y, imgDraw.Width, imgDraw.Height), GraphicsUnit.Pixel); - renderer.Clip = prevClip; - //renderer.DrawImage(bufferImg, bounds, bounds, GraphicsUnit.Pixel); + renderer.SetClip(prevClip); } } diff --git a/Source/Painting/ISvgStylable.cs b/Source/Painting/ISvgStylable.cs index 47274168bb89245f98e496c0bed2d3466e41b574..fb8a19e7f23ecda874eb922cf8571ebd6e0401be 100644 --- a/Source/Painting/ISvgStylable.cs +++ b/Source/Painting/ISvgStylable.cs @@ -19,6 +19,6 @@ namespace Svg float StrokeMiterLimit { get; set; } SvgUnitCollection StrokeDashArray { get; set; } SvgUnit StrokeDashOffset { get; set; } - GraphicsPath Path(SvgRenderer renderer); + GraphicsPath Path(ISvgRenderer renderer); } } \ No newline at end of file diff --git a/Source/Painting/SvgColourServer.cs b/Source/Painting/SvgColourServer.cs index 99fdf7c7b67aa5d1c92f1b837b961ee9919541c8..67fa17d0886f77aeddd817d6771530445c3ada78 100644 --- a/Source/Painting/SvgColourServer.cs +++ b/Source/Painting/SvgColourServer.cs @@ -35,7 +35,7 @@ namespace Svg set { this._colour = value; } } - public override Brush GetBrush(SvgVisualElement styleOwner, SvgRenderer renderer, float opacity) + public override Brush GetBrush(SvgVisualElement styleOwner, ISvgRenderer renderer, float opacity) { //is none? if (this == SvgPaintServer.None) return new SolidBrush(System.Drawing.Color.Transparent); diff --git a/Source/Painting/SvgDeferredPaintServer.cs b/Source/Painting/SvgDeferredPaintServer.cs index 22adfc87cfa53f823892c890e98423000a8d2a22..cb675180d10e413e3bd68842f3ada5e02bc743c0 100644 --- a/Source/Painting/SvgDeferredPaintServer.cs +++ b/Source/Painting/SvgDeferredPaintServer.cs @@ -44,7 +44,7 @@ namespace Svg } } - public override System.Drawing.Brush GetBrush(SvgVisualElement styleOwner, SvgRenderer renderer, float opacity) + public override System.Drawing.Brush GetBrush(SvgVisualElement styleOwner, ISvgRenderer renderer, float opacity) { EnsureServer(styleOwner); return _concreteServer.GetBrush(styleOwner, renderer, opacity); diff --git a/Source/Painting/SvgGradientServer.cs b/Source/Painting/SvgGradientServer.cs index 155286937ce3673cab778e910f597a808af0e964..aea48926dcbb618f54560e3b5c163db910289e61 100644 --- a/Source/Painting/SvgGradientServer.cs +++ b/Source/Painting/SvgGradientServer.cs @@ -130,7 +130,7 @@ namespace Svg /// /// The parent . /// The opacity of the colour blend. - protected ColorBlend GetColorBlend(SvgRenderer renderer, float opacity, bool radial) + protected ColorBlend GetColorBlend(ISvgRenderer renderer, float opacity, bool radial) { int colourBlends = this.Stops.Count; bool insertStart = false; @@ -184,7 +184,7 @@ namespace Svg for (int i = 0; i < colourBlends; i++) { var currentStop = this.Stops[radial ? this.Stops.Count - 1 - actualStops : actualStops]; - var boundWidth = renderer.Boundable().Bounds.Width; + var boundWidth = renderer.GetBoundable().Bounds.Width; mergedOpacity = opacity * currentStop.Opacity; position = diff --git a/Source/Painting/SvgLinearGradientServer.cs b/Source/Painting/SvgLinearGradientServer.cs index cc791efa9084c376d7aa8012b5637e174cc844c7..f419e769697a77585d5833f511d1a5654f74e596 100644 --- a/Source/Painting/SvgLinearGradientServer.cs +++ b/Source/Painting/SvgLinearGradientServer.cs @@ -79,7 +79,7 @@ namespace Svg Y2 = new SvgUnit(SvgUnitType.Percentage, 0F); } - public override Brush GetBrush(SvgVisualElement renderingElement, SvgRenderer renderer, float opacity) + public override Brush GetBrush(SvgVisualElement renderingElement, ISvgRenderer renderer, float opacity) { LoadStops(renderingElement); if (IsInvalid) @@ -89,7 +89,7 @@ namespace Svg try { - if (this.GradientUnits == SvgCoordinateUnits.ObjectBoundingBox) renderer.Boundable(renderingElement); + if (this.GradientUnits == SvgCoordinateUnits.ObjectBoundingBox) renderer.SetBoundable(renderingElement); var specifiedStart = CalculateStart(renderer); var specifiedEnd = CalculateEnd(renderer); @@ -116,12 +116,12 @@ namespace Svg } } - private PointF CalculateStart(SvgRenderer renderer) + private PointF CalculateStart(ISvgRenderer renderer) { return TransformPoint(SvgUnit.GetDevicePointOffset(this.X1, this.Y1, renderer, this)); } - private PointF CalculateEnd(SvgRenderer renderer) + private PointF CalculateEnd(ISvgRenderer renderer) { return TransformPoint(SvgUnit.GetDevicePointOffset(this.X2, this.Y2, renderer, this)); } @@ -181,7 +181,7 @@ namespace Svg return new GradientPoints(effectiveStart, effectiveEnd); } - private ColorBlend CalculateColorBlend(SvgRenderer renderer, float opacity, PointF specifiedStart, PointF effectiveStart, PointF specifiedEnd, PointF effectiveEnd) + private ColorBlend CalculateColorBlend(ISvgRenderer renderer, float opacity, PointF specifiedStart, PointF effectiveStart, PointF specifiedEnd, PointF effectiveEnd) { var colorBlend = GetColorBlend(renderer, opacity, false); diff --git a/Source/Painting/SvgMarker.cs b/Source/Painting/SvgMarker.cs index 12b4f80e9ed096eeffbf8d06f73a1efccb6f429d..a959e3d83f122bf7e7a578ae8ed2c5fa366a0d3a 100644 --- a/Source/Painting/SvgMarker.cs +++ b/Source/Painting/SvgMarker.cs @@ -87,7 +87,7 @@ namespace Svg Overflow = SvgOverflow.hidden; } - public override System.Drawing.Drawing2D.GraphicsPath Path(SvgRenderer renderer) + public override System.Drawing.Drawing2D.GraphicsPath Path(ISvgRenderer renderer) { var path = this.Children.FirstOrDefault(x => x is SvgPath); if (path != null) @@ -131,7 +131,7 @@ namespace Svg /// /// /// - public void RenderMarker(SvgRenderer pRenderer, SvgPath pOwner, PointF pRefPoint, PointF pMarkerPoint1, PointF pMarkerPoint2) + public void RenderMarker(ISvgRenderer pRenderer, SvgPath pOwner, PointF pRefPoint, PointF pMarkerPoint1, PointF pMarkerPoint2) { float xDiff = pMarkerPoint2.X - pMarkerPoint1.X; float yDiff = pMarkerPoint2.Y - pMarkerPoint1.Y; @@ -148,7 +148,7 @@ namespace Svg /// /// /// - public void RenderMarker(SvgRenderer pRenderer, SvgPath pOwner, PointF pRefPoint, PointF pMarkerPoint1, PointF pMarkerPoint2, PointF pMarkerPoint3) + public void RenderMarker(ISvgRenderer pRenderer, SvgPath pOwner, PointF pRefPoint, PointF pMarkerPoint1, PointF pMarkerPoint2, PointF pMarkerPoint3) { float xDiff = pMarkerPoint2.X - pMarkerPoint1.X; float yDiff = pMarkerPoint2.Y - pMarkerPoint1.Y; @@ -168,7 +168,7 @@ namespace Svg /// /// /// - private void RenderPart2(float fAngle, SvgRenderer pRenderer, SvgPath pOwner, PointF pMarkerPoint) + private void RenderPart2(float fAngle, ISvgRenderer pRenderer, SvgPath pOwner, PointF pMarkerPoint) { Pen pRenderPen = CreatePen(pOwner, pRenderer); @@ -216,7 +216,7 @@ namespace Svg /// /// /// - private Pen CreatePen(SvgPath pPath, SvgRenderer renderer) + private Pen CreatePen(SvgPath pPath, ISvgRenderer renderer) { Brush pBrush = pPath.Stroke.GetBrush(this, renderer, Opacity); switch (MarkerUnits) diff --git a/Source/Painting/SvgPaintServer.cs b/Source/Painting/SvgPaintServer.cs index 11c342e5045f6e06d23fcb716a88dfd07c385291..417024ce7acff4a6113fc873b4a00046a1ee67d0 100644 --- a/Source/Painting/SvgPaintServer.cs +++ b/Source/Painting/SvgPaintServer.cs @@ -26,10 +26,10 @@ namespace Svg } /// - /// Renders the and contents to the specified object. + /// Renders the and contents to the specified object. /// - /// The object to render to. - protected override void Render(SvgRenderer renderer) + /// The object to render to. + protected override void Render(ISvgRenderer renderer) { // Never render paint servers or their children } @@ -39,7 +39,7 @@ namespace Svg /// /// The owner . /// The opacity of the brush. - public abstract Brush GetBrush(SvgVisualElement styleOwner, SvgRenderer renderer, float opacity); + public abstract Brush GetBrush(SvgVisualElement styleOwner, ISvgRenderer renderer, float opacity); /// /// Returns a that represents the current . diff --git a/Source/Painting/SvgPatternServer.cs b/Source/Painting/SvgPatternServer.cs index 9afa695d35c2b6ad84e0fe6cbec142068eab203f..48adb39d3cd2a8f84d892da21f12ed9d245ef4e0 100644 --- a/Source/Painting/SvgPatternServer.cs +++ b/Source/Painting/SvgPatternServer.cs @@ -20,8 +20,8 @@ namespace Svg private SvgUnit _x; private SvgUnit _y; private SvgViewBox _viewBox; - private SvgCoordinateUnits _patternUnits; - private SvgCoordinateUnits _patternContentUnits; + private SvgCoordinateUnits _patternUnits = SvgCoordinateUnits.ObjectBoundingBox; + private SvgCoordinateUnits _patternContentUnits = SvgCoordinateUnits.UserSpaceOnUse; [SvgAttribute("overflow")] public SvgOverflow Overflow @@ -129,7 +129,7 @@ namespace Svg /// /// The owner . /// The opacity of the brush. - public override Brush GetBrush(SvgVisualElement renderingElement, SvgRenderer renderer, float opacity) + public override Brush GetBrush(SvgVisualElement renderingElement, ISvgRenderer renderer, float opacity) { // If there aren't any children, return null if (this.Children.Count == 0) @@ -141,7 +141,7 @@ namespace Svg try { - if (this.PatternUnits == SvgCoordinateUnits.ObjectBoundingBox) renderer.Boundable(renderingElement); + if (this.PatternUnits == SvgCoordinateUnits.ObjectBoundingBox) renderer.SetBoundable(renderingElement); float width = this._width.ToDeviceValue(renderer, UnitRenderingType.Horizontal, this); float height = this._height.ToDeviceValue(renderer, UnitRenderingType.Vertical, this); @@ -153,11 +153,7 @@ namespace Svg float x = this._x.ToDeviceValue(renderer, UnitRenderingType.HorizontalOffset, this); float y = this._y.ToDeviceValue(renderer, UnitRenderingType.VerticalOffset, this); - patternMatrix.Translate(x + -1.0f, y + -1.0f); - } - else - { - patternMatrix.Translate(-1, -1); + patternMatrix.Translate(x, y); } if (this.ViewBox.Height > 0 || this.ViewBox.Width > 0) @@ -167,20 +163,16 @@ namespace Svg } Bitmap image = new Bitmap((int)width, (int)height); - using (SvgRenderer iRenderer = SvgRenderer.FromImage(image)) + using (var iRenderer = SvgRenderer.FromImage(image)) { - iRenderer.Boundable((_patternContentUnits == SvgCoordinateUnits.ObjectBoundingBox) ? new GenericBoundable(0, 0, width, height) : renderer.Boundable()); + iRenderer.SetBoundable((_patternContentUnits == SvgCoordinateUnits.ObjectBoundingBox) ? new GenericBoundable(0, 0, width, height) : renderer.GetBoundable()); iRenderer.Transform = patternMatrix; - iRenderer.CompositingQuality = CompositingQuality.HighQuality; iRenderer.SmoothingMode = SmoothingMode.AntiAlias; - iRenderer.PixelOffsetMode = PixelOffsetMode.Half; - + foreach (SvgElement child in this.Children) { child.RenderElement(iRenderer); } - - iRenderer.Save(); } image.Save(string.Format(@"C:\test{0:D3}.png", imgNumber++)); diff --git a/Source/Painting/SvgRadialGradientServer.cs b/Source/Painting/SvgRadialGradientServer.cs index 5a1a6cd443c7211079475a3df32870a719b086b7..240f87ea34e7fabcb606c8cdbdbef2b73d0e7c93 100644 --- a/Source/Painting/SvgRadialGradientServer.cs +++ b/Source/Painting/SvgRadialGradientServer.cs @@ -98,16 +98,16 @@ namespace Svg private object _lockObj = new Object(); - public override Brush GetBrush(SvgVisualElement renderingElement, SvgRenderer renderer, float opacity) + public override Brush GetBrush(SvgVisualElement renderingElement, ISvgRenderer renderer, float opacity) { LoadStops(renderingElement); try { - if (this.GradientUnits == SvgCoordinateUnits.ObjectBoundingBox) renderer.Boundable(renderingElement); + if (this.GradientUnits == SvgCoordinateUnits.ObjectBoundingBox) renderer.SetBoundable(renderingElement); // Calculate the path and transform it appropriately - var origin = renderer.Boundable().Location; + var origin = renderer.GetBoundable().Location; var center = new PointF(origin.X + CenterX.ToDeviceValue(renderer, UnitRenderingType.HorizontalOffset, this), origin.Y + CenterY.ToDeviceValue(renderer, UnitRenderingType.VerticalOffset, this)); var specifiedRadius = Radius.ToDeviceValue(renderer, UnitRenderingType.Other, this); @@ -181,7 +181,7 @@ namespace Svg return bounds.Height / (points[2].Y - points[1].Y); } - private PointF CalculateFocalPoint(SvgRenderer renderer, PointF origin) + private PointF CalculateFocalPoint(ISvgRenderer renderer, PointF origin) { var deviceFocalX = origin.X + FocalX.ToDeviceValue(renderer, UnitRenderingType.HorizontalOffset, this); var deviceFocalY = origin.Y + FocalY.ToDeviceValue(renderer, UnitRenderingType.VerticalOffset, this); @@ -203,7 +203,7 @@ namespace Svg return path; } - private ColorBlend CalculateColorBlend(SvgRenderer renderer, float opacity, float scale, out float outScale) + private ColorBlend CalculateColorBlend(ISvgRenderer renderer, float opacity, float scale, out float outScale) { var colorBlend = GetColorBlend(renderer, opacity, true); float newScale; diff --git a/Source/Paths/SvgPath.cs b/Source/Paths/SvgPath.cs index 95757d2b98b8cb80a29f7e9b4dead90595a6aea1..25de7c228d817bb5d3dda3d1f75f8b78ff19f3ee 100644 --- a/Source/Paths/SvgPath.cs +++ b/Source/Paths/SvgPath.cs @@ -38,9 +38,9 @@ namespace Svg /// Gets or sets the length of the path. /// [SvgAttribute("pathLength")] - public int PathLength + public float PathLength { - get { return this.Attributes.GetAttribute("pathLength"); } + get { return this.Attributes.GetAttribute("pathLength"); } set { this.Attributes["pathLength"] = value; } } @@ -81,7 +81,7 @@ namespace Svg /// /// Gets the for this element. /// - public override GraphicsPath Path(SvgRenderer renderer) + public override GraphicsPath Path(ISvgRenderer renderer) { if (this._path == null || this.IsPathDirty) { @@ -131,15 +131,15 @@ namespace Svg } /// - /// Renders the stroke of the to the specified + /// Renders the stroke of the to the specified /// - /// The object to render to. - protected internal override void RenderStroke(SvgRenderer renderer) + /// The object to render to. + protected internal override void RenderStroke(ISvgRenderer renderer) { - if (this.Stroke != null) + if (this.Stroke != null && this.Stroke != SvgColourServer.None) { float strokeWidth = this.StrokeWidth.ToDeviceValue(renderer, UnitRenderingType.Other, this); - using (Pen pen = new Pen(this.Stroke.GetBrush(this, renderer, this.StrokeOpacity), strokeWidth)) + using (Pen pen = new Pen(this.Stroke.GetBrush(this, renderer, this.StrokeOpacity * this.Opacity), strokeWidth)) { if (this.StrokeDashArray != null && this.StrokeDashArray.Count > 0) { diff --git a/Source/Rendering/IGraphicsProvider.cs b/Source/Rendering/IGraphicsProvider.cs new file mode 100644 index 0000000000000000000000000000000000000000..743c50f63e54f166c96c9310ff3b81d59a668eca --- /dev/null +++ b/Source/Rendering/IGraphicsProvider.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Drawing; + +namespace Svg +{ + public interface IGraphicsProvider + { + Graphics GetGraphics(); + } +} diff --git a/Source/Rendering/ISvgRenderer.cs b/Source/Rendering/ISvgRenderer.cs new file mode 100644 index 0000000000000000000000000000000000000000..0488abd94c81bf0af0b2a4fc357c38327f3c590c --- /dev/null +++ b/Source/Rendering/ISvgRenderer.cs @@ -0,0 +1,29 @@ +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); + float FontBaselineOffset(IFontDefn font); + ISvgBoundable GetBoundable(); + Region GetClip(); + IList MeasureCharacters(string text, IFontDefn font); + SizeF MeasureString(string text, IFontDefn font); + ISvgBoundable PopBoundable(); + void RotateTransform(float fAngle, MatrixOrder order = MatrixOrder.Append); + void ScaleTransform(float sx, float sy, MatrixOrder order = MatrixOrder.Append); + 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); + } +} diff --git a/Source/Rendering/SvgRenderer.cs b/Source/Rendering/SvgRenderer.cs new file mode 100644 index 0000000000000000000000000000000000000000..2c03b01d59cfdbd205ad61122812a07af5f20511 --- /dev/null +++ b/Source/Rendering/SvgRenderer.cs @@ -0,0 +1,150 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Text; + +namespace Svg +{ + /// + /// Convenience wrapper around a graphics object + /// + public sealed class SvgRenderer : IDisposable, IGraphicsProvider, ISvgRenderer + { + private Graphics _innerGraphics; + private Stack _boundables = new Stack(); + + public void SetBoundable(ISvgBoundable boundable) + { + _boundables.Push(boundable); + } + public ISvgBoundable GetBoundable() + { + return _boundables.Peek(); + } + public ISvgBoundable PopBoundable() + { + return _boundables.Pop(); + } + + public float DpiY + { + get { return _innerGraphics.DpiY; } + } + + /// + /// Initializes a new instance of the class. + /// + private SvgRenderer(Graphics graphics) + { + this._innerGraphics = graphics; + } + + public void DrawImage(Image image, RectangleF destRect, RectangleF srcRect, GraphicsUnit graphicsUnit) + { + _innerGraphics.DrawImage(image, destRect, srcRect, graphicsUnit); + } + public void DrawImageUnscaled(Image image, Point location) + { + this._innerGraphics.DrawImageUnscaled(image, location); + } + public void DrawPath(Pen pen, GraphicsPath path) + { + this._innerGraphics.DrawPath(pen, path); + } + public void FillPath(Brush brush, GraphicsPath path) + { + this._innerGraphics.FillPath(brush, path); + } + public Region GetClip() + { + return this._innerGraphics.Clip; + } + public void RotateTransform(float fAngle, MatrixOrder order = MatrixOrder.Append) + { + this._innerGraphics.RotateTransform(fAngle, order); + } + public void ScaleTransform(float sx, float sy, MatrixOrder order = MatrixOrder.Append) + { + this._innerGraphics.ScaleTransform(sx, sy, order); + } + public void SetClip(Region region, CombineMode combineMode = CombineMode.Replace) + { + this._innerGraphics.SetClip(region, combineMode); + } + public void TranslateTransform(float dx, float dy, MatrixOrder order = MatrixOrder.Append) + { + this._innerGraphics.TranslateTransform(dx, dy, order); + } + + + + public SmoothingMode SmoothingMode + { + get { return this._innerGraphics.SmoothingMode; } + set { this._innerGraphics.SmoothingMode = value; } + } + + public Matrix Transform + { + get { return this._innerGraphics.Transform; } + set { this._innerGraphics.Transform = value; } + } + + public void Dispose() + { + this._innerGraphics.Dispose(); + } + + public float FontBaselineOffset(IFontDefn font) + { + return font.Ascent(this); + } + + public IList MeasureCharacters(string text, IFontDefn font) + { + return font.MeasureCharacters(this, text); + } + + public SizeF MeasureString(string text, IFontDefn font) + { + return font.MeasureString(this, text); + } + + Graphics IGraphicsProvider.GetGraphics() + { + return _innerGraphics; + } + + /// + /// Creates a new from the specified . + /// + /// from which to create the new . + public static ISvgRenderer FromImage(Image image) + { + 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); + } + + /// + /// Creates a new from the specified . + /// + /// The to create the renderer from. + public static ISvgRenderer FromGraphics(Graphics graphics) + { + return new SvgRenderer(graphics); + } + + public static ISvgRenderer FromNull() + { + var img = new Bitmap(1, 1); + return SvgRenderer.FromImage(img); + } + } +} \ No newline at end of file diff --git a/Source/Svg.csproj b/Source/Svg.csproj index 7e2ee28c3dc68fcf372139d6c699a103bc2ed1da..56a1bb4c18dbc68660539c8a15ee37c5861de0a2 100644 --- a/Source/Svg.csproj +++ b/Source/Svg.csproj @@ -101,8 +101,15 @@ + + + + + + + @@ -235,7 +242,7 @@ - + @@ -276,10 +283,24 @@ + + + + + + + + + + + + + + diff --git a/Source/SvgAttributeAttribute.cs b/Source/SvgAttributeAttribute.cs index cc8c72e47eeb01c1fd3b44870c024b6605e34b2e..eca8f18082ae400e5c6e8497f25c9dce8f9809be 100644 --- a/Source/SvgAttributeAttribute.cs +++ b/Source/SvgAttributeAttribute.cs @@ -15,13 +15,14 @@ namespace Svg /// /// Gets a containing the XLink namespace (http://www.w3.org/1999/xlink). /// - 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> Namespaces = new List>() { - new KeyValuePair("", SVG_NAMESPACE), + new KeyValuePair("", SvgNamespace), new KeyValuePair(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; } /// diff --git a/Source/SvgAttributeCollection.cs b/Source/SvgAttributeCollection.cs index db60897651b574f6c60ff777c33028d8e551c430..39e85f64de85069dc18f27dc01bebcd6a88f39d2 100644 --- a/Source/SvgAttributeCollection.cs +++ b/Source/SvgAttributeCollection.cs @@ -89,8 +89,9 @@ namespace Svg (value is SvgFontWeight && (SvgFontWeight)value == SvgFontWeight.inherit) || (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 SvgTextDecoration && (SvgTextDecoration)value == SvgTextDecoration.inherit) || + (value is XmlSpaceHandling && (XmlSpaceHandling)value == XmlSpaceHandling.inherit) || + (value is string && (string)value == "inherit") ); } diff --git a/Source/SvgDocument.cs b/Source/SvgDocument.cs index c5fd0dcbb266f1724667364b2e7eb7fe0613a463..14f1ef8e9668f610505ca47b820f0d55f5829f88 100644 --- a/Source/SvgDocument.cs +++ b/Source/SvgDocument.cs @@ -22,6 +22,18 @@ namespace Svg public static readonly int PointsPerInch = 96; private SvgElementIdManager _idManager; + private Dictionary> _fontDefns = null; + internal Dictionary> FontDefns() + { + if (_fontDefns == null) + { + _fontDefns = (from f in Descendants().OfType() + group f by f.FontFamily into family + select family).ToDictionary(f => f.Key, f => (IEnumerable)f); + } + return _fontDefns; + } + /// /// Initializes a new instance of the class. /// @@ -379,18 +391,18 @@ namespace Svg } /// - /// Renders the to the specified . + /// Renders the to the specified . /// - /// The to render the document with. + /// The to render the document with. /// The parameter cannot be null. - 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 +419,7 @@ namespace Svg } var renderer = SvgRenderer.FromGraphics(graphics); - renderer.Boundable(this); + renderer.SetBoundable(this); this.Render(renderer); } @@ -447,12 +459,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 diff --git a/Source/SvgElement.cs b/Source/SvgElement.cs index b2ceb6cb40f85970602ffb76879981fe3d3834c0..d808b9c50803c16af83514de1384c2466bd864c8 100644 --- a/Source/SvgElement.cs +++ b/Source/SvgElement.cs @@ -255,13 +255,13 @@ namespace Svg } /// - /// Applies the required transforms to . + /// Applies the required transforms to . /// - /// The to be transformed. - protected internal virtual bool PushTransforms(SvgRenderer renderer) + /// The to be transformed. + 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) @@ -283,10 +283,10 @@ namespace Svg } /// - /// Removes any previously applied transforms from the specified . + /// Removes any previously applied transforms from the specified . /// - /// The that should have transforms removed. - protected internal virtual void PopTransforms(SvgRenderer renderer) + /// The that should have transforms removed. + protected internal virtual void PopTransforms(ISvgRenderer renderer) { renderer.Transform = _graphicsMatrix; _graphicsMatrix = null; @@ -295,19 +295,19 @@ namespace Svg } /// - /// Applies the required transforms to . + /// Applies the required transforms to . /// - /// The to be transformed. - void ISvgTransformable.PushTransforms(SvgRenderer renderer) + /// The to be transformed. + void ISvgTransformable.PushTransforms(ISvgRenderer renderer) { this.PushTransforms(renderer); } /// - /// Removes any previously applied transforms from the specified . + /// Removes any previously applied transforms from the specified . /// - /// The that should have transforms removed. - void ISvgTransformable.PopTransforms(SvgRenderer renderer) + /// The that should have transforms removed. + void ISvgTransformable.PopTransforms(ISvgRenderer renderer) { this.PopTransforms(renderer); } @@ -344,6 +344,17 @@ namespace Svg } } + /// + /// Gets or sets the text anchor. + /// + /// The text anchor. + [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 logElementOldIDNewID = null) { // Don't do anything if it hasn't changed @@ -469,10 +480,10 @@ namespace Svg /// - /// Renders this element to the . + /// Renders this element to the . /// - /// The that the element should use to render itself. - public void RenderElement(SvgRenderer renderer) + /// The that the element should use to render itself. + public void RenderElement(ISvgRenderer renderer) { this.Render(renderer); } @@ -632,10 +643,10 @@ namespace Svg } /// - /// Renders the and contents to the specified object. + /// Renders the and contents to the specified object. /// - /// The object to render to. - protected virtual void Render(SvgRenderer renderer) + /// The object to render to. + protected virtual void Render(ISvgRenderer renderer) { this.PushTransforms(renderer); this.RenderChildren(renderer); @@ -645,8 +656,8 @@ namespace Svg /// /// Renders the children of this . /// - /// The to render the child s to. - protected virtual void RenderChildren(SvgRenderer renderer) + /// The to render the child s to. + protected virtual void RenderChildren(ISvgRenderer renderer) { foreach (SvgElement element in this.Children) { @@ -655,10 +666,10 @@ namespace Svg } /// - /// Renders the and contents to the specified object. + /// Renders the and contents to the specified object. /// - /// The object to render to. - void ISvgElement.Render(SvgRenderer renderer) + /// The object to render to. + void ISvgElement.Render(ISvgRenderer renderer) { this.Render(renderer); } @@ -698,7 +709,7 @@ namespace Svg /// /// /// - protected GraphicsPath GetPaths(SvgElement elem, SvgRenderer renderer) + protected GraphicsPath GetPaths(SvgElement elem, ISvgRenderer renderer) { var ret = new GraphicsPath(); @@ -1117,6 +1128,6 @@ namespace Svg SvgElementCollection Children { get; } IList Nodes { get; } - void Render(SvgRenderer renderer); + void Render(ISvgRenderer renderer); } } \ No newline at end of file diff --git a/Source/SvgElementFactory.cs b/Source/SvgElementFactory.cs index 8f812bf25234184fccf77ef6f2e5f7737268ed95..aaef9ddfba478d03c55d1af82799393daf19118b 100644 --- a/Source/SvgElementFactory.cs +++ b/Source/SvgElementFactory.cs @@ -15,7 +15,6 @@ namespace Svg internal class SvgElementFactory { private static List availableElements; - private const string svgNS = "http://www.w3.org/2000/svg"; private static Parser cssParser = new Parser(); /// @@ -84,7 +83,7 @@ namespace Svg //Trace.TraceInformation("Begin CreateElement: {0}", elementName); - if (elementNS == svgNS) + if (elementNS == SvgAttributeAttribute.SvgNamespace || string.IsNullOrEmpty(elementNS)) { if (elementName == "svg") { diff --git a/Source/SvgElementIdManager.cs b/Source/SvgElementIdManager.cs index a2d92b08c61ee1a0e5e018f2c49ad38eada1924a..a4d10b787517ef89391d04849efeab19b4082bcb 100644 --- a/Source/SvgElementIdManager.cs +++ b/Source/SvgElementIdManager.cs @@ -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,29 @@ namespace Svg public virtual SvgElement GetElementById(Uri uri) { + 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(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(webResponse.GetResponseStream()); + return doc.IdManager.GetElementById(hash); + } + default: + throw new NotSupportedException(); + } + + } return this.GetElementById(uri.ToString()); } diff --git a/Source/SvgRenderer.cs b/Source/SvgRenderer.cs deleted file mode 100644 index 8ed934cb55d190cd7f94cff09941efd0e15b6a4c..0000000000000000000000000000000000000000 --- a/Source/SvgRenderer.cs +++ /dev/null @@ -1,197 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Drawing; -using System.Drawing.Drawing2D; -using System.Drawing.Text; - -namespace Svg -{ - /// - /// Convenience wrapper around a graphics object - /// - public sealed class SvgRenderer : IDisposable - { - private Graphics _innerGraphics; - private Stack _boundables = new Stack(); - - public void Boundable(ISvgBoundable boundable) - { - _boundables.Push(boundable); - } - public ISvgBoundable Boundable() - { - return _boundables.Peek(); - } - public ISvgBoundable PopBoundable() - { - return _boundables.Pop(); - } - - /// - /// Initializes a new instance of the class. - /// - private SvgRenderer() - { - } - - public Region Clip - { - get { return this._innerGraphics.Clip; } - set { this._innerGraphics.Clip = value; } - } - - /// - /// Creates a new from the specified . - /// - /// from which to create the new . - public static SvgRenderer FromImage(Image image) - { - SvgRenderer renderer = new SvgRenderer(); - renderer._innerGraphics = Graphics.FromImage(image); - return renderer; - } - - /// - /// Creates a new from the specified . - /// - /// The to create the renderer from. - 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); - } - - public void DrawImage(Image image, RectangleF destRect, RectangleF srcRect, GraphicsUnit graphicsUnit) - { - _innerGraphics.DrawImage(image, destRect, srcRect, graphicsUnit); - } - - public void AddClip(Region region) - { - this._innerGraphics.SetClip(region, CombineMode.Intersect); - } - public void SetClip(Region region) - { - this._innerGraphics.SetClip(region, CombineMode.Replace); - } - - public void FillPath(Brush brush, GraphicsPath path) - { - this._innerGraphics.FillPath(brush, path); - } - - public void DrawPath(Pen pen, GraphicsPath path) - { - this._innerGraphics.DrawPath(pen, path); - } - - public void RotateTransform(float fAngle, MatrixOrder order) - { - this._innerGraphics.RotateTransform(fAngle, order); - } - - public void RotateTransform(float fAngle) - { - this.RotateTransform(fAngle, MatrixOrder.Append); - } - - public void TranslateTransform(float dx, float dy, MatrixOrder order) - { - this._innerGraphics.TranslateTransform(dx, dy, order); - } - - public void TranslateTransform(float dx, float dy) - { - this.TranslateTransform(dx, dy, MatrixOrder.Append); - } - - 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 - { - get { return this._innerGraphics.SmoothingMode; } - 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 - { - get { return this._innerGraphics.TextRenderingHint; } - set { this._innerGraphics.TextRenderingHint = value; } - } - - public int TextContrast - { - get { return this._innerGraphics.TextContrast; } - set { this._innerGraphics.TextContrast = value; } - } - - public Matrix Transform - { - get { return this._innerGraphics.Transform; } - set { this._innerGraphics.Transform = value; } - } - - public void Save() - { - this._innerGraphics.Save(); - } - - public void Dispose() - { - this._innerGraphics.Dispose(); - } - - public SizeF MeasureString(string text, Font font) - { - 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); - } - } -} \ No newline at end of file diff --git a/Source/SvgTextReader.cs b/Source/SvgTextReader.cs index 7ef556cb6f48222de0963acd7891059ab74b9a14..b04f48c9b8d6b89c6edde782801badb54a4f4d17 100644 --- a/Source/SvgTextReader.cs +++ b/Source/SvgTextReader.cs @@ -123,7 +123,6 @@ namespace Svg { const string entityText = " + /// http://stackoverflow.com/questions/3633000/net-enumerate-winforms-font-styles + /// + 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(); + //} + + + } +} diff --git a/Source/Text/GdiFontDefn.cs b/Source/Text/GdiFontDefn.cs new file mode 100644 index 0000000000000000000000000000000000000000..ef8d99ddd4830008d49bbbae3eeaf64021d747e8 --- /dev/null +++ b/Source/Text/GdiFontDefn.cs @@ -0,0 +1,90 @@ +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 MeasureCharacters(ISvgRenderer renderer, string text) + { + var g = GetGraphics(renderer); + var regions = new List(); + 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(); + } + } + } +} diff --git a/Source/Text/IFontDefn.cs b/Source/Text/IFontDefn.cs new file mode 100644 index 0000000000000000000000000000000000000000..3b53896d3826835fcb2f3535422d06804968ecd7 --- /dev/null +++ b/Source/Text/IFontDefn.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Drawing; +using System.Drawing.Drawing2D; + +namespace Svg +{ + public interface IFontDefn + { + float Size { get; } + float SizeInPoints { get; } + void AddStringToPath(ISvgRenderer renderer, GraphicsPath path, string text, PointF location); + float Ascent(ISvgRenderer renderer); + IList MeasureCharacters(ISvgRenderer renderer, string text); + SizeF MeasureString(ISvgRenderer renderer, string text); + } +} diff --git a/Source/Text/PathStatistics.cs b/Source/Text/PathStatistics.cs new file mode 100644 index 0000000000000000000000000000000000000000..a02d968a2a3a13e75b53bf4f6265b8afd7657780 --- /dev/null +++ b/Source/Text/PathStatistics.cs @@ -0,0 +1,262 @@ +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 _segments = new List(); + + 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 _integral; + private SortedList _lengths = new SortedList(); + + 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); + } + } + } + + /// + /// Evaluates the integral of the function over the integral using the specified number of points + /// + /// + /// + /// + /// + public static double GaussianQuadrature(Func 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(); + } + + /// http://en.wikipedia.org/wiki/B%C3%A9zier_curve + 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)); + } + + /// http://www.cs.mtu.edu/~shene/COURSES/cs3621/NOTES/spline/Bezier/bezier-der.html + 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)); + } + } + } +} diff --git a/Source/Text/SvgFont.cs b/Source/Text/SvgFont.cs new file mode 100644 index 0000000000000000000000000000000000000000..da38bc73da1ec53d566201ce48de901e5b8395d6 --- /dev/null +++ b/Source/Text/SvgFont.cs @@ -0,0 +1,55 @@ +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().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().First().Attributes["ascent"] == null ? 0 : this.Children.OfType().First().Ascent) : + (float)this.Attributes["vert-origin-y"]); } + set { this.Attributes["vert-origin-y"] = value; } + } + + public override SvgElement DeepCopy() + { + return base.DeepCopy(); + } + } +} diff --git a/Source/Text/SvgFontDefn.cs b/Source/Text/SvgFontDefn.cs new file mode 100644 index 0000000000000000000000000000000000000000..d26f3b6963d8607bd1253ace5a843d128333962a --- /dev/null +++ b/Source/Text/SvgFontDefn.cs @@ -0,0 +1,130 @@ +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 _glyphs; + private Dictionary _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().First(); + _emScale = _size / face.UnitsPerEm; + } + + public float Ascent(ISvgRenderer renderer) + { + float ascent = _font.Descendants().OfType().First().Ascent; + float baselineOffset = this.SizeInPoints * (_emScale / _size) * ascent; + return renderer.DpiY / 72f * baselineOffset; + } + + public IList MeasureCharacters(ISvgRenderer renderer, string text) + { + var result = new List(); + GetPath(renderer, text, result, false); + return result; + } + + public System.Drawing.SizeF MeasureString(ISvgRenderer renderer, string text) + { + var result = new List(); + GetPath(renderer, text, result, true); + 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) + { + 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 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().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); + + 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().ToDictionary(g => g.Unicode ?? g.GlyphName ?? g.ID); + if (_kerning == null) _kerning = _font.Descendants().OfType().ToDictionary(k => k.Glyph1 + "|" + k.Glyph2); + } + } +} diff --git a/Source/Text/SvgFontFace.cs b/Source/Text/SvgFontFace.cs new file mode 100644 index 0000000000000000000000000000000000000000..09aacaef46462b62d8c661327490ff047b2c96e9 --- /dev/null +++ b/Source/Text/SvgFontFace.cs @@ -0,0 +1,116 @@ +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; } + } + + /// + /// Indicates which font family is to be used to render the text. + /// + [SvgAttribute("font-family")] + public virtual string FontFamily + { + get { return this.Attributes["font-family"] as string; } + set { this.Attributes["font-family"] = value; } + } + + /// + /// Refers to the size of the font from baseline to baseline when multiple lines of text are set solid in a multiline layout environment. + /// + [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; } + } + + /// + /// Refers to the style of the font. + /// + [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; } + } + + /// + /// Refers to the varient of the font. + /// + [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; } + } + + /// + /// Refers to the boldness of the font. + /// + [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(); + } + } +} diff --git a/Source/Text/SvgFontFaceSrc.cs b/Source/Text/SvgFontFaceSrc.cs new file mode 100644 index 0000000000000000000000000000000000000000..5b0fdb6852d07a3bee2cc9819340b5ec3679fd6a --- /dev/null +++ b/Source/Text/SvgFontFaceSrc.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Svg +{ + [SvgElement("font-face-src")] + public class SvgFontFaceSrc : SvgElement + { + public override SvgElement DeepCopy() + { + return base.DeepCopy(); + } + } +} diff --git a/Source/Text/SvgFontFaceUri.cs b/Source/Text/SvgFontFaceUri.cs new file mode 100644 index 0000000000000000000000000000000000000000..58d155d6aedbb321f7eb9cf46a9eee82be629020 --- /dev/null +++ b/Source/Text/SvgFontFaceUri.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Svg +{ + [SvgElement("font-face-uri")] + public class SvgFontFaceUri : SvgElement + { + private Uri _referencedElement; + + [SvgAttribute("href", SvgAttributeAttribute.XLinkNamespace)] + public virtual Uri ReferencedElement + { + get { return this._referencedElement; } + set { this._referencedElement = value; } + } + + public override SvgElement DeepCopy() + { + return DeepCopy(); + } + + public override SvgElement DeepCopy() + { + var newObj = base.DeepCopy() as SvgFontFaceUri; + newObj.ReferencedElement = this.ReferencedElement; + + return newObj; + } + } +} diff --git a/Source/Text/SvgGlyph.cs b/Source/Text/SvgGlyph.cs new file mode 100644 index 0000000000000000000000000000000000000000..bd1045b7b1b27911c3424c16e46424db665ad1c8 --- /dev/null +++ b/Source/Text/SvgGlyph.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Svg.Pathing; +using System.Drawing.Drawing2D; + +namespace Svg +{ + [SvgElement("glyph")] + public class SvgGlyph : SvgVisualElement + { + private GraphicsPath _path; + + /// + /// Gets or sets a of path data. + /// + [SvgAttribute("d")] + public SvgPathSegmentList PathData + { + get { return this.Attributes.GetAttribute("d"); } + set { this.Attributes["d"] = value; } + } + + [SvgAttribute("glyph-name")] + public virtual string GlyphName + { + get { return this.Attributes["glyph-name"] as string; } + set { this.Attributes["glyph-name"] = value; } + } + [SvgAttribute("horiz-adv-x")] + public float HorizAdvX + { + get { return (this.Attributes["horiz-adv-x"] == null ? this.Parents.OfType().First().HorizAdvX : (float)this.Attributes["horiz-adv-x"]); } + set { this.Attributes["horiz-adv-x"] = value; } + } + [SvgAttribute("unicode")] + public string Unicode + { + get { return this.Attributes["unicode"] as string; } + set { this.Attributes["unicode"] = value; } + } + [SvgAttribute("vert-adv-y")] + public float VertAdvY + { + get { return (this.Attributes["vert-adv-y"] == null ? this.Parents.OfType().First().VertAdvY : (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.Parents.OfType().First().VertOriginX : (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.Parents.OfType().First().VertOriginY : (float)this.Attributes["vert-origin-y"]); } + set { this.Attributes["vert-origin-y"] = value; } + } + + + /// + /// Gets the for this element. + /// + public override GraphicsPath Path(ISvgRenderer renderer) + { + if (this._path == null || this.IsPathDirty) + { + _path = new GraphicsPath(); + + foreach (SvgPathSegment segment in this.PathData) + { + segment.AddToPath(_path); + } + + this.IsPathDirty = false; + } + return _path; + } + + + /// + /// Gets or sets a value to determine if anti-aliasing should occur when the element is being rendered. + /// + protected override bool RequiresSmoothRendering + { + get { return true; } + } + + /// + /// Gets the bounds of the element. + /// + /// The bounds. + public override System.Drawing.RectangleF Bounds + { + get { return this.Path(null).GetBounds(); } + } + + /// + /// Initializes a new instance of the class. + /// + public SvgGlyph() + { + var pathData = new SvgPathSegmentList(); + this.Attributes["d"] = pathData; + } + + public override SvgElement DeepCopy() + { + return DeepCopy(); + } + + public override SvgElement DeepCopy() + { + var newObj = base.DeepCopy() as SvgGlyph; + foreach (var pathData in this.PathData) + newObj.PathData.Add(pathData.Clone()); + return newObj; + + } + } +} diff --git a/Source/Text/SvgKern.cs b/Source/Text/SvgKern.cs new file mode 100644 index 0000000000000000000000000000000000000000..bdc5bd1128a4d6c763b2178e2d644e87e46773f6 --- /dev/null +++ b/Source/Text/SvgKern.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Svg +{ + public abstract class SvgKern : SvgElement + { + [SvgAttribute("g1")] + public string Glyph1 + { + get { return this.Attributes["g1"] as string; } + set { this.Attributes["g1"] = value; } + } + [SvgAttribute("g2")] + public string Glyph2 + { + get { return this.Attributes["g2"] as string; } + set { this.Attributes["g2"] = value; } + } + [SvgAttribute("u1")] + public string Unicode1 + { + get { return this.Attributes["u1"] as string; } + set { this.Attributes["u1"] = value; } + } + [SvgAttribute("u2")] + public string Unicode2 + { + get { return this.Attributes["u2"] as string; } + set { this.Attributes["u2"] = value; } + } + [SvgAttribute("k")] + public float Kerning + { + get { return (this.Attributes["k"] == null ? 0 : (float)this.Attributes["k"]); } + set { this.Attributes["k"] = value; } + } + } + + [SvgElement("vkern")] + public class SvgVerticalKern : SvgKern + { + public override SvgElement DeepCopy() + { + return base.DeepCopy(); + } + } + [SvgElement("hkern")] + public class SvgHorizontalKern : SvgKern + { + public override SvgElement DeepCopy() + { + return base.DeepCopy(); + } + } +} diff --git a/Source/Text/SvgMissingGlyph.cs b/Source/Text/SvgMissingGlyph.cs new file mode 100644 index 0000000000000000000000000000000000000000..22173acf58628ecc544e167e8167cecf8ea33d19 --- /dev/null +++ b/Source/Text/SvgMissingGlyph.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Svg +{ + [SvgElement("missing-glyph")] + public class SvgMissingGlyph : SvgGlyph + { + [SvgAttribute("glyph-name")] + public override string GlyphName + { + get { return this.Attributes["glyph-name"] as string ?? "__MISSING_GLYPH__"; } + set { this.Attributes["glyph-name"] = value; } + } + } +} diff --git a/Source/Text/SvgTextBase.cs b/Source/Text/SvgTextBase.cs index 8729f8ed35e036ba2f7c2e5bdaaf9a3e75076459..7e90db6e184b18653b893ef62f99e27d46c52337 100644 --- a/Source/Text/SvgTextBase.cs +++ b/Source/Text/SvgTextBase.cs @@ -11,34 +11,15 @@ using System.Linq; namespace Svg { - public enum XmlSpaceHandling - { - @default, - preserve - } - public abstract class SvgTextBase : SvgVisualElement { - private SvgUnitCollection _x = new SvgUnitCollection(); - private SvgUnitCollection _y = new SvgUnitCollection(); - private SvgUnitCollection _dy = new SvgUnitCollection(); - private SvgUnitCollection _dx = new SvgUnitCollection(); - private SvgUnit _letterSpacing; - private SvgUnit _wordSpacing; - private static readonly SvgRenderer _stringMeasure; + protected SvgUnitCollection _x = new SvgUnitCollection(); + protected SvgUnitCollection _y = new SvgUnitCollection(); + protected SvgUnitCollection _dy = new SvgUnitCollection(); + protected SvgUnitCollection _dx = new SvgUnitCollection(); + private string _rotate; + private List _rotations = new List(); - private XmlSpaceHandling _space = XmlSpaceHandling.@default; - - /// - /// Initializes the class. - /// - static SvgTextBase() - { - Bitmap bitmap = new Bitmap(1, 1); - _stringMeasure = SvgRenderer.FromImage(bitmap); - _stringMeasure.TextRenderingHint = TextRenderingHint.AntiAlias; - } - /// /// Gets or sets the text to be rendered. /// @@ -66,6 +47,12 @@ namespace Svg set { this.Attributes["baseline-shift"] = value; this.IsPathDirty = true; } } + public override XmlSpaceHandling SpaceHandling + { + get { return base.SpaceHandling; } + set { base.SpaceHandling = value; this.IsPathDirty = true; } + } + /// /// Gets or sets the X. /// @@ -142,14 +129,56 @@ namespace Svg } } + /// + /// Gets or sets the rotate. + /// + /// The rotate. + [SvgAttribute("rotate")] + public virtual string Rotate + { + get { return this._rotate; } + set + { + if (_rotate != value) + { + this._rotate = value; + this._rotations.Clear(); + this._rotations.AddRange(from r in _rotate.Split(new char[] {',', ' ', '\r', '\n', '\t'}, StringSplitOptions.RemoveEmptyEntries) select float.Parse(r)); + this.IsPathDirty = true; + OnAttributeChanged(new AttributeEventArgs { Attribute = "rotate", Value = value }); + } + } + } + + /// + /// The pre-calculated length of the text + /// + [SvgAttribute("textLength")] + public virtual SvgUnit TextLength + { + get { return (this.Attributes["textLength"] == null ? SvgUnit.None : (SvgUnit)this.Attributes["textLength"]); } + set { this.Attributes["textLength"] = value; this.IsPathDirty = true; } + } + + /// + /// Gets or sets the text anchor. + /// + /// The text anchor. + [SvgAttribute("lengthAdjust")] + public virtual SvgTextLengthAdjust LengthAdjust + { + get { return (this.Attributes["lengthAdjust"] == null) ? SvgTextLengthAdjust.spacing : (SvgTextLengthAdjust)this.Attributes["lengthAdjust"]; } + set { this.Attributes["lengthAdjust"] = value; this.IsPathDirty = true; } + } + /// /// Specifies spacing behavior between text characters. /// [SvgAttribute("letter-spacing")] public virtual SvgUnit LetterSpacing { - get { return this._letterSpacing; } - set { this._letterSpacing = value; this.IsPathDirty = true; } + get { return (this.Attributes["letter-spacing"] == null ? SvgUnit.None : (SvgUnit)this.Attributes["letter-spacing"]); } + set { this.Attributes["letter-spacing"] = value; this.IsPathDirty = true; } } /// @@ -158,8 +187,8 @@ namespace Svg [SvgAttribute("word-spacing")] public virtual SvgUnit WordSpacing { - get { return this._wordSpacing; } - set { this._wordSpacing = value; this.IsPathDirty = true; } + get { return (this.Attributes["word-spacing"] == null ? SvgUnit.None : (SvgUnit)this.Attributes["word-spacing"]); } + set { this.Attributes["word-spacing"] = value; this.IsPathDirty = true; } } /// @@ -215,9 +244,9 @@ namespace Svg /// /// Renders the and contents to the specified object. /// - /// The object to render to. + /// The object to render to. /// Necessary to make sure that any internal tspan elements get rendered as well - protected override void Render(SvgRenderer renderer) + protected override void Render(ISvgRenderer renderer) { if ((this.Path(renderer) != null) && this.Visible && this.Displayable) { @@ -245,192 +274,135 @@ namespace Svg } } - private GraphicsPath _path; - - protected class NodeBounds + internal virtual IEnumerable GetContentNodes() { - public float xOffset { get; set; } - public SizeF Bounds { get; set; } - public ISvgNode Node { get; set; } + return (this.Nodes == null || this.Nodes.Count < 1 ? this.Children.OfType() : this.Nodes); } - protected class BoundsData + protected virtual GraphicsPath GetBaselinePath(ISvgRenderer renderer) { - private List _nodes = new List(); - public IList Nodes - { - get { return _nodes; } - } - public SizeF Bounds { get; set; } + return null; } - protected BoundsData GetTextBounds(SvgRenderer renderer) + protected virtual float GetAuthorPathLength() { - var font = GetFont(renderer); - SvgTextBase innerText; - SizeF stringBounds; - float totalHeight = 0; - float totalWidth = 0; - - var result = new BoundsData(); - var nodes = (from n in this.Nodes - where (n is SvgContentNode || n is SvgTextBase) && !string.IsNullOrEmpty(n.Content) - select n).ToList(); - - // Individual character spacing - if (nodes.FirstOrDefault() is SvgContentNode && _x.Count > 1) - { - string ch; - var content = nodes.First() as SvgContentNode; - nodes.RemoveAt(0); - int posCount = Math.Min(content.Content.Length, _x.Count); - var text = PrepareText(content.Content, false, (nodes.Count > 1 && nodes[1] is SvgTextBase)); - - for (var i = 0; i < posCount; i++) - { - ch = (i == posCount - 1 ? text.Substring(i) : text.Substring(i, 1)); - stringBounds = _stringMeasure.MeasureString(ch, font); - totalHeight = Math.Max(totalHeight, stringBounds.Height); - result.Nodes.Add(new NodeBounds() - { - Bounds = stringBounds, - Node = new SvgContentNode() { Content = ch }, - xOffset = (i == 0 ? 0 : _x[i].ToDeviceValue(renderer, UnitRenderingType.Horizontal, this) - - _x[0].ToDeviceValue(renderer, UnitRenderingType.Horizontal, this)) - }); - } - } - - // Calculate the bounds of the text - ISvgNode node; - var accumulateDims = true; - for (var i = 0; i < nodes.Count; i++) - { - node = nodes[i]; - lock (_stringMeasure) - { - innerText = node as SvgTextBase; - if (innerText == null) - { - stringBounds = _stringMeasure.MeasureString(PrepareText(node.Content, - i > 0 && nodes[i - 1] is SvgTextBase, - i < nodes.Count - 1 && nodes[i + 1] is SvgTextBase), font); - result.Nodes.Add(new NodeBounds() { Bounds = stringBounds, Node = node, xOffset = totalWidth }); - } - else - { - stringBounds = innerText.GetTextBounds(renderer).Bounds; - result.Nodes.Add(new NodeBounds() { Bounds = stringBounds, Node = node, xOffset = totalWidth }); - accumulateDims = accumulateDims && SvgUnitCollection.IsNullOrEmpty(innerText.X) && SvgUnitCollection.IsNullOrEmpty(innerText.Y); - if (accumulateDims && innerText.Dx.Count == 1) totalWidth += innerText.Dx[0].ToDeviceValue(renderer, UnitRenderingType.Horizontal, this); - } - - if (accumulateDims) - { - totalHeight = Math.Max(totalHeight, stringBounds.Height); - totalWidth += stringBounds.Width; - } - } - } - result.Bounds = new SizeF(totalWidth, totalHeight); - return result; + return 0; } - protected float _calcX = 0; - protected float _calcY = 0; + private GraphicsPath _path; /// /// Gets the for this element. /// /// - public override System.Drawing.Drawing2D.GraphicsPath Path(SvgRenderer renderer) + public override System.Drawing.Drawing2D.GraphicsPath Path(ISvgRenderer renderer) { // Make sure the path is always null if there is no text //if there is a TSpan inside of this text element then path should not be null (even if this text is empty!) - if ((string.IsNullOrEmpty(this.Text) || this.Text.Trim().Length < 1) && this.Children.Where(x => x is SvgTextSpan).Select(x => x as SvgTextSpan).Count() == 0) - return _path = null; - //NOT SURE WHAT THIS IS ABOUT - Path gets created again anyway - WTF? - // When an empty string is passed to GraphicsPath, it rises an InvalidArgumentException... - + var nodes = this.GetContentNodes().ToList(); + if (nodes.Count < 1) return _path = null; + if (nodes.Count == 1 && nodes[0] is SvgContentNode && + (string.IsNullOrEmpty(nodes[0].Content) || nodes[0].Content.Trim().Length < 1)) return _path = null; + if (_path == null || this.IsPathDirty) { renderer = (renderer ?? SvgRenderer.FromNull()); - // Measure the overall bounds of all the text - var boundsData = GetTextBounds(renderer); - - var font = GetFont(renderer); - SvgTextBase innerText; - float x = (_x.Count < 1 ? _calcX : _x[0].ToDeviceValue(renderer, UnitRenderingType.HorizontalOffset, this)) + - (_dx.Count < 1 ? 0 : _dx[0].ToDeviceValue(renderer, UnitRenderingType.Horizontal, this)); - float y = (_y.Count < 1 ? _calcY : _y[0].ToDeviceValue(renderer, UnitRenderingType.VerticalOffset, this)) + - (_dy.Count < 1 ? 0 : _dy[0].ToDeviceValue(renderer, UnitRenderingType.Vertical, this)); + this.SetPath(new TextDrawingState(renderer, this)); + } + return _path; + } - _path = new GraphicsPath(); - _path.StartFigure(); + private void SetPath(TextDrawingState state) + { + SetPath(state, true); + } - // Determine the location of the start point - switch (this.TextAnchor) + /// + /// Sets the path on this element and all child elements. Uses the state + /// object to track the state of the drawing + /// + /// State of the drawing operation + private void SetPath(TextDrawingState state, bool doMeasurements) + { + SvgTextBase inner; + TextDrawingState newState; + TextDrawingState origState = null; + bool alignOnBaseline = state.BaselinePath != null && (this.TextAnchor == SvgTextAnchor.Middle || this.TextAnchor == SvgTextAnchor.End); + if (doMeasurements) + { + if (this.TextLength != SvgUnit.None) + { + origState = state.Clone(); + } + else if (alignOnBaseline) { - case SvgTextAnchor.Middle: - x -= (boundsData.Bounds.Width / 2); - break; - case SvgTextAnchor.End: - x -= boundsData.Bounds.Width; - break; + origState = state.Clone(); + state.BaselinePath = null; } + } - try + foreach (var node in GetContentNodes()) + { + inner = node as SvgTextBase; + if (inner == null) { - renderer.Boundable(new FontBoundable(font)); - switch (this.BaselineShift) - { - case null: - case "": - case "baseline": - case "inherit": - // do nothing - break; - case "sub": - y += new SvgUnit(SvgUnitType.Ex, 1).ToDeviceValue(renderer, UnitRenderingType.Vertical, this); - break; - case "super": - y -= new SvgUnit(SvgUnitType.Ex, 1).ToDeviceValue(renderer, UnitRenderingType.Vertical, this); - break; - default: - var convert = new SvgUnitConverter(); - var shift = (SvgUnit)convert.ConvertFromInvariantString(this.BaselineShift); - y -= shift.ToDeviceValue(renderer, UnitRenderingType.Vertical, this); - break; - } + if (!string.IsNullOrEmpty(node.Content)) state.DrawString(PrepareText(node.Content)); } - finally + else { - renderer.PopBoundable(); + newState = new TextDrawingState(state, inner); + inner.SetPath(newState); + state.NumChars += newState.NumChars; + state.Current = newState.Current; } + } + + var path = state.GetPath() ?? new GraphicsPath(); - NodeBounds data; - var yCummOffset = 0.0f; - for (var i = 0; i < boundsData.Nodes.Count; i++) + // Apply any text length adjustments + if (doMeasurements) + { + if (this.TextLength != SvgUnit.None) + { + var bounds = path.GetBounds(); + var specLength = this.TextLength.ToDeviceValue(state.Renderer, UnitRenderingType.Horizontal, this); + var actLength = bounds.Width; + var diff = (actLength - specLength); + if (Math.Abs(diff) > 1.5) + { + if (this.LengthAdjust == SvgTextLengthAdjust.spacing) + { + origState.LetterSpacingAdjust = -1 * diff / (state.NumChars - origState.NumChars - 1); + SetPath(origState, false); + return; + } + else + { + var matrix = new Matrix(); + matrix.Translate(-1 * bounds.X, 0, MatrixOrder.Append); + matrix.Scale(specLength / actLength, 1, MatrixOrder.Append); + matrix.Translate(bounds.X, 0, MatrixOrder.Append); + path.Transform(matrix); + } + } + } + else if (alignOnBaseline) { - data = boundsData.Nodes[i]; - innerText = data.Node as SvgTextBase; - if (innerText == null) + var bounds = path.GetBounds(); + if (this.TextAnchor == SvgTextAnchor.Middle) { - // Minus FontSize because the x/y coords mark the bottom left, not bottom top. - DrawString(renderer, _path, x + data.xOffset, y - boundsData.Bounds.Height, font, - PrepareText(data.Node.Content, i > 0 && boundsData.Nodes[i - 1].Node is SvgTextBase, - i < boundsData.Nodes.Count - 1 && boundsData.Nodes[i + 1].Node is SvgTextBase)); + origState.StartOffsetAdjust = -1 * bounds.Width / 2; } else { - innerText._calcX = x + data.xOffset; - innerText._calcY = y + yCummOffset; - if (innerText.Dy.Count == 1) yCummOffset += innerText.Dy[0].ToDeviceValue(renderer, UnitRenderingType.Vertical, this); + origState.StartOffsetAdjust = -1 * bounds.Width; } + SetPath(origState, false); + return; } - - _path.CloseFigure(); - this.IsPathDirty = false; } - return _path; + + + _path = path; + this.IsPathDirty = false; } private static readonly Regex MultipleSpaces = new Regex(@" {2,}", RegexOptions.Compiled); @@ -440,67 +412,19 @@ namespace Svg /// /// Text to be prepared /// Prepared text - protected string PrepareText(string value, bool leadingSpace, bool trailingSpace) + protected string PrepareText(string value) { - if (_space == XmlSpaceHandling.preserve) + if (this.SpaceHandling == XmlSpaceHandling.preserve) { return value.Replace('\t', ' ').Replace("\r\n", " ").Replace('\r', ' ').Replace('\n', ' '); } else { var convValue = MultipleSpaces.Replace(value.Replace("\r", "").Replace("\n", "").Replace('\t', ' '), " "); - //if (!leadingSpace) convValue = convValue.TrimStart(); - //if (!trailingSpace) convValue = convValue.TrimEnd(); return convValue; } } - /// - /// Draws a string on a path at a specified location and with a specified font. - /// - internal void DrawString(SvgRenderer renderer, GraphicsPath path, float x, float y, Font font, string text) - { - PointF location = new PointF(x, y); - - // No way to do letter-spacing or word-spacing, so do manually - if (this.LetterSpacing.Value > 0.0f || this.WordSpacing.Value > 0.0f) - { - // Cut up into words, or just leave as required - string[] words = (this.WordSpacing.Value > 0.0f) ? text.Split(' ') : new string[] { text }; - float wordSpacing = this.WordSpacing.ToDeviceValue(renderer, UnitRenderingType.Horizontal, this); - float letterSpacing = this.LetterSpacing.ToDeviceValue(renderer, UnitRenderingType.Horizontal, this); - float start = x; - - foreach (string word in words) - { - // Only do if there is line spacing, just write the word otherwise - if (this.LetterSpacing.Value > 0.0f) - { - char[] characters = word.ToCharArray(); - foreach (char currentCharacter in characters) - { - path.AddString(currentCharacter.ToString(), font.FontFamily, (int)font.Style, font.Size, location, StringFormat.GenericTypographic); - location = new PointF(path.GetBounds().Width + start + letterSpacing, location.Y); - } - } - else - { - path.AddString(word, font.FontFamily, (int)font.Style, font.Size, location, StringFormat.GenericTypographic); - } - - // Move the location of the word to be written along - location = new PointF(path.GetBounds().Width + start + wordSpacing, location.Y); - } - } - else - { - if (!string.IsNullOrEmpty(text)) - { - path.AddString(text, font.FontFamily, (int)font.Style, font.Size, location, StringFormat.GenericTypographic); - } - } - } - [SvgAttribute("onchange")] public event EventHandler Change; @@ -519,6 +443,23 @@ namespace Svg } } + + + //private static GraphicsPath GetPath(string text, Font font) + //{ + // var fontMetrics = (from c in text.Distinct() + // select new { Char = c, Metrics = Metrics(c, font) }). + // ToDictionary(c => c.Char, c=> c.Metrics); + // // Measure each character and check the metrics against the overall metrics of rendering + // // an entire word with kerning. + //} + //private static RectangleF Metrics(char c, Font font) + //{ + // var path = new GraphicsPath(); + // path.AddString(c.ToString(), font.FontFamily, (int)font.Style, font.Size, new Point(0, 0), StringFormat.GenericTypographic); + // return path.GetBounds(); + //} + #if Net4 public override void RegisterEvents(ISvgEventCaller caller) { @@ -542,11 +483,17 @@ namespace Svg private class FontBoundable : ISvgBoundable { - private Font _font; + private IFontDefn _font; + private float _width = 1; - public FontBoundable(Font font) + public FontBoundable(IFontDefn font) + { + _font = font; + } + public FontBoundable(IFontDefn font, float width) { _font = font; + _width = width; } public PointF Location @@ -556,7 +503,7 @@ namespace Svg public SizeF Size { - get { return new SizeF(1, _font.Size); } + get { return new SizeF(_width, _font.Size); } } public RectangleF Bounds @@ -564,5 +511,402 @@ namespace Svg get { return new RectangleF(this.Location, this.Size); } } } + + private class TextDrawingState + { + private float _xAnchor = float.MinValue; + private IList _anchoredPaths = new List(); + private GraphicsPath _currPath = null; + private GraphicsPath _finalPath = null; + private float _authorPathLength = 0; + + public GraphicsPath BaselinePath { get; set; } + public PointF Current { get; set; } + public SvgTextBase Element { get; set; } + public float LetterSpacingAdjust { get; set; } + public int NumChars { get; set; } + public TextDrawingState Parent { get; set; } + public ISvgRenderer Renderer { get; set; } + public float StartOffsetAdjust { get; set; } + + private TextDrawingState() { } + public TextDrawingState(ISvgRenderer renderer, SvgTextBase element) + { + this.Element = element; + this.Renderer = renderer; + this.Current = PointF.Empty; + _xAnchor = 0; + this.BaselinePath = element.GetBaselinePath(renderer); + _authorPathLength = element.GetAuthorPathLength(); + } + public TextDrawingState(TextDrawingState parent, SvgTextBase element) + { + this.Element = element; + this.Renderer = parent.Renderer; + this.Parent = parent; + this.Current = parent.Current; + this.BaselinePath = element.GetBaselinePath(parent.Renderer) ?? parent.BaselinePath; + var currPathLength = element.GetAuthorPathLength(); + _authorPathLength = currPathLength == 0 ? parent._authorPathLength : currPathLength; + } + + public GraphicsPath GetPath() + { + FlushPath(); + return _finalPath; + } + + public TextDrawingState Clone() + { + var result = new TextDrawingState(); + result._anchoredPaths = this._anchoredPaths.ToList(); + result.BaselinePath = this.BaselinePath; + result._xAnchor = this._xAnchor; + result.Current = this.Current; + result.Element = this.Element; + result.NumChars = this.NumChars; + result.Parent = this.Parent; + result.Renderer = this.Renderer; + return result; + } + + public void DrawString(string value) + { + // Get any defined anchors + var xAnchors = GetValues(value.Length, e => e._x, UnitRenderingType.HorizontalOffset); + var yAnchors = GetValues(value.Length, e => e._y, UnitRenderingType.VerticalOffset); + var font = this.Element.GetFont(this.Renderer); + var fontBaselineHeight = this.Renderer.FontBaselineOffset(font); + PathStatistics pathStats = null; + var pathScale = 1.0; + if (BaselinePath != null) + { + pathStats = new PathStatistics(BaselinePath.PathData); + if (_authorPathLength > 0) pathScale = _authorPathLength / pathStats.TotalLength; + } + + // Get all of the offsets (explicit and defined by spacing) + IList xOffsets; + IList yOffsets; + IList rotations; + float baselineShift = 0.0f; + + try + { + this.Renderer.SetBoundable(new FontBoundable(font, (float)(pathStats == null ? 1 : pathStats.TotalLength))); + xOffsets = GetValues(value.Length, e => e._dx, UnitRenderingType.Horizontal); + yOffsets = GetValues(value.Length, e => e._dy, UnitRenderingType.Vertical); + if (StartOffsetAdjust != 0.0f) + { + if (xOffsets.Count < 1) + { + xOffsets.Add(StartOffsetAdjust); + } + else + { + xOffsets[0] += StartOffsetAdjust; + } + } + + if (this.Element.LetterSpacing.Value != 0.0f || this.Element.WordSpacing.Value != 0.0f || this.LetterSpacingAdjust != 0.0f) + { + var spacing = this.Element.LetterSpacing.ToDeviceValue(this.Renderer, UnitRenderingType.Horizontal, this.Element) + this.LetterSpacingAdjust; + var wordSpacing = this.Element.WordSpacing.ToDeviceValue(this.Renderer, UnitRenderingType.Horizontal, this.Element); + if (this.Parent == null && this.NumChars == 0 && xOffsets.Count < 1) xOffsets.Add(0); + for (int i = (this.Parent == null && this.NumChars == 0 ? 1 : 0); i < value.Length; i++) + { + if (i >= xOffsets.Count) + { + xOffsets.Add(spacing + (char.IsWhiteSpace(value[i]) ? wordSpacing : 0)); + } + else + { + xOffsets[i] += spacing + (char.IsWhiteSpace(value[i]) ? wordSpacing : 0); + } + } + } + + rotations = GetValues(value.Length, e => e._rotations); + + // Calculate Y-offset due to baseline shift. Don't inherit the value so that it is not accumulated multiple times. + var baselineShiftText = this.Element.Attributes.GetAttribute("baseline-shift"); + + switch (baselineShiftText) + { + case null: + case "": + case "baseline": + case "inherit": + // do nothing + break; + case "sub": + baselineShift = new SvgUnit(SvgUnitType.Ex, 1).ToDeviceValue(this.Renderer, UnitRenderingType.Vertical, this.Element); + break; + case "super": + baselineShift = -1 * new SvgUnit(SvgUnitType.Ex, 1).ToDeviceValue(this.Renderer, UnitRenderingType.Vertical, this.Element); + break; + default: + var convert = new SvgUnitConverter(); + var shiftUnit = (SvgUnit)convert.ConvertFromInvariantString(baselineShiftText); + baselineShift = -1 * shiftUnit.ToDeviceValue(this.Renderer, UnitRenderingType.Vertical, this.Element); + break; + } + + if (baselineShift != 0.0f) + { + if (yOffsets.Any()) + { + yOffsets[0] += baselineShift; + } + else + { + yOffsets.Add(baselineShift); + } + } + } + finally + { + this.Renderer.PopBoundable(); + } + + // NOTE: Assuming a horizontal left-to-right font + // Render absolutely positioned items in the horizontal direction + var yPos = Current.Y; + for (int i = 0; i < xAnchors.Count - 1; i++) + { + FlushPath(); + _xAnchor = xAnchors[i] + (xOffsets.Count > i ? xOffsets[i] : 0); + EnsurePath(); + yPos = (yAnchors.Count > i ? yAnchors[i] : yPos) + (yOffsets.Count > i ? yOffsets[i] : 0); + + DrawStringOnCurrPath(value[i].ToString(), font, new PointF(_xAnchor, yPos), + fontBaselineHeight, (rotations.Count > i ? rotations[i] : rotations.LastOrDefault())); + } + + // Render any remaining characters + var renderChar = 0; + var xPos = this.Current.X; + if (xAnchors.Any()) + { + FlushPath(); + renderChar = xAnchors.Count - 1; + xPos = xAnchors.Last(); + _xAnchor = xPos; + } + EnsurePath(); + + + // Render individual characters as necessary + var lastIndividualChar = renderChar + Math.Max(Math.Max(Math.Max(Math.Max(xOffsets.Count, yOffsets.Count), yAnchors.Count), rotations.Count) - renderChar - 1, 0); + if (rotations.LastOrDefault() != 0.0f || pathStats != null) lastIndividualChar = value.Length; + if (lastIndividualChar > renderChar) + { + var charBounds = this.Renderer.MeasureCharacters(value.Substring(renderChar, Math.Min(lastIndividualChar + 1, value.Length) - renderChar), font); + PointF pathPoint; + float rotation; + float halfWidth; + for (int i = renderChar; i < lastIndividualChar; i++) + { + xPos += (float)pathScale * (xOffsets.Count > i ? xOffsets[i] : 0) + (charBounds[i - renderChar].X - (i == renderChar ? 0 : charBounds[i - renderChar - 1].X)); + yPos = (yAnchors.Count > i ? yAnchors[i] : yPos) + (yOffsets.Count > i ? yOffsets[i] : 0); + if (pathStats == null) + { + DrawStringOnCurrPath(value[i].ToString(), font, new PointF(xPos, yPos), + fontBaselineHeight, (rotations.Count > i ? rotations[i] : rotations.LastOrDefault())); + } + else + { + xPos = Math.Max(xPos, 0); + halfWidth = charBounds[i-renderChar].Width / 2; + if (pathStats.OffsetOnPath(xPos + halfWidth)) + { + pathStats.LocationAngleAtOffset(xPos + halfWidth, out pathPoint, out rotation); + pathPoint = new PointF((float)(pathPoint.X - halfWidth * Math.Cos(rotation * Math.PI / 180) - (float)pathScale * yPos * Math.Sin(rotation * Math.PI / 180)), + (float)(pathPoint.Y - halfWidth * Math.Sin(rotation * Math.PI / 180) + (float)pathScale * yPos * Math.Cos(rotation * Math.PI / 180))); + DrawStringOnCurrPath(value[i].ToString(), font, pathPoint, fontBaselineHeight, rotation); + } + } + } + + // Add the kerning to the next character + if (lastIndividualChar < value.Length) + { + xPos += charBounds[charBounds.Count - 1].X - charBounds[charBounds.Count - 2].X; + } + else + { + xPos += charBounds.Last().Width; + } + } + + // Render the string normally + if (lastIndividualChar < value.Length) + { + xPos += (xOffsets.Count > lastIndividualChar ? xOffsets[lastIndividualChar] : 0); + yPos = (yAnchors.Count > lastIndividualChar ? yAnchors[lastIndividualChar] : yPos) + + (yOffsets.Count > lastIndividualChar ? yOffsets[lastIndividualChar] : 0); + DrawStringOnCurrPath(value.Substring(lastIndividualChar), font, new PointF(xPos, yPos), + fontBaselineHeight, rotations.LastOrDefault()); + var bounds = this.Renderer.MeasureString(value.Substring(lastIndividualChar), font); + xPos += bounds.Width; + } + + + NumChars += value.Length; + // Undo any baseline shift. This is not persisted, unlike normal vertical offsets. + this.Current = new PointF(xPos, yPos - baselineShift); + } + + private void DrawStringOnCurrPath(string value, IFontDefn font, PointF location, float fontBaselineHeight, float rotation) + { + var drawPath = _currPath; + if (rotation != 0.0f) drawPath = new GraphicsPath(); + font.AddStringToPath(this.Renderer, drawPath, value, new PointF(location.X, location.Y - fontBaselineHeight)); + if (rotation != 0.0f && drawPath.PointCount > 0) + { + var matrix = new Matrix(); + matrix.Translate(-1 * location.X, -1 * location.Y, MatrixOrder.Append); + matrix.Rotate(rotation, MatrixOrder.Append); + matrix.Translate(location.X, location.Y, MatrixOrder.Append); + drawPath.Transform(matrix); + _currPath.AddPath(drawPath, false); + } + + } + + private void EnsurePath() + { + if (_currPath == null) + { + _currPath = new GraphicsPath(); + _currPath.StartFigure(); + + var currState = this; + while (currState != null && currState._xAnchor <= float.MinValue) + { + currState = currState.Parent; + } + currState._anchoredPaths.Add(_currPath); + } + } + + private void FlushPath() + { + if (_currPath != null) + { + _currPath.CloseFigure(); + + // Abort on empty paths (e.g. rendering a space) + if (_currPath.PointCount < 1) + { + _anchoredPaths.Clear(); + _xAnchor = float.MinValue; + _currPath = null; + return; + } + + if (_xAnchor > float.MinValue) + { + float minX = float.MaxValue; + float maxX = float.MinValue; + RectangleF bounds; + foreach (var path in _anchoredPaths) + { + bounds = path.GetBounds(); + if (bounds.Left < minX) minX = bounds.Left; + if (bounds.Right > maxX) maxX = bounds.Right; + } + + var xOffset = _xAnchor - minX; + switch (Element.TextAnchor) + { + case SvgTextAnchor.Middle: + xOffset -= (maxX - minX) / 2; + break; + case SvgTextAnchor.End: + xOffset -= (maxX - minX); + break; + } + + if (xOffset != 0) + { + var matrix = new Matrix(); + matrix.Translate(xOffset, 0); + foreach (var path in _anchoredPaths) + { + path.Transform(matrix); + } + } + + _anchoredPaths.Clear(); + _xAnchor = float.MinValue; + + } + + if (_finalPath == null) + { + _finalPath = _currPath; + } + else + { + _finalPath.AddPath(_currPath, false); + } + + _currPath = null; + } + } + + private IList GetValues(int maxCount, Func> listGetter) + { + var currState = this; + int charCount = 0; + var results = new List(); + int resultCount = 0; + + while (currState != null) + { + charCount += currState.NumChars; + results.AddRange(listGetter.Invoke(currState.Element).Skip(charCount).Take(maxCount)); + if (results.Count > resultCount) + { + maxCount -= results.Count - resultCount; + charCount += results.Count - resultCount; + resultCount = results.Count; + } + + if (maxCount < 1) return results; + + currState = currState.Parent; + } + + return results; + } + private IList GetValues(int maxCount, Func> listGetter, UnitRenderingType renderingType) + { + var currState = this; + int charCount = 0; + var results = new List(); + int resultCount = 0; + + while (currState != null) + { + charCount += currState.NumChars; + results.AddRange(listGetter.Invoke(currState.Element).Skip(charCount).Take(maxCount).Select(p => p.ToDeviceValue(currState.Renderer, renderingType, currState.Element))); + if (results.Count > resultCount) + { + maxCount -= results.Count - resultCount; + charCount += results.Count - resultCount; + resultCount = results.Count; + } + + if (maxCount < 1) return results; + + currState = currState.Parent; + } + + return results; + } + } + } } diff --git a/Source/Text/SvgTextPath.cs b/Source/Text/SvgTextPath.cs new file mode 100644 index 0000000000000000000000000000000000000000..e80b866a3403c8a705c7a8226f7a4b07f1eaa0e0 --- /dev/null +++ b/Source/Text/SvgTextPath.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Diagnostics; + +namespace Svg +{ + /// + /// The element defines a graphics element consisting of text. + /// + [SvgElement("textPath")] + public class SvgTextPath : SvgTextBase + { + private Uri _referencedPath; + + public override SvgUnitCollection Dx + { + get { return null; } + set { /* do nothing */ } + } + + [SvgAttribute("startOffset")] + public virtual SvgUnit StartOffset + { + get { return (_dx.Count < 1 ? SvgUnit.None : _dx[0]); } + set + { + if (_dx.Count < 1) + { + _dx.Add(value); + } + else + { + _dx[0] = value; + } + } + } + + [SvgAttribute("method")] + public virtual SvgTextPathMethod Method + { + get { return (this.Attributes["method"] == null ? SvgTextPathMethod.align : (SvgTextPathMethod)this.Attributes["method"]); } + set { this.Attributes["method"] = value; } + } + + [SvgAttribute("spacing")] + public virtual SvgTextPathSpacing Spacing + { + get { return (this.Attributes["spacing"] == null ? SvgTextPathSpacing.exact : (SvgTextPathSpacing)this.Attributes["spacing"]); } + set { this.Attributes["spacing"] = value; } + } + + [SvgAttribute("href", SvgAttributeAttribute.XLinkNamespace)] + public virtual Uri ReferencedPath + { + get { return this._referencedPath; } + set { this._referencedPath = value; } + } + + protected override GraphicsPath GetBaselinePath(ISvgRenderer renderer) + { + var path = this.OwnerDocument.IdManager.GetElementById(this.ReferencedPath) as SvgVisualElement; + if (path == null) return null; + var pathData = (GraphicsPath)path.Path(renderer).Clone(); + if (path.Transforms.Count > 0) + { + Matrix transformMatrix = new Matrix(1, 0, 0, 1, 0, 0); + + foreach (var transformation in path.Transforms) + { + transformMatrix.Multiply(transformation.Matrix); + } + + pathData.Transform(transformMatrix); + } + return pathData; + } + protected override float GetAuthorPathLength() + { + var path = this.OwnerDocument.IdManager.GetElementById(this.ReferencedPath) as SvgPath; + if (path == null) return 0; + return path.PathLength; + } + + public override SvgElement DeepCopy() + { + return base.DeepCopy(); + } + + + + + } +} diff --git a/Source/Text/SvgTextRef.cs b/Source/Text/SvgTextRef.cs new file mode 100644 index 0000000000000000000000000000000000000000..45a878dba42c9f5fa7f23c144218aac4830acea7 --- /dev/null +++ b/Source/Text/SvgTextRef.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Svg +{ + [SvgElement("tref")] + public class SvgTextRef : SvgTextBase + { + private Uri _referencedElement; + + [SvgAttribute("href", SvgAttributeAttribute.XLinkNamespace)] + public virtual Uri ReferencedElement + { + get { return this._referencedElement; } + set { this._referencedElement = value; } + } + + internal override IEnumerable GetContentNodes() + { + var refText = this.OwnerDocument.IdManager.GetElementById(this.ReferencedElement) as SvgTextBase; + if (refText == null) + { + return base.GetContentNodes(); + } + else + { + return refText.GetContentNodes(); + } + } + + public override SvgElement DeepCopy() + { + return DeepCopy(); + } + + public override SvgElement DeepCopy() + { + var newObj = base.DeepCopy() as SvgTextRef; + newObj.X = this.X; + newObj.Y = this.Y; + newObj.Dx = this.Dx; + newObj.Dy = this.Dy; + newObj.Text = this.Text; + newObj.ReferencedElement = this.ReferencedElement; + + return newObj; + } + + + } +} diff --git a/Source/Transforms/ISvgTransformable.cs b/Source/Transforms/ISvgTransformable.cs index ed1a63e1952513b5d6f08be03fcf6d4f8447c082..348c049dc8c0fd11bc366ea0221525c43bf347c9 100644 --- a/Source/Transforms/ISvgTransformable.cs +++ b/Source/Transforms/ISvgTransformable.cs @@ -18,14 +18,14 @@ namespace Svg /// SvgTransformCollection Transforms { get; set; } /// - /// Applies the required transforms to . + /// Applies the required transforms to . /// - /// The to be transformed. - void PushTransforms(SvgRenderer renderer); + /// The to be transformed. + void PushTransforms(ISvgRenderer renderer); /// - /// Removes any previously applied transforms from the specified . + /// Removes any previously applied transforms from the specified . /// - /// The that should have transforms removed. - void PopTransforms(SvgRenderer renderer); + /// The that should have transforms removed. + void PopTransforms(ISvgRenderer renderer); } } \ No newline at end of file diff --git a/Tests/SvgW3CTestRunner/View.cs b/Tests/SvgW3CTestRunner/View.cs index c39a31dc7e583087f42363bb97a91e77b8b81948..c8faec4fe66951cccd436135d0783bf8a0b7b4fd 100644 --- a/Tests/SvgW3CTestRunner/View.cs +++ b/Tests/SvgW3CTestRunner/View.cs @@ -5,6 +5,7 @@ using System.Windows.Forms; using System.Drawing; using System.IO; using Svg; +using System.Diagnostics; namespace SvgW3CTestRunner { diff --git a/Tests/W3CTestSuite/png/__issue-015-01.png b/Tests/W3CTestSuite/png/__issue-015-01.png new file mode 100644 index 0000000000000000000000000000000000000000..3d8dd4a83eed5886fe2e1a8cbaa06e59115e42e5 Binary files /dev/null and b/Tests/W3CTestSuite/png/__issue-015-01.png differ diff --git a/Tests/W3CTestSuite/png/__issue-34-02.png b/Tests/W3CTestSuite/png/__issue-034-02.png similarity index 100% rename from Tests/W3CTestSuite/png/__issue-34-02.png rename to Tests/W3CTestSuite/png/__issue-034-02.png diff --git a/Tests/W3CTestSuite/png/__issue-036-01.png b/Tests/W3CTestSuite/png/__issue-036-01.png new file mode 100644 index 0000000000000000000000000000000000000000..fd2aefdbc4c98df85e9096a9dc4f59ac24d98585 Binary files /dev/null and b/Tests/W3CTestSuite/png/__issue-036-01.png differ diff --git a/Tests/W3CTestSuite/png/__issue-064-01.png b/Tests/W3CTestSuite/png/__issue-064-01.png new file mode 100644 index 0000000000000000000000000000000000000000..125aad6afdda6a85f3fb48ba7fa7a2dca57145cf Binary files /dev/null and b/Tests/W3CTestSuite/png/__issue-064-01.png differ diff --git a/Tests/W3CTestSuite/png/__issue-064-02.png b/Tests/W3CTestSuite/png/__issue-064-02.png new file mode 100644 index 0000000000000000000000000000000000000000..c014043602423404ab304c74f9c0c23519286a6d Binary files /dev/null and b/Tests/W3CTestSuite/png/__issue-064-02.png differ diff --git a/Tests/W3CTestSuite/png/__issue-83-01.png b/Tests/W3CTestSuite/png/__issue-083-01.png similarity index 100% rename from Tests/W3CTestSuite/png/__issue-83-01.png rename to Tests/W3CTestSuite/png/__issue-083-01.png diff --git a/Tests/W3CTestSuite/svg/__issue-015-01.svg b/Tests/W3CTestSuite/svg/__issue-015-01.svg new file mode 100644 index 0000000000000000000000000000000000000000..cdcceec13b76fba0e709e6eec5930b8de3c56d89 --- /dev/null +++ b/Tests/W3CTestSuite/svg/__issue-015-01.svg @@ -0,0 +1,11 @@ + + Created with Highcharts 3.0.2 + + + + + + + + + \ No newline at end of file diff --git a/Tests/W3CTestSuite/svg/__issue-34-02.svg b/Tests/W3CTestSuite/svg/__issue-034-02.svg similarity index 100% rename from Tests/W3CTestSuite/svg/__issue-34-02.svg rename to Tests/W3CTestSuite/svg/__issue-034-02.svg diff --git a/Tests/W3CTestSuite/svg/__issue-036-01.svg b/Tests/W3CTestSuite/svg/__issue-036-01.svg new file mode 100644 index 0000000000000000000000000000000000000000..cbd03734f4b4cb80859953111ec30c60f9bc4eb0 --- /dev/null +++ b/Tests/W3CTestSuite/svg/__issue-036-01.svg @@ -0,0 +1 @@ +Created with Highcharts 3.0.1DestinationCountDestination Summary8587746333129221UnknownHospital 2Clinic AAbacusHospital 3Hospital 1ABCD RegionalMed CtrHospital 501000250500750 \ No newline at end of file diff --git a/Tests/W3CTestSuite/svg/__issue-064-01.svg b/Tests/W3CTestSuite/svg/__issue-064-01.svg new file mode 100644 index 0000000000000000000000000000000000000000..a55bb613f56b68f3b3ed25029fe068656f097887 --- /dev/null +++ b/Tests/W3CTestSuite/svg/__issue-064-01.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/W3CTestSuite/svg/__issue-064-02.svg b/Tests/W3CTestSuite/svg/__issue-064-02.svg new file mode 100644 index 0000000000000000000000000000000000000000..f29eae3bc4094ae8aad39a4eb439f5717d9784fe --- /dev/null +++ b/Tests/W3CTestSuite/svg/__issue-064-02.svg @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/W3CTestSuite/svg/__issue-83-01.svg b/Tests/W3CTestSuite/svg/__issue-083-01.svg similarity index 100% rename from Tests/W3CTestSuite/svg/__issue-83-01.svg rename to Tests/W3CTestSuite/svg/__issue-083-01.svg diff --git a/Tests/W3CTestSuite/svg/text-path-02-b.svg b/Tests/W3CTestSuite/svg/text-path-02-b.svg index 3513226c26728cd966a58f147355099b7925ed11..1e00df5013a3979476049528ba76ac74d5aef6c2 100644 --- a/Tests/W3CTestSuite/svg/text-path-02-b.svg +++ b/Tests/W3CTestSuite/svg/text-path-02-b.svg @@ -63,26 +63,26 @@ - Positive offset Negative offset + Negative offset - Positive offset Negative offset + Negative offset - Positive offset Negative offset + Positive offset - Positive offset Negative offset + Positive offset