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

merged

parents cc4f2940 1818255b
......@@ -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
{
......
......@@ -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;
}
......
......@@ -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();
}
public override Bitmap Process()
var children = this.Children.OfType<SvgMergeNode>().ToList();
var inputImage = buffer[children.First().Input];
var result = new Bitmap(inputImage.Width, inputImage.Height);
using (var g = Graphics.FromImage(result))
{
//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;
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 SvgElement DeepCopy()
{
throw new NotImplementedException();
return DeepCopy<SvgMerge>();
}
}
......
......@@ -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<string>("in"); }
set { this.Attributes["in"] = value; }
}
public override SvgElement DeepCopy()
......
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
/// </summary>
[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;
}
......
......@@ -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)
......
......@@ -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
......@@ -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, 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, SvgRenderer 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,17 +101,11 @@ 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; }
}
private Matrix EffectiveGradientTransform
protected Matrix EffectiveGradientTransform
{
get
{
......@@ -130,7 +124,7 @@ namespace Svg
/// </summary>
/// <param name="owner">The parent <see cref="SvgVisualElement"/>.</param>
/// <param name="opacity">The opacity of the colour blend.</param>
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,9 +178,9 @@ 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;
mergedOpacity = opacity * currentStop.GetOpacity();
position =
radial
? 1 - (currentStop.Offset.ToDeviceValue(renderer, UnitRenderingType.Horizontal, this) / boundWidth)
......@@ -229,24 +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 static double CalculateDistance(PointF first, PointF second)
{
return Math.Sqrt(Math.Pow(first.X - second.X, 2) + Math.Pow(first.Y - second.Y, 2));
......
......@@ -13,8 +13,6 @@ namespace Svg
public class SvgGradientStop : SvgElement
{
private SvgUnit _offset;
private SvgPaintServer _colour;
private float _opacity;
/// <summary>
/// Gets or sets the offset, i.e. where the stop begins from the beginning, of the gradient stop.
......@@ -59,20 +57,31 @@ namespace Svg
/// </summary>
[SvgAttribute("stop-color")]
[TypeConverter(typeof(SvgPaintServerFactory))]
public SvgPaintServer Colour
public SvgPaintServer StopColor
{
get { return this._colour; }
set { this._colour = value; }
get
{
var direct = this.Attributes.GetAttribute<SvgPaintServer>("stop-color", SvgColourServer.NotSet);
if (direct == SvgColourServer.Inherit) return this.Attributes["stop-color"] as SvgPaintServer ?? SvgColourServer.NotSet;
return direct;
}
set { this.Attributes["stop-color"] = value; }
}
/// <summary>
/// Gets or sets the opacity of the gradient stop (0-1).
/// </summary>
[SvgAttribute("stop-opacity")]
public float Opacity
public string Opacity
{
get { return this.Attributes["stop-opacity"] as string; }
set { this.Attributes["stop-opacity"] = value; }
}
public float GetOpacity()
{
get { return this._opacity; }
set { this._opacity = value; }
var opacity = this.Opacity;
return string.IsNullOrEmpty(opacity) ? 1.0f : float.Parse(opacity);
}
/// <summary>
......@@ -81,8 +90,6 @@ namespace Svg
public SvgGradientStop()
{
this._offset = new SvgUnit(0.0f);
this._colour = SvgColourServer.NotSet;
this._opacity = 1.0f;
}
/// <summary>
......@@ -93,13 +100,11 @@ namespace Svg
public SvgGradientStop(SvgUnit offset, Color colour)
{
this._offset = offset;
this._colour = new SvgColourServer(colour);
this._opacity = 1.0f;
}
public Color GetColor(SvgElement parent)
{
var core = SvgDeferredPaintServer.TryGet<SvgColourServer>(_colour, parent);
var core = SvgDeferredPaintServer.TryGet<SvgColourServer>(this.StopColor, parent);
if (core == null) throw new InvalidOperationException("Invalid paint server for gradient stop detected.");
return core.Colour;
}
......@@ -113,9 +118,6 @@ namespace Svg
{
var newObj = base.DeepCopy<T>() as SvgGradientStop;
newObj.Offset = this.Offset;
newObj.Colour = this.Colour;
newObj.Opacity = this.Opacity;
return newObj;
}
}
......
......@@ -79,36 +79,94 @@ 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, bool forStroke = false)
{
LoadStops(renderingElement);
if (IsInvalid)
if (this.Stops.Count < 1) return null;
if (this.Stops.Count == 1)
{
return null;
var stopColor = this.Stops[0].GetColor(renderingElement);
int alpha = (int)((opacity * (stopColor.A/255.0f) ) * 255);
Color colour = System.Drawing.Color.FromArgb(alpha, stopColor);
return new SolidBrush(colour);
}
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);
var points = new PointF[] {
SvgUnit.GetDevicePoint(NormalizeUnit(this.X1), NormalizeUnit(this.Y1), renderer, this),
SvgUnit.GetDevicePoint(NormalizeUnit(this.X2), NormalizeUnit(this.Y2), renderer, this)
};
var effectiveStart = specifiedStart;
var effectiveEnd = specifiedEnd;
var bounds = renderer.GetBoundable().Bounds;
if (bounds.Width <= 0 || bounds.Height <= 0)
{
if (this.GetCallback != null) return GetCallback().GetBrush(renderingElement, renderer, opacity, forStroke);
return null;
}
using (var transform = EffectiveGradientTransform)
{
var midPoint = new PointF((points[0].X + points[1].X) / 2, (points[0].Y + points[1].Y) / 2);
transform.Translate(bounds.X, bounds.Y, MatrixOrder.Prepend);
if (this.GradientUnits == SvgCoordinateUnits.ObjectBoundingBox)
{
// Transform a normal (i.e. perpendicular line) according to the transform
transform.Scale(bounds.Width, bounds.Height, MatrixOrder.Prepend);
transform.RotateAt(-90.0f, midPoint, MatrixOrder.Prepend);
}
transform.TransformPoints(points);
}
if (NeedToExpandGradient(renderingElement, specifiedStart, specifiedEnd))
if (this.GradientUnits == SvgCoordinateUnits.ObjectBoundingBox)
{
var expansion = ExpandGradient(renderingElement, specifiedStart, specifiedEnd);
// Transform the normal line back to a line such that the gradient still starts in the correct corners, but
// has the proper normal vector based on the transforms. If you work out the geometry, these formulas should work.
var midPoint = new PointF((points[0].X + points[1].X) / 2, (points[0].Y + points[1].Y) / 2);
var dy = (points[1].Y - points[0].Y);
var dx = (points[1].X - points[0].X);
var x2 = points[0].X;
var y2 = points[1].Y;
if (Math.Round(dx, 4) == 0)
{
points[0] = new PointF(midPoint.X + dy / 2 * bounds.Width / bounds.Height, midPoint.Y);
points[1] = new PointF(midPoint.X - dy / 2 * bounds.Width / bounds.Height, midPoint.Y);
}
else if (Math.Round(dy, 4) == 0)
{
points[0] = new PointF(midPoint.X, midPoint.Y - dx / 2 * bounds.Height / bounds.Width);
points[1] = new PointF(midPoint.X, midPoint.Y + dx / 2 * bounds.Height / bounds.Width); ;
}
else
{
var startX = (float)((dy * dx * (midPoint.Y - y2) + Math.Pow(dx, 2) * midPoint.X + Math.Pow(dy, 2) * x2) /
(Math.Pow(dx, 2) + Math.Pow(dy, 2)));
var startY = dy * (startX - x2) / dx + y2;
points[0] = new PointF(startX, startY);
points[1] = new PointF(midPoint.X + (midPoint.X - startX), midPoint.Y + (midPoint.Y - startY));
}
}
var effectiveStart = points[0];
var effectiveEnd = points[1];
if (PointsToMove(renderingElement, points[0], points[1]) > LinePoints.None)
{
var expansion = ExpandGradient(renderingElement, points[0], points[1]);
effectiveStart = expansion.StartPoint;
effectiveEnd = expansion.EndPoint;
}
return new LinearGradientBrush(effectiveStart, effectiveEnd, System.Drawing.Color.Transparent, System.Drawing.Color.Transparent)
var result = new LinearGradientBrush(effectiveStart, effectiveEnd, System.Drawing.Color.Transparent, System.Drawing.Color.Transparent)
{
InterpolationColors = CalculateColorBlend(renderer, opacity, specifiedStart, effectiveStart, specifiedEnd, effectiveEnd),
InterpolationColors = CalculateColorBlend(renderer, opacity, points[0], effectiveStart, points[1], effectiveEnd),
WrapMode = WrapMode.TileFlipX
};
return result;
}
finally
{
......@@ -116,19 +174,36 @@ namespace Svg
}
}
private PointF CalculateStart(SvgRenderer renderer)
private SvgUnit NormalizeUnit(SvgUnit orig)
{
return TransformPoint(SvgUnit.GetDevicePointOffset(this.X1, this.Y1, renderer, this));
return (orig.Type == SvgUnitType.Percentage && this.GradientUnits == SvgCoordinateUnits.ObjectBoundingBox ?
new SvgUnit(SvgUnitType.User, orig.Value / 100) :
orig);
}
private PointF CalculateEnd(SvgRenderer renderer)
[Flags]
private enum LinePoints
{
return TransformPoint(SvgUnit.GetDevicePointOffset(this.X2, this.Y2, renderer, this));
None = 0,
Start = 1,
End = 2
}
private bool NeedToExpandGradient(ISvgBoundable boundable, PointF specifiedStart, PointF specifiedEnd)
private LinePoints PointsToMove(ISvgBoundable boundable, PointF specifiedStart, PointF specifiedEnd)
{
return SpreadMethod == SvgGradientSpreadMethod.Pad && (boundable.Bounds.Contains(specifiedStart) || boundable.Bounds.Contains(specifiedEnd));
var bounds = boundable.Bounds;
if (specifiedStart.X == specifiedEnd.X)
{
return (bounds.Top < specifiedStart.Y && specifiedStart.Y < bounds.Bottom ? LinePoints.Start : LinePoints.None) |
(bounds.Top < specifiedEnd.Y && specifiedEnd.Y < bounds.Bottom ? LinePoints.End : LinePoints.None);
}
else if (specifiedStart.Y == specifiedEnd.Y)
{
return (bounds.Left < specifiedStart.X && specifiedStart.X < bounds.Right ? LinePoints.Start : LinePoints.None) |
(bounds.Left < specifiedEnd.X && specifiedEnd.X < bounds.Right ? LinePoints.End : LinePoints.None);
}
return (boundable.Bounds.Contains(specifiedStart) ? LinePoints.Start : LinePoints.None) |
(boundable.Bounds.Contains(specifiedEnd) ? LinePoints.End : LinePoints.None);
}
public struct GradientPoints
......@@ -145,44 +220,98 @@ namespace Svg
private GradientPoints ExpandGradient(ISvgBoundable boundable, PointF specifiedStart, PointF specifiedEnd)
{
if (!NeedToExpandGradient(boundable, specifiedStart, specifiedEnd))
var pointsToMove = PointsToMove(boundable, specifiedStart, specifiedEnd);
if (pointsToMove == LinePoints.None)
{
Debug.Fail("Unexpectedly expanding gradient when not needed!");
return new GradientPoints(specifiedStart, specifiedEnd);
}
var specifiedLength = CalculateDistance(specifiedStart, specifiedEnd);
var specifiedUnitVector = new PointF((specifiedEnd.X - specifiedStart.X) / (float)specifiedLength, (specifiedEnd.Y - specifiedStart.Y) / (float)specifiedLength);
var bounds = boundable.Bounds;
var effectiveStart = specifiedStart;
var effectiveEnd = specifiedEnd;
var intersectionPoints = CandidateIntersections(bounds, specifiedStart, specifiedEnd);
var elementDiagonal = (float)CalculateDistance(new PointF(boundable.Bounds.Left, boundable.Bounds.Top), new PointF(boundable.Bounds.Right, boundable.Bounds.Bottom));
Debug.Assert(intersectionPoints.Count == 2, "Unanticipated number of intersection points");
var expandedStart = MovePointAlongVector(effectiveStart, specifiedUnitVector, -elementDiagonal);
var expandedEnd = MovePointAlongVector(effectiveEnd, specifiedUnitVector, elementDiagonal);
if (!(Math.Sign(intersectionPoints[1].X - intersectionPoints[0].X) == Math.Sign(specifiedEnd.X - specifiedStart.X) &&
Math.Sign(intersectionPoints[1].Y - intersectionPoints[0].Y) == Math.Sign(specifiedEnd.Y - specifiedStart.Y)))
{
intersectionPoints = intersectionPoints.Reverse().ToList();
}
var intersectionPoints = new LineF(expandedStart.X, expandedStart.Y, expandedEnd.X, expandedEnd.Y).Intersection(boundable.Bounds);
if ((pointsToMove & LinePoints.Start) > 0) effectiveStart = intersectionPoints[0];
if ((pointsToMove & LinePoints.End) > 0) effectiveEnd = intersectionPoints[1];
if (boundable.Bounds.Contains(specifiedStart))
switch (SpreadMethod)
{
effectiveStart = CalculateClosestIntersectionPoint(expandedStart, intersectionPoints);
case SvgGradientSpreadMethod.Reflect:
case SvgGradientSpreadMethod.Repeat:
var specifiedLength = CalculateDistance(specifiedStart, specifiedEnd);
var specifiedUnitVector = new PointF((specifiedEnd.X - specifiedStart.X) / (float)specifiedLength, (specifiedEnd.Y - specifiedStart.Y) / (float)specifiedLength);
var oppUnitVector = new PointF(-specifiedUnitVector.X, -specifiedUnitVector.Y);
effectiveStart = MovePointAlongVector(effectiveStart, specifiedUnitVector, -1);
var startExtend = (float)(Math.Ceiling(CalculateDistance(effectiveStart, specifiedStart) / specifiedLength) * specifiedLength);
effectiveStart = MovePointAlongVector(specifiedStart, oppUnitVector, startExtend);
var endExtend = (float)(Math.Ceiling(CalculateDistance(effectiveEnd, specifiedEnd) / specifiedLength) * specifiedLength);
effectiveEnd = MovePointAlongVector(specifiedEnd, specifiedUnitVector, endExtend);
break;
}
if (boundable.Bounds.Contains(specifiedEnd))
{
effectiveEnd = CalculateClosestIntersectionPoint(effectiveEnd, intersectionPoints);
return new GradientPoints(effectiveStart, effectiveEnd);
}
effectiveEnd = MovePointAlongVector(effectiveEnd, specifiedUnitVector, 1);
private IList<PointF> CandidateIntersections(RectangleF bounds, PointF p1, PointF p2)
{
var results = new List<PointF>();
if (Math.Round(Math.Abs(p1.Y - p2.Y), 4) == 0)
{
results.Add(new PointF(bounds.Left, p1.Y));
results.Add(new PointF(bounds.Right, p1.Y));
}
else if (Math.Round(Math.Abs(p1.X - p2.X), 4) == 0)
{
results.Add(new PointF(p1.X, bounds.Top));
results.Add(new PointF(p1.X, bounds.Bottom));
}
else
{
PointF candidate;
// Save some effort and duplication in the trivial case
if ((p1.X == bounds.Left || p1.X == bounds.Right) && (p1.Y == bounds.Top || p1.Y == bounds.Bottom))
{
results.Add(p1);
}
else
{
candidate = new PointF(bounds.Left, (p2.Y - p1.Y) / (p2.X - p1.X) * (bounds.Left - p1.X) + p1.Y);
if (bounds.Top <= candidate.Y && candidate.Y <= bounds.Bottom) results.Add(candidate);
candidate = new PointF(bounds.Right, (p2.Y - p1.Y) / (p2.X - p1.X) * (bounds.Right - p1.X) + p1.Y);
if (bounds.Top <= candidate.Y && candidate.Y <= bounds.Bottom) results.Add(candidate);
}
if ((p2.X == bounds.Left || p2.X == bounds.Right) && (p2.Y == bounds.Top || p2.Y == bounds.Bottom))
{
results.Add(p2);
}
else
{
candidate = new PointF((bounds.Top - p1.Y) / (p2.Y - p1.Y) * (p2.X - p1.X) + p1.X, bounds.Top);
if (bounds.Left <= candidate.X && candidate.X <= bounds.Right) results.Add(candidate);
candidate = new PointF((bounds.Bottom - p1.Y) / (p2.Y - p1.Y) * (p2.X - p1.X) + p1.X, bounds.Bottom);
if (bounds.Left <= candidate.X && candidate.X <= bounds.Right) results.Add(candidate);
}
}
return new GradientPoints(effectiveStart, effectiveEnd);
return results;
}
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)
{
float startExtend;
float endExtend;
List<Color> colors;
List<float> positions;
var colorBlend = GetColorBlend(renderer, opacity, false);
var startDelta = CalculateDistance(specifiedStart, effectiveStart);
......@@ -198,6 +327,80 @@ namespace Svg
var effectiveLength = CalculateDistance(effectiveStart, effectiveEnd);
switch (SpreadMethod)
{
case SvgGradientSpreadMethod.Reflect:
startExtend = (float)(Math.Ceiling(CalculateDistance(effectiveStart, specifiedStart) / specifiedLength));
endExtend = (float)(Math.Ceiling(CalculateDistance(effectiveEnd, specifiedEnd) / specifiedLength));
colors = colorBlend.Colors.ToList();
positions = (from p in colorBlend.Positions select p + startExtend).ToList();
for (var i = 0; i < startExtend; i++)
{
if (i % 2 == 0)
{
for (var j = 1; j < colorBlend.Positions.Length; j++)
{
positions.Insert(0, (float)((startExtend - 1 - i) + 1 - colorBlend.Positions[j]));
colors.Insert(0, colorBlend.Colors[j]);
}
}
else
{
for (var j = 0; j < colorBlend.Positions.Length - 1; j++)
{
positions.Insert(j, (float)((startExtend - 1 - i) + colorBlend.Positions[j]));
colors.Insert(j, colorBlend.Colors[j]);
}
}
}
int insertPos;
for (var i = 0; i < endExtend; i++)
{
if (i % 2 == 0)
{
insertPos = positions.Count;
for (var j = 0; j < colorBlend.Positions.Length - 1; j++)
{
positions.Insert(insertPos, (float)((startExtend + 1 + i) + 1 - colorBlend.Positions[j]));
colors.Insert(insertPos, colorBlend.Colors[j]);
}
}
else
{
for (var j = 1; j < colorBlend.Positions.Length; j++)
{
positions.Add((float)((startExtend + 1 + i) + colorBlend.Positions[j]));
colors.Add(colorBlend.Colors[j]);
}
}
}
colorBlend.Colors = colors.ToArray();
colorBlend.Positions = (from p in positions select p / (startExtend + 1 + endExtend)).ToArray();
break;
case SvgGradientSpreadMethod.Repeat:
startExtend = (float)(Math.Ceiling(CalculateDistance(effectiveStart, specifiedStart) / specifiedLength));
endExtend = (float)(Math.Ceiling(CalculateDistance(effectiveEnd, specifiedEnd) / specifiedLength));
colors = new List<Color>();
positions = new List<float>();
for (int i = 0; i < startExtend + endExtend + 1; i++)
{
for (int j = 0; j < colorBlend.Positions.Length; j++)
{
positions.Add((i + colorBlend.Positions[j] * 0.9999f) / (startExtend + endExtend + 1));
colors.Add(colorBlend.Colors[j]);
}
}
positions[positions.Count - 1] = 1.0f;
colorBlend.Colors = colors.ToArray();
colorBlend.Positions = positions.ToArray();
break;
default:
for (var i = 0; i < colorBlend.Positions.Length; i++)
{
var originalPoint = MovePointAlongVector(specifiedStart, specifiedUnitVector, (float)specifiedLength * colorBlend.Positions[i]);
......@@ -218,6 +421,8 @@ namespace Svg
colorBlend.Positions = colorBlend.Positions.Concat(new[] { 1F }).ToArray();
colorBlend.Colors = colorBlend.Colors.Concat(new[] { colorBlend.Colors.Last() }).ToArray();
}
break;
}
return colorBlend;
}
......@@ -296,48 +501,45 @@ namespace Svg
return result;
}
/// <remarks>http://community.topcoder.com/tc?module=Static&d1=tutorials&d2=geometry2</remarks>
private PointF? Intersection(LineF other)
{
var a1 = Y2 - Y1;
var b1 = X1 - X2;
var c1 = X2 * Y1 - X1 * Y2;
const int precision = 8;
var a1 = (double)Y2 - Y1;
var b1 = (double)X1 - X2;
var c1 = a1 * X1 + b1 * Y1;
var r3 = a1 * other.X1 + b1 * other.Y1 + c1;
var r4 = a1 * other.X2 + b1 * other.Y2 + c1;
var a2 = (double)other.Y2 - other.Y1;
var b2 = (double)other.X1 - other.X2;
var c2 = a2 * other.X1 + b2 * other.Y1;
if (r3 != 0 && r4 != 0 && Math.Sign(r3) == Math.Sign(r4))
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;
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))
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 (null);
return new PointF((float)xi, (float)yi);
}
var denom = a1 * b2 - a2 * b1;
if (denom == 0)
else
{
return null;
}
var offset = denom < 0 ? -denom / 2 : denom / 2;
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;
return new PointF(x, y);
}
}
private static void AddIfIntersect(LineF first, LineF second, ICollection<PointF> result)
......
......@@ -87,11 +87,11 @@ 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);
var path = this.Children.FirstOrDefault(x => x is SvgVisualElement);
if (path != null)
return (path as SvgPath).Path(renderer);
return (path as SvgVisualElement).Path(renderer);
return null;
}
......@@ -131,7 +131,7 @@ namespace Svg
/// <param name="pOwner"></param>
/// <param name="pMarkerPoint1"></param>
/// <param name="pMarkerPoint2"></param>
public void RenderMarker(SvgRenderer pRenderer, SvgPath pOwner, PointF pRefPoint, PointF pMarkerPoint1, PointF pMarkerPoint2)
public void RenderMarker(ISvgRenderer pRenderer, SvgVisualElement 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
/// <param name="pMarkerPoint1"></param>
/// <param name="pMarkerPoint2"></param>
/// <param name="pMarkerPoint3"></param>
public void RenderMarker(SvgRenderer pRenderer, SvgPath pOwner, PointF pRefPoint, PointF pMarkerPoint1, PointF pMarkerPoint2, PointF pMarkerPoint3)
public void RenderMarker(ISvgRenderer pRenderer, SvgVisualElement pOwner, PointF pRefPoint, PointF pMarkerPoint1, PointF pMarkerPoint2, PointF pMarkerPoint3)
{
float xDiff = pMarkerPoint2.X - pMarkerPoint1.X;
float yDiff = pMarkerPoint2.Y - pMarkerPoint1.Y;
......@@ -168,13 +168,14 @@ namespace Svg
/// <param name="pRenderer"></param>
/// <param name="pOwner"></param>
/// <param name="pMarkerPoint"></param>
private void RenderPart2(float fAngle, SvgRenderer pRenderer, SvgPath pOwner, PointF pMarkerPoint)
private void RenderPart2(float fAngle, ISvgRenderer pRenderer, SvgVisualElement pOwner, PointF pMarkerPoint)
{
using (var pRenderPen = CreatePen(pOwner, pRenderer))
{
using (var markerPath = GetClone(pOwner))
{
using (var transMatrix = new Matrix())
{
Pen pRenderPen = CreatePen(pOwner, pRenderer);
GraphicsPath markerPath = GetClone(pOwner);
Matrix transMatrix = new Matrix();
transMatrix.Translate(pMarkerPoint.X, pMarkerPoint.Y);
if (Orient.IsAuto)
transMatrix.Rotate(fAngle);
......@@ -194,21 +195,22 @@ namespace Svg
break;
}
markerPath.Transform(transMatrix);
pRenderer.DrawPath(pRenderPen, markerPath);
if (pRenderPen != null) pRenderer.DrawPath(pRenderPen, markerPath);
SvgPaintServer pFill = Fill;
SvgPaintServer pFill = this.Children.First().Fill;
SvgFillRule pFillRule = FillRule; // TODO: What do we use the fill rule for?
float fOpacity = FillOpacity;
if (pFill != null)
{
Brush pBrush = pFill.GetBrush(this, pRenderer, fOpacity);
using (var pBrush = pFill.GetBrush(this, pRenderer, fOpacity))
{
pRenderer.FillPath(pBrush, markerPath);
pBrush.Dispose();
}
pRenderPen.Dispose();
markerPath.Dispose();
transMatrix.Dispose();
}
}
}
}
}
/// <summary>
......@@ -216,8 +218,9 @@ namespace Svg
/// </summary>
/// <param name="pStroke"></param>
/// <returns></returns>
private Pen CreatePen(SvgPath pPath, SvgRenderer renderer)
private Pen CreatePen(SvgVisualElement pPath, ISvgRenderer renderer)
{
if (pPath.Stroke == null) return null;
Brush pBrush = pPath.Stroke.GetBrush(this, renderer, Opacity);
switch (MarkerUnits)
{
......@@ -231,18 +234,20 @@ namespace Svg
}
/// <summary>
/// Get a clone of the current path, scaled for the stroke with
/// Get a clone of the current path, scaled for the stroke width
/// </summary>
/// <returns></returns>
private GraphicsPath GetClone(SvgPath pPath)
private GraphicsPath GetClone(SvgVisualElement pPath)
{
GraphicsPath pRet = Path(null).Clone() as GraphicsPath;
switch (MarkerUnits)
{
case SvgMarkerUnits.strokeWidth:
Matrix transMatrix = new Matrix();
using (var transMatrix = new Matrix())
{
transMatrix.Scale(AdjustForViewBoxWidth(pPath.StrokeWidth), AdjustForViewBoxHeight(pPath.StrokeWidth));
pRet.Transform(transMatrix);
}
break;
case SvgMarkerUnits.userSpaceOnUse:
break;
......@@ -258,7 +263,7 @@ namespace Svg
private float AdjustForViewBoxWidth(float fWidth)
{
// TODO: We know this isn't correct
return (fWidth / ViewBox.Width);
return (ViewBox.Width <= 0 ? 1 : fWidth / ViewBox.Width);
}
/// <summary>
......@@ -269,7 +274,7 @@ namespace Svg
private float AdjustForViewBoxHeight(float fHeight)
{
// TODO: We know this isn't correct
return (fHeight / ViewBox.Height);
return (ViewBox.Height <= 0 ? 1 : fHeight / ViewBox.Height);
}
}
}
\ No newline at end of file
......@@ -13,6 +13,8 @@ namespace Svg
[TypeConverter(typeof(SvgPaintServerFactory))]
public abstract class SvgPaintServer : SvgElement
{
public Func<SvgPaintServer> GetCallback { get; set; }
/// <summary>
/// An unspecified <see cref="SvgPaintServer"/>.
/// </summary>
......@@ -26,10 +28,10 @@ namespace Svg
}
/// <summary>
/// Renders the <see cref="SvgElement"/> and contents to the specified <see cref="SvgRenderer"/> object.
/// Renders the <see cref="SvgElement"/> and contents to the specified <see cref="ISvgRenderer"/> object.
/// </summary>
/// <param name="renderer">The <see cref="SvgRenderer"/> object to render to.</param>
protected override void Render(SvgRenderer renderer)
/// <param name="renderer">The <see cref="ISvgRenderer"/> object to render to.</param>
protected override void Render(ISvgRenderer renderer)
{
// Never render paint servers or their children
}
......@@ -39,7 +41,7 @@ namespace Svg
/// </summary>
/// <param name="styleOwner">The owner <see cref="SvgVisualElement"/>.</param>
/// <param name="opacity">The opacity of the brush.</param>
public abstract Brush GetBrush(SvgVisualElement styleOwner, SvgRenderer renderer, float opacity);
public abstract Brush GetBrush(SvgVisualElement styleOwner, ISvgRenderer renderer, float opacity, bool forStroke = false);
/// <summary>
/// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
......
using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
using System.ComponentModel;
using System.Drawing;
using System.Globalization;
using System.Linq;
namespace Svg
{
internal class SvgPaintServerFactory : TypeConverter
{
private static readonly SvgColourConverter _colourConverter;
private static readonly Regex _urlRefPattern;
static SvgPaintServerFactory()
{
_colourConverter = new SvgColourConverter();
_urlRefPattern = new Regex(@"url\((#[^)]+)\)");
}
public static SvgPaintServer Create(string value, SvgDocument document)
......@@ -35,11 +31,18 @@ namespace Svg
{
return new SvgDeferredPaintServer(document, value);
}
else if (value.IndexOf("url(#") > -1)
else
{
var servers = new List<SvgPaintServer>();
while (!string.IsNullOrEmpty(value))
{
Match match = _urlRefPattern.Match(value);
Uri id = new Uri(match.Groups[1].Value, UriKind.Relative);
return (SvgPaintServer)document.IdManager.GetElementById(id);
if (value.StartsWith("url(#"))
{
var leftParen = value.IndexOf(')', 5);
Uri id = new Uri(value.Substring(5, leftParen - 5), UriKind.Relative);
value = value.Substring(leftParen + 1).Trim();
servers.Add((SvgPaintServer)document.IdManager.GetElementById(id));
}
// If referenced to to a different (linear or radial) gradient
else if (document.IdManager.GetElementById(value) != null && document.IdManager.GetElementById(value).GetType().BaseType == typeof(SvgGradientServer))
......@@ -48,19 +51,49 @@ namespace Svg
}
else if (value.StartsWith("#")) // Otherwise try and parse as colour
{
try
switch(CountHexDigits(value, 1))
{
case 3:
servers.Add(new SvgColourServer((Color)_colourConverter.ConvertFrom(value.Substring(0, 4))));
value = value.Substring(4).Trim();
break;
case 6:
servers.Add(new SvgColourServer((Color)_colourConverter.ConvertFrom(value.Substring(0, 7))));
value = value.Substring(7).Trim();
break;
default:
return new SvgDeferredPaintServer(document, value);
}
}
else
{
return new SvgColourServer((Color)_colourConverter.ConvertFrom(value.Trim()));
}
catch
}
if (servers.Count > 1)
{
return new SvgDeferredPaintServer(document, value);
return new SvgFallbackPaintServer(servers[0], servers.Skip(1));
}
return servers[0];
}
else
}
private static int CountHexDigits(string value, int start)
{
return new SvgColourServer((Color)_colourConverter.ConvertFrom(value.Trim()));
int i = Math.Max(start, 0);
int count = 0;
while (i < value.Length &&
((value[i] >= '0' && value[i] <= '9') ||
(value[i] >= 'a' && value[i] <= 'f') ||
(value[i] >= 'A' && value[i] <= 'F')))
{
count++;
i++;
}
return count;
}
public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
......
......@@ -6,6 +6,7 @@ using System.Drawing;
using System.ComponentModel;
using Svg.Transforms;
using System.Linq;
namespace Svg
{
......@@ -19,9 +20,10 @@ namespace Svg
private SvgUnit _height;
private SvgUnit _x;
private SvgUnit _y;
private SvgPaintServer _inheritGradient;
private SvgViewBox _viewBox;
private SvgCoordinateUnits _patternUnits;
private SvgCoordinateUnits _patternContentUnits;
private SvgCoordinateUnits _patternUnits = SvgCoordinateUnits.Inherit;
private SvgCoordinateUnits _patternContentUnits = SvgCoordinateUnits.Inherit;
[SvgAttribute("overflow")]
public SvgOverflow Overflow
......@@ -76,7 +78,7 @@ namespace Svg
/// <summary>
/// Gets or sets the width of the pattern.
/// </summary>
[SvgAttribute("patternUnits")]
[SvgAttribute("patternContentUnits")]
public SvgCoordinateUnits PatternContentUnits
{
get { return this._patternContentUnits; }
......@@ -113,15 +115,56 @@ namespace Svg
set { this._y = value; }
}
/// <summary>
/// Gets or sets another gradient fill from which to inherit the stops from.
/// </summary>
[SvgAttribute("href", SvgAttributeAttribute.XLinkNamespace)]
public SvgPaintServer InheritGradient
{
get { return this._inheritGradient; }
set
{
this._inheritGradient = value;
}
}
[SvgAttribute("patternTransform")]
public SvgTransformCollection PatternTransform
{
get { return (this.Attributes.GetAttribute<SvgTransformCollection>("gradientTransform")); }
set { this.Attributes["gradientTransform"] = value; }
}
protected Matrix EffectivePatternTransform
{
get
{
var transform = new Matrix();
if (PatternTransform != null)
{
transform.Multiply(PatternTransform.GetMatrix());
}
return transform;
}
}
/// <summary>
/// Initializes a new instance of the <see cref="SvgPatternServer"/> class.
/// </summary>
public SvgPatternServer()
{
this._x = new SvgUnit(0.0f);
this._y = new SvgUnit(0.0f);
this._width = new SvgUnit(0.0f);
this._height = new SvgUnit(0.0f);
this._x = SvgUnit.None;
this._y = SvgUnit.None;
this._width = SvgUnit.None;
this._height = SvgUnit.None;
}
private SvgUnit NormalizeUnit(SvgUnit orig)
{
return (orig.Type == SvgUnitType.Percentage && this.PatternUnits == SvgCoordinateUnits.ObjectBoundingBox ?
new SvgUnit(SvgUnitType.User, orig.Value / 100) :
orig);
}
/// <summary>
......@@ -129,76 +172,85 @@ namespace Svg
/// </summary>
/// <param name="renderingElement">The owner <see cref="SvgVisualElement"/>.</param>
/// <param name="opacity">The opacity of the brush.</param>
public override Brush GetBrush(SvgVisualElement renderingElement, SvgRenderer renderer, float opacity)
public override Brush GetBrush(SvgVisualElement renderingElement, ISvgRenderer renderer, float opacity, bool forStroke = false)
{
var chain = new List<SvgPatternServer>();
var curr = this;
while (curr != null)
{
// If there aren't any children, return null
if (this.Children.Count == 0)
return null;
chain.Add(curr);
curr = SvgDeferredPaintServer.TryGet<SvgPatternServer>(curr._inheritGradient, renderingElement);
}
// Can't render if there are no dimensions
if (this._width.Value == 0.0f || this._height.Value == 0.0f)
return null;
var childElem = chain.Where((p) => p.Children != null && p.Children.Count > 0).FirstOrDefault();
if (childElem == null) return null;
var widthElem = chain.Where((p) => p.Width != null && p.Width != SvgUnit.None).FirstOrDefault();
var heightElem = chain.Where((p) => p.Height != null && p.Height != SvgUnit.None).FirstOrDefault();
if (widthElem == null && heightElem == null) return null;
try
{
if (this.PatternUnits == SvgCoordinateUnits.ObjectBoundingBox) renderer.Boundable(renderingElement);
var viewBoxElem = chain.Where((p) => p.ViewBox != null && p.ViewBox != SvgViewBox.Empty).FirstOrDefault();
var viewBox = viewBoxElem == null ? SvgViewBox.Empty : viewBoxElem.ViewBox;
var xElem = chain.Where((p) => p.X != null && p.X != SvgUnit.None).FirstOrDefault();
var yElem = chain.Where((p) => p.Y != null && p.Y != SvgUnit.None).FirstOrDefault();
var xUnit = xElem == null ? SvgUnit.Empty : xElem.X;
var yUnit = yElem == null ? SvgUnit.Empty : yElem.Y;
float width = this._width.ToDeviceValue(renderer, UnitRenderingType.Horizontal, this);
float height = this._height.ToDeviceValue(renderer, UnitRenderingType.Vertical, this);
var patternUnitElem = chain.Where((p) => p.PatternUnits != SvgCoordinateUnits.Inherit).FirstOrDefault();
var patternUnits = (patternUnitElem == null ? SvgCoordinateUnits.ObjectBoundingBox : patternUnitElem.PatternUnits);
var patternContentUnitElem = chain.Where((p) => p.PatternContentUnits != SvgCoordinateUnits.Inherit).FirstOrDefault();
var patternContentUnits = (patternContentUnitElem == null ? SvgCoordinateUnits.UserSpaceOnUse : patternContentUnitElem.PatternContentUnits);
Matrix patternMatrix = new Matrix();
// Apply a translate if needed
if (this._x.Value > 0.0f || this._y.Value > 0.0f)
try
{
float x = this._x.ToDeviceValue(renderer, UnitRenderingType.HorizontalOffset, this);
float y = this._y.ToDeviceValue(renderer, UnitRenderingType.VerticalOffset, this);
if (patternUnits == SvgCoordinateUnits.ObjectBoundingBox) renderer.SetBoundable(renderingElement);
patternMatrix.Translate(x + -1.0f, y + -1.0f);
}
else
using (var patternMatrix = new Matrix())
{
patternMatrix.Translate(-1, -1);
}
var bounds = renderer.GetBoundable().Bounds;
var xScale = (patternUnits == SvgCoordinateUnits.ObjectBoundingBox ? bounds.Width : 1);
var yScale = (patternUnits == SvgCoordinateUnits.ObjectBoundingBox ? bounds.Height : 1);
if (this.ViewBox.Height > 0 || this.ViewBox.Width > 0)
{
patternMatrix.Scale(this.Width.ToDeviceValue(renderer, UnitRenderingType.Horizontal, this) / this.ViewBox.Width,
this.Height.ToDeviceValue(renderer, UnitRenderingType.Vertical, this) / this.ViewBox.Height);
}
float x = xScale * NormalizeUnit(xUnit).ToDeviceValue(renderer, UnitRenderingType.Horizontal, this);
float y = yScale * NormalizeUnit(yUnit).ToDeviceValue(renderer, UnitRenderingType.Vertical, this);
float width = xScale * NormalizeUnit(widthElem.Width).ToDeviceValue(renderer, UnitRenderingType.Horizontal, this);
float height = yScale * NormalizeUnit(heightElem.Height).ToDeviceValue(renderer, UnitRenderingType.Vertical, this);
// Apply a scale if needed
patternMatrix.Scale((patternContentUnits == SvgCoordinateUnits.ObjectBoundingBox ? bounds.Width : 1) *
(viewBox.Width > 0 ? width / viewBox.Width : 1),
(patternContentUnits == SvgCoordinateUnits.ObjectBoundingBox ? bounds.Height : 1) *
(viewBox.Height > 0 ? height / viewBox.Height : 1), MatrixOrder.Prepend);
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;
iRenderer.SetClip(new Region(new RectangleF(0, 0,
viewBox.Width > 0 ? viewBox.Width : width,
viewBox.Height > 0 ? viewBox.Height : height)));
foreach (SvgElement child in this.Children)
foreach (SvgElement child in childElem.Children)
{
child.RenderElement(iRenderer);
}
iRenderer.Save();
}
image.Save(string.Format(@"C:\test{0:D3}.png", imgNumber++));
TextureBrush textureBrush = new TextureBrush(image);
var brushTransform = EffectivePatternTransform.Clone();
brushTransform.Translate(x, y, MatrixOrder.Append);
textureBrush.Transform = brushTransform;
return textureBrush;
}
}
finally
{
if (this.PatternUnits == SvgCoordinateUnits.ObjectBoundingBox) renderer.PopBoundable();
}
}
private static int imgNumber = 0;
public override SvgElement DeepCopy()
{
return DeepCopy<SvgPatternServer>();
......
using System;
using System.Diagnostics;
using System.Drawing;
using System.Collections.Generic;
using System.Drawing.Drawing2D;
using System.Linq;
......@@ -95,88 +96,207 @@ namespace Svg
Radius = new SvgUnit(SvgUnitType.Percentage, 50F);
}
public override Brush GetBrush(SvgVisualElement renderingElement, SvgRenderer renderer, float opacity)
private object _lockObj = new Object();
private SvgUnit NormalizeUnit(SvgUnit orig)
{
return (orig.Type == SvgUnitType.Percentage && this.GradientUnits == SvgCoordinateUnits.ObjectBoundingBox ?
new SvgUnit(SvgUnitType.User, orig.Value / 100) :
orig);
}
public override Brush GetBrush(SvgVisualElement renderingElement, ISvgRenderer renderer, float opacity, bool forStroke = false)
{
LoadStops(renderingElement);
try
{
if (this.GradientUnits == SvgCoordinateUnits.ObjectBoundingBox) renderer.Boundable(renderingElement);
var origin = renderer.Boundable().Location;
var centerPoint = CalculateCenterPoint(renderer, origin);
var focalPoint = CalculateFocalPoint(renderer, origin);
if (this.GradientUnits == SvgCoordinateUnits.ObjectBoundingBox) renderer.SetBoundable(renderingElement);
var specifiedRadius = CalculateRadius(renderer);
var effectiveRadius = CalculateEffectiveRadius(renderingElement, centerPoint, specifiedRadius);
// Calculate the path and transform it appropriately
var center = new PointF(NormalizeUnit(CenterX).ToDeviceValue(renderer, UnitRenderingType.Horizontal, this),
NormalizeUnit(CenterY).ToDeviceValue(renderer, UnitRenderingType.Vertical, this));
var focals = new PointF[] {new PointF(NormalizeUnit(FocalX).ToDeviceValue(renderer, UnitRenderingType.Horizontal, this),
NormalizeUnit(FocalY).ToDeviceValue(renderer, UnitRenderingType.Vertical, this)) };
var specifiedRadius = NormalizeUnit(Radius).ToDeviceValue(renderer, UnitRenderingType.Other, this);
var path = new GraphicsPath();
path.AddEllipse(
center.X - specifiedRadius, center.Y - specifiedRadius,
specifiedRadius * 2, specifiedRadius * 2
);
var brush = new PathGradientBrush(CreateGraphicsPath(origin, centerPoint, effectiveRadius))
using (var transform = EffectiveGradientTransform)
{
InterpolationColors = CalculateColorBlend(renderer, opacity, specifiedRadius, effectiveRadius),
CenterPoint = focalPoint
};
var bounds = renderer.GetBoundable().Bounds;
transform.Translate(bounds.X, bounds.Y, MatrixOrder.Prepend);
if (this.GradientUnits == SvgCoordinateUnits.ObjectBoundingBox)
{
transform.Scale(bounds.Width, bounds.Height, MatrixOrder.Prepend);
}
path.Transform(transform);
transform.TransformPoints(focals);
}
Debug.Assert(brush.Rectangle.Contains(renderingElement.Bounds), "Brush rectangle does not contain rendering element bounds!");
return brush;
// Calculate any required scaling
var scaleBounds = RectangleF.Inflate(renderingElement.Bounds, renderingElement.StrokeWidth, renderingElement.StrokeWidth);
var scale = CalcScale(scaleBounds, path);
// Not ideal, but this makes sure that the rest of the shape gets properly filled or drawn
if (scale > 1.0f && SpreadMethod == SvgGradientSpreadMethod.Pad)
{
var stop = Stops.Last();
var origColor = stop.GetColor(renderingElement);
var renderColor = System.Drawing.Color.FromArgb((int)(opacity * stop.GetOpacity() * 255), origColor);
var origClip = renderer.GetClip();
try
{
using (var solidBrush = new SolidBrush(renderColor))
{
var newClip = origClip.Clone();
newClip.Exclude(path);
renderer.SetClip(newClip);
var renderPath = (GraphicsPath)renderingElement.Path(renderer);
if (forStroke)
{
using (var pen = new Pen(solidBrush, renderingElement.StrokeWidth.ToDeviceValue(renderer, UnitRenderingType.Other, renderingElement)))
{
renderer.DrawPath(pen, renderPath);
}
}
else
{
renderer.FillPath(solidBrush, renderPath);
}
}
}
finally
{
if (this.GradientUnits == SvgCoordinateUnits.ObjectBoundingBox) renderer.PopBoundable();
renderer.SetClip(origClip);
}
}
private PointF CalculateCenterPoint(SvgRenderer renderer, PointF origin)
// 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);
using (var scaleMat = new Matrix())
{
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;
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);
}
private PointF CalculateFocalPoint(SvgRenderer renderer, PointF origin)
// calculate the brush
var brush = new PathGradientBrush(path);
brush.CenterPoint = focals[0];
brush.InterpolationColors = blend;
return brush;
}
finally
{
var deviceFocalX = origin.X + FocalX.ToDeviceValue(renderer, UnitRenderingType.HorizontalOffset, this);
var deviceFocalY = origin.Y + FocalY.ToDeviceValue(renderer, UnitRenderingType.VerticalOffset, this);
var transformedFocalPoint = TransformPoint(new PointF(deviceFocalX, deviceFocalY));
return transformedFocalPoint;
if (this.GradientUnits == SvgCoordinateUnits.ObjectBoundingBox) renderer.PopBoundable();
}
}
private float CalculateRadius(SvgRenderer renderer)
/// <summary>
/// Determine how much (approximately) the path must be scaled to contain the rectangle
/// </summary>
/// <param name="bounds">Bounds that the path must contain</param>
/// <param name="path">Path of the gradient</param>
/// <returns>Scale factor</returns>
/// <remarks>
/// 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%
/// </remarks>
private float CalcScale(RectangleF bounds, GraphicsPath path)
{
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);
using (var transform = new Matrix())
{
transform.Translate(-1 * pathCenter.X, -1 * pathCenter.Y, MatrixOrder.Append);
transform.Scale(.95f, .95f, MatrixOrder.Append);
transform.Translate(pathCenter.X, pathCenter.Y, MatrixOrder.Append);
var boundsTest = RectangleF.Inflate(bounds, 0, 0);
while (!(path.IsVisible(points[0]) && path.IsVisible(points[1]) &&
path.IsVisible(points[2]) && path.IsVisible(points[3])))
{
var radius = Radius.ToDeviceValue(renderer, UnitRenderingType.Other, this);
var transformRadiusVector = TransformVector(new PointF(radius, 0));
var transformedRadius = CalculateLength(transformRadiusVector);
return transformedRadius;
transform.TransformPoints(points);
}
}
return bounds.Height / (points[2].Y - points[1].Y);
}
//New plan:
// scale the outer rectangle to always encompass ellipse
// cut the ellipse in half (either vertical or horizontal)
// determine the region on each side of the ellipse
private static IEnumerable<GraphicsPath> GetDifference(RectangleF subject, GraphicsPath clip)
{
var clipFlat = (GraphicsPath)clip.Clone();
clipFlat.Flatten();
var clipBounds = clipFlat.GetBounds();
var bounds = RectangleF.Union(subject, clipBounds);
bounds.Inflate(bounds.Width * .3f, bounds.Height * 0.3f);
private float CalculateEffectiveRadius(ISvgBoundable boundable, PointF centerPoint, float specifiedRadius)
var clipMidPoint = new PointF((clipBounds.Left + clipBounds.Right) / 2, (clipBounds.Top + clipBounds.Bottom) / 2);
var leftPoints = new List<PointF>();
var rightPoints = new List<PointF>();
foreach (var pt in clipFlat.PathPoints)
{
if (pt.X <= clipMidPoint.X)
{
if (SpreadMethod != SvgGradientSpreadMethod.Pad)
leftPoints.Add(pt);
}
else
{
return specifiedRadius;
rightPoints.Add(pt);
}
}
leftPoints.Sort((p, q) => p.Y.CompareTo(q.Y));
rightPoints.Sort((p, q) => p.Y.CompareTo(q.Y));
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 point = new PointF((leftPoints.Last().X + rightPoints.Last().X) / 2,
(leftPoints.Last().Y + rightPoints.Last().Y) / 2);
leftPoints.Add(point);
rightPoints.Add(point);
point = new PointF(point.X, bounds.Bottom);
leftPoints.Add(point);
rightPoints.Add(point);
var effectiveRadius = (float)Math.Ceiling(
Math.Max(
Math.Max(
CalculateDistance(centerPoint, topLeft),
CalculateDistance(centerPoint, topRight)
),
Math.Max(
CalculateDistance(centerPoint, bottomRight),
CalculateDistance(centerPoint, bottomLeft)
)
)
);
leftPoints.Add(new PointF(bounds.Left, bounds.Bottom));
leftPoints.Add(new PointF(bounds.Left, bounds.Top));
rightPoints.Add(new PointF(bounds.Right, bounds.Bottom));
rightPoints.Add(new PointF(bounds.Right, bounds.Top));
point = new PointF((leftPoints.First().X + rightPoints.First().X) / 2, bounds.Top);
leftPoints.Add(point);
rightPoints.Add(point);
point = new PointF(point.X, (leftPoints.First().Y + rightPoints.First().Y) / 2);
leftPoints.Add(point);
rightPoints.Add(point);
effectiveRadius = Math.Max(effectiveRadius, specifiedRadius);
var path = new GraphicsPath(FillMode.Winding);
path.AddPolygon(leftPoints.ToArray());
yield return path;
return effectiveRadius;
path.Reset();
path.AddPolygon(rightPoints.ToArray());
yield return path;
}
private static GraphicsPath CreateGraphicsPath(PointF origin, PointF centerPoint, float effectiveRadius)
......@@ -193,22 +313,75 @@ namespace Svg
return path;
}
private ColorBlend CalculateColorBlend(SvgRenderer renderer, float opacity, float specifiedRadius, float effectiveRadius)
private ColorBlend CalculateColorBlend(ISvgRenderer renderer, float opacity, float scale, out float outScale)
{
var colorBlend = GetColorBlend(renderer, opacity, true);
float newScale;
List<float> pos;
List<Color> colors;
if (specifiedRadius >= effectiveRadius)
outScale = scale;
if (scale > 1)
{
return colorBlend;
switch (this.SpreadMethod)
{
case SvgGradientSpreadMethod.Reflect:
newScale = (float)Math.Ceiling(scale);
pos = (from p in colorBlend.Positions select 1 + (p - 1) / newScale).ToList();
colors = colorBlend.Colors.ToList();
for (var i = 1; i < newScale; i++)
{
if (i % 2 == 1)
{
for (int j = 1; j < colorBlend.Positions.Length; j++)
{
pos.Insert(0, (newScale - i - 1) / newScale + 1 - colorBlend.Positions[j]);
colors.Insert(0, colorBlend.Colors[j]);
}
}
else
{
for (int j = 0; j < colorBlend.Positions.Length - 1; j++)
{
pos.Insert(j, (newScale - i - 1) / newScale + colorBlend.Positions[j]);
colors.Insert(j, colorBlend.Colors[j]);
}
}
}
for (var i = 0; i < colorBlend.Positions.Length - 1; i++)
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++)
{
colorBlend.Positions[i] = 1 - (specifiedRadius / effectiveRadius) * (1 - colorBlend.Positions[i]);
pos.AddRange(from p in colorBlend.Positions select (i + (p <= 0 ? 0.001f : p)) / newScale);
colors.AddRange(colorBlend.Colors);
}
colorBlend.Positions = new[] { 0F }.Concat(colorBlend.Positions).ToArray();
colorBlend.Colors = new[] { colorBlend.Colors.First() }.Concat(colorBlend.Colors).ToArray();
colorBlend.Positions = pos.ToArray();
colorBlend.Colors = colors.ToArray();
outScale = newScale;
break;
default:
outScale = 1.0f;
//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;
}
}
return colorBlend;
}
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;
namespace Svg
{
internal class CoordinateParser
{
private enum NumState
{
invalid,
separator,
prefix,
integer,
decPlace,
fraction,
exponent,
expPrefix,
expValue
}
private string _coords;
private int _pos = 0;
private NumState _currState = NumState.separator;
private NumState _newState = NumState.separator;
private int i = 0;
private bool _parseWorked = true;
public int Position { get { return _pos; } }
public CoordinateParser(string coords)
{
_coords = coords;
if (string.IsNullOrEmpty(_coords)) _parseWorked = false;
if (char.IsLetter(coords[0])) i++;
}
public bool HasMore { get { return _parseWorked; } }
private bool MarkState(bool state)
{
_parseWorked = state;
i++;
return state;
}
public bool TryGetBool(out bool result)
{
while (i < _coords.Length && _parseWorked)
{
switch (_currState)
{
case NumState.separator:
if (IsCoordSeparator(_coords[i]))
{
_newState = NumState.separator;
}
else if (_coords[i] == '0')
{
result = false;
_newState = NumState.separator;
_pos = i + 1;
return MarkState(true);
}
else if (_coords[i] == '1')
{
result = true;
_newState = NumState.separator;
_pos = i + 1;
return MarkState(true);
}
else
{
result = false;
return MarkState(false);
}
break;
default:
result = false;
return MarkState(false);
}
i++;
}
result = false;
return MarkState(false);
}
public bool TryGetFloat(out float result)
{
while (i < _coords.Length && _parseWorked)
{
switch (_currState)
{
case NumState.separator:
if (char.IsNumber(_coords[i]))
{
_newState = NumState.integer;
}
else if (IsCoordSeparator(_coords[i]))
{
_newState = NumState.separator;
}
else
{
switch (_coords[i])
{
case '.':
_newState = NumState.decPlace;
break;
case '+':
case '-':
_newState = NumState.prefix;
break;
default:
_newState = NumState.invalid;
break;
}
}
break;
case NumState.prefix:
if (char.IsNumber(_coords[i]))
{
_newState = NumState.integer;
}
else if (_coords[i] == '.')
{
_newState = NumState.decPlace;
}
else
{
_newState = NumState.invalid;
}
break;
case NumState.integer:
if (char.IsNumber(_coords[i]))
{
_newState = NumState.integer;
}
else if (IsCoordSeparator(_coords[i]))
{
_newState = NumState.separator;
}
else
{
switch (_coords[i])
{
case '.':
_newState = NumState.decPlace;
break;
case 'E':
case 'e':
_newState = NumState.exponent;
break;
case '+':
case '-':
_newState = NumState.prefix;
break;
default:
_newState = NumState.invalid;
break;
}
}
break;
case NumState.decPlace:
if (char.IsNumber(_coords[i]))
{
_newState = NumState.fraction;
}
else if (IsCoordSeparator(_coords[i]))
{
_newState = NumState.separator;
}
else
{
switch (_coords[i])
{
case 'E':
case 'e':
_newState = NumState.exponent;
break;
case '+':
case '-':
_newState = NumState.prefix;
break;
default:
_newState = NumState.invalid;
break;
}
}
break;
case NumState.fraction:
if (char.IsNumber(_coords[i]))
{
_newState = NumState.fraction;
}
else if (IsCoordSeparator(_coords[i]))
{
_newState = NumState.separator;
}
else
{
switch (_coords[i])
{
case '.':
_newState = NumState.decPlace;
break;
case 'E':
case 'e':
_newState = NumState.exponent;
break;
case '+':
case '-':
_newState = NumState.prefix;
break;
default:
_newState = NumState.invalid;
break;
}
}
break;
case NumState.exponent:
if (char.IsNumber(_coords[i]))
{
_newState = NumState.expValue;
}
else if (IsCoordSeparator(_coords[i]))
{
_newState = NumState.invalid;
}
else
{
switch (_coords[i])
{
case '+':
case '-':
_newState = NumState.expPrefix;
break;
default:
_newState = NumState.invalid;
break;
}
}
break;
case NumState.expPrefix:
if (char.IsNumber(_coords[i]))
{
_newState = NumState.expValue;
}
else
{
_newState = NumState.invalid;
}
break;
case NumState.expValue:
if (char.IsNumber(_coords[i]))
{
_newState = NumState.expValue;
}
else if (IsCoordSeparator(_coords[i]))
{
_newState = NumState.separator;
}
else
{
switch (_coords[i])
{
case '.':
_newState = NumState.decPlace;
break;
case '+':
case '-':
_newState = NumState.prefix;
break;
default:
_newState = NumState.invalid;
break;
}
}
break;
}
if (_newState < _currState)
{
result = float.Parse(_coords.Substring(_pos, i - _pos), NumberStyles.Float, CultureInfo.InvariantCulture);
_pos = i;
_currState = _newState;
return MarkState(true);
}
else if (_newState != _currState && _currState == NumState.separator)
{
_pos = i;
}
if (_newState == NumState.invalid)
{
result = float.MinValue;
return MarkState(false);
}
_currState = _newState;
i++;
}
if (_currState == NumState.separator || !_parseWorked || _pos >= _coords.Length)
{
result = float.MinValue;
return MarkState(false);
}
else
{
result = float.Parse(_coords.Substring(_pos, _coords.Length - _pos), NumberStyles.Float, CultureInfo.InvariantCulture);
_pos = _coords.Length;
return MarkState(true);
}
}
private static bool IsCoordSeparator(char value)
{
switch (value)
{
case ' ':
case '\t':
case '\n':
case '\r':
case ',':
return true;
}
return false;
}
}
}
......@@ -38,9 +38,9 @@ namespace Svg
/// Gets or sets the length of the path.
/// </summary>
[SvgAttribute("pathLength")]
public int PathLength
public float PathLength
{
get { return this.Attributes.GetAttribute<int>("pathLength"); }
get { return this.Attributes.GetAttribute<float>("pathLength"); }
set { this.Attributes["pathLength"] = value; }
}
......@@ -81,7 +81,7 @@ namespace Svg
/// <summary>
/// Gets the <see cref="GraphicsPath"/> for this element.
/// </summary>
public override GraphicsPath Path(SvgRenderer renderer)
public override GraphicsPath Path(ISvgRenderer renderer)
{
if (this._path == null || this.IsPathDirty)
{
......@@ -131,24 +131,13 @@ namespace Svg
}
/// <summary>
/// Renders the stroke of the <see cref="SvgVisualElement"/> to the specified <see cref="SvgRenderer"/>
/// Renders the stroke of the <see cref="SvgVisualElement"/> to the specified <see cref="ISvgRenderer"/>
/// </summary>
/// <param name="renderer">The <see cref="SvgRenderer"/> object to render to.</param>
protected internal override void RenderStroke(SvgRenderer renderer)
/// <param name="renderer">The <see cref="ISvgRenderer"/> object to render to.</param>
protected internal override bool RenderStroke(ISvgRenderer renderer)
{
if (this.Stroke != null)
{
float strokeWidth = this.StrokeWidth.ToDeviceValue(renderer, UnitRenderingType.Other, this);
using (Pen pen = new Pen(this.Stroke.GetBrush(this, renderer, this.StrokeOpacity), 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 / ((strokeWidth <= 0) ? 1 : strokeWidth)).ToArray();
}
var result = base.RenderStroke(renderer);
var path = this.Path(renderer);
renderer.DrawPath(pen, path);
if (this.MarkerStart != null)
{
......@@ -168,8 +157,8 @@ namespace Svg
SvgMarker marker = this.OwnerDocument.GetElementById<SvgMarker>(this.MarkerEnd.ToString());
marker.RenderMarker(renderer, this, path.PathPoints[path.PathPoints.Length - 1], path.PathPoints[path.PathPoints.Length - 2], path.PathPoints[path.PathPoints.Length - 1]);
}
}
}
return result;
}
public override SvgElement DeepCopy()
......
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