diff --git a/Svg.Tests.Win/SvgClipPathTest.cs b/Svg.Tests.Win/SvgClipPathTest.cs index 6efa7d358..d1d091cef 100644 --- a/Svg.Tests.Win/SvgClipPathTest.cs +++ b/Svg.Tests.Win/SvgClipPathTest.cs @@ -1,10 +1,7 @@ -using System.Diagnostics; -using System.Linq; +using System.Linq; using Shouldly; using NUnit.Framework; using SkiaSharp; -using Svg.Interfaces; -using Svg.Pathing; using Svg.Platform; namespace Svg.Tests.Win; @@ -82,4 +79,110 @@ public void WhenGettingCliPathBounds_BasedOnClipPathsChildren_GetBounds() Assert.AreEqual(clip.Bounds.Width, 200); Assert.AreEqual(clip.Bounds.Height, 50); } + + [Test] + public void WhenGroupClipPathContainsTextTransform_AndTextIsOutsideClip_TextShouldNotBeRendered() + { + // Arrange + const string svg = + """ + + + + + text + + """; + + // Act + using var rendered = RenderSvgFromString(svg, 200, 200); + + // Assert + CountNonTransparentPixels(rendered).ShouldBe(0); + } + + [Test] + public void WhenGroupClipPathContainsTextTransform_AndTextIsInsideClip_TextShouldBeRendered() + { + // Arrange + const string svg = + """ + + + + + + text + + + """; + + // Act + using var rendered = RenderSvgFromString(svg, 200, 200); + + // Assert + CountNonTransparentPixels(rendered).ShouldBeGreaterThan(0); + } + + [Test] + public void WhenClipPathIsOnGroup_ChildTextTransformShouldNotMoveClipRegion() + { + // Arrange + const string svg = + """ + + + + + + top + bottom + + + """; + + // Act + using var rendered = RenderSvgFromString(svg, 220, 220); + + // Assert + CountNonTransparentPixelsInRegion(rendered, 0, 0, 220, 70).ShouldBeGreaterThan(0); + CountNonTransparentPixelsInRegion(rendered, 0, 150, 220, 70).ShouldBe(0); + } + + private static SKBitmap RenderSvgFromString(string svg, int width, int height) + { + using SvgDocument doc = SvgDocument.FromSvg(svg); + using var surface = SKSurface.Create(new SKImageInfo(width, height)); + using var renderer = SvgRenderer.FromGraphics(new SkiaGraphics(surface)); + doc.Draw(renderer); + using var image = surface.Snapshot(); + return SKBitmap.FromImage(image); + } + + private static int CountNonTransparentPixels(SKBitmap bitmap) + { + return CountNonTransparentPixelsInRegion(bitmap, 0, 0, bitmap.Width, bitmap.Height); + } + + private static int CountNonTransparentPixelsInRegion(SKBitmap bitmap, int x, int y, int width, int height) + { + var xStart = x < 0 ? 0 : x; + var yStart = y < 0 ? 0 : y; + var xEnd = x + width > bitmap.Width ? bitmap.Width : x + width; + var yEnd = y + height > bitmap.Height ? bitmap.Height : y + height; + + var count = 0; + for (var yy = yStart; yy < yEnd; yy++) + { + for (var xx = xStart; xx < xEnd; xx++) + { + if (bitmap.GetPixel(xx, yy).Alpha > 0) + { + count++; + } + } + } + + return count; + } } \ No newline at end of file diff --git a/Svg/Basic Shapes/SvgVisualElement.cs b/Svg/Basic Shapes/SvgVisualElement.cs index a9fa45066..d5caf20c9 100644 --- a/Svg/Basic Shapes/SvgVisualElement.cs +++ b/Svg/Basic Shapes/SvgVisualElement.cs @@ -150,10 +150,10 @@ protected internal virtual bool IntersectsWith(RectangleF rectangle, Matrix tran return true; } - private SvgAttributeCollection.InheritedAttribute _clip; - private SvgAttributeCollection.InheritedAttribute _clipPath; - private SvgAttributeCollection.InheritedAttribute _filter; - private SvgAttributeCollection.Attribute _clipRule; + private SvgAttributeCollection.Attribute _clip; + private SvgAttributeCollection.Attribute _clipPath; + private SvgAttributeCollection.Attribute _filter; + private SvgAttributeCollection.InheritedAttribute _clipRule; /// /// Gets the associated if one has been specified. @@ -161,7 +161,7 @@ protected internal virtual bool IntersectsWith(RectangleF rectangle, Matrix tran [SvgAttribute("clip")] public virtual string Clip { - get { return (_clip ??= this.Attributes.GetInheritedAttribute("clip")).GetValue(); } + get { return (_clip ??= this.Attributes.GetAttribute("clip")).GetValue(); } set { this.Attributes["clip"] = value; } } @@ -171,7 +171,7 @@ public virtual string Clip [SvgAttribute("clip-path")] public virtual Uri ClipPath { - get { return (_clipPath ??= this.Attributes.GetInheritedAttribute("clip-path")).GetValue(); } + get { return (_clipPath ??= this.Attributes.GetAttribute("clip-path")).GetValue(); } set { this.Attributes["clip-path"] = value; } } @@ -181,7 +181,7 @@ public virtual Uri ClipPath [SvgAttribute("clip-rule")] public SvgClipRule ClipRule { - get { return (_clipRule ??= this.Attributes.GetAttribute("clip-rule", SvgClipRule.NonZero)).GetValue(); } + get { return (_clipRule ??= this.Attributes.GetInheritedAttribute("clip-rule")).GetValue(); } set { this.Attributes["clip-rule"] = value; } } @@ -191,7 +191,7 @@ public SvgClipRule ClipRule [SvgAttribute("filter")] public virtual Uri Filter { - get { return (_filter ??= this.Attributes.GetInheritedAttribute("filter")).GetValue(); } + get { return (_filter ??= this.Attributes.GetAttribute("filter")).GetValue(); } set { this.Attributes["filter"] = value; } } @@ -278,7 +278,9 @@ private void Render(ISvgRenderer renderer, bool renderFilter, RenderCacheEntry c } else { + this.SetClip(renderer); base.RenderChildren(renderer); + this.ResetClip(renderer); } this.PopTransforms(renderer); } diff --git a/Svg/Converters/BaseConverter.cs b/Svg/Converters/BaseConverter.cs index 4bbae5022..5cdbb13d7 100644 --- a/Svg/Converters/BaseConverter.cs +++ b/Svg/Converters/BaseConverter.cs @@ -235,6 +235,9 @@ public SvgFontVariantConverter() : base(SvgFontVariant.Normal) { } public override object ConvertFromString(string value, Type targetType, SvgDocument document) { + if (value == "none") + return SvgFontVariant.Normal; + if (value == "small-caps") return SvgFontVariant.Smallcaps; diff --git a/Svg/Svg.csproj b/Svg/Svg.csproj index 7b02f8710..0c486a62d 100644 --- a/Svg/Svg.csproj +++ b/Svg/Svg.csproj @@ -3,10 +3,12 @@ netstandard2.0;net48;net9.0 PackageReference - 3.1.2-optiq06 + 3.1.2-optiq07 gentledpp,zepr Opti-Q GmbH + #3.1.2-optiq07 + Fixed clip path property being inherited #3.1.2-optiq06 Fixed: Sketch, when setting text and cancel dialog, opens new dialog and asks for font size #3.1.2-optiq05