Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 107 additions & 4 deletions Svg.Tests.Win/SvgClipPathTest.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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 =
"""
<svg width="200" height="200" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
<defs>
<clipPath id="c"><rect x="0" y="0" width="200" height="40" /></clipPath>
</defs>
<g clip-path="url(#c)"><text x="0" y="0" font-size="48" fill="black" transform="matrix(1 0 0 1 0 150)">text</text></g>
</svg>
""";

// Act
using var rendered = RenderSvgFromString(svg, 200, 200);

// Assert
CountNonTransparentPixels(rendered).ShouldBe(0);
}

[Test]
public void WhenGroupClipPathContainsTextTransform_AndTextIsInsideClip_TextShouldBeRendered()
{
// Arrange
const string svg =
"""
<svg width="200" height="200" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
<defs>
<clipPath id="c"><rect x="0" y="120" width="200" height="80" /></clipPath>
</defs>
<g clip-path="url(#c)">
<text x="0" y="0" font-size="48" fill="black" transform="matrix(1 0 0 1 0 150)">text</text>
</g>
</svg>
""";

// Act
using var rendered = RenderSvgFromString(svg, 200, 200);

// Assert
CountNonTransparentPixels(rendered).ShouldBeGreaterThan(0);
}

[Test]
public void WhenClipPathIsOnGroup_ChildTextTransformShouldNotMoveClipRegion()
{
// Arrange
const string svg =
"""
<svg width="220" height="220" viewBox="0 0 220 220" xmlns="http://www.w3.org/2000/svg">
<defs>
<clipPath id="c"><rect x="0" y="0" width="220" height="60" /></clipPath>
</defs>
<g clip-path="url(#c)">
<text x="5" y="45" font-size="40" fill="black">top</text>
<text x="5" y="0" font-size="40" fill="black" transform="matrix(1 0 0 1 0 170)">bottom</text>
</g>
</svg>
""";

// 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<SvgDocument>(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;
}
}
18 changes: 10 additions & 8 deletions Svg/Basic Shapes/SvgVisualElement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -150,18 +150,18 @@ protected internal virtual bool IntersectsWith(RectangleF rectangle, Matrix tran
return true;
}

private SvgAttributeCollection.InheritedAttribute<string> _clip;
private SvgAttributeCollection.InheritedAttribute<Uri> _clipPath;
private SvgAttributeCollection.InheritedAttribute<Uri> _filter;
private SvgAttributeCollection.Attribute<SvgClipRule> _clipRule;
private SvgAttributeCollection.Attribute<string> _clip;
private SvgAttributeCollection.Attribute<Uri> _clipPath;
private SvgAttributeCollection.Attribute<Uri> _filter;
private SvgAttributeCollection.InheritedAttribute<SvgClipRule> _clipRule;

/// <summary>
/// Gets the associated <see cref="SvgClipPath"/> if one has been specified.
/// </summary>
[SvgAttribute("clip")]
public virtual string Clip
{
get { return (_clip ??= this.Attributes.GetInheritedAttribute<string>("clip")).GetValue(); }
get { return (_clip ??= this.Attributes.GetAttribute<string>("clip")).GetValue(); }
set { this.Attributes["clip"] = value; }
}

Expand All @@ -171,7 +171,7 @@ public virtual string Clip
[SvgAttribute("clip-path")]
public virtual Uri ClipPath
{
get { return (_clipPath ??= this.Attributes.GetInheritedAttribute<Uri>("clip-path")).GetValue(); }
get { return (_clipPath ??= this.Attributes.GetAttribute<Uri>("clip-path")).GetValue(); }
set { this.Attributes["clip-path"] = value; }
}

Expand All @@ -181,7 +181,7 @@ public virtual Uri ClipPath
[SvgAttribute("clip-rule")]
public SvgClipRule ClipRule
{
get { return (_clipRule ??= this.Attributes.GetAttribute<SvgClipRule>("clip-rule", SvgClipRule.NonZero)).GetValue(); }
get { return (_clipRule ??= this.Attributes.GetInheritedAttribute<SvgClipRule>("clip-rule")).GetValue(); }
set { this.Attributes["clip-rule"] = value; }
}

Expand All @@ -191,7 +191,7 @@ public SvgClipRule ClipRule
[SvgAttribute("filter")]
public virtual Uri Filter
{
get { return (_filter ??= this.Attributes.GetInheritedAttribute<Uri>("filter")).GetValue(); }
get { return (_filter ??= this.Attributes.GetAttribute<Uri>("filter")).GetValue(); }
set { this.Attributes["filter"] = value; }
}

Expand Down Expand Up @@ -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);
}
Expand Down
3 changes: 3 additions & 0 deletions Svg/Converters/BaseConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A change unrelated to the bug.
When testing something I got hundreds of exceptions when the font was set to none in an svg

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only necessary if you changed something in the editor


if (value == "small-caps")
return SvgFontVariant.Smallcaps;

Expand Down
4 changes: 3 additions & 1 deletion Svg/Svg.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net48;net9.0</TargetFrameworks>
<RestoreProjectStyle>PackageReference</RestoreProjectStyle>
<Version>3.1.2-optiq06</Version>
<Version>3.1.2-optiq07</Version>
<Authors>gentledpp,zepr</Authors>
<Company>Opti-Q GmbH</Company>
<PackageReleaseNotes>
#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
Expand Down
Loading