diff --git a/QuestPDF/Drawing/DocumentGenerator.cs b/QuestPDF/Drawing/DocumentGenerator.cs index 84b0b3d..b64baac 100644 --- a/QuestPDF/Drawing/DocumentGenerator.cs +++ b/QuestPDF/Drawing/DocumentGenerator.cs @@ -172,7 +172,7 @@ namespace QuestPDF.Drawing { if (textBlockItem is TextBlockSpan textSpan) { - textSpan.Style = textSpan.Style.ApplyGlobalStyle(TextStyle.LibraryDefault); + textSpan.Style = textSpan.Style.ApplyGlobalStyle(documentDefaultTextStyle); } else if (textBlockItem is TextBlockElement textElement) { diff --git a/QuestPDF/Elements/Text/FontFallback.cs b/QuestPDF/Elements/Text/FontFallback.cs index 79caeba..e9acbfd 100644 --- a/QuestPDF/Elements/Text/FontFallback.cs +++ b/QuestPDF/Elements/Text/FontFallback.cs @@ -85,22 +85,26 @@ namespace QuestPDF.Elements.Text if (fallbackOption.Font.ContainsGlyph(codepoint)) return fallbackOption; } - + + throw CreateNotMatchingFontException(codepoint); + } + + static Exception CreateNotMatchingFontException(int codepoint) + { var character = char.ConvertFromUtf32(codepoint); var unicode = $"U-{codepoint:X4}"; - var proposedFonts = FindFontsContainingGlyph(codepoint); var proposedFontsFormatted = proposedFonts.Any() ? string.Join(", ", proposedFonts) : "no fonts available"; - throw new DocumentDrawingException( + return new DocumentDrawingException( $"Could not find an appropriate font fallback for glyph: {unicode} '{character}'. " + $"Font families available on current environment that contain this glyph: {proposedFontsFormatted}. " + $"Possible solutions: " + $"1) Use one of the listed fonts as the primary font in your document. " + $"2) Configure the fallback TextStyle using the 'TextStyle.Fallback' method with one of the listed fonts. "); } - + static IEnumerable FindFontsContainingGlyph(int codepoint) { var fontManager = SKFontManager.Default; diff --git a/QuestPDF/Fluent/TextStyleExtensions.cs b/QuestPDF/Fluent/TextStyleExtensions.cs index 723cec5..6119f6d 100644 --- a/QuestPDF/Fluent/TextStyleExtensions.cs +++ b/QuestPDF/Fluent/TextStyleExtensions.cs @@ -156,7 +156,7 @@ namespace QuestPDF.Fluent public static TextStyle Fallback(this TextStyle style, TextStyle? value = null) { - return style.Mutate(x => x.Fallback = value); + return style.Mutate(TextStyleProperty.Fallback, value); } public static TextStyle Fallback(this TextStyle style, Func handler) diff --git a/QuestPDF/Infrastructure/TextStyleManager.cs b/QuestPDF/Infrastructure/TextStyleManager.cs index 0d30c84..031c56b 100644 --- a/QuestPDF/Infrastructure/TextStyleManager.cs +++ b/QuestPDF/Infrastructure/TextStyleManager.cs @@ -16,13 +16,15 @@ namespace QuestPDF.Infrastructure IsItalic, HasStrikethrough, HasUnderline, - WrapAnywhere + WrapAnywhere, + Fallback } internal static class TextStyleManager { - public static ConcurrentDictionary<(TextStyle origin, TextStyleProperty property, object value), TextStyle> TextStyleMutateCache = new(); - public static ConcurrentDictionary<(TextStyle origin, TextStyle parent, bool overrideValue), TextStyle> TextStyleApplyCache = new(); + private static readonly ConcurrentDictionary<(TextStyle origin, TextStyleProperty property, object value), TextStyle> TextStyleMutateCache = new(); + private static readonly ConcurrentDictionary<(TextStyle origin, TextStyle parent), TextStyle> TextStyleApplyGlobalCache = new(); + private static readonly ConcurrentDictionary<(TextStyle origin, TextStyle parent), TextStyle> TextStyleOverrideCache = new(); public static TextStyle Mutate(this TextStyle origin, TextStyleProperty property, object value) { @@ -30,7 +32,7 @@ namespace QuestPDF.Infrastructure return TextStyleMutateCache.GetOrAdd(cacheKey, x => MutateStyle(x.origin, x.property, x.value)); } - private static TextStyle MutateStyle(TextStyle origin, TextStyleProperty property, object value, bool overrideValue = true) + private static TextStyle MutateStyle(TextStyle origin, TextStyleProperty property, object? value, bool overrideValue = true) { if (overrideValue && value == null) return origin; @@ -177,38 +179,69 @@ namespace QuestPDF.Infrastructure return origin with { WrapAnywhere = castedValue }; } + + if (property == TextStyleProperty.Fallback) + { + if (!overrideValue && origin.Fallback != null) + return origin; + + var castedValue = (TextStyle?)value; + + if (origin.Fallback == castedValue) + return origin; + + return origin with { Fallback = castedValue }; + } throw new ArgumentOutOfRangeException(nameof(property), property, "Expected to mutate the TextStyle object. Provided property type is not supported."); } internal static TextStyle ApplyGlobalStyle(this TextStyle style, TextStyle parent) { - var cacheKey = (style, parent, false); - return TextStyleApplyCache.GetOrAdd(cacheKey, key => ApplyStyle(key.origin, key.parent, key.overrideValue)); + var cacheKey = (style, parent); + return TextStyleApplyGlobalCache.GetOrAdd(cacheKey, key => ApplyStyle(key.origin, key.parent, overrideStyle: false).ApplyFontFallback()); + } + + private static TextStyle ApplyFontFallback(this TextStyle style) + { + var targetFallbackStyle = style + ?.Fallback + ?.ApplyStyle(style, overrideStyle: false, applyFallback: false) + ?.ApplyFontFallback(); + + return MutateStyle(style, TextStyleProperty.Fallback, targetFallbackStyle); } internal static TextStyle OverrideStyle(this TextStyle style, TextStyle parent) { - var cacheKey = (style, parent, true); - return TextStyleApplyCache.GetOrAdd(cacheKey, key => ApplyStyle(key.origin, key.parent, key.overrideValue)); + var cacheKey = (style, parent); + + return TextStyleOverrideCache.GetOrAdd(cacheKey, key => + { + var result = ApplyStyle(key.origin, key.parent); + return MutateStyle(result, TextStyleProperty.Fallback, key.parent.Fallback); + }); } - private static TextStyle ApplyStyle(TextStyle style, TextStyle parent, bool overrideValue) + private static TextStyle ApplyStyle(this TextStyle style, TextStyle parent, bool overrideStyle = true, bool applyFallback = true) { var result = style; - result = MutateStyle(result, TextStyleProperty.Color, parent.Color, overrideValue); - result = MutateStyle(result, TextStyleProperty.BackgroundColor, parent.BackgroundColor, overrideValue); - result = MutateStyle(result, TextStyleProperty.FontFamily, parent.FontFamily, overrideValue); - result = MutateStyle(result, TextStyleProperty.Size, parent.Size, overrideValue); - result = MutateStyle(result, TextStyleProperty.LineHeight, parent.LineHeight, overrideValue); - result = MutateStyle(result, TextStyleProperty.FontWeight, parent.FontWeight, overrideValue); - result = MutateStyle(result, TextStyleProperty.FontPosition, parent.FontPosition, overrideValue); - result = MutateStyle(result, TextStyleProperty.IsItalic, parent.IsItalic, overrideValue); - result = MutateStyle(result, TextStyleProperty.HasStrikethrough, parent.HasStrikethrough, overrideValue); - result = MutateStyle(result, TextStyleProperty.HasUnderline, parent.HasUnderline, overrideValue); - result = MutateStyle(result, TextStyleProperty.WrapAnywhere, parent.WrapAnywhere, overrideValue); + result = MutateStyle(result, TextStyleProperty.Color, parent.Color, overrideStyle); + result = MutateStyle(result, TextStyleProperty.BackgroundColor, parent.BackgroundColor, overrideStyle); + result = MutateStyle(result, TextStyleProperty.FontFamily, parent.FontFamily, overrideStyle); + result = MutateStyle(result, TextStyleProperty.Size, parent.Size, overrideStyle); + result = MutateStyle(result, TextStyleProperty.LineHeight, parent.LineHeight, overrideStyle); + result = MutateStyle(result, TextStyleProperty.FontWeight, parent.FontWeight, overrideStyle); + result = MutateStyle(result, TextStyleProperty.FontPosition, parent.FontPosition, overrideStyle); + result = MutateStyle(result, TextStyleProperty.IsItalic, parent.IsItalic, overrideStyle); + result = MutateStyle(result, TextStyleProperty.HasStrikethrough, parent.HasStrikethrough, overrideStyle); + result = MutateStyle(result, TextStyleProperty.HasUnderline, parent.HasUnderline, overrideStyle); + result = MutateStyle(result, TextStyleProperty.WrapAnywhere, parent.WrapAnywhere, overrideStyle); + if (applyFallback) + result = MutateStyle(result, TextStyleProperty.Fallback, parent.Fallback, overrideStyle); + return result; } }