diff --git a/Source/QuestPDF.Examples/TextExamples.cs b/Source/QuestPDF.Examples/TextExamples.cs index 3aa3df3..d0eb2f3 100644 --- a/Source/QuestPDF.Examples/TextExamples.cs +++ b/Source/QuestPDF.Examples/TextExamples.cs @@ -1004,5 +1004,40 @@ namespace QuestPDF.Examples }); }); } + + [Test] + public void FontFallback_Nested() + { + RenderingTest + .Create() + .ProduceImages() + .ShowResults() + .RenderDocument(container => + { + container.Page(page => + { + page.Margin(50); + page.PageColor(Colors.White); + page.Size(PageSizes.A5.Landscape()); + + page.DefaultTextStyle(x => x + .FontSize(24) + .Bold() + .FontFamily("Times New Roman") + .Fallback(y => y + .FontFamily("Microsoft YaHei") + .Underline() + .BackgroundColor(Colors.Red.Lighten2))); + + page.Content().Text(text => + { + text.Line("Default times new roman 中文文本 text."); + text.Line("Normal weight and green 中文文本 text.").NormalWeight().BackgroundColor(Colors.Green.Lighten2); + text.Line("Strikethrough without underline 中文文本 text.").Strikethrough().Underline(false); + text.Line("Lato italic 中文文本 text.").FontFamily("Lato").Italic(); + }); + }); + }); + } } } \ No newline at end of file diff --git a/Source/QuestPDF.UnitTests/QuestPDF.UnitTests.csproj b/Source/QuestPDF.UnitTests/QuestPDF.UnitTests.csproj index e062de1..622e661 100644 --- a/Source/QuestPDF.UnitTests/QuestPDF.UnitTests.csproj +++ b/Source/QuestPDF.UnitTests/QuestPDF.UnitTests.csproj @@ -3,6 +3,7 @@ netcoreapp3.1 false + 9 diff --git a/Source/QuestPDF.UnitTests/TextStyleTests.cs b/Source/QuestPDF.UnitTests/TextStyleTests.cs new file mode 100644 index 0000000..8b35711 --- /dev/null +++ b/Source/QuestPDF.UnitTests/TextStyleTests.cs @@ -0,0 +1,59 @@ +using FluentAssertions; +using NUnit.Framework; +using QuestPDF.Fluent; +using QuestPDF.Helpers; +using QuestPDF.Infrastructure; + +namespace QuestPDF.UnitTests +{ + [TestFixture] + public class TextStyleTests + { + [Test] + public void ApplyInheritedAndGlobalStyle() + { + // arrange + var defaultTextStyle = TextStyle + .Default + .FontSize(20) + .FontFamily("Arial") + .BackgroundColor(Colors.Green.Lighten2) + .Fallback(y => y + .FontFamily("Microsoft YaHei") + .Underline() + .NormalWeight() + .BackgroundColor(Colors.Blue.Lighten2)); + + var spanTextStyle = TextStyle + .Default + .FontFamily("Times New Roman") + .Bold() + .Strikethrough() + .BackgroundColor(Colors.Red.Lighten2); + + // act + var targetStyle = spanTextStyle.ApplyInheritedStyle(defaultTextStyle).ApplyGlobalStyle(); + + // assert + var expectedStyle = TextStyle.LibraryDefault with + { + Size = 20, + FontFamily = "Times New Roman", + FontWeight = FontWeight.Bold, + BackgroundColor = Colors.Red.Lighten2, + HasStrikethrough = true, + Fallback = TextStyle.LibraryDefault with + { + Size = 20, + FontFamily = "Microsoft YaHei", + FontWeight = FontWeight.Bold, + BackgroundColor = Colors.Red.Lighten2, + HasUnderline = true, + HasStrikethrough = true + } + }; + + targetStyle.Should().BeEquivalentTo(expectedStyle); + } + } +} \ No newline at end of file diff --git a/Source/QuestPDF/Drawing/DocumentGenerator.cs b/Source/QuestPDF/Drawing/DocumentGenerator.cs index d982e3b..3e30ef0 100644 --- a/Source/QuestPDF/Drawing/DocumentGenerator.cs +++ b/Source/QuestPDF/Drawing/DocumentGenerator.cs @@ -64,7 +64,7 @@ namespace QuestPDF.Drawing var container = new DocumentContainer(); document.Compose(container); var content = container.Compose(); - ApplyDefaultTextStyle(content, TextStyle.LibraryDefault); + ApplyInheritedAndGlobalTexStyle(content, TextStyle.Default); ApplyContentDirection(content, ContentDirection.LeftToRight); var debuggingState = Settings.EnableDebugging ? ApplyDebugging(content) : null; @@ -192,7 +192,7 @@ namespace QuestPDF.Drawing ApplyContentDirection(child, direction); } - internal static void ApplyDefaultTextStyle(this Element? content, TextStyle documentDefaultTextStyle) + internal static void ApplyInheritedAndGlobalTexStyle(this Element? content, TextStyle documentDefaultTextStyle) { if (content == null) return; @@ -202,26 +202,23 @@ namespace QuestPDF.Drawing foreach (var textBlockItem in textBlock.Items) { if (textBlockItem is TextBlockSpan textSpan) - { - textSpan.Style = textSpan.Style.ApplyGlobalStyle(documentDefaultTextStyle); - } - else if (textBlockItem is TextBlockElement textElement) - { - ApplyDefaultTextStyle(textElement.Element, documentDefaultTextStyle); - } + textSpan.Style = textSpan.Style.ApplyInheritedStyle(documentDefaultTextStyle).ApplyGlobalStyle(); + + if (textBlockItem is TextBlockElement textElement) + ApplyInheritedAndGlobalTexStyle(textElement.Element, documentDefaultTextStyle); } return; } if (content is DynamicHost dynamicHost) - dynamicHost.TextStyle = dynamicHost.TextStyle.ApplyGlobalStyle(documentDefaultTextStyle); + dynamicHost.TextStyle = dynamicHost.TextStyle.ApplyInheritedStyle(documentDefaultTextStyle); if (content is DefaultTextStyle defaultTextStyleElement) - documentDefaultTextStyle = defaultTextStyleElement.TextStyle.ApplyGlobalStyle(documentDefaultTextStyle); + documentDefaultTextStyle = defaultTextStyleElement.TextStyle.ApplyInheritedStyle(documentDefaultTextStyle); foreach (var child in content.GetChildren()) - ApplyDefaultTextStyle(child, documentDefaultTextStyle); + ApplyInheritedAndGlobalTexStyle(child, documentDefaultTextStyle); } } } \ No newline at end of file diff --git a/Source/QuestPDF/Elements/Dynamic.cs b/Source/QuestPDF/Elements/Dynamic.cs index 450ec19..2e2c5f3 100644 --- a/Source/QuestPDF/Elements/Dynamic.cs +++ b/Source/QuestPDF/Elements/Dynamic.cs @@ -89,7 +89,7 @@ namespace QuestPDF.Elements var container = new DynamicElement(); content(container); - container.ApplyDefaultTextStyle(TextStyle); + container.ApplyInheritedAndGlobalTexStyle(TextStyle); container.ApplyContentDirection(ContentDirection); container.InjectDependencies(PageContext, Canvas); diff --git a/Source/QuestPDF/Infrastructure/TextStyleManager.cs b/Source/QuestPDF/Infrastructure/TextStyleManager.cs index 444dee9..6ab5d33 100644 --- a/Source/QuestPDF/Infrastructure/TextStyleManager.cs +++ b/Source/QuestPDF/Infrastructure/TextStyleManager.cs @@ -25,16 +25,17 @@ namespace QuestPDF.Infrastructure internal static class TextStyleManager { 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> TextStyleApplyInheritedCache = new(); + private static readonly ConcurrentDictionary TextStyleApplyGlobalCache = new(); private static readonly ConcurrentDictionary<(TextStyle origin, TextStyle parent), TextStyle> TextStyleOverrideCache = new(); public static TextStyle Mutate(this TextStyle origin, TextStyleProperty property, object value) { var cacheKey = (origin, property, value); - return TextStyleMutateCache.GetOrAdd(cacheKey, x => MutateStyle(x.origin, x.property, x.value)); + return TextStyleMutateCache.GetOrAdd(cacheKey, x => MutateStyle(x.origin, x.property, x.value, overrideValue: true)); } - private static TextStyle MutateStyle(TextStyle origin, TextStyleProperty property, object? value, bool overrideValue = true) + private static TextStyle MutateStyle(this TextStyle origin, TextStyleProperty property, object? value, bool overrideValue) { if (overrideValue && value == null) return origin; @@ -224,37 +225,37 @@ namespace QuestPDF.Infrastructure 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) + internal static TextStyle ApplyInheritedStyle(this TextStyle style, TextStyle parent) { var cacheKey = (style, parent); - return TextStyleApplyGlobalCache.GetOrAdd(cacheKey, key => ApplyStyle(key.origin, key.parent, overrideStyle: false).ApplyFontFallback()); + return TextStyleApplyInheritedCache.GetOrAdd(cacheKey, key => key.origin.ApplyStyleProperties(key.parent, overrideStyle: false, overrideFontFamily: false, applyFallback: true).UpdateFontFallback(overrideStyle: true)); } - private static TextStyle ApplyFontFallback(this TextStyle style) + internal static TextStyle ApplyGlobalStyle(this TextStyle style) + { + return TextStyleApplyGlobalCache.GetOrAdd(style, key => key.ApplyStyleProperties(TextStyle.LibraryDefault, overrideStyle: false, overrideFontFamily: false, applyFallback: true).UpdateFontFallback(overrideStyle: false)); + } + + private static TextStyle UpdateFontFallback(this TextStyle style, bool overrideStyle) { var targetFallbackStyle = style ?.Fallback - ?.ApplyStyle(style, overrideStyle: false, applyFallback: false) - ?.ApplyFontFallback(); + ?.ApplyStyleProperties(style, overrideStyle: overrideStyle, overrideFontFamily: false, applyFallback: false) + ?.UpdateFontFallback(overrideStyle); - return MutateStyle(style, TextStyleProperty.Fallback, targetFallbackStyle); + return style.MutateStyle(TextStyleProperty.Fallback, targetFallbackStyle, overrideValue: true); } internal static TextStyle OverrideStyle(this TextStyle style, TextStyle parent) { var cacheKey = (style, parent); - - return TextStyleOverrideCache.GetOrAdd(cacheKey, key => - { - var result = ApplyStyle(key.origin, key.parent); - return MutateStyle(result, TextStyleProperty.Fallback, key.parent.Fallback); - }); + return TextStyleOverrideCache.GetOrAdd(cacheKey, key => ApplyStyleProperties(key.origin, key.parent, overrideStyle: true, overrideFontFamily: true, applyFallback: true)); } - private static TextStyle ApplyStyle(this TextStyle style, TextStyle parent, bool overrideStyle = true, bool applyFallback = true) + private static TextStyle ApplyStyleProperties(this TextStyle style, TextStyle parent, bool overrideStyle, bool overrideFontFamily, bool applyFallback) { var result = style; - + result = MutateStyle(result, TextStyleProperty.Color, parent.Color, overrideStyle); result = MutateStyle(result, TextStyleProperty.BackgroundColor, parent.BackgroundColor, overrideStyle); result = MutateStyle(result, TextStyleProperty.FontFamily, parent.FontFamily, overrideStyle);