Commit 3f4ee333 authored by Eric Domke's avatar Eric Domke
Browse files

Gradient, Pattern, and Clip Fixes

- Better parser for dealing with parsing edge cases
- Fix corner radius for rectangles
- Fix to gradients.  Most tests now pass
- Add the possibility for a fallback paint server
- Initial fixes to clipping
- Start marking passed test
- Fixes to pattern rendering
parent 9e878812
......@@ -43,9 +43,10 @@ namespace SVGViewer
private void RenderSvg(SvgDocument svgDoc)
{
var render = new DebugRenderer();
svgDoc.Draw(render);
//var render = new DebugRenderer();
//svgDoc.Draw(render);
svgImage.Image = svgDoc.Draw();
svgImage.Image.Save(System.IO.Path.Combine(System.IO.Path.GetDirectoryName(svgDoc.BaseUri.LocalPath), "output.png"));
}
}
}
......@@ -15,13 +15,13 @@ namespace Svg
public class SvgPolygon : SvgVisualElement
{
private GraphicsPath _path;
private SvgUnitCollection _points;
private SvgPointCollection _points;
/// <summary>
/// The points that make up the SvgPolygon
/// </summary>
[SvgAttribute("points")]
public SvgUnitCollection Points
public SvgPointCollection Points
{
get { return this._points; }
set { this._points = value; this.IsPathDirty = true; }
......@@ -41,7 +41,7 @@ namespace Svg
try
{
for (int i = 2; i < this._points.Count; i+=2)
for (int i = 2; (i + 1) < this._points.Count; i += 2)
{
var endPoint = SvgUnit.GetDevicePoint(this._points[i], this._points[i+1], renderer, this);
......@@ -81,7 +81,7 @@ namespace Svg
public override SvgElement DeepCopy<T>()
{
var newObj = base.DeepCopy<T>() as SvgPolygon;
newObj.Points = new SvgUnitCollection();
newObj.Points = new SvgPointCollection();
foreach (var pt in this.Points)
newObj.Points.Add(pt);
return newObj;
......
......@@ -22,7 +22,7 @@ namespace Svg
try
{
for (int i = 0; i < Points.Count; i += 2)
for (int i = 0; (i + 1) < Points.Count; i += 2)
{
PointF endPoint = new PointF(Points[i].ToDeviceValue(renderer, UnitRenderingType.Horizontal, this),
Points[i + 1].ToDeviceValue(renderer, UnitRenderingType.Vertical, this));
......
......@@ -197,8 +197,8 @@ namespace Svg
var lineEnd = new PointF();
var width = Width.ToDeviceValue(renderer, UnitRenderingType.Horizontal, this);
var height = Height.ToDeviceValue(renderer, UnitRenderingType.Vertical, this);
var rx = CornerRadiusX.ToDeviceValue(renderer, UnitRenderingType.Horizontal, this) * 2;
var ry = CornerRadiusY.ToDeviceValue(renderer, UnitRenderingType.Vertical, this) * 2;
var rx = Math.Min(CornerRadiusX.ToDeviceValue(renderer, UnitRenderingType.Horizontal, this) * 2, width);
var ry = Math.Min(CornerRadiusY.ToDeviceValue(renderer, UnitRenderingType.Vertical, this) * 2, height);
var location = Location.ToDeviceValue(renderer, this);
// Start
......
......@@ -2,6 +2,7 @@ using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Diagnostics;
using System.Linq;
namespace Svg
{
......@@ -53,13 +54,23 @@ namespace Svg
set { this._dirty = value; }
}
/// <summary>
/// Gets the associated <see cref="SvgClipPath"/> if one has been specified.
/// </summary>
[SvgAttribute("clip")]
public virtual string Clip
{
get { return this.Attributes.GetInheritedAttribute<string>("clip"); }
set { this.Attributes["clip"] = value; }
}
/// <summary>
/// Gets the associated <see cref="SvgClipPath"/> if one has been specified.
/// </summary>
[SvgAttribute("clip-path")]
public virtual Uri ClipPath
{
get { return this.Attributes.GetAttribute<Uri>("clip-path"); }
get { return this.Attributes.GetInheritedAttribute<Uri>("clip-path"); }
set { this.Attributes["clip-path"] = value; }
}
......@@ -79,7 +90,7 @@ namespace Svg
[SvgAttribute("filter")]
public virtual Uri Filter
{
get { return this.Attributes.GetAttribute<Uri>("filter"); }
get { return this.Attributes.GetInheritedAttribute<Uri>("filter"); }
set { this.Attributes["filter"] = value; }
}
......@@ -180,7 +191,7 @@ namespace Svg
{
if (this.Fill != null)
{
using (Brush brush = this.Fill.GetBrush(this, renderer, Math.Min(Math.Max(this.FillOpacity * this.Opacity, 0), 1)))
using (var brush = this.Fill.GetBrush(this, renderer, Math.Min(Math.Max(this.FillOpacity * this.Opacity, 0), 1)))
{
if (brush != null)
{
......@@ -200,18 +211,25 @@ namespace Svg
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))
using (var brush = this.Stroke.GetBrush(this, renderer, Math.Min(Math.Max(this.StrokeOpacity * this.Opacity, 0), 1), true))
{
if (brush != null)
{
using (var pen = new Pen(brush, strokeWidth))
{
if (this.StrokeDashArray != null && this.StrokeDashArray.Count > 0)
{
/* divide by stroke width - GDI behaviour that I don't quite understand yet.*/
pen.DashPattern = this.StrokeDashArray.ConvertAll(u => ((u.Value <= 0) ? 1 : u.Value) / ((strokeWidth <= 0) ? 1 : strokeWidth)).ToArray();
pen.DashPattern = this.StrokeDashArray.ConvertAll(u => ((u.ToDeviceValue(renderer, UnitRenderingType.Other, this) <= 0) ? 1 : u.ToDeviceValue(renderer, UnitRenderingType.Other, this)) /
((strokeWidth <= 0) ? 1 : strokeWidth)).ToArray();
}
renderer.DrawPath(pen, this.Path(renderer));
}
}
}
}
}
/// <summary>
/// Sets the clipping region of the specified <see cref="ISvgRenderer"/>.
......@@ -219,14 +237,27 @@ namespace Svg
/// <param name="renderer">The <see cref="ISvgRenderer"/> to have its clipping region set.</param>
protected internal virtual void SetClip(ISvgRenderer renderer)
{
if (this.ClipPath != null || !string.IsNullOrEmpty(this.Clip))
{
this._previousClip = renderer.GetClip();
if (this.ClipPath != null)
{
SvgClipPath clipPath = this.OwnerDocument.GetElementById<SvgClipPath>(this.ClipPath.ToString());
this._previousClip = renderer.GetClip();
if (clipPath != null) renderer.SetClip(clipPath.GetClipRegion(this), CombineMode.Intersect);
}
if (clipPath != null)
var clip = this.Clip;
if (!string.IsNullOrEmpty(clip) && clip.StartsWith("rect("))
{
renderer.SetClip(clipPath.GetClipRegion(this), CombineMode.Intersect);
clip = clip.Trim();
var offsets = (from o in clip.Substring(5, clip.Length - 6).Split(',')
select float.Parse(o.Trim())).ToList();
var bounds = this.Bounds;
var clipRect = new RectangleF(bounds.Left + offsets[3], bounds.Top + offsets[0],
bounds.Width - (offsets[3] + offsets[1]),
bounds.Height - (offsets[2] + offsets[0]));
renderer.SetClip(new Region(clipRect), CombineMode.Intersect);
}
}
}
......
......@@ -156,6 +156,18 @@ namespace Svg
set { this.Attributes["stroke-opacity"] = FixOpacityValue(value); }
}
/// <summary>
/// Gets or sets the colour of the gradient stop.
/// </summary>
/// <remarks>Apparently this can be set on non-sensical elements. Don't ask; just check the tests.</remarks>
[SvgAttribute("stop-color")]
[TypeConverter(typeof(SvgPaintServerFactory))]
public SvgPaintServer StopColor
{
get { return this.Attributes["stop-color"] as SvgPaintServer; }
set { this.Attributes["stop-color"] = value; }
}
/// <summary>
/// Gets or sets the opacity of the element. 1.0 is fully opaque; 0.0 is transparent.
/// </summary>
......
......@@ -26,7 +26,7 @@ namespace Svg
/// </summary>
public SvgClipPath()
{
this.ClipPathUnits = SvgCoordinateUnits.ObjectBoundingBox;
this.ClipPathUnits = SvgCoordinateUnits.Inherit;
}
private GraphicsPath cachedClipPath = null;
......@@ -49,7 +49,20 @@ namespace Svg
this._pathDirty = false;
}
return new Region(cachedClipPath);
var result = cachedClipPath;
if (ClipPathUnits == SvgCoordinateUnits.ObjectBoundingBox)
{
result = (GraphicsPath)cachedClipPath.Clone();
using (var transform = new Matrix())
{
var bounds = owner.Bounds;
transform.Scale(bounds.Width, bounds.Height, MatrixOrder.Append);
transform.Translate(bounds.Left, bounds.Top, MatrixOrder.Append);
result.Transform(transform);
}
}
return new Region(result);
}
/// <summary>
......
......@@ -2,14 +2,17 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
namespace Svg
{
/// <summary>
/// Defines the various coordinate units certain SVG elements may use.
/// </summary>
[TypeConverter(typeof(SvgCoordinateUnitsConverter))]
public enum SvgCoordinateUnits
{
Inherit,
/// <summary>
/// Indicates that the coordinate system of the owner element is to be used.
/// </summary>
......
......@@ -7,10 +7,10 @@ namespace Svg
{
public enum SvgOverflow
{
inherit,
auto,
visible,
hidden,
scroll,
inherit
scroll
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Globalization;
namespace Svg
{
/// <summary>
/// Represents a list of <see cref="SvgUnits"/> used with the <see cref="SvgPolyline"/> and <see cref="SvgPolygon"/>.
/// </summary>
[TypeConverter(typeof(SvgPointCollectionConverter))]
public class SvgPointCollection : List<SvgUnit>
{
public override string ToString()
{
string ret = "";
foreach (var unit in this)
{
ret += unit.ToString() + " ";
}
return ret;
}
}
/// <summary>
/// A class to convert string into <see cref="SvgUnitCollection"/> instances.
/// </summary>
internal class SvgPointCollectionConverter : TypeConverter
{
//private static readonly SvgUnitConverter _unitConverter = new SvgUnitConverter();
/// <summary>
/// Converts the given object to the type of this converter, using the specified context and culture information.
/// </summary>
/// <param name="context">An <see cref="T:System.ComponentModel.ITypeDescriptorContext"/> that provides a format context.</param>
/// <param name="culture">The <see cref="T:System.Globalization.CultureInfo"/> to use as the current culture.</param>
/// <param name="value">The <see cref="T:System.Object"/> to convert.</param>
/// <returns>
/// An <see cref="T:System.Object"/> that represents the converted value.
/// </returns>
/// <exception cref="T:System.NotSupportedException">The conversion cannot be performed. </exception>
public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
if (value is string)
{
var strValue = ((string)value).Trim();
if (string.Compare(strValue, "none", StringComparison.InvariantCultureIgnoreCase) == 0) return null;
var parser = new CoordinateParser(strValue);
var pointValue = 0.0f;
var result = new SvgPointCollection();
while (parser.TryGetFloat(out pointValue))
{
result.Add(new SvgUnit(SvgUnitType.User, pointValue));
}
return result;
}
return base.ConvertFrom(context, culture, value);
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
{
return true;
}
return base.CanConvertTo(context, destinationType);
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
if (destinationType == typeof(string))
{
return ((SvgPointCollection)value).ToString();
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
}
......@@ -20,7 +20,7 @@ namespace Svg
/// <summary>
/// Gets and empty <see cref="SvgUnit"/>.
/// </summary>
public static readonly SvgUnit Empty = new SvgUnit(SvgUnitType.User, 0);
public static readonly SvgUnit Empty = new SvgUnit(SvgUnitType.User, 0) { _isEmpty = true };
/// <summary>
/// Gets an <see cref="SvgUnit"/> with a value of none.
......@@ -87,25 +87,6 @@ namespace Svg
var type = this.Type;
var value = this.Value;
// Deal with fractional pattern units
var coordElem = owner as ISvgSupportsCoordinateUnits;
if (coordElem != null && coordElem.GetUnits() == SvgCoordinateUnits.ObjectBoundingBox && type != SvgUnitType.Percentage)
{
type = SvgUnitType.Percentage;
value *= 100;
}
var element = owner as SvgElement;
if (element != null)
{
var pattern = element.Parents.OfType<SvgPatternServer>().FirstOrDefault();
if (pattern != null && pattern.PatternContentUnits == SvgCoordinateUnits.ObjectBoundingBox && type != SvgUnitType.Percentage)
{
type = SvgUnitType.Percentage;
value *= 100;
}
}
float points;
switch (type)
......@@ -320,9 +301,9 @@ namespace Svg
/// <param name="value">The value.</param>
public SvgUnit(SvgUnitType type, float value)
{
this._isEmpty = false;
this._type = type;
this._value = value;
this._isEmpty = (this._value == 0.0f);
this._deviceValue = null;
}
......@@ -332,9 +313,9 @@ namespace Svg
/// <param name="value">The value.</param>
public SvgUnit(float value)
{
this._isEmpty = false;
this._value = value;
this._type = SvgUnitType.User;
this._isEmpty = (this._value == 0.0f);
this._deviceValue = null;
}
......
......@@ -35,7 +35,7 @@ namespace Svg
for (int i = 0; i < unit.Length; i++)
{
// If the character is a percent sign or a letter which is not an exponent 'e'
if (unit[i] == '%' || (char.IsLetter(unit[i]) && !(unit[i] == 'e' && i < unit.Length - 1 && !char.IsLetter(unit[i + 1]))))
if (unit[i] == '%' || (char.IsLetter(unit[i]) && !((unit[i] == 'e' || unit[i] == 'E') && i < unit.Length - 1 && !char.IsLetter(unit[i + 1]))))
{
identifierIndex = i;
break;
......
......@@ -126,7 +126,14 @@ namespace Svg
public void AddViewBoxTransform(SvgAspectRatio aspectRatio, ISvgRenderer renderer, SvgFragment frag)
{
if (this.Equals(SvgViewBox.Empty)) return;
var x = (frag == null ? 0 : frag.X.ToDeviceValue(renderer, UnitRenderingType.Horizontal, frag));
var y = (frag == null ? 0 : frag.Y.ToDeviceValue(renderer, UnitRenderingType.Vertical, frag));
if (this.Equals(SvgViewBox.Empty))
{
renderer.TranslateTransform(x, y);
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));
......@@ -191,9 +198,6 @@ namespace Svg
}
}
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);
......
......@@ -158,6 +158,34 @@ namespace Svg
return true;
}
protected override void Render(ISvgRenderer renderer)
{
switch (this.Overflow)
{
case SvgOverflow.auto:
case SvgOverflow.visible:
case SvgOverflow.scroll:
base.Render(renderer);
break;
default:
var prevClip = renderer.GetClip();
try
{
var size = (this.Parent == null ? renderer.GetBoundable().Bounds.Size : GetDimensions());
var clip = new RectangleF(this.X.ToDeviceValue(renderer, UnitRenderingType.Horizontal, this),
this.Y.ToDeviceValue(renderer, UnitRenderingType.Horizontal, this),
size.Width, size.Height);
renderer.SetClip(new Region(clip), CombineMode.Intersect);
base.Render(renderer);
}
finally
{
renderer.SetClip(prevClip, CombineMode.Replace);
}
break;
}
}
/// <summary>
/// Gets the <see cref="GraphicsPath"/> for this element.
/// </summary>
......
......@@ -74,7 +74,7 @@ namespace Svg
{
this.SetClip(renderer);
var element = (SvgVisualElement)this.OwnerDocument.IdManager.GetElementById(this.ReferencedElement);
var element = this.OwnerDocument.IdManager.GetElementById(this.ReferencedElement) as SvgVisualElement;
if (element != null)
{
var origParent = element.Parent;
......
......@@ -137,6 +137,23 @@ namespace Svg
}
}
public sealed class SvgCoordinateUnitsConverter : EnumBaseConverter<SvgCoordinateUnits>
{
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value == null || value.ToString() == "") return SvgCoordinateUnits.Inherit;
return base.ConvertFrom(context, culture, value);
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
if (destinationType == typeof(string) && value is SvgCoordinateUnits && (SvgCoordinateUnits)value == SvgCoordinateUnits.Inherit)
{
return null;
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
public sealed class SvgTextDecorationConverter : EnumBaseConverter<SvgTextDecoration>
{
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
......
......@@ -35,7 +35,7 @@ namespace Svg
set { this._colour = value; }
}
public override Brush GetBrush(SvgVisualElement styleOwner, ISvgRenderer renderer, float opacity)
public override Brush GetBrush(SvgVisualElement styleOwner, ISvgRenderer renderer, float opacity, bool forStroke = false)
{
//is none?
if (this == SvgPaintServer.None) return new SolidBrush(System.Drawing.Color.Transparent);
......
......@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
namespace Svg
{
......@@ -44,10 +45,10 @@ namespace Svg
}
}
public override System.Drawing.Brush GetBrush(SvgVisualElement styleOwner, ISvgRenderer renderer, float opacity)
public override Brush GetBrush(SvgVisualElement styleOwner, ISvgRenderer renderer, float opacity, bool forStroke = false)
{
EnsureServer(styleOwner);
return _concreteServer.GetBrush(styleOwner, renderer, opacity);
return _concreteServer.GetBrush(styleOwner, renderer, opacity, forStroke);
}
public override SvgElement DeepCopy()
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
namespace Svg
{
/// <summary>
/// A wrapper for a paint server has a fallback if the primary server doesn't work.
/// </summary>
public class SvgFallbackPaintServer : SvgPaintServer
{
private IEnumerable<SvgPaintServer> _fallbacks;
private SvgPaintServer _primary;
public SvgFallbackPaintServer() : base() { }
public SvgFallbackPaintServer(SvgPaintServer primary, IEnumerable<SvgPaintServer> fallbacks) : this()
{
_fallbacks = fallbacks;
_primary = primary;
}
public override Brush GetBrush(SvgVisualElement styleOwner, ISvgRenderer renderer, float opacity, bool forStroke = false)
{
try
{
_primary.GetCallback = () => _fallbacks.FirstOrDefault();
return _primary.GetBrush(styleOwner, renderer, opacity, forStroke);
}
finally
{
_primary.GetCallback = null;
}
}
public override SvgElement DeepCopy()
{
return base.DeepCopy<SvgFallbackPaintServer>();
}
public override SvgElement DeepCopy<T>()
{
var newObj = base.DeepCopy<T>() as SvgFallbackPaintServer;
newObj._fallbacks = this._fallbacks;
newObj._primary = this._primary;
return newObj;
}
}
}
......@@ -88,7 +88,7 @@ namespace Svg
/// <summary>
/// Gets or sets another gradient fill from which to inherit the stops from.
/// </summary>
[SvgAttribute("href")]
[SvgAttribute("href", SvgAttributeAttribute.XLinkNamespace)]
public SvgPaintServer InheritGradient
{
get { return this._inheritGradient; }
......@@ -101,14 +101,8 @@ namespace Svg
[SvgAttribute("gradientTransform")]
public SvgTransformCollection GradientTransform
{
get
{
return (this.Attributes.GetAttribute<SvgTransformCollection>("gradientTransform"));
}
set
{
this.Attributes["gradientTransform"] = value;
}
get { return (this.Attributes.GetAttribute<SvgTransformCollection>("gradientTransform")); }
set { this.Attributes["gradientTransform"] = value; }
}
protected Matrix EffectiveGradientTransform
......@@ -186,7 +180,7 @@ namespace Svg
var currentStop = this.Stops[radial ? this.Stops.Count - 1 - actualStops : actualStops];
var boundWidth = renderer.GetBoundable().Bounds.Width;
mergedOpacity = opacity * currentStop.Opacity;
mergedOpacity = opacity * currentStop.GetOpacity();
position =
radial
? 1 - (currentStop.Offset.ToDeviceValue(renderer, UnitRenderingType.Horizontal, this) / boundWidth)
......@@ -229,33 +223,6 @@ namespace Svg
}
}
protected PointF TransformPoint(PointF originalPoint)
{
var newPoint = new[] { originalPoint };
EffectiveGradientTransform.TransformPoints(newPoint);
return newPoint[0];
}
protected PointF TransformVector(PointF originalVector)
{
var newVector = new[] { originalVector };
EffectiveGradientTransform.TransformVectors(newVector);
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));
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment