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
17 changes: 17 additions & 0 deletions src/ShimSkiaSharp/SKPaint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public sealed class SKPaint : ICloneable, IDeepCloneable<SKPaint>
private SKStrokeCap _strokeCap = SKStrokeCap.Butt;
private SKStrokeJoin _strokeJoin = SKStrokeJoin.Miter;
private float _strokeMiter = 4;
private bool _isStrokeNonScaling;
private SKTypeface? _typeface;
private float _textSize = 12;
private SKTextAlign _textAlign = SKTextAlign.Left;
Expand Down Expand Up @@ -135,6 +136,21 @@ public float StrokeMiter
}
}

public bool IsStrokeNonScaling
{
get => _isStrokeNonScaling;
set
{
if (_isStrokeNonScaling == value)
{
return;
}

_isStrokeNonScaling = value;
_version++;
}
}

public SKTypeface? Typeface
{
get => _typeface;
Expand Down Expand Up @@ -353,6 +369,7 @@ internal SKPaint DeepClone(CloneContext context)
clone.StrokeCap = StrokeCap;
clone.StrokeJoin = StrokeJoin;
clone.StrokeMiter = StrokeMiter;
clone.IsStrokeNonScaling = IsStrokeNonScaling;
clone.Typeface = Typeface?.DeepClone(context);
clone.TextSize = TextSize;
clone.TextAlign = TextAlign;
Expand Down
24 changes: 23 additions & 1 deletion src/Svg.CodeGen.Skia/SkiaCSharpModelExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1911,7 +1911,29 @@ public static void ToSKPicture(this SKPicture? picture, SkiaCSharpCodeGenCounter
drawPathCanvasCommand.Path.ToSKPath(counter, sb, indent);
var counterPaint = ++counter.Paint;
drawPathCanvasCommand.Paint.ToSKPaint(counter, sb, indent);
sb.AppendLine($"{indent}{counter.CanvasVarName}{counterCanvas}.DrawPath({counter.PathVarName}{counterPath}, {counter.PaintVarName}{counterPaint});");
if (drawPathCanvasCommand.Paint.IsStrokeNonScaling &&
drawPathCanvasCommand.Paint.Style == SKPaintStyle.Stroke)
{
var counterNonScalingPath = ++counter.Path;
sb.AppendLine($"{indent}var matrix{counterNonScalingPath} = {counter.CanvasVarName}{counterCanvas}.TotalMatrix;");
sb.AppendLine($"{indent}if (matrix{counterNonScalingPath}.IsIdentity)");
sb.AppendLine($"{indent}{{");
sb.AppendLine($"{indent} {counter.CanvasVarName}{counterCanvas}.DrawPath({counter.PathVarName}{counterPath}, {counter.PaintVarName}{counterPaint});");
sb.AppendLine($"{indent}}}");
sb.AppendLine($"{indent}else");
sb.AppendLine($"{indent}{{");
sb.AppendLine($"{indent} using var {counter.PathVarName}{counterNonScalingPath} = new SKPath({counter.PathVarName}{counterPath});");
sb.AppendLine($"{indent} {counter.PathVarName}{counterNonScalingPath}.Transform(matrix{counterNonScalingPath});");
sb.AppendLine($"{indent} {counter.CanvasVarName}{counterCanvas}.Save();");
sb.AppendLine($"{indent} {counter.CanvasVarName}{counterCanvas}.ResetMatrix();");
sb.AppendLine($"{indent} {counter.CanvasVarName}{counterCanvas}.DrawPath({counter.PathVarName}{counterNonScalingPath}, {counter.PaintVarName}{counterPaint});");
sb.AppendLine($"{indent} {counter.CanvasVarName}{counterCanvas}.Restore();");
sb.AppendLine($"{indent}}}");
}
else
{
sb.AppendLine($"{indent}{counter.CanvasVarName}{counterCanvas}.DrawPath({counter.PathVarName}{counterPath}, {counter.PaintVarName}{counterPaint});");
}

// NOTE: Do not dispose created SKTypeface by font manager.
#if USE_DISPOSE_TYPEFACE
Expand Down
1 change: 1 addition & 0 deletions src/Svg.Custom/Compatibility/SvgElementFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@ private static bool IsStyleAttribute(string name)
case "text-rendering":
case "text-transform":
case "unicode-bidi":
case "vector-effect":
case "visibility":
case "word-spacing":
case "writing-mode":
Expand Down
28 changes: 28 additions & 0 deletions src/Svg.Custom/Painting/SvgVectorEffect.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System.ComponentModel;

namespace Svg
{
[TypeConverter(typeof(SvgVectorEffectConverter))]
public enum SvgVectorEffect
{
None,
NonScalingStroke
}

public sealed class SvgVectorEffectConverter : EnumBaseConverter<SvgVectorEffect>
{
public SvgVectorEffectConverter() : base(CaseHandling.KebabCase)
{
}
}

public abstract partial class SvgVisualElement
{
[SvgAttribute("vector-effect")]
public virtual SvgVectorEffect VectorEffect
{
get { return GetAttribute("vector-effect", false, SvgVectorEffect.None); }
set { Attributes["vector-effect"] = value; }
}
}
}
38 changes: 38 additions & 0 deletions src/Svg.Model/Services/GeometryHitTestService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,24 @@ public static bool ContainsStroke(SKPath path, SKPoint point, SKMatrix transform
return ContainsStrokeLocal(path, localPoint, Math.Max(strokeWidth / 2f, MinStrokeTolerance));
}

public static bool ContainsStroke(SKPath path, SKPoint point, SKMatrix transform, float strokeWidth, bool isStrokeNonScaling)
{
if (!isStrokeNonScaling)
{
return ContainsStroke(path, point, transform, strokeWidth);
}

if (strokeWidth <= 0f)
{
return false;
}

var tolerance = Math.Max(strokeWidth / 2f, MinStrokeTolerance);
return transform.IsIdentity
? ContainsStrokeLocal(path, point, tolerance)
: ContainsTransformedStroke(path, point, transform, tolerance);
}

public static bool IntersectsFill(SKPath path, SKRect rect, SKMatrix transform)
{
if (rect.IsEmpty)
Expand Down Expand Up @@ -248,6 +266,26 @@ private static bool ContainsFillLocal(SKPath path, SKPoint point)
private static bool ContainsStrokeLocal(SKPath path, SKPoint point, float tolerance)
{
var contours = FlattenPath(path);
return ContainsStroke(contours, point, tolerance);
}

private static bool ContainsTransformedStroke(SKPath path, SKPoint point, SKMatrix transform, float tolerance)
{
var contours = FlattenPath(path);
for (var i = 0; i < contours.Count; i++)
{
var points = contours[i].Points;
for (var j = 0; j < points.Count; j++)
{
points[j] = transform.MapPoint(points[j]);
}
}

return ContainsStroke(contours, point, tolerance);
}

private static bool ContainsStroke(List<FlattenedContour> contours, SKPoint point, float tolerance)
{
if (contours.Count == 0)
{
return false;
Expand Down
1 change: 1 addition & 0 deletions src/Svg.SceneGraph/SvgSceneCompiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1184,6 +1184,7 @@ private static bool TryCompileDirectElementNode(
node.HitTestPath = path;
node.SupportsFillHitTest = SvgScenePaintingService.IsValidFill(visualElement);
node.SupportsStrokeHitTest = SvgScenePaintingService.IsValidStroke(visualElement, node.GeometryBounds);
node.IsStrokeNonScaling = visualElement.VectorEffect == SvgVectorEffect.NonScalingStroke;
node.HitTestTargetElement = GetDefaultHitTestTargetElement(node, element);
AssignRetainedVisualState(node, element);
AssignRetainedResourceKeys(node, element, compileContext.GetElementAddressKey);
Expand Down
1 change: 1 addition & 0 deletions src/Svg.SceneGraph/SvgSceneDocument.cs
Original file line number Diff line number Diff line change
Expand Up @@ -753,6 +753,7 @@ private void ResolveRuntimePayload(SvgSceneNode node, bool refreshRetainedMetada
? SvgScenePaintingService.GetStrokePaint(visualElement, node.GeometryBounds, AssetLoader, IgnoreAttributes)
: null;
node.StrokeWidth = hasOwnPaintPayload ? node.Stroke?.StrokeWidth ?? 0f : 0f;
node.IsStrokeNonScaling = hasOwnPaintPayload && visualElement.VectorEffect == SvgVectorEffect.NonScalingStroke;
node.SetMask(null);
node.MaskPaint = null;
node.MaskDstIn = null;
Expand Down
9 changes: 7 additions & 2 deletions src/Svg.SceneGraph/SvgSceneHitTestService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,12 @@ private static bool HitTestStrokeCore(SvgSceneNode node, SKPoint point)
{
if (node.HitTestPath is { } hitTestPath)
{
return GeometryHitTestService.ContainsStroke(hitTestPath, point, node.TotalTransform, node.StrokeWidth);
return GeometryHitTestService.ContainsStroke(
hitTestPath,
point,
node.TotalTransform,
node.StrokeWidth,
node.IsStrokeNonScaling);
}

return node.StrokeWidth > 0f && GetDirectStrokeBounds(node).Contains(point);
Expand Down Expand Up @@ -334,7 +339,7 @@ private static bool UsesStructuralBounds(SvgSceneNode node)
private static SKRect GetRectHitBounds(SvgSceneNode node)
{
var bounds = UsesStructuralBounds(node)
? GetStructuralBounds(node)
? SvgSceneNodeBoundsService.GetRenderablePaintBounds(node)
: GetDirectFillBounds(node);

return SvgSceneNodeBoundsService.GetInflatedBounds(node, bounds);
Expand Down
3 changes: 3 additions & 0 deletions src/Svg.SceneGraph/SvgSceneNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ internal SvgSceneNode(

public float StrokeWidth { get; internal set; }

public bool IsStrokeNonScaling { get; internal set; }

public bool IsRenderable { get; internal set; }

public bool IsAntialias { get; internal set; }
Expand Down Expand Up @@ -188,6 +190,7 @@ internal void ReplaceWith(SvgSceneNode replacement)
SupportsFillHitTest = replacement.SupportsFillHitTest;
SupportsStrokeHitTest = replacement.SupportsStrokeHitTest;
StrokeWidth = replacement.StrokeWidth;
IsStrokeNonScaling = replacement.IsStrokeNonScaling;
IsRenderable = replacement.IsRenderable;
IsAntialias = replacement.IsAntialias;
SuppressSubtreeRendering = replacement.SuppressSubtreeRendering;
Expand Down
10 changes: 7 additions & 3 deletions src/Svg.SceneGraph/SvgSceneNodeBoundsService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,13 @@ public static SKRect GetInflatedBounds(SvgSceneNode node, SKRect bounds)
return bounds;
}

var scaleX = Math.Sqrt((node.TotalTransform.ScaleX * node.TotalTransform.ScaleX) + (node.TotalTransform.SkewY * node.TotalTransform.SkewY));
var scaleY = Math.Sqrt((node.TotalTransform.SkewX * node.TotalTransform.SkewX) + (node.TotalTransform.ScaleY * node.TotalTransform.ScaleY));
var inflation = (float)(Math.Max(scaleX, scaleY) * node.StrokeWidth / 2f);
var inflation = node.StrokeWidth / 2f;
if (!node.IsStrokeNonScaling)
{
var scaleX = Math.Sqrt((node.TotalTransform.ScaleX * node.TotalTransform.ScaleX) + (node.TotalTransform.SkewY * node.TotalTransform.SkewY));
var scaleY = Math.Sqrt((node.TotalTransform.SkewX * node.TotalTransform.SkewX) + (node.TotalTransform.ScaleY * node.TotalTransform.ScaleY));
inflation = (float)(Math.Max(scaleX, scaleY) * inflation);
}
if (inflation <= 0f)
{
return bounds;
Expand Down
1 change: 1 addition & 0 deletions src/Svg.SceneGraph/SvgScenePaintingService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ internal static SKPaint CreateSolidFillPaint(SolidFillPaintCacheKey key)

skPaint.StrokeMiter = svgVisualElement.StrokeMiterLimit;
skPaint.StrokeWidth = svgVisualElement.StrokeWidth.ToDeviceValue(UnitRenderingType.Other, svgVisualElement, skBounds);
skPaint.IsStrokeNonScaling = svgVisualElement.VectorEffect == SvgVectorEffect.NonScalingStroke;

if (svgVisualElement.StrokeDashArray is { })
{
Expand Down
32 changes: 26 additions & 6 deletions src/Svg.Skia/SKSvg.AnimationLayers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -308,30 +308,50 @@ private bool TryDrawAnimationLayers(SkiaSharp.SKCanvas canvas)
return false;
}

SKPicture? staticLayerModel;
SKPicture? dynamicLayerModel;
SkiaSharp.SKPicture? staticLayerPicture;
SkiaSharp.SKPicture? dynamicLayerPicture;
lock (Sync)
{
staticLayerModel = _staticAnimationLayerModel;
dynamicLayerModel = _dynamicAnimationLayerModel;
staticLayerPicture = _staticAnimationLayerPicture;
dynamicLayerPicture = _dynamicAnimationLayerPicture;
}

if (staticLayerPicture is null && dynamicLayerPicture is null)
if (staticLayerModel is null &&
dynamicLayerModel is null &&
staticLayerPicture is null &&
dynamicLayerPicture is null)
{
return false;
}

if (staticLayerPicture is { })
DrawAnimationLayer(staticLayerModel, staticLayerPicture, canvas);
DrawAnimationLayer(dynamicLayerModel, dynamicLayerPicture, canvas);

return true;
}

private void DrawAnimationLayer(SKPicture? model, SkiaSharp.SKPicture? picture, SkiaSharp.SKCanvas canvas)
{
if (model is { } && ContainsNonScalingStroke(model))
{
canvas.DrawPicture(staticLayerPicture);
SkiaModel.Draw(model, canvas);
return;
}

if (dynamicLayerPicture is { })
if (picture is { })
{
canvas.DrawPicture(dynamicLayerPicture);
canvas.DrawPicture(picture);
return;
}

return true;
if (model is { })
{
SkiaModel.Draw(model, canvas);
}
}

private bool TryRefreshAnimationLayerEntries(
Expand Down
Loading
Loading