From 87b2183b438cf6f274bf39ecbc212ac937c9167a Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Mon, 11 Feb 2019 03:22:50 +0200 Subject: [PATCH] Add support for path/polygon/polyline fill-rule=evenodd. Fixes #43 --- .../SkiaSharp.Extended.Svg.Shared/SKSvg.cs | 69 ++++++++++++------- .../SkiaSharp.Extended.Svg.Tests/SKSvgTest.cs | 10 +++ .../images/fill-rule.svg | 15 ++++ 3 files changed, 71 insertions(+), 23 deletions(-) create mode 100644 SkiaSharp.Extended.Svg/tests/SkiaSharp.Extended.Svg.Tests/images/fill-rule.svg diff --git a/SkiaSharp.Extended.Svg/source/SkiaSharp.Extended.Svg.Shared/SKSvg.cs b/SkiaSharp.Extended.Svg/source/SkiaSharp.Extended.Svg.Shared/SKSvg.cs index ab9141ce..8e03e3bd 100644 --- a/SkiaSharp.Extended.Svg/source/SkiaSharp.Extended.Svg.Shared/SKSvg.cs +++ b/SkiaSharp.Extended.Svg/source/SkiaSharp.Extended.Svg.Shared/SKSvg.cs @@ -274,7 +274,7 @@ private void ReadElement(XElement e, SKCanvas canvas, SKPaint stroke, SKPaint fi case "line": if (stroke != null || fill != null) { - var elementPath = ReadElement(e); + var elementPath = ReadElement(e, style); if (elementPath == null) break; @@ -408,7 +408,7 @@ private SKSvgImage ReadImage(XElement e) return new SKSvgImage(rect, uri, bytes); } - private SKPath ReadElement(XElement e) + private SKPath ReadElement(XElement e, Dictionary style = null) { var path = new SKPath(); @@ -431,25 +431,25 @@ private SKPath ReadElement(XElement e) path.AddCircle(circle.Center.X, circle.Center.Y, circle.Radius); break; case "path": - var d = e.Attribute("d")?.Value; - if (!string.IsNullOrWhiteSpace(d)) - { - path.Dispose(); - path = SKPath.ParseSvgPathData(d); - } - break; case "polygon": case "polyline": - var close = elementName == "polygon"; - var p = e.Attribute("points")?.Value; - if (!string.IsNullOrWhiteSpace(p)) + string data = null; + if (elementName == "path") + { + data = e.Attribute("d")?.Value; + } + else + { + data = "M" + e.Attribute("points")?.Value; + if (elementName == "polygon") + data += " Z"; + } + if (!string.IsNullOrWhiteSpace(data)) { - p = "M" + p; - if (close) - p += " Z"; path.Dispose(); - path = SKPath.ParseSvgPathData(p); + path = SKPath.ParseSvgPathData(data); } + path.FillType = ReadFillRule(style); break; case "line": var line = ReadLine(e); @@ -583,7 +583,7 @@ private void ReadFontAttributes(XElement e, SKPaint paint) { var fontStyle = ReadStyle(e); - if (!fontStyle.TryGetValue("font-family", out string ffamily) || string.IsNullOrWhiteSpace(ffamily)) + if (fontStyle == null || !fontStyle.TryGetValue("font-family", out string ffamily) || string.IsNullOrWhiteSpace(ffamily)) ffamily = paint.Typeface?.FamilyName; var fweight = ReadFontWeight(fontStyle, paint.Typeface?.FontWeight ?? (int)SKFontStyleWeight.Normal); var fwidth = ReadFontWidth(fontStyle, paint.Typeface?.FontWidth ?? (int)SKFontStyleWidth.Normal); @@ -591,15 +591,38 @@ private void ReadFontAttributes(XElement e, SKPaint paint) paint.Typeface = SKTypeface.FromFamilyName(ffamily, fweight, fwidth, fstyle); - if (fontStyle.TryGetValue("font-size", out string fsize) && !string.IsNullOrWhiteSpace(fsize)) + if (fontStyle != null && fontStyle.TryGetValue("font-size", out string fsize) && !string.IsNullOrWhiteSpace(fsize)) paint.TextSize = ReadNumber(fsize); } + private static SKPathFillType ReadFillRule(Dictionary style, SKPathFillType defaultFillRule = SKPathFillType.Winding) + { + var fillRule = defaultFillRule; + + if (style != null && style.TryGetValue("fill-rule", out string rule) && !string.IsNullOrWhiteSpace(rule)) + { + switch (rule) + { + case "evenodd": + fillRule = SKPathFillType.EvenOdd; + break; + case "nonzero": + fillRule = SKPathFillType.Winding; + break; + default: + fillRule = defaultFillRule; + break; + } + } + + return fillRule; + } + private static SKFontStyleSlant ReadFontStyle(Dictionary fontStyle, SKFontStyleSlant defaultStyle = SKFontStyleSlant.Upright) { var style = defaultStyle; - if (fontStyle.TryGetValue("font-style", out string fstyle) && !string.IsNullOrWhiteSpace(fstyle)) + if (fontStyle != null && fontStyle.TryGetValue("font-style", out string fstyle) && !string.IsNullOrWhiteSpace(fstyle)) { switch (fstyle) { @@ -624,7 +647,7 @@ private static SKFontStyleSlant ReadFontStyle(Dictionary fontSty private int ReadFontWidth(Dictionary fontStyle, int defaultWidth = (int)SKFontStyleWidth.Normal) { var width = defaultWidth; - if (fontStyle.TryGetValue("font-stretch", out string fwidth) && !string.IsNullOrWhiteSpace(fwidth) && !int.TryParse(fwidth, out width)) + if (fontStyle != null && fontStyle.TryGetValue("font-stretch", out string fwidth) && !string.IsNullOrWhiteSpace(fwidth) && !int.TryParse(fwidth, out width)) { switch (fwidth) { @@ -674,7 +697,7 @@ private int ReadFontWeight(Dictionary fontStyle, int defaultWeig { var weight = defaultWeight; - if (fontStyle.TryGetValue("font-weight", out string fweight) && !string.IsNullOrWhiteSpace(fweight) && !int.TryParse(fweight, out weight)) + if (fontStyle != null && fontStyle.TryGetValue("font-weight", out string fweight) && !string.IsNullOrWhiteSpace(fweight) && !int.TryParse(fweight, out weight)) { switch (fweight) { @@ -709,7 +732,7 @@ private void LogOrThrow(string message) private string GetString(Dictionary style, string name, string defaultValue = "") { - if (style.TryGetValue(name, out string v)) + if (style != null && style.TryGetValue(name, out string v)) return v; return defaultValue; } @@ -1342,7 +1365,7 @@ private float ReadOpacity(Dictionary style) private float ReadNumber(Dictionary style, string key, float defaultValue) { float value = defaultValue; - if (style.TryGetValue(key, out string strValue)) + if (style != null && style.TryGetValue(key, out string strValue)) { value = ReadNumber(strValue); } diff --git a/SkiaSharp.Extended.Svg/tests/SkiaSharp.Extended.Svg.Tests/SKSvgTest.cs b/SkiaSharp.Extended.Svg/tests/SkiaSharp.Extended.Svg.Tests/SKSvgTest.cs index 4c80b9ae..624d7c7d 100644 --- a/SkiaSharp.Extended.Svg/tests/SkiaSharp.Extended.Svg.Tests/SKSvgTest.cs +++ b/SkiaSharp.Extended.Svg/tests/SkiaSharp.Extended.Svg.Tests/SKSvgTest.cs @@ -413,6 +413,16 @@ public void SvgFillsAreCorrect() Assert.Equal(SKColors.White, bmp.GetPixel(11, 20)); } + [Fact] + public void FillRulesAreRespected() + { + var path = Path.Combine(PathToImages, "fill-rule.svg"); + var bmp = LoadSvgBitmap(path, SKColors.Green); + + Assert.Equal(SKColors.Black, bmp.GetPixel(60, 60)); + Assert.Equal(SKColors.Green, bmp.GetPixel(160, 60)); + } + private static SKBitmap LoadSvgBitmap(string svgPath, SKColor? background = null) { // open the SVG diff --git a/SkiaSharp.Extended.Svg/tests/SkiaSharp.Extended.Svg.Tests/images/fill-rule.svg b/SkiaSharp.Extended.Svg/tests/SkiaSharp.Extended.Svg.Tests/images/fill-rule.svg new file mode 100644 index 00000000..8776579d --- /dev/null +++ b/SkiaSharp.Extended.Svg/tests/SkiaSharp.Extended.Svg.Tests/images/fill-rule.svg @@ -0,0 +1,15 @@ + + + + + + + \ No newline at end of file