diff --git a/src/Svg.Skia/SkiaSvgAssetLoader.cs b/src/Svg.Skia/SkiaSvgAssetLoader.cs index e396e6d0b1..6c996e6fce 100644 --- a/src/Svg.Skia/SkiaSvgAssetLoader.cs +++ b/src/Svg.Skia/SkiaSvgAssetLoader.cs @@ -51,6 +51,7 @@ public ShimSkiaSharp.SKImage LoadImage(System.IO.Stream stream) var preferredTypeface = paintPreferredTypeface.Typeface; var weight = _skiaModel.ToSKFontStyleWeight(preferredTypeface?.FontWeight ?? ShimSkiaSharp.SKFontStyleWeight.Normal); + var requestedWeight = preferredTypeface is null ? default(SkiaSharp.SKFontStyleWeight?) : weight; var width = _skiaModel.ToSKFontStyleWidth(preferredTypeface?.FontWidth ?? ShimSkiaSharp.SKFontStyleWidth.Normal); var slant = _skiaModel.ToSKFontStyleSlant(preferredTypeface?.FontSlant ?? ShimSkiaSharp.SKFontStyleSlant.Upright); var preferredFamily = preferredTypeface?.FamilyName; @@ -73,13 +74,7 @@ void YieldCurrentTypefaceText() ret.Add(new(currentTypefaceText, _skiaModel.GetTextAdvance(currentTypefaceText, runningPaint), runningPaint.Typeface is null ? null - : ShimSkiaSharp.SKTypeface.FromFamilyName( - runningPaint.Typeface.FamilyName, - // SkiaSharp provides int properties here. Let's just assume our - // ShimSkiaSharp defines the same values as SkiaSharp and convert directly - (ShimSkiaSharp.SKFontStyleWeight)runningPaint.Typeface.FontWeight, - (ShimSkiaSharp.SKFontStyleWidth)runningPaint.Typeface.FontWidth, - (ShimSkiaSharp.SKFontStyleSlant)runningPaint.Typeface.FontSlant) + : ToShimTypeface(runningPaint.Typeface, requestedWeight) )); } @@ -131,7 +126,7 @@ runningPaint.Typeface is null EnsureTypefaceProviderCaches(); - var codepoints = CollectDistinctRenderableCodepoints(text); + var codepoints = CollectDistinctRenderableCodepoints(text!); if (codepoints.Count == 0) { return paintPreferredTypeface.Typeface; @@ -139,6 +134,7 @@ runningPaint.Typeface is null var preferredTypeface = paintPreferredTypeface.Typeface; var preferredWeight = _skiaModel.ToSKFontStyleWeight(preferredTypeface?.FontWeight ?? ShimSkiaSharp.SKFontStyleWeight.Normal); + var requestedWeight = preferredTypeface is null ? default(SkiaSharp.SKFontStyleWeight?) : preferredWeight; var preferredWidth = _skiaModel.ToSKFontStyleWidth(preferredTypeface?.FontWidth ?? ShimSkiaSharp.SKFontStyleWidth.Normal); var preferredSlant = _skiaModel.ToSKFontStyleSlant(preferredTypeface?.FontSlant ?? ShimSkiaSharp.SKFontStyleSlant.Upright); var preferredFamily = preferredTypeface?.FamilyName; @@ -185,7 +181,7 @@ void AddCandidate(SkiaSharp.SKTypeface? candidate) var candidate = candidates[i]; if (CanRenderAllCodepoints(candidate, codepoints)) { - return ToShimTypeface(candidate); + return ToShimTypeface(candidate, requestedWeight); } } @@ -564,15 +560,25 @@ private static bool CanRenderAllCodepoints(SkiaSharp.SKTypeface? typeface, IRead return true; } - private static ShimSkiaSharp.SKTypeface? ToShimTypeface(SkiaSharp.SKTypeface? typeface) + private static ShimSkiaSharp.SKTypeface? ToShimTypeface( + SkiaSharp.SKTypeface? typeface, + SkiaSharp.SKFontStyleWeight? requestedWeight) { - return typeface is null || typeface.Handle == IntPtr.Zero - ? null - : ShimSkiaSharp.SKTypeface.FromFamilyName( - typeface.FamilyName, - (ShimSkiaSharp.SKFontStyleWeight)typeface.FontWeight, - (ShimSkiaSharp.SKFontStyleWidth)typeface.FontWidth, - (ShimSkiaSharp.SKFontStyleSlant)typeface.FontSlant); + if (typeface is null || typeface.Handle == IntPtr.Zero) + { + return null; + } + + var resolvedWeight = (SkiaSharp.SKFontStyleWeight)typeface.FontWeight; + var shimWeight = requestedWeight is { } weight && resolvedWeight < weight + ? (ShimSkiaSharp.SKFontStyleWeight)weight + : (ShimSkiaSharp.SKFontStyleWeight)resolvedWeight; + + return ShimSkiaSharp.SKTypeface.FromFamilyName( + typeface.FamilyName, + shimWeight, + (ShimSkiaSharp.SKFontStyleWidth)typeface.FontWidth, + (ShimSkiaSharp.SKFontStyleSlant)typeface.FontSlant); } private SkiaSharp.SKTypeface? GetProviderTypeface( diff --git a/tests/Svg.Skia.UnitTests/Issue462Tests.cs b/tests/Svg.Skia.UnitTests/Issue462Tests.cs new file mode 100644 index 0000000000..06466e2c93 --- /dev/null +++ b/tests/Svg.Skia.UnitTests/Issue462Tests.cs @@ -0,0 +1,119 @@ +#pragma warning disable CS0618 // FakeBoldText is deprecated on SKPaint; shim keeps the legacy surface for compatibility + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using ShimSkiaSharp; +using Svg.Skia.TypefaceProviders; +using Svg.Skia.UnitTests.Common; +using Xunit; +using NativeTypeface = SkiaSharp.SKTypeface; + +namespace Svg.Skia.UnitTests; + +public class Issue462Tests : SvgUnitTest +{ + [Fact] + public void FindTypefaces_PreservesRequestedBoldForRegularOnlyResolvedFace() + { + using var provider = new RegularOnlyTypefaceProvider(GetFontsPath("SourceSansPro-Regular.ttf")); + var settings = new SKSvgSettings + { + TypefaceProviders = new List { provider } + }; + var model = new SkiaModel(settings); + var assetLoader = new SkiaSvgAssetLoader(model); + var paint = CreateRequestedBoldPaint(provider.FamilyName); + + var span = Assert.Single(assetLoader.FindTypefaces("Bold Text", paint)); + + Assert.NotNull(span.Typeface); + Assert.Equal(SKFontStyleWeight.Bold, span.Typeface!.FontWeight); + AssertUsesSyntheticBold(model, span.Typeface); + } + + [Fact] + public void FindRunTypeface_PreservesRequestedBoldForRegularOnlyResolvedFace() + { + using var provider = new RegularOnlyTypefaceProvider(GetFontsPath("SourceSansPro-Regular.ttf")); + var settings = new SKSvgSettings + { + TypefaceProviders = new List { provider } + }; + var model = new SkiaModel(settings); + var assetLoader = new SkiaSvgAssetLoader(model); + var paint = CreateRequestedBoldPaint(provider.FamilyName); + + var typeface = assetLoader.FindRunTypeface("Bold Text", paint); + + Assert.NotNull(typeface); + Assert.Equal(SKFontStyleWeight.Bold, typeface!.FontWeight); + AssertUsesSyntheticBold(model, typeface); + } + + private static SKPaint CreateRequestedBoldPaint(string familyName) + { + return new SKPaint + { + TextSize = 48f, + Typeface = SKTypeface.FromFamilyName( + familyName, + SKFontStyleWeight.Bold, + SKFontStyleWidth.Normal, + SKFontStyleSlant.Upright) + }; + } + + private static void AssertUsesSyntheticBold(SkiaModel model, SKTypeface typeface) + { + using var localPaint = model.ToSKPaint(new SKPaint + { + TextSize = 48f, + Typeface = typeface + }); + + Assert.NotNull(localPaint); + Assert.True(localPaint!.FakeBoldText); + } + + private sealed class RegularOnlyTypefaceProvider : ITypefaceProvider, IDisposable + { + private readonly NativeTypeface _typeface; + + public RegularOnlyTypefaceProvider(string path) + { + if (!File.Exists(path)) + { + throw new FileNotFoundException("Test font was not found.", path); + } + + _typeface = NativeTypeface.FromFile(path); + FamilyName = _typeface.FamilyName; + } + + public string FamilyName { get; } + + public NativeTypeface? FromFamilyName( + string fontFamily, + SkiaSharp.SKFontStyleWeight fontWeight, + SkiaSharp.SKFontStyleWidth fontWidth, + SkiaSharp.SKFontStyleSlant fontStyle) + { + var requestedFamilies = fontFamily + .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) + .Select(static family => family.Trim().Trim('"', '\'')); + + return requestedFamilies.Contains(FamilyName, StringComparer.OrdinalIgnoreCase) + ? _typeface + : null; + } + + public void Dispose() + { + _typeface.Dispose(); + } + } +} + +#pragma warning restore CS0618