From 1a00f391ca24d95087ee1842d6f26a100c911e0d Mon Sep 17 00:00:00 2001 From: Eric Domke Date: Sun, 10 Aug 2014 10:33:48 -0400 Subject: [PATCH] Bug fixes: Gradients and Filters - Support of generic fonts (serif, sans-serif, monospace) - Initial support of filters - Fixes to gradient rendering --- Source/Basic Shapes/SvgVisualElement.cs | 58 +++-- Source/Basic Shapes/SvgVisualElementStyle.cs | 40 +++- Source/DataTypes/SvgUnitCollection.cs | 6 + Source/Document Structure/SvgGroup.cs | 34 +-- Source/Document Structure/SvgUse.cs | 2 + Source/Extensibility/SvgForeignObject.cs | 30 +-- Source/External/ExCSS/Parser.cs | 22 ++ Source/Filter Effects/ImageBuffer.cs | 215 ++++++++++++++++++ Source/Filter Effects/SvgFilter.cs | 105 +++------ Source/Filter Effects/SvgFilterPrimitive.cs | 14 +- .../feColourMatrix/SvgColourMatrix.cs | 73 +++++- .../feColourMatrix/SvgColourMatrixType.cs | 2 +- .../feGaussianBlur/SvgGaussianBlur.cs | 13 +- Source/Filter Effects/feMerge/SvgMerge.cs | 44 ++-- Source/Filter Effects/feMerge/SvgMergeNode.cs | 10 +- Source/Filter Effects/feOffset/SvgOffset.cs | 21 +- Source/Painting/SvgGradientServer.cs | 11 +- Source/Painting/SvgLinearGradientServer.cs | 93 +++++--- Source/Painting/SvgRadialGradientServer.cs | 187 +++++++++------ Source/Svg.csproj | 1 + Source/Text/SvgTextBase.cs | 14 +- Tests/SvgW3CTestRunner/View.cs | 13 +- 22 files changed, 734 insertions(+), 274 deletions(-) create mode 100644 Source/Filter Effects/ImageBuffer.cs diff --git a/Source/Basic Shapes/SvgVisualElement.cs b/Source/Basic Shapes/SvgVisualElement.cs index 96d19d4..5216942 100644 --- a/Source/Basic Shapes/SvgVisualElement.cs +++ b/Source/Basic Shapes/SvgVisualElement.cs @@ -99,34 +99,66 @@ namespace Svg this._requiresSmoothRendering = false; } + protected virtual bool Renderable { get { return true; } } + /// /// Renders the and contents to the specified object. /// /// The object to render to. protected override void Render(SvgRenderer renderer) { - if ((this.Path(renderer) != null) && this.Visible && this.Displayable) + this.Render(renderer, true); + } + + private void Render(SvgRenderer renderer, bool renderFilter) + { + if (this.Visible && this.Displayable && this.PushTransforms(renderer) && + (!Renderable || this.Path(renderer) != null)) { - this.PushTransforms(renderer); - this.SetClip(renderer); + bool renderNormal = true; - // If this element needs smoothing enabled turn anti-aliasing on - if (this.RequiresSmoothRendering) + if (renderFilter && this.Filter != null) { - renderer.SmoothingMode = SmoothingMode.AntiAlias; + var filter = this.OwnerDocument.IdManager.GetElementById(this.Filter) as FilterEffects.SvgFilter; + if (filter != null) + { + this.PopTransforms(renderer); + filter.ApplyFilter(this, renderer, (r) => this.Render(r, false)); + renderNormal = false; + } } - this.RenderFill(renderer); - this.RenderStroke(renderer); - // Reset the smoothing mode - if (this.RequiresSmoothRendering && renderer.SmoothingMode == SmoothingMode.AntiAlias) + if (renderNormal) { - renderer.SmoothingMode = SmoothingMode.Default; + this.SetClip(renderer); + + if (Renderable) + { + // If this element needs smoothing enabled turn anti-aliasing on + if (this.RequiresSmoothRendering) + { + renderer.SmoothingMode = SmoothingMode.AntiAlias; + } + + this.RenderFill(renderer); + this.RenderStroke(renderer); + + // Reset the smoothing mode + if (this.RequiresSmoothRendering && renderer.SmoothingMode == SmoothingMode.AntiAlias) + { + renderer.SmoothingMode = SmoothingMode.Default; + } + } + else + { + base.RenderChildren(renderer); + } + + this.ResetClip(renderer); + this.PopTransforms(renderer); } - this.ResetClip(renderer); - this.PopTransforms(renderer); } } diff --git a/Source/Basic Shapes/SvgVisualElementStyle.cs b/Source/Basic Shapes/SvgVisualElementStyle.cs index 1788463..6433cbb 100644 --- a/Source/Basic Shapes/SvgVisualElementStyle.cs +++ b/Source/Basic Shapes/SvgVisualElementStyle.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Drawing; using System.Text; using System.Reflection; using System.ComponentModel; @@ -53,6 +54,16 @@ namespace Svg } } + /// + /// Gets or sets the fill of this element. + /// + [SvgAttribute("enable-background")] + public virtual string EnableBackground + { + get { return this.Attributes["enable-background"] as string; } + set { this.Attributes["enable-background"] = value; } + } + /// /// Gets or sets the fill of this element. /// @@ -361,26 +372,43 @@ namespace Svg break; } + var family = ValidateFontFamily(this.FontFamily); + if (!family.IsStyleAvailable(fontStyle)) + { + // Do Something + } + // Get the font-family - string family = ValidateFontFamily(this.FontFamily) ?? DefaultFontFamily; return new System.Drawing.Font(family, fontSize, fontStyle, System.Drawing.GraphicsUnit.Pixel); } - private static string ValidateFontFamily(string fontFamilyList) + private static FontFamily ValidateFontFamily(string fontFamilyList) { // 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; // 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.Where(f => families.Any(family => family.Name.ToLower() == f.ToLower()))) + foreach (var f in fontParts) { - return f; + family = families.FirstOrDefault(ff => ff.Name.ToLower() == f.ToLower()); + if (family != null) return family; + + switch (f) + { + case "serif": + return System.Drawing.FontFamily.GenericSerif; + case "sans-serif": + return System.Drawing.FontFamily.GenericSansSerif; + case "monospace": + return System.Drawing.FontFamily.GenericMonospace; + } } + // No valid font family found from the list requested. - return null; + return System.Drawing.FontFamily.GenericSansSerif; } } diff --git a/Source/DataTypes/SvgUnitCollection.cs b/Source/DataTypes/SvgUnitCollection.cs index 284dfe0..4d831d5 100644 --- a/Source/DataTypes/SvgUnitCollection.cs +++ b/Source/DataTypes/SvgUnitCollection.cs @@ -23,6 +23,12 @@ namespace Svg return ret; } + + public static bool IsNullOrEmpty(SvgUnitCollection collection) + { + return collection == null || collection.Count < 1 || + (collection.Count == 1 && (collection[0] == SvgUnit.Empty || collection[0] == SvgUnit.None)); + } } /// diff --git a/Source/Document Structure/SvgGroup.cs b/Source/Document Structure/SvgGroup.cs index 3dfc691..f4826f8 100644 --- a/Source/Document Structure/SvgGroup.cs +++ b/Source/Document Structure/SvgGroup.cs @@ -56,23 +56,25 @@ namespace Svg } } - /// - /// Renders the and contents to the specified object. - /// - /// The object to render to. - protected override void Render(SvgRenderer renderer) - { - if (!Visible || !Displayable) - return; + protected override bool Renderable { get { return false; } } - if (this.PushTransforms(renderer)) - { - this.SetClip(renderer); - base.RenderChildren(renderer); - this.ResetClip(renderer); - this.PopTransforms(renderer); - } - } + ///// + ///// 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() diff --git a/Source/Document Structure/SvgUse.cs b/Source/Document Structure/SvgUse.cs index 147d0b9..89dbc54 100644 --- a/Source/Document Structure/SvgUse.cs +++ b/Source/Document Structure/SvgUse.cs @@ -77,6 +77,8 @@ namespace Svg // } // } + protected override bool Renderable { get { return false; } } + protected override void Render(SvgRenderer renderer) { if (!Visible || !Displayable) diff --git a/Source/Extensibility/SvgForeignObject.cs b/Source/Extensibility/SvgForeignObject.cs index 3fc4cdd..d61a954 100644 --- a/Source/Extensibility/SvgForeignObject.cs +++ b/Source/Extensibility/SvgForeignObject.cs @@ -56,21 +56,23 @@ namespace Svg } } - /// - /// Renders the and contents to the specified object. - /// - /// The object to render to. - protected override void Render(SvgRenderer renderer) - { - if (!Visible || !Displayable) - return; + protected override bool Renderable { get { return false; } } - this.PushTransforms(renderer); - this.SetClip(renderer); - base.RenderChildren(renderer); - this.ResetClip(renderer); - this.PopTransforms(renderer); - } + ///// + ///// Renders the and contents to the specified object. + ///// + ///// The object to render to. + //protected override void Render(SvgRenderer renderer) + //{ + // if (!Visible || !Displayable) + // return; + + // this.PushTransforms(renderer); + // this.SetClip(renderer); + // base.RenderChildren(renderer); + // this.ResetClip(renderer); + // this.PopTransforms(renderer); + //} public override SvgElement DeepCopy() { diff --git a/Source/External/ExCSS/Parser.cs b/Source/External/ExCSS/Parser.cs index b44db37..43b8900 100644 --- a/Source/External/ExCSS/Parser.cs +++ b/Source/External/ExCSS/Parser.cs @@ -7,6 +7,28 @@ using ExCSS.Model.TextBlocks; // ReSharper disable once CheckNamespace using System; +//The MIT License (MIT) + +//Copyright (c) [year] [fullname] + +//Permission is hereby granted, free of charge, to any person obtaining a copy +//of this software and associated documentation files (the "Software"), to deal +//in the Software without restriction, including without limitation the rights +//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//copies of the Software, and to permit persons to whom the Software is +//furnished to do so, subject to the following conditions: + +//The above copyright notice and this permission notice shall be included in all +//copies or substantial portions of the Software. + +//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//SOFTWARE. + namespace ExCSS { diff --git a/Source/Filter Effects/ImageBuffer.cs b/Source/Filter Effects/ImageBuffer.cs new file mode 100644 index 0000000..5a8ec99 --- /dev/null +++ b/Source/Filter Effects/ImageBuffer.cs @@ -0,0 +1,215 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Drawing; +using System.Drawing.Imaging; +using System.Drawing.Drawing2D; + +namespace Svg.FilterEffects +{ + public class ImageBuffer : IDictionary + { + private const string BufferKey = "__!!BUFFER"; + + private Dictionary _images; + private RectangleF _bounds; + private SvgRenderer _renderer; + private Action _renderMethod; + private float _inflate; + + public Matrix Transform { get; set; } + + public Bitmap Buffer + { + get { return _images[BufferKey]; } + } + public int Count + { + get { return _images.Count; } + } + public Bitmap this[string key] + { + get + { + return ProcessResult(key, _images[ProcessKey(key)]); + } + set + { + _images[ProcessKey(key)] = value; + if (key != null) _images[BufferKey] = value; + } + } + + public ImageBuffer(RectangleF bounds, float inflate, SvgRenderer renderer, Action renderMethod) + { + _bounds = bounds; + _inflate = inflate; + _renderer = renderer; + _renderMethod = renderMethod; + _images = new Dictionary(); + _images[SvgFilterPrimitive.BackgroundAlpha] = null; + _images[SvgFilterPrimitive.BackgroundImage] = null; + _images[SvgFilterPrimitive.FillPaint] = null; + _images[SvgFilterPrimitive.SourceAlpha] = null; + _images[SvgFilterPrimitive.SourceGraphic] = null; + _images[SvgFilterPrimitive.StrokePaint] = null; + } + + public void Add(string key, Bitmap value) + { + _images.Add(ProcessKey(key), value); + } + public bool ContainsKey(string key) + { + return _images.ContainsKey(ProcessKey(key)); + } + public void Clear() + { + _images.Clear(); + } + public IEnumerator> GetEnumerator() + { + return _images.GetEnumerator(); + } + public bool Remove(string key) + { + switch (key) + { + case SvgFilterPrimitive.BackgroundAlpha: + case SvgFilterPrimitive.BackgroundImage: + case SvgFilterPrimitive.FillPaint: + case SvgFilterPrimitive.SourceAlpha: + case SvgFilterPrimitive.SourceGraphic: + case SvgFilterPrimitive.StrokePaint: + return false; + default: + return _images.Remove(ProcessKey(key)); + } + } + public bool TryGetValue(string key, out Bitmap value) + { + if (_images.TryGetValue(ProcessKey(key), out value)) + { + value = ProcessResult(key, value); + return true; + } + else + { + return false; + } + } + + private Bitmap ProcessResult(string key, Bitmap curr) + { + if (curr == null) + { + switch (key) + { + case SvgFilterPrimitive.BackgroundAlpha: + case SvgFilterPrimitive.BackgroundImage: + case SvgFilterPrimitive.FillPaint: + case SvgFilterPrimitive.StrokePaint: + // Do nothing + return null; + case SvgFilterPrimitive.SourceAlpha: + _images[key] = CreateSourceAlpha(); + return _images[key]; + case SvgFilterPrimitive.SourceGraphic: + _images[key] = CreateSourceGraphic(); + return _images[key]; + } + } + return curr; + } + private string ProcessKey(string key) + { + if (string.IsNullOrEmpty(key)) return BufferKey; + return key; + } + + + + private Bitmap CreateSourceGraphic() + { + var graphic = new Bitmap((int)(_bounds.Width + 2 * _inflate * _bounds.Width + _bounds.X), + (int)(_bounds.Height + 2 * _inflate * _bounds.Height + _bounds.Y)); + using (var renderer = SvgRenderer.FromImage(graphic)) + { + renderer.Boundable(_renderer.Boundable()); + var transform = new Matrix(); + transform.Translate(_bounds.Width * _inflate, _bounds.Height * _inflate); + renderer.Transform = transform; + //renderer.Transform = _renderer.Transform; + //renderer.Clip = _renderer.Clip; + _renderMethod.Invoke(renderer); + } + return graphic; + } + + private Bitmap CreateSourceAlpha() + { + Bitmap source = this[SvgFilterPrimitive.SourceGraphic]; + + float[][] colorMatrixElements = { + new float[] {0, 0, 0, 0, 0}, // red + new float[] {0, 0, 0, 0, 0}, // green + new float[] {0, 0, 0, 0, 0}, // blue + new float[] {0, 0, 0, 1, 1}, // alpha + new float[] {0, 0, 0, 0, 0} }; // translations + + var matrix = new ColorMatrix(colorMatrixElements); + + ImageAttributes attributes = new ImageAttributes(); + attributes.SetColorMatrix(matrix); + + var sourceAlpha = new Bitmap(source.Width, source.Height); + + using (var graphics = Graphics.FromImage(sourceAlpha)) + { + + graphics.DrawImage(source, new Rectangle(0, 0, source.Width, source.Height), 0, 0, + source.Width, source.Height, GraphicsUnit.Pixel, attributes); + graphics.Save(); + } + + return sourceAlpha; + } + + + + bool ICollection>.IsReadOnly + { + get { return false; } + } + ICollection IDictionary.Keys + { + get { return _images.Keys; } + } + ICollection IDictionary.Values + { + get { return _images.Values; } + } + + void ICollection>.Add(KeyValuePair item) + { + _images.Add(item.Key, item.Value); + } + bool ICollection>.Contains(KeyValuePair item) + { + return ((IDictionary)_images).Contains(item); + } + void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) + { + ((IDictionary)_images).CopyTo(array, arrayIndex); + } + bool ICollection>.Remove(KeyValuePair item) + { + return _images.Remove(item.Key); + } + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return _images.GetEnumerator(); + } + } +} diff --git a/Source/Filter Effects/SvgFilter.cs b/Source/Filter Effects/SvgFilter.cs index 2ac722d..51c3ccc 100644 --- a/Source/Filter Effects/SvgFilter.cs +++ b/Source/Filter Effects/SvgFilter.cs @@ -72,15 +72,11 @@ namespace Svg.FilterEffects set { this.Attributes["color-interpolation-filters"] = value; } } - - internal Dictionary> Buffer { get; private set; } - /// /// Initializes a new instance of the class. /// public SvgFilter() { - this.Buffer = new Dictionary>(); } /// @@ -103,10 +99,32 @@ namespace Svg.FilterEffects return (SvgFilter)this.MemberwiseClone(); } - public void ApplyFilter(SvgVisualElement element, SvgRenderer renderer) + private Matrix GetTransform(SvgVisualElement element) + { + var transformMatrix = new Matrix(); + foreach (var transformation in element.Transforms) + { + transformMatrix.Multiply(transformation.Matrix); + } + return transformMatrix; + } + + private RectangleF GetPathBounds(SvgVisualElement element, SvgRenderer renderer, Matrix transform) + { + var bounds = element.Path(renderer).GetBounds(); + var pts = new PointF[] { bounds.Location, new PointF(bounds.Right, bounds.Bottom) }; + transform.TransformPoints(pts); + + return new RectangleF(Math.Min(pts[0].X, pts[1].X), Math.Min(pts[0].Y, pts[1].Y), + 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) { - this.Buffer.Clear(); - this.PopulateDefaults(element, renderer); + var inflate = 0.5f; + var transform = GetTransform(element); + var bounds = GetPathBounds(element, renderer, transform); + var buffer = new ImageBuffer(bounds, inflate, renderer, renderMethod) { Transform = transform }; IEnumerable primitives = this.Children.OfType(); @@ -114,21 +132,21 @@ namespace Svg.FilterEffects { foreach (var primitive in primitives) { - this.Buffer.Add(primitive.Result, (e, r) => primitive.Process()); + primitive.Process(buffer); } // Render the final filtered image - renderer.DrawImageUnscaled(this.Buffer.Last().Value(element, renderer), new Point(0, 0)); + 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); + 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); } } - - private void PopulateDefaults(SvgVisualElement element, SvgRenderer renderer) - { - this.ResetDefaults(); - - this.Buffer.Add(SvgFilterPrimitive.SourceGraphic, this.CreateSourceGraphic); - this.Buffer.Add(SvgFilterPrimitive.SourceAlpha, this.CreateSourceAlpha); - } + #region Defaults @@ -147,58 +165,7 @@ namespace Svg.FilterEffects } } - private Bitmap CreateSourceGraphic(SvgVisualElement element, SvgRenderer renderer) - { - if (this.sourceGraphic == null) - { - RectangleF bounds = element.Path(renderer).GetBounds(); - this.sourceGraphic = new Bitmap((int)bounds.Width, (int)bounds.Height); - - using (var graphics = Graphics.FromImage(this.sourceGraphic)) - { - graphics.Clip = renderer.Clip; - graphics.Transform = renderer.Transform; - - element.RenderElement(SvgRenderer.FromGraphics(graphics)); - - graphics.Save(); - } - } - - return this.sourceGraphic; - } - - private Bitmap CreateSourceAlpha(SvgVisualElement element, SvgRenderer renderer) - { - if (this.sourceAlpha == null) - { - Bitmap source = this.Buffer[SvgFilterPrimitive.SourceGraphic](element, renderer); - - float[][] colorMatrixElements = { - new float[] {0, 0, 0, 0, 0}, // red - new float[] {0, 0, 0, 0, 0}, // green - new float[] {0, 0, 0, 0, 0}, // blue - new float[] {0, 0, 0, 1, 1}, // alpha - new float[] {0, 0, 0, 0, 0} }; // translations - - var matrix = new ColorMatrix(colorMatrixElements); - - ImageAttributes attributes = new ImageAttributes(); - attributes.SetColorMatrix(matrix); - - this.sourceAlpha = new Bitmap(source.Width, source.Height); - - using (var graphics = Graphics.FromImage(this.sourceAlpha)) - { - - graphics.DrawImage(source, new Rectangle(0, 0, source.Width, source.Height), 0, 0, - source.Width, source.Height, GraphicsUnit.Pixel, attributes); - graphics.Save(); - } - } - - return this.sourceAlpha; - } + #endregion diff --git a/Source/Filter Effects/SvgFilterPrimitive.cs b/Source/Filter Effects/SvgFilterPrimitive.cs index b962c34..3055f27 100644 --- a/Source/Filter Effects/SvgFilterPrimitive.cs +++ b/Source/Filter Effects/SvgFilterPrimitive.cs @@ -8,12 +8,12 @@ namespace Svg.FilterEffects { public abstract class SvgFilterPrimitive : SvgElement { - public static readonly string SourceGraphic = "SourceGraphic"; - public static readonly string SourceAlpha = "SourceAlpha"; - public static readonly string BackgroundImage = "BackgroundImage"; - public static readonly string BackgroundAlpha = "BackgroundAlpha"; - public static readonly string FillPaint = "FillPaint"; - public static readonly string StrokePaint = "StrokePaint"; + public const string SourceGraphic = "SourceGraphic"; + public const string SourceAlpha = "SourceAlpha"; + public const string BackgroundImage = "BackgroundImage"; + public const string BackgroundAlpha = "BackgroundAlpha"; + public const string FillPaint = "FillPaint"; + public const string StrokePaint = "StrokePaint"; [SvgAttribute("in")] public string Input @@ -34,6 +34,6 @@ namespace Svg.FilterEffects get { return (SvgFilter)this.Parent; } } - public abstract Bitmap Process(); + public abstract void Process(ImageBuffer buffer); } } \ No newline at end of file diff --git a/Source/Filter Effects/feColourMatrix/SvgColourMatrix.cs b/Source/Filter Effects/feColourMatrix/SvgColourMatrix.cs index 9ae6f1b..e577ef3 100644 --- a/Source/Filter Effects/feColourMatrix/SvgColourMatrix.cs +++ b/Source/Filter Effects/feColourMatrix/SvgColourMatrix.cs @@ -1,7 +1,8 @@ using System; using System.Drawing; using System.Collections.Generic; -using Svg.Filter_Effects.feColourMatrix; +using System.Linq; +using System.Drawing.Imaging; namespace Svg.FilterEffects { @@ -19,8 +20,7 @@ namespace Svg.FilterEffects /// [SvgAttribute("type")] public SvgColourMatrixType Type { get; set; } - - + /// /// list of s @@ -29,13 +29,74 @@ namespace Svg.FilterEffects /// [SvgAttribute("values")] public string Values { get; set; } + + public override void Process(ImageBuffer buffer) + { + var inputImage = buffer[this.Input]; + float[][] colorMatrixElements; + float value; + switch (this.Type) + { + case SvgColourMatrixType.hueRotate: + value = (string.IsNullOrEmpty(this.Values) ? 0 : float.Parse(this.Values)); + colorMatrixElements = new float[][] { + new float[] {(float)(0.213 + Math.Cos(value) * +0.787 + Math.Sin(value) * -0.213), + (float)(0.715 + Math.Cos(value) * -0.715 + Math.Sin(value) * -0.715), + (float)(0.072 + Math.Cos(value) * -0.072 + Math.Sin(value) * +0.928), 0, 0}, + new float[] {(float)(0.213 + Math.Cos(value) * -0.213 + Math.Sin(value) * +0.143), + (float)(0.715 + Math.Cos(value) * +0.285 + Math.Sin(value) * +0.140), + (float)(0.072 + Math.Cos(value) * -0.072 + Math.Sin(value) * -0.283), 0, 0}, + new float[] {(float)(0.213 + Math.Cos(value) * -0.213 + Math.Sin(value) * -0.787), + (float)(0.715 + Math.Cos(value) * -0.715 + Math.Sin(value) * +0.715), + (float)(0.072 + Math.Cos(value) * +0.928 + Math.Sin(value) * +0.072), 0, 0}, + new float[] {0, 0, 0, 1, 0}, + new float[] {0, 0, 0, 0, 1} + }; + break; + case SvgColourMatrixType.luminanceToAlpha: + colorMatrixElements = new float[][] { + new float[] {0, 0, 0, 0, 0}, + new float[] {0, 0, 0, 0, 0}, + new float[] {0, 0, 0, 0, 0}, + new float[] {0.2125f, 0.7154f, 0.0721f, 0, 0}, + new float[] {0, 0, 0, 0, 1} + }; + break; + case SvgColourMatrixType.saturate: + value = (string.IsNullOrEmpty(this.Values) ? 1 : float.Parse(this.Values)); + colorMatrixElements = new float[][] { + new float[] {(float)(0.213+0.787*value), (float)(0.715-0.715*value), (float)(0.072-0.072*value), 0, 0}, + new float[] {(float)(0.213-0.213*value), (float)(0.715+0.285*value), (float)(0.072-0.072*value), 0, 0}, + new float[] {(float)(0.213-0.213*value), (float)(0.715-0.715*value), (float)(0.072+0.928*value), 0, 0}, + new float[] {0, 0, 0, 1, 0}, + new float[] {0, 0, 0, 0, 1} + }; + break; + default: // Matrix + var parts = this.Values.Split(new char[] { ' ', '\t', '\n', '\r', ',' }); + colorMatrixElements = new float[5][]; + for (int i = 0; i < 4; i++) + { + colorMatrixElements[i] = parts.Skip(i * 5).Take(5).Select(v => float.Parse(v)).ToArray(); + } + colorMatrixElements[4] = new float[] { 0, 0, 0, 0, 1 }; + break; + } + var colorMatrix = new ColorMatrix(colorMatrixElements); + var imageAttrs = new ImageAttributes(); + imageAttrs.SetColorMatrix(colorMatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap); - public override Bitmap Process() - { - return null; + var result = new Bitmap(inputImage.Width, inputImage.Height); + using (var g = Graphics.FromImage(result)) + { + g.DrawImage(inputImage, new Rectangle(0, 0, inputImage.Width, inputImage.Height), + 0, 0, inputImage.Width, inputImage.Height, GraphicsUnit.Pixel, imageAttrs); + g.Flush(); + } + buffer[this.Result] = result; } diff --git a/Source/Filter Effects/feColourMatrix/SvgColourMatrixType.cs b/Source/Filter Effects/feColourMatrix/SvgColourMatrixType.cs index 417c0eb..9941f63 100644 --- a/Source/Filter Effects/feColourMatrix/SvgColourMatrixType.cs +++ b/Source/Filter Effects/feColourMatrix/SvgColourMatrixType.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; -namespace Svg.Filter_Effects.feColourMatrix +namespace Svg.FilterEffects { public enum SvgColourMatrixType { diff --git a/Source/Filter Effects/feGaussianBlur/SvgGaussianBlur.cs b/Source/Filter Effects/feGaussianBlur/SvgGaussianBlur.cs index dc46d30..d88631f 100644 --- a/Source/Filter Effects/feGaussianBlur/SvgGaussianBlur.cs +++ b/Source/Filter Effects/feGaussianBlur/SvgGaussianBlur.cs @@ -67,7 +67,10 @@ namespace Svg.FilterEffects public Bitmap Apply(Image inputImage) { - using (RawBitmap src = new RawBitmap(new Bitmap(inputImage))) + var bitmapSrc = inputImage as Bitmap; + if (bitmapSrc == null) bitmapSrc = new Bitmap(inputImage); + + using (RawBitmap src = new RawBitmap(bitmapSrc)) { using (RawBitmap dest = new RawBitmap(new Bitmap(inputImage.Width, inputImage.Height))) { @@ -250,11 +253,11 @@ namespace Svg.FilterEffects - public override Bitmap Process() + public override void Process(ImageBuffer buffer) { - //Todo - - return null; + var inputImage = buffer[this.Input]; + var result = Apply(inputImage); + buffer[this.Result] = result; } diff --git a/Source/Filter Effects/feMerge/SvgMerge.cs b/Source/Filter Effects/feMerge/SvgMerge.cs index a18ffdc..2be7eb9 100644 --- a/Source/Filter Effects/feMerge/SvgMerge.cs +++ b/Source/Filter Effects/feMerge/SvgMerge.cs @@ -5,44 +5,34 @@ using System.Text; using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; +using System.Linq; namespace Svg.FilterEffects { - [SvgElement("feMerge")] public class SvgMerge : SvgFilterPrimitive { - public StringCollection MergeResults { get; private set; } - - public SvgMerge() + public override void Process(ImageBuffer buffer) { - MergeResults = new StringCollection(); + var children = this.Children.OfType().ToList(); + var inputImage = buffer[children.First().Input]; + var result = new Bitmap(inputImage.Width, inputImage.Height); + using (var g = Graphics.FromImage(result)) + { + foreach (var child in children) + { + g.DrawImage(buffer[child.Input], new Rectangle(0, 0, inputImage.Width, inputImage.Height), + 0, 0, inputImage.Width, inputImage.Height, GraphicsUnit.Pixel); + } + g.Flush(); + } + result.Save(@"C:\test.png"); + buffer[this.Result] = result; } - public override Bitmap Process() - { - //Todo - - //Bitmap merged = new Bitmap((int)this.Owner.Width.Value, (int)this.Owner.Height.Value); - //Graphics mergedGraphics = Graphics.FromImage(merged); - - //foreach (string resultId in this.MergeResults) - //{ - // mergedGraphics.DrawImageUnscaled(this.Owner.Results[resultId](), new Point(0, 0)); - //} - - //mergedGraphics.Save(); - //mergedGraphics.Dispose(); - - //results.Add(this.Result, () => merged); - - return null; - } - - public override SvgElement DeepCopy() { - throw new NotImplementedException(); + return DeepCopy(); } } diff --git a/Source/Filter Effects/feMerge/SvgMergeNode.cs b/Source/Filter Effects/feMerge/SvgMergeNode.cs index a302a7a..c10ae20 100644 --- a/Source/Filter Effects/feMerge/SvgMergeNode.cs +++ b/Source/Filter Effects/feMerge/SvgMergeNode.cs @@ -10,13 +10,13 @@ namespace Svg.FilterEffects { [SvgElement("feMergeNode")] - public class SvgMergeNode : SvgFilterPrimitive + public class SvgMergeNode : SvgElement { - public override Bitmap Process() + [SvgAttribute("in")] + public string Input { - //Todo - - return null; + get { return this.Attributes.GetAttribute("in"); } + set { this.Attributes["in"] = value; } } public override SvgElement DeepCopy() diff --git a/Source/Filter Effects/feOffset/SvgOffset.cs b/Source/Filter Effects/feOffset/SvgOffset.cs index 37680e7..43ba4d0 100644 --- a/Source/Filter Effects/feOffset/SvgOffset.cs +++ b/Source/Filter Effects/feOffset/SvgOffset.cs @@ -1,7 +1,6 @@ using System; using System.Drawing; using System.Collections.Generic; -using Svg.Filter_Effects.feColourMatrix; namespace Svg.FilterEffects { @@ -28,13 +27,27 @@ namespace Svg.FilterEffects /// Note: this is not used in calculations to bitmap - used only to allow for svg xml output /// [SvgAttribute("dy")] - public string Dy { get; set; } + public SvgUnit Dy { get; set; } - public override Bitmap Process() + public override void Process(ImageBuffer buffer) { - return null; + var inputImage = buffer[this.Input]; + var result = new Bitmap(inputImage.Width, inputImage.Height); + + var pts = new PointF[] { new PointF(this.Dx.ToDeviceValue(null, UnitRenderingType.Horizontal, null), + this.Dy.ToDeviceValue(null, UnitRenderingType.Vertical, null)) }; + buffer.Transform.TransformVectors(pts); + + using (var g = Graphics.FromImage(result)) + { + g.DrawImage(inputImage, new Rectangle((int)pts[0].X, (int)pts[0].Y, + inputImage.Width, inputImage.Height), + 0, 0, inputImage.Width, inputImage.Height, GraphicsUnit.Pixel); + g.Flush(); + } + buffer[this.Result] = result; } diff --git a/Source/Painting/SvgGradientServer.cs b/Source/Painting/SvgGradientServer.cs index f5c3661..1552869 100644 --- a/Source/Painting/SvgGradientServer.cs +++ b/Source/Painting/SvgGradientServer.cs @@ -111,7 +111,7 @@ namespace Svg } } - private Matrix EffectiveGradientTransform + protected Matrix EffectiveGradientTransform { get { @@ -247,6 +247,15 @@ namespace Svg return newVector[0]; } + protected float TransformDistance(float dist) + { + var newVector = new[] { new PointF(dist, 0) }; + + EffectiveGradientTransform.TransformVectors(newVector); + + return (float)Math.Sqrt(Math.Pow(newVector[0].X, 2) + Math.Pow(newVector[0].Y, 2)); + } + protected static double CalculateDistance(PointF first, PointF second) { return Math.Sqrt(Math.Pow(first.X - second.X, 2) + Math.Pow(first.Y - second.Y, 2)); diff --git a/Source/Painting/SvgLinearGradientServer.cs b/Source/Painting/SvgLinearGradientServer.cs index d2356fd..cc791ef 100644 --- a/Source/Painting/SvgLinearGradientServer.cs +++ b/Source/Painting/SvgLinearGradientServer.cs @@ -173,7 +173,7 @@ namespace Svg if (boundable.Bounds.Contains(specifiedEnd)) { - effectiveEnd = CalculateClosestIntersectionPoint(effectiveEnd, intersectionPoints); + effectiveEnd = CalculateClosestIntersectionPoint(expandedEnd, intersectionPoints); effectiveEnd = MovePointAlongVector(effectiveEnd, specifiedUnitVector, 1); } @@ -296,48 +296,87 @@ namespace Svg return result; } + /// http://community.topcoder.com/tc?module=Static&d1=tutorials&d2=geometry2 private PointF? Intersection(LineF other) { - var a1 = Y2 - Y1; - var b1 = X1 - X2; - var c1 = X2 * Y1 - X1 * Y2; + const int precision = 8; - var r3 = a1 * other.X1 + b1 * other.Y1 + c1; - var r4 = a1 * other.X2 + b1 * other.Y2 + c1; + var a1 = (double)Y2 - Y1; + var b1 = (double)X1 - X2; + var c1 = a1 * X1 + b1 * Y1; - if (r3 != 0 && r4 != 0 && Math.Sign(r3) == Math.Sign(r4)) + var a2 = (double)other.Y2 - other.Y1; + var b2 = (double)other.X1 - other.X2; + var c2 = a2 * other.X1 + b2 * other.Y1; + + var det = a1 * b2 - a2 * b1; + if (det == 0) { return null; } + else + { + var xi = (b2 * c1 - b1 * c2) / det; + var yi = (a1 * c2 - a2 * c1) / det; + + if (Math.Round(Math.Min(X1, X2), precision) <= Math.Round(xi, precision) && + Math.Round(xi, precision) <= Math.Round(Math.Max(X1, X2), precision) && + Math.Round(Math.Min(Y1, Y2), precision) <= Math.Round(yi, precision) && + Math.Round(yi, precision) <= Math.Round(Math.Max(Y1, Y2), precision) && + Math.Round(Math.Min(other.X1, other.X2), precision) <= Math.Round(xi, precision) && + Math.Round(xi, precision) <= Math.Round(Math.Max(other.X1, other.X2), precision) && + Math.Round(Math.Min(other.Y1, other.Y2), precision) <= Math.Round(yi, precision) && + Math.Round(yi, precision) <= Math.Round(Math.Max(other.Y1, other.Y2), precision)) + { + return new PointF((float)xi, (float)yi); + } + else + { + return null; + } + } - var a2 = other.Y2 - other.Y1; - var b2 = other.X1 - other.X2; - var c2 = other.X2 * other.Y1 - other.X1 * other.Y2; - var r1 = a2 * X1 + b2 * Y1 + c2; - var r2 = a2 * X2 + b2 * Y2 + c2; + //var a1 = Y2 - Y1; + //var b1 = X1 - X2; + //var c1 = X2 * Y1 - X1 * Y2; - if (r1 != 0 && r2 != 0 && Math.Sign(r1) == Math.Sign(r2)) - { - return (null); - } + //var r3 = a1 * other.X1 + b1 * other.Y1 + c1; + //var r4 = a1 * other.X2 + b1 * other.Y2 + c1; - var denom = a1 * b2 - a2 * b1; + //if (r3 != 0 && r4 != 0 && Math.Sign(r3) == Math.Sign(r4)) + //{ + // return null; + //} - if (denom == 0) - { - return null; - } + //var a2 = other.Y2 - other.Y1; + //var b2 = other.X1 - other.X2; + //var c2 = other.X2 * other.Y1 - other.X1 * other.Y2; + + //var r1 = a2 * X1 + b2 * Y1 + c2; + //var r2 = a2 * X2 + b2 * Y2 + c2; + + //if (r1 != 0 && r2 != 0 && Math.Sign(r1) == Math.Sign(r2)) + //{ + // return (null); + //} + + //var denom = a1 * b2 - a2 * b1; + + //if (denom == 0) + //{ + // return null; + //} - var offset = denom < 0 ? -denom / 2 : denom / 2; + //var offset = denom < 0 ? -denom / 2 : denom / 2; - var num = b1 * c2 - b2 * c1; - var x = (num < 0 ? num - offset : num + offset) / denom; + //var num = b1 * c2 - b2 * c1; + //var x = (num < 0 ? num - offset : num + offset) / denom; - num = a2 * c1 - a1 * c2; - var y = (num < 0 ? num - offset : num + offset) / denom; + //num = a2 * c1 - a1 * c2; + //var y = (num < 0 ? num - offset : num + offset) / denom; - return new PointF(x, y); + //return new PointF(x, y); } private static void AddIfIntersect(LineF first, LineF second, ICollection result) diff --git a/Source/Painting/SvgRadialGradientServer.cs b/Source/Painting/SvgRadialGradientServer.cs index 349c5dc..5a1a6cd 100644 --- a/Source/Painting/SvgRadialGradientServer.cs +++ b/Source/Painting/SvgRadialGradientServer.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics; using System.Drawing; +using System.Collections.Generic; using System.Drawing.Drawing2D; using System.Linq; @@ -95,6 +96,8 @@ namespace Svg Radius = new SvgUnit(SvgUnitType.Percentage, 50F); } + private object _lockObj = new Object(); + public override Brush GetBrush(SvgVisualElement renderingElement, SvgRenderer renderer, float opacity) { LoadStops(renderingElement); @@ -102,20 +105,39 @@ namespace Svg try { if (this.GradientUnits == SvgCoordinateUnits.ObjectBoundingBox) renderer.Boundable(renderingElement); + + // Calculate the path and transform it appropriately var origin = renderer.Boundable().Location; - var centerPoint = CalculateCenterPoint(renderer, origin); - var focalPoint = CalculateFocalPoint(renderer, origin); - - var specifiedRadius = CalculateRadius(renderer); - var effectiveRadius = CalculateEffectiveRadius(renderingElement, centerPoint, specifiedRadius); - - var brush = new PathGradientBrush(CreateGraphicsPath(origin, centerPoint, effectiveRadius)) - { - InterpolationColors = CalculateColorBlend(renderer, opacity, specifiedRadius, effectiveRadius), - CenterPoint = focalPoint - }; - - Debug.Assert(brush.Rectangle.Contains(renderingElement.Bounds), "Brush rectangle does not contain rendering element bounds!"); + 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); + var path = new GraphicsPath(); + path.AddEllipse( + origin.X + center.X - specifiedRadius, origin.Y + center.Y - specifiedRadius, + specifiedRadius * 2, specifiedRadius * 2 + ); + path.Transform(EffectiveGradientTransform); + + + // Calculate any required scaling + var scale = CalcScale(renderingElement.Bounds, path); + + // Get the color blend and any tweak to the scaling + var blend = CalculateColorBlend(renderer, opacity, scale, out scale); + + // Transform the path based on the scaling + var gradBounds = path.GetBounds(); + var transCenter = new PointF(gradBounds.Left + gradBounds.Width / 2, gradBounds.Top + gradBounds.Height / 2); + var scaleMat = new Matrix(); + scaleMat.Translate(-1 * transCenter.X, -1 * transCenter.Y, MatrixOrder.Append); + scaleMat.Scale(scale, scale, MatrixOrder.Append); + scaleMat.Translate(transCenter.X, transCenter.Y, MatrixOrder.Append); + path.Transform(scaleMat); + + // calculate the brush + var brush = new PathGradientBrush(path); + brush.CenterPoint = CalculateFocalPoint(renderer, origin); + brush.InterpolationColors = blend; return brush; } @@ -125,12 +147,38 @@ namespace Svg } } - private PointF CalculateCenterPoint(SvgRenderer renderer, PointF origin) + /// + /// Determine how much (approximately) the path must be scaled to contain the rectangle + /// + /// Bounds that the path must contain + /// Path of the gradient + /// Scale factor + /// + /// This method continually transforms the rectangle (fewer points) until it is contained by the path + /// and returns the result of the search. The scale factor is set to a constant 95% + /// + private float CalcScale(RectangleF bounds, GraphicsPath path) { - var deviceCenterX = origin.X + CenterX.ToDeviceValue(renderer, UnitRenderingType.HorizontalOffset, this); - var deviceCenterY = origin.Y + CenterY.ToDeviceValue(renderer, UnitRenderingType.VerticalOffset, this); - var transformedCenterPoint = TransformPoint(new PointF(deviceCenterX, deviceCenterY)); - return transformedCenterPoint; + var points = new PointF[] { + new PointF(bounds.Left, bounds.Top), + new PointF(bounds.Right, bounds.Top), + new PointF(bounds.Right, bounds.Bottom), + new PointF(bounds.Left, bounds.Bottom) + }; + var pathBounds = path.GetBounds(); + var pathCenter = new PointF(pathBounds.X + pathBounds.Width / 2, pathBounds.Y + pathBounds.Height / 2); + var transform = new Matrix(); + transform.Translate(-1 * pathCenter.X, -1 * pathCenter.Y, MatrixOrder.Append); + transform.Scale(.95f, .95f, MatrixOrder.Append); + transform.Translate(pathCenter.X, pathCenter.Y, MatrixOrder.Append); + + var boundsTest = RectangleF.Inflate(bounds, 0, 0); + while (!(path.IsVisible(points[0]) && path.IsVisible(points[1]) && + path.IsVisible(points[2]) && path.IsVisible(points[3]))) + { + transform.TransformPoints(points); + } + return bounds.Height / (points[2].Y - points[1].Y); } private PointF CalculateFocalPoint(SvgRenderer renderer, PointF origin) @@ -141,44 +189,6 @@ namespace Svg return transformedFocalPoint; } - private float CalculateRadius(SvgRenderer renderer) - { - var radius = Radius.ToDeviceValue(renderer, UnitRenderingType.Other, this); - var transformRadiusVector = TransformVector(new PointF(radius, 0)); - var transformedRadius = CalculateLength(transformRadiusVector); - return transformedRadius; - } - - private float CalculateEffectiveRadius(ISvgBoundable boundable, PointF centerPoint, float specifiedRadius) - { - if (SpreadMethod != SvgGradientSpreadMethod.Pad) - { - return specifiedRadius; - } - - var topLeft = new PointF(boundable.Bounds.Left, boundable.Bounds.Top); - var topRight = new PointF(boundable.Bounds.Right, boundable.Bounds.Top); - var bottomRight = new PointF(boundable.Bounds.Right, boundable.Bounds.Bottom); - var bottomLeft = new PointF(boundable.Bounds.Left, boundable.Bounds.Bottom); - - var effectiveRadius = (float)Math.Ceiling( - Math.Max( - Math.Max( - CalculateDistance(centerPoint, topLeft), - CalculateDistance(centerPoint, topRight) - ), - Math.Max( - CalculateDistance(centerPoint, bottomRight), - CalculateDistance(centerPoint, bottomLeft) - ) - ) - ); - - effectiveRadius = Math.Max(effectiveRadius, specifiedRadius); - - return effectiveRadius; - } - private static GraphicsPath CreateGraphicsPath(PointF origin, PointF centerPoint, float effectiveRadius) { var path = new GraphicsPath(); @@ -193,23 +203,66 @@ namespace Svg return path; } - private ColorBlend CalculateColorBlend(SvgRenderer renderer, float opacity, float specifiedRadius, float effectiveRadius) + private ColorBlend CalculateColorBlend(SvgRenderer renderer, float opacity, float scale, out float outScale) { var colorBlend = GetColorBlend(renderer, opacity, true); + float newScale; + List pos; + List colors; - if (specifiedRadius >= effectiveRadius) + outScale = scale; + if (scale > 1) { - return colorBlend; - } - - for (var i = 0; i < colorBlend.Positions.Length - 1; i++) - { - colorBlend.Positions[i] = 1 - (specifiedRadius / effectiveRadius) * (1 - colorBlend.Positions[i]); + switch (this.SpreadMethod) + { + case SvgGradientSpreadMethod.Reflect: + newScale = (float)Math.Ceiling(scale); + pos = (from p in colorBlend.Positions select p / newScale).ToList(); + colors = colorBlend.Colors.ToList(); + + for (var i = 1; i < newScale; i++) + { + if (i % 2 == 1) + { + pos.AddRange(from p in colorBlend.Positions.Reverse().Skip(1) select (1 - p + i) / newScale); + colors.AddRange(colorBlend.Colors.Reverse().Skip(1)); + } + else + { + pos.AddRange(from p in colorBlend.Positions.Skip(1) select (p + i) / newScale); + colors.AddRange(colorBlend.Colors.Skip(1)); + } + } + + colorBlend.Positions = pos.ToArray(); + colorBlend.Colors = colors.ToArray(); + outScale = newScale; + break; + case SvgGradientSpreadMethod.Repeat: + newScale = (float)Math.Ceiling(scale); + pos = (from p in colorBlend.Positions select p / newScale).ToList(); + colors = colorBlend.Colors.ToList(); + + for (var i = 1; i < newScale; i++) + { + pos.AddRange(from p in colorBlend.Positions select (p <= 0 ? 0.001f : p) / newScale); + colors.AddRange(colorBlend.Colors); + } + + break; + default: + for (var i = 0; i < colorBlend.Positions.Length - 1; i++) + { + colorBlend.Positions[i] = 1 - (1 - colorBlend.Positions[i]) / scale; + } + + colorBlend.Positions = new[] { 0F }.Concat(colorBlend.Positions).ToArray(); + colorBlend.Colors = new[] { colorBlend.Colors.First() }.Concat(colorBlend.Colors).ToArray(); + + break; + } } - colorBlend.Positions = new[] { 0F }.Concat(colorBlend.Positions).ToArray(); - colorBlend.Colors = new[] { colorBlend.Colors.First() }.Concat(colorBlend.Colors).ToArray(); - return colorBlend; } diff --git a/Source/Svg.csproj b/Source/Svg.csproj index b66ced0..7e2ee28 100644 --- a/Source/Svg.csproj +++ b/Source/Svg.csproj @@ -101,6 +101,7 @@ + diff --git a/Source/Text/SvgTextBase.cs b/Source/Text/SvgTextBase.cs index 35040b7..8729f8e 100644 --- a/Source/Text/SvgTextBase.cs +++ b/Source/Text/SvgTextBase.cs @@ -275,6 +275,7 @@ namespace Svg 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; @@ -298,7 +299,9 @@ namespace Svg } } + // Calculate the bounds of the text ISvgNode node; + var accumulateDims = true; for (var i = 0; i < nodes.Count; i++) { node = nodes[i]; @@ -316,10 +319,15 @@ namespace Svg { stringBounds = innerText.GetTextBounds(renderer).Bounds; result.Nodes.Add(new NodeBounds() { Bounds = stringBounds, Node = node, xOffset = totalWidth }); - if (innerText.Dx.Count == 1) totalWidth += innerText.Dx[0].ToDeviceValue(renderer, UnitRenderingType.Horizontal, this); + 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; } - totalHeight = Math.Max(totalHeight, stringBounds.Height); - totalWidth += stringBounds.Width; } } result.Bounds = new SizeF(totalWidth, totalHeight); diff --git a/Tests/SvgW3CTestRunner/View.cs b/Tests/SvgW3CTestRunner/View.cs index 8a09148..c39a31d 100644 --- a/Tests/SvgW3CTestRunner/View.cs +++ b/Tests/SvgW3CTestRunner/View.cs @@ -32,9 +32,16 @@ namespace SvgW3CTestRunner try { var doc = SvgDocument.Open(_svgBasePath + fileName); - var img = new Bitmap(480, 360); - doc.Draw(img); - picSvg.Image = img; + if (fileName.StartsWith("__")) + { + picSvg.Image = doc.Draw(); + } + else + { + var img = new Bitmap(480, 360); + doc.Draw(img); + picSvg.Image = img; + } } catch (Exception ex) { -- GitLab