From 78eef45ce2bc2799405d9260cd5b850d3aaf0450 Mon Sep 17 00:00:00 2001 From: Benjamin Swart Date: Mon, 24 Jan 2022 15:13:41 +0100 Subject: [PATCH 01/12] FontManager: Add support for multiple variants of font families registered at runtime --- QuestPDF.UnitTests/FontStyleSetTests.cs | 123 ++++++++++++++++++++++++ QuestPDF/Drawing/FontManager.cs | 46 ++++++--- QuestPDF/Drawing/FontStyleSet.cs | 108 +++++++++++++++++++++ 3 files changed, 265 insertions(+), 12 deletions(-) create mode 100644 QuestPDF.UnitTests/FontStyleSetTests.cs create mode 100644 QuestPDF/Drawing/FontStyleSet.cs diff --git a/QuestPDF.UnitTests/FontStyleSetTests.cs b/QuestPDF.UnitTests/FontStyleSetTests.cs new file mode 100644 index 0000000..5f5a47d --- /dev/null +++ b/QuestPDF.UnitTests/FontStyleSetTests.cs @@ -0,0 +1,123 @@ +using NUnit.Framework; +using QuestPDF.Drawing; +using SkiaSharp; + +namespace QuestPDF.UnitTests +{ + [TestFixture] + public class FontStyleSetTests + { + private void ExpectComparisonOrder(SKFontStyle target, params SKFontStyle[] styles) + { + for (int i = 0; i < styles.Length - 1; i++) + { + Assert.True(FontStyleSet.IsBetterMatch(target, styles[i], styles[i + 1])); + Assert.False(FontStyleSet.IsBetterMatch(target, styles[i + 1], styles[i])); + } + } + + [Test] + public void FontStyleSet_IsBetterMatch_CondensedWidth() + { + ExpectComparisonOrder( + new SKFontStyle(500, 5, SKFontStyleSlant.Upright), + new SKFontStyle(500, 5, SKFontStyleSlant.Upright), + new SKFontStyle(500, 4, SKFontStyleSlant.Upright), + new SKFontStyle(500, 3, SKFontStyleSlant.Upright), + new SKFontStyle(500, 6, SKFontStyleSlant.Upright) + ); + } + + [Test] + public void FontStyleSet_IsBetterMatch_ExpandedWidth() + { + ExpectComparisonOrder( + new SKFontStyle(500, 6, SKFontStyleSlant.Upright), + new SKFontStyle(500, 6, SKFontStyleSlant.Upright), + new SKFontStyle(500, 7, SKFontStyleSlant.Upright), + new SKFontStyle(500, 8, SKFontStyleSlant.Upright), + new SKFontStyle(500, 5, SKFontStyleSlant.Upright) + ); + } + + [Test] + public void FontStyleSet_IsBetterMatch_ItalicSlant() + { + ExpectComparisonOrder( + new SKFontStyle(500, 5, SKFontStyleSlant.Italic), + new SKFontStyle(500, 5, SKFontStyleSlant.Italic), + new SKFontStyle(500, 5, SKFontStyleSlant.Oblique), + new SKFontStyle(500, 5, SKFontStyleSlant.Upright) + ); + } + + [Test] + public void FontStyleSet_IsBetterMatch_ObliqueSlant() + { + ExpectComparisonOrder( + new SKFontStyle(500, 5, SKFontStyleSlant.Oblique), + new SKFontStyle(500, 5, SKFontStyleSlant.Oblique), + new SKFontStyle(500, 5, SKFontStyleSlant.Italic), + new SKFontStyle(500, 5, SKFontStyleSlant.Upright) + ); + } + + [Test] + public void FontStyleSet_IsBetterMatch_UprightSlant() + { + ExpectComparisonOrder( + new SKFontStyle(500, 5, SKFontStyleSlant.Upright), + new SKFontStyle(500, 5, SKFontStyleSlant.Upright), + new SKFontStyle(500, 5, SKFontStyleSlant.Oblique), + new SKFontStyle(500, 5, SKFontStyleSlant.Italic) + ); + } + + [Test] + public void FontStyleSet_IsBetterMatch_ThinWeight() + { + ExpectComparisonOrder( + new SKFontStyle(300, 5, SKFontStyleSlant.Upright), + new SKFontStyle(300, 5, SKFontStyleSlant.Upright), + new SKFontStyle(200, 5, SKFontStyleSlant.Upright), + new SKFontStyle(100, 5, SKFontStyleSlant.Upright), + new SKFontStyle(400, 5, SKFontStyleSlant.Upright) + ); + } + + [Test] + public void FontStyleSet_IsBetterMatch_RegularWeight() + { + ExpectComparisonOrder( + new SKFontStyle(400, 5, SKFontStyleSlant.Upright), + new SKFontStyle(500, 5, SKFontStyleSlant.Upright), + new SKFontStyle(300, 5, SKFontStyleSlant.Upright), + new SKFontStyle(100, 5, SKFontStyleSlant.Upright), + new SKFontStyle(600, 5, SKFontStyleSlant.Upright) + ); + } + + [Test] + public void FontStyleSet_IsBetterMatch_BoldWeight() + { + ExpectComparisonOrder( + new SKFontStyle(600, 5, SKFontStyleSlant.Upright), + new SKFontStyle(600, 5, SKFontStyleSlant.Upright), + new SKFontStyle(700, 5, SKFontStyleSlant.Upright), + new SKFontStyle(800, 5, SKFontStyleSlant.Upright), + new SKFontStyle(500, 5, SKFontStyleSlant.Upright) + ); + } + + [Test] + public void FontStyleSet_RespectsPriority() + { + ExpectComparisonOrder( + new SKFontStyle(500, 5, SKFontStyleSlant.Upright), + new SKFontStyle(600, 5, SKFontStyleSlant.Italic), + new SKFontStyle(600, 6, SKFontStyleSlant.Upright), + new SKFontStyle(500, 6, SKFontStyleSlant.Italic) + ); + } + } +} \ No newline at end of file diff --git a/QuestPDF/Drawing/FontManager.cs b/QuestPDF/Drawing/FontManager.cs index cd600e4..131c1bc 100644 --- a/QuestPDF/Drawing/FontManager.cs +++ b/QuestPDF/Drawing/FontManager.cs @@ -8,16 +8,29 @@ namespace QuestPDF.Drawing { public static class FontManager { - private static ConcurrentDictionary Typefaces = new ConcurrentDictionary(); + private static ConcurrentDictionary StyleSets = new ConcurrentDictionary(); private static ConcurrentDictionary FontMetrics = new ConcurrentDictionary(); private static ConcurrentDictionary Paints = new ConcurrentDictionary(); private static ConcurrentDictionary ColorPaint = new ConcurrentDictionary(); + private static void RegisterFontType(string fontName, SKTypeface typeface) + { + FontStyleSet set = StyleSets.GetOrAdd(fontName, _ => new FontStyleSet()); + set.Add(typeface); + } + public static void RegisterFontType(string fontName, Stream stream) { - Typefaces.TryAdd(fontName, SKTypeface.FromStream(stream)); + SKTypeface typeface = SKTypeface.FromStream(stream); + RegisterFontType(fontName, typeface); } - + + public static void RegisterFontType(Stream stream) + { + SKTypeface typeface = SKTypeface.FromStream(stream); + RegisterFontType(typeface.FamilyName, typeface); + } + internal static SKPaint ColorToPaint(this string color) { return ColorPaint.GetOrAdd(color, Convert); @@ -30,11 +43,11 @@ namespace QuestPDF.Drawing }; } } - + internal static SKPaint ToPaint(this TextStyle style) { return Paints.GetOrAdd(style.Key, key => Convert(style)); - + static SKPaint Convert(TextStyle style) { return new SKPaint @@ -48,13 +61,22 @@ namespace QuestPDF.Drawing static SKTypeface GetTypeface(TextStyle style) { - if (Typefaces.TryGetValue(style.FontType, out var result)) - return result; - - var slant = (style.IsItalic ?? false) ? SKFontStyleSlant.Italic : SKFontStyleSlant.Upright; - - return SKTypeface.FromFamilyName(style.FontType, (int)(style.FontWeight ?? FontWeight.Normal), (int)SKFontStyleWidth.Normal, slant) - ?? throw new ArgumentException($"The typeface {style.FontType} could not be found."); + SKFontStyleWeight weight = (SKFontStyleWeight)(style.FontWeight ?? FontWeight.Normal); + SKFontStyleWidth width = SKFontStyleWidth.Normal; + SKFontStyleSlant slant = (style.IsItalic ?? false) ? SKFontStyleSlant.Italic : SKFontStyleSlant.Upright; + + SKFontStyle skFontStyle = new SKFontStyle(weight, width, slant); + + FontStyleSet set; + if (StyleSets.TryGetValue(style.FontType, out set)) + { + return set.Match(skFontStyle); + } + else + { + return SKTypeface.FromFamilyName(style.FontType, skFontStyle) + ?? throw new ArgumentException($"The typeface {style.FontType} could not be found."); + } } } diff --git a/QuestPDF/Drawing/FontStyleSet.cs b/QuestPDF/Drawing/FontStyleSet.cs new file mode 100644 index 0000000..a96b633 --- /dev/null +++ b/QuestPDF/Drawing/FontStyleSet.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections.Generic; +using System.Collections.Concurrent; +using SkiaSharp; + +namespace QuestPDF.Drawing +{ + internal class FontStyleSet + { + private ConcurrentDictionary Styles = new ConcurrentDictionary(); + + public void Add(SKTypeface typeface) + { + SKFontStyle style = typeface.FontStyle; + Styles.AddOrUpdate(style, (_) => typeface, (_, _) => typeface); + } + + public SKTypeface Match(SKFontStyle target) + { + SKFontStyle bestStyle = null; + SKTypeface bestTypeface = null; + + foreach (var entry in Styles) + { + if (IsBetterMatch(target, entry.Key, bestStyle)) + { + bestStyle = entry.Key; + bestTypeface = entry.Value; + } + } + + return bestTypeface; + } + + private static Dictionary> SlantFallbacks = new() + { + { SKFontStyleSlant.Italic, new() { SKFontStyleSlant.Italic, SKFontStyleSlant.Oblique, SKFontStyleSlant.Upright } }, + { SKFontStyleSlant.Oblique, new() { SKFontStyleSlant.Oblique, SKFontStyleSlant.Italic, SKFontStyleSlant.Upright } }, + { SKFontStyleSlant.Upright, new() { SKFontStyleSlant.Upright, SKFontStyleSlant.Oblique, SKFontStyleSlant.Italic } }, + }; + + // Checks whether style a is a better match for the target then style b. Uses the CSS font style matching algorithm + internal static bool IsBetterMatch(SKFontStyle target, SKFontStyle a, SKFontStyle b) + { + // A font is better than no font + if (b == null) return true; + if (a == null) return false; + + // First check font width + // For normal and condensed widths prefer smaller widths + // For expanded widths prefer larger widths + if (target.Width <= (int)SKFontStyleWidth.Normal) + { + if (a.Width <= target.Width && b.Width > target.Width) return true; + if (a.Width > target.Width && b.Width <= target.Width) return false; + } + else + { + if (a.Width >= target.Width && b.Width < target.Width) return true; + if (a.Width < target.Width && b.Width >= target.Width) return false; + } + + // Prefer closest match + int widthDifferenceA = Math.Abs(a.Width - target.Width); + int widthDifferenceB = Math.Abs(b.Width - target.Width); + + if (widthDifferenceA < widthDifferenceB) return true; + if (widthDifferenceB < widthDifferenceA) return false; + + // Prefer closest slant based on provided fallback list + List slantFallback = SlantFallbacks[target.Slant]; + int slantIndexA = slantFallback.IndexOf(a.Slant); + int slantIndexB = slantFallback.IndexOf(b.Slant); + + if (slantIndexA < slantIndexB) return true; + if (slantIndexB < slantIndexA) return false; + + // Check weight last + // For thin (<400) weights, prefer thinner weights + // For regular (400-500) weights, prefer other regular weights, then use rule for thin or bold + // For bold (>500) weights, prefer thicker weights + // Behavior for values other than multiples of 100 is not given in the specification + + if (target.Weight >= 400 && target.Weight <= 500) + { + if ((a.Weight >= 400 && a.Weight <= 500) && !(b.Weight >= 400 && b.Weight <= 500)) return true; + if (!(a.Weight >= 400 && a.Weight <= 500) && (b.Weight >= 400 && b.Weight <= 500)) return false; + } + + if (target.Weight < 450) + { + if (a.Weight <= target.Weight && b.Weight > target.Weight) return true; + if (a.Weight > target.Weight && b.Weight <= target.Weight) return false; + } + else + { + if (a.Weight >= target.Weight && b.Weight < target.Weight) return true; + if (a.Weight < target.Weight && b.Weight >= target.Weight) return false; + } + + // Prefer closest weight + int weightDifferenceA = Math.Abs(a.Weight - target.Weight); + int weightDifferenceB = Math.Abs(b.Weight - target.Weight); + + return weightDifferenceA < weightDifferenceB; + } + } +} \ No newline at end of file From 4479a49ad015839ab4a5b6379942a5d7efc44277 Mon Sep 17 00:00:00 2001 From: MarcinZiabek Date: Wed, 16 Feb 2022 15:34:54 +0100 Subject: [PATCH 02/12] Improved support for page sections --- .../Layouts/TableOfContentsTemplate.cs | 15 +++++- QuestPDF/Elements/InternalLocation.cs | 3 +- .../Text/Items/TextBlockPageNumber.cs | 14 ++--- QuestPDF/Fluent/TextExtensions.cs | 48 ++++++++++++----- QuestPDF/Infrastructure/IPageContext.cs | 16 ++++-- QuestPDF/Infrastructure/PageContext.cs | 51 ++++++++++--------- QuestPDF/Infrastructure/Unit.cs | 4 ++ 7 files changed, 98 insertions(+), 53 deletions(-) diff --git a/QuestPDF.ReportSample/Layouts/TableOfContentsTemplate.cs b/QuestPDF.ReportSample/Layouts/TableOfContentsTemplate.cs index 3f93f93..16444c0 100644 --- a/QuestPDF.ReportSample/Layouts/TableOfContentsTemplate.cs +++ b/QuestPDF.ReportSample/Layouts/TableOfContentsTemplate.cs @@ -1,5 +1,7 @@ using System.Collections.Generic; +using System.Drawing; using QuestPDF.Fluent; +using QuestPDF.Helpers; using QuestPDF.Infrastructure; namespace QuestPDF.ReportSample.Layouts @@ -43,7 +45,18 @@ namespace QuestPDF.ReportSample.Layouts { row.ConstantItem(25).Text($"{number}."); row.RelativeItem().Text(locationName); - row.ConstantItem(150).AlignRight().Text(text => text.PageNumberOfLocation(locationName)); + row.ConstantItem(150).AlignRight().Text(text => + { + text.BeginPageNumberOfLocation(locationName); + text.Span(" - "); + text.EndPageNumberOfLocation(locationName); + + var lengthStyle = TextStyle.Default.Color(Colors.Grey.Medium); + + text.Span(" (", lengthStyle); + text.PageLengthOfLocation(locationName, x => x == 1 ? "1 page long" : $"{x} pages long", lengthStyle); + text.Span(")", lengthStyle); + }); }); } } diff --git a/QuestPDF/Elements/InternalLocation.cs b/QuestPDF/Elements/InternalLocation.cs index 99c122d..ec21260 100644 --- a/QuestPDF/Elements/InternalLocation.cs +++ b/QuestPDF/Elements/InternalLocation.cs @@ -16,12 +16,11 @@ namespace QuestPDF.Elements { if (!IsRendered) { - PageContext.SetLocationPage(LocationName); - Canvas.DrawLocation(LocationName); IsRendered = true; } + PageContext.SetLocationPage(LocationName); base.Draw(availableSpace); } } diff --git a/QuestPDF/Elements/Text/Items/TextBlockPageNumber.cs b/QuestPDF/Elements/Text/Items/TextBlockPageNumber.cs index 85a829d..b48b65d 100644 --- a/QuestPDF/Elements/Text/Items/TextBlockPageNumber.cs +++ b/QuestPDF/Elements/Text/Items/TextBlockPageNumber.cs @@ -1,11 +1,13 @@ -using QuestPDF.Elements.Text.Calculation; +using System; +using QuestPDF.Elements.Text.Calculation; using QuestPDF.Infrastructure; namespace QuestPDF.Elements.Text.Items { internal class TextBlockPageNumber : TextBlockSpan { - public string SlotName { get; set; } + public const string PageNumberPlaceholder = "123"; + public Func Source { get; set; } = _ => PageNumberPlaceholder; public override TextMeasurementResult? Measure(TextMeasurementRequest request) { @@ -21,13 +23,7 @@ namespace QuestPDF.Elements.Text.Items private void SetPageNumber(IPageContext context) { - var pageNumberPlaceholder = 123; - - var pageNumber = context.GetRegisteredLocations().Contains(SlotName) - ? context.GetLocationPage(SlotName) - : pageNumberPlaceholder; - - Text = pageNumber.ToString(); + Text = Source(context) ?? PageNumberPlaceholder; } } } \ No newline at end of file diff --git a/QuestPDF/Fluent/TextExtensions.cs b/QuestPDF/Fluent/TextExtensions.cs index 2d688b4..eb2d680 100644 --- a/QuestPDF/Fluent/TextExtensions.cs +++ b/QuestPDF/Fluent/TextExtensions.cs @@ -90,36 +90,60 @@ namespace QuestPDF.Fluent Span(Environment.NewLine); } - private void PageNumber(string slotName, TextStyle? style = null) + private void PageNumber(Func source, TextStyle? style = null) { - if (IsNullOrEmpty(slotName)) - throw new ArgumentException(nameof(slotName)); - style ??= TextStyle.Default; AddItemToLastTextBlock(new TextBlockPageNumber() { - Style = style, - SlotName = slotName + Source = source, + Style = style }); } + public void CurrentPageNumber(Func format, TextStyle? style = null) + { + PageNumber(context => format(context.CurrentPage), style); + } + public void CurrentPageNumber(TextStyle? style = null) { - PageNumber(PageContext.CurrentPageSlot, style); + CurrentPageNumber(x => x.ToString(), style); + } + + public void TotalPages(Func format, TextStyle? style = null) + { + PageNumber(context => format(context.GetLocation(PageContext.DocumentLocation)?.Length), style); } public void TotalPages(TextStyle? style = null) { - PageNumber(PageContext.TotalPagesSlot, style); + TotalPages(x => x.ToString(), style); } public void PageNumberOfLocation(string locationName, TextStyle? style = null) { - if (IsNullOrEmpty(locationName)) - throw new ArgumentException(nameof(locationName)); - - PageNumber(locationName, style); + BeginPageNumberOfLocation(locationName); + } + + public void BeginPageNumberOfLocation(string locationName, TextStyle? style = null) + { + PageNumber(context => (context.GetLocation(locationName)?.PageStart ?? 123).ToString(), style); + } + + public void EndPageNumberOfLocation(string locationName, TextStyle? style = null) + { + PageNumber(context => (context.GetLocation(locationName)?.PageEnd ?? 123).ToString(), style); + } + + public void PageNumberWithinLocation(string locationName, Func format, TextStyle? style = null) + { + PageNumber(context => format(context.CurrentPage + 1 - context.GetLocation(locationName)?.PageEnd), style); + } + + public void PageLengthOfLocation(string locationName, Func format, TextStyle? style = null) + { + PageNumber(context => format(context.GetLocation(locationName)?.Length), style); } public void InternalLocation(string? text, string locationName, TextStyle? style = null) diff --git a/QuestPDF/Infrastructure/IPageContext.cs b/QuestPDF/Infrastructure/IPageContext.cs index b445994..b9253bd 100644 --- a/QuestPDF/Infrastructure/IPageContext.cs +++ b/QuestPDF/Infrastructure/IPageContext.cs @@ -2,10 +2,18 @@ namespace QuestPDF.Infrastructure { - public interface IPageContext + internal class DocumentLocation { - void SetLocationPage(string key); - int GetLocationPage(string key); - ICollection GetRegisteredLocations(); + public string Name { get; set; } + public int PageStart { get; set; } + public int PageEnd { get; set; } + public int Length => PageEnd - PageStart + 1; + } + + internal interface IPageContext + { + int CurrentPage { get; } + void SetLocationPage(string name); + DocumentLocation? GetLocation(string name); } } \ No newline at end of file diff --git a/QuestPDF/Infrastructure/PageContext.cs b/QuestPDF/Infrastructure/PageContext.cs index bc2597b..1497a3c 100644 --- a/QuestPDF/Infrastructure/PageContext.cs +++ b/QuestPDF/Infrastructure/PageContext.cs @@ -1,44 +1,45 @@ using System; using System.Collections.Generic; +using System.Linq; namespace QuestPDF.Infrastructure { - public class PageContext : IPageContext + internal class PageContext : IPageContext { - public const string CurrentPageSlot = "currentPage"; - public const string TotalPagesSlot = "totalPages"; + public const string DocumentLocation = "document"; - private Dictionary Locations { get; } = new Dictionary(); - private int PageNumber { get; set; } + private List Locations { get; } = new(); + public int CurrentPage { get; private set; } internal void SetPageNumber(int number) { - PageNumber = number; - Locations[CurrentPageSlot] = number; - - if (!Locations.ContainsKey(TotalPagesSlot) || Locations[TotalPagesSlot] < number) - Locations[TotalPagesSlot] = number; + CurrentPage = number; + SetLocationPage(DocumentLocation); } - public void SetLocationPage(string key) + public void SetLocationPage(string name) { - if (Locations.ContainsKey(key)) - return; - - Locations[key] = PageNumber; + var location = GetLocation(name); + + if (location == null) + { + location = new DocumentLocation + { + Name = name, + PageStart = CurrentPage, + PageEnd = CurrentPage + }; + + Locations.Add(location); + } + + if (location.PageEnd < CurrentPage) + location.PageEnd = CurrentPage; } - public int GetLocationPage(string key) + public DocumentLocation? GetLocation(string name) { - if (!Locations.ContainsKey(key)) - throw new ArgumentException($"The location '{key}' does not exists."); - - return Locations[key]; - } - - public ICollection GetRegisteredLocations() - { - return Locations.Keys; + return Locations.FirstOrDefault(x => x.Name == name); } } } \ No newline at end of file diff --git a/QuestPDF/Infrastructure/Unit.cs b/QuestPDF/Infrastructure/Unit.cs index a4d55fc..af4af4a 100644 --- a/QuestPDF/Infrastructure/Unit.cs +++ b/QuestPDF/Infrastructure/Unit.cs @@ -13,6 +13,10 @@ namespace QuestPDF.Infrastructure Feet, Inch, + + /// + /// 1/1000th of inch + /// Mill } From d73c694452d3fcbdb35584da5c86accbce6b675b Mon Sep 17 00:00:00 2001 From: MarcinZiabek Date: Sun, 20 Feb 2022 16:17:43 +0100 Subject: [PATCH 03/12] Added minimal API --- QuestPDF.Examples/MinimalApiExamples.cs | 31 ++++++++++++++++++++++ QuestPDF/Fluent/MinimalApi.cs | 35 +++++++++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 QuestPDF.Examples/MinimalApiExamples.cs create mode 100644 QuestPDF/Fluent/MinimalApi.cs diff --git a/QuestPDF.Examples/MinimalApiExamples.cs b/QuestPDF.Examples/MinimalApiExamples.cs new file mode 100644 index 0000000..ecea44e --- /dev/null +++ b/QuestPDF.Examples/MinimalApiExamples.cs @@ -0,0 +1,31 @@ +using System.Diagnostics; +using NUnit.Framework; +using QuestPDF.Examples.Engine; +using QuestPDF.Fluent; +using QuestPDF.Helpers; +using QuestPDF.Infrastructure; +using ShimSkiaSharp; +using SKTypeface = SkiaSharp.SKTypeface; + +namespace QuestPDF.Examples +{ + public class MinimalApiExamples + { + [Test] + public void MinimalApi() + { + Document + .Create(container => + { + container.Page(page => + { + page.Margin(2, Unit.Centimetre); + page.Content().Text("Hello PDF!"); + }); + }) + .GeneratePdf("hello.pdf"); + + Process.Start("explorer.exe", "hello.pdf"); + } + } +} \ No newline at end of file diff --git a/QuestPDF/Fluent/MinimalApi.cs b/QuestPDF/Fluent/MinimalApi.cs new file mode 100644 index 0000000..a37110d --- /dev/null +++ b/QuestPDF/Fluent/MinimalApi.cs @@ -0,0 +1,35 @@ +using System; +using QuestPDF.Drawing; +using QuestPDF.Infrastructure; + +namespace QuestPDF.Fluent +{ + public class Document : IDocument + { + private Action ContentSource { get; } + private DocumentMetadata Metadata { get; set; } = DocumentMetadata.Default; + + private Document(Action contentSource) + { + ContentSource = contentSource; + } + + public static Document Create(Action handler) + { + return new Document(handler); + } + + public Document WithMetadata(DocumentMetadata metadata) + { + Metadata = metadata ?? Metadata; + return this; + } + + #region IDocument + + public DocumentMetadata GetMetadata() => Metadata; + public void Compose(IDocumentContainer container) => ContentSource(container); + + #endregion + } +} \ No newline at end of file From 68df390d0e9ec51d2e06b64bf87a8f1e5df5d304 Mon Sep 17 00:00:00 2001 From: MarcinZiabek Date: Mon, 21 Feb 2022 00:57:53 +0100 Subject: [PATCH 04/12] Element renaming + custom text format --- QuestPDF.ReportSample/Helpers.cs | 28 ++++++++ .../Layouts/StandardReport.cs | 4 +- .../Layouts/TableOfContentsTemplate.cs | 6 +- QuestPDF.UnitTests/ExternalLinkTests.cs | 2 +- QuestPDF.UnitTests/InternalLinkTests.cs | 2 +- QuestPDF.UnitTests/InternalLocationTests.cs | 2 +- QuestPDF.UnitTests/TestEngine/MockCanvas.cs | 8 +-- .../TestEngine/OperationRecordingCanvas.cs | 6 +- QuestPDF/Drawing/FreeCanvas.cs | 6 +- QuestPDF/Drawing/SkiaCanvasBase.cs | 10 +-- .../{ExternalLink.cs => Hyperlink.cs} | 4 +- .../{InternalLocation.cs => Section.cs} | 6 +- .../{InternalLink.cs => SectionLink.cs} | 6 +- ...kExternalLink.cs => TextBlockHyperlink.cs} | 4 +- ...ternalLink.cs => TextBlockSectionlLink.cs} | 6 +- QuestPDF/Fluent/ElementExtensions.cs | 30 ++++++-- QuestPDF/Fluent/TextExtensions.cs | 72 ++++++++++--------- QuestPDF/Infrastructure/ICanvas.cs | 6 +- QuestPDF/Infrastructure/IPageContext.cs | 2 +- QuestPDF/Infrastructure/PageContext.cs | 4 +- 20 files changed, 133 insertions(+), 81 deletions(-) rename QuestPDF/Elements/{ExternalLink.cs => Hyperlink.cs} (80%) rename QuestPDF/Elements/{InternalLocation.cs => Section.cs} (73%) rename QuestPDF/Elements/{InternalLink.cs => SectionLink.cs} (69%) rename QuestPDF/Elements/Text/Items/{TextBlockExternalLink.cs => TextBlockHyperlink.cs} (78%) rename QuestPDF/Elements/Text/Items/{TextBlockInternalLink.cs => TextBlockSectionlLink.cs} (72%) diff --git a/QuestPDF.ReportSample/Helpers.cs b/QuestPDF.ReportSample/Helpers.cs index cb9af62..572284f 100644 --- a/QuestPDF.ReportSample/Helpers.cs +++ b/QuestPDF.ReportSample/Helpers.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.IO; using SkiaSharp; @@ -24,5 +25,32 @@ namespace QuestPDF.ReportSample Latitude = Helpers.Random.NextDouble() * 180f - 90f }; } + + private static readonly ConcurrentDictionary RomanNumeralCache = new ConcurrentDictionary(); + + public static string FormatAsRomanNumeral(this int number) + { + if (number < 0 || number > 3999) + throw new ArgumentOutOfRangeException("Number should be in range from 1 to 3999"); + + return RomanNumeralCache.GetOrAdd(number, x => + { + if (x >= 1000) return "M" + FormatAsRomanNumeral(x - 1000); + if (x >= 900) return "CM" + FormatAsRomanNumeral(x - 900); + if (x >= 500) return "D" + FormatAsRomanNumeral(x - 500); + if (x >= 400) return "CD" + FormatAsRomanNumeral(x - 400); + if (x >= 100) return "C" + FormatAsRomanNumeral(x - 100); + if (x >= 90) return "XC" + FormatAsRomanNumeral(x - 90); + if (x >= 50) return "L" + FormatAsRomanNumeral(x - 50); + if (x >= 40) return "XL" + FormatAsRomanNumeral(x - 40); + if (x >= 10) return "X" + FormatAsRomanNumeral(x - 10); + if (x >= 9) return "IX" + FormatAsRomanNumeral(x - 9); + if (x >= 5) return "V" + FormatAsRomanNumeral(x - 5); + if (x >= 4) return "IV" + FormatAsRomanNumeral(x - 4); + if (x >= 1) return "I" + FormatAsRomanNumeral(x - 1); + + return string.Empty; + }); + } } } \ No newline at end of file diff --git a/QuestPDF.ReportSample/Layouts/StandardReport.cs b/QuestPDF.ReportSample/Layouts/StandardReport.cs index 2bba69a..143d5c9 100644 --- a/QuestPDF.ReportSample/Layouts/StandardReport.cs +++ b/QuestPDF.ReportSample/Layouts/StandardReport.cs @@ -39,9 +39,9 @@ namespace QuestPDF.ReportSample.Layouts page.Footer().AlignCenter().Text(text => { - text.CurrentPageNumber(); + text.CurrentPageNumber(format: x => x?.FormatAsRomanNumeral() ?? "-----"); text.Span(" / "); - text.TotalPages(); + text.TotalPages(format: x => x?.FormatAsRomanNumeral() ?? "-----"); }); }); } diff --git a/QuestPDF.ReportSample/Layouts/TableOfContentsTemplate.cs b/QuestPDF.ReportSample/Layouts/TableOfContentsTemplate.cs index 16444c0..62c02e3 100644 --- a/QuestPDF.ReportSample/Layouts/TableOfContentsTemplate.cs +++ b/QuestPDF.ReportSample/Layouts/TableOfContentsTemplate.cs @@ -47,14 +47,14 @@ namespace QuestPDF.ReportSample.Layouts row.RelativeItem().Text(locationName); row.ConstantItem(150).AlignRight().Text(text => { - text.BeginPageNumberOfLocation(locationName); + text.BeginPageNumberOfSection(locationName); text.Span(" - "); - text.EndPageNumberOfLocation(locationName); + text.EndPageNumberOfSection(locationName); var lengthStyle = TextStyle.Default.Color(Colors.Grey.Medium); text.Span(" (", lengthStyle); - text.PageLengthOfLocation(locationName, x => x == 1 ? "1 page long" : $"{x} pages long", lengthStyle); + text.TotalPagesWithinSection(locationName, lengthStyle, x => x == 1 ? "1 page long" : $"{x} pages long"); text.Span(")", lengthStyle); }); }); diff --git a/QuestPDF.UnitTests/ExternalLinkTests.cs b/QuestPDF.UnitTests/ExternalLinkTests.cs index cd6853d..5461cf2 100644 --- a/QuestPDF.UnitTests/ExternalLinkTests.cs +++ b/QuestPDF.UnitTests/ExternalLinkTests.cs @@ -8,7 +8,7 @@ namespace QuestPDF.UnitTests public class ExternalLinkTests { [Test] - public void Measure() => SimpleContainerTests.Measure(); + public void Measure() => SimpleContainerTests.Measure(); // TODO: consider tests for the Draw method } diff --git a/QuestPDF.UnitTests/InternalLinkTests.cs b/QuestPDF.UnitTests/InternalLinkTests.cs index bd52d1c..1ad9432 100644 --- a/QuestPDF.UnitTests/InternalLinkTests.cs +++ b/QuestPDF.UnitTests/InternalLinkTests.cs @@ -8,7 +8,7 @@ namespace QuestPDF.UnitTests public class InternalLinkTests { [Test] - public void Measure() => SimpleContainerTests.Measure(); + public void Measure() => SimpleContainerTests.Measure(); // TODO: consider tests for the Draw method } diff --git a/QuestPDF.UnitTests/InternalLocationTests.cs b/QuestPDF.UnitTests/InternalLocationTests.cs index 8a21fcb..5966c36 100644 --- a/QuestPDF.UnitTests/InternalLocationTests.cs +++ b/QuestPDF.UnitTests/InternalLocationTests.cs @@ -8,7 +8,7 @@ namespace QuestPDF.UnitTests public class InternalLocationTests { [Test] - public void Measure() => SimpleContainerTests.Measure(); + public void Measure() => SimpleContainerTests.Measure(); // TODO: consider tests for the Draw method } diff --git a/QuestPDF.UnitTests/TestEngine/MockCanvas.cs b/QuestPDF.UnitTests/TestEngine/MockCanvas.cs index fbed591..6e4926e 100644 --- a/QuestPDF.UnitTests/TestEngine/MockCanvas.cs +++ b/QuestPDF.UnitTests/TestEngine/MockCanvas.cs @@ -20,9 +20,9 @@ namespace QuestPDF.UnitTests.TestEngine public void DrawRectangle(Position vector, Size size, string color) => DrawRectFunc(vector, size, color); public void DrawText(string text, Position position, TextStyle style) => DrawTextFunc(text, position, style); public void DrawImage(SKImage image, Position position, Size size) => DrawImageFunc(image, position, size); - - public void DrawExternalLink(string url, Size size) => throw new NotImplementedException(); - public void DrawLocationLink(string locationName, Size size) => throw new NotImplementedException(); - public void DrawLocation(string locationName) => throw new NotImplementedException(); + + public void DrawHyperlink(string url, Size size) => throw new NotImplementedException(); + public void DrawSectionLink(string sectionName, Size size) => throw new NotImplementedException(); + public void DrawSection(string sectionName) => throw new NotImplementedException(); } } \ No newline at end of file diff --git a/QuestPDF.UnitTests/TestEngine/OperationRecordingCanvas.cs b/QuestPDF.UnitTests/TestEngine/OperationRecordingCanvas.cs index 3617052..4fbedfe 100644 --- a/QuestPDF.UnitTests/TestEngine/OperationRecordingCanvas.cs +++ b/QuestPDF.UnitTests/TestEngine/OperationRecordingCanvas.cs @@ -18,8 +18,8 @@ namespace QuestPDF.UnitTests.TestEngine public void DrawText(string text, Position position, TextStyle style) => Operations.Add(new CanvasDrawTextOperation(text, position, style)); public void DrawImage(SKImage image, Position position, Size size) => Operations.Add(new CanvasDrawImageOperation(position, size)); - public void DrawExternalLink(string url, Size size) => throw new NotImplementedException(); - public void DrawLocationLink(string locationName, Size size) => throw new NotImplementedException(); - public void DrawLocation(string locationName) => throw new NotImplementedException(); + public void DrawHyperlink(string url, Size size) => throw new NotImplementedException(); + public void DrawSectionLink(string sectionName, Size size) => throw new NotImplementedException(); + public void DrawSection(string sectionName) => throw new NotImplementedException(); } } \ No newline at end of file diff --git a/QuestPDF/Drawing/FreeCanvas.cs b/QuestPDF/Drawing/FreeCanvas.cs index 6716e9e..251c9d1 100644 --- a/QuestPDF/Drawing/FreeCanvas.cs +++ b/QuestPDF/Drawing/FreeCanvas.cs @@ -51,17 +51,17 @@ namespace QuestPDF.Drawing } - public void DrawExternalLink(string url, Size size) + public void DrawHyperlink(string url, Size size) { } - public void DrawLocationLink(string locationName, Size size) + public void DrawSectionLink(string sectionName, Size size) { } - public void DrawLocation(string locationName) + public void DrawSection(string sectionName) { } diff --git a/QuestPDF/Drawing/SkiaCanvasBase.cs b/QuestPDF/Drawing/SkiaCanvasBase.cs index 9ec37a8..aaf8e25 100644 --- a/QuestPDF/Drawing/SkiaCanvasBase.cs +++ b/QuestPDF/Drawing/SkiaCanvasBase.cs @@ -37,19 +37,19 @@ namespace QuestPDF.Drawing Canvas.DrawImage(image, new SKRect(vector.X, vector.Y, size.Width, size.Height)); } - public void DrawExternalLink(string url, Size size) + public void DrawHyperlink(string url, Size size) { Canvas.DrawUrlAnnotation(new SKRect(0, 0, size.Width, size.Height), url); } - public void DrawLocationLink(string locationName, Size size) + public void DrawSectionLink(string sectionName, Size size) { - Canvas.DrawLinkDestinationAnnotation(new SKRect(0, 0, size.Width, size.Height), locationName); + Canvas.DrawLinkDestinationAnnotation(new SKRect(0, 0, size.Width, size.Height), sectionName); } - public void DrawLocation(string locationName) + public void DrawSection(string sectionName) { - Canvas.DrawNamedDestinationAnnotation(new SKPoint(0, 0), locationName); + Canvas.DrawNamedDestinationAnnotation(new SKPoint(0, 0), sectionName); } public void Rotate(float angle) diff --git a/QuestPDF/Elements/ExternalLink.cs b/QuestPDF/Elements/Hyperlink.cs similarity index 80% rename from QuestPDF/Elements/ExternalLink.cs rename to QuestPDF/Elements/Hyperlink.cs index 67c2d84..73e9bce 100644 --- a/QuestPDF/Elements/ExternalLink.cs +++ b/QuestPDF/Elements/Hyperlink.cs @@ -3,7 +3,7 @@ using QuestPDF.Infrastructure; namespace QuestPDF.Elements { - internal class ExternalLink : ContainerElement + internal class Hyperlink : ContainerElement { public string Url { get; set; } = "https://www.questpdf.com"; @@ -14,7 +14,7 @@ namespace QuestPDF.Elements if (targetSize.Type == SpacePlanType.Wrap) return; - Canvas.DrawExternalLink(Url, targetSize); + Canvas.DrawHyperlink(Url, targetSize); base.Draw(availableSpace); } } diff --git a/QuestPDF/Elements/InternalLocation.cs b/QuestPDF/Elements/Section.cs similarity index 73% rename from QuestPDF/Elements/InternalLocation.cs rename to QuestPDF/Elements/Section.cs index ec21260..46fe3c0 100644 --- a/QuestPDF/Elements/InternalLocation.cs +++ b/QuestPDF/Elements/Section.cs @@ -2,7 +2,7 @@ namespace QuestPDF.Elements { - internal class InternalLocation : ContainerElement, IStateResettable + internal class Section : ContainerElement, IStateResettable { public string LocationName { get; set; } private bool IsRendered { get; set; } @@ -16,11 +16,11 @@ namespace QuestPDF.Elements { if (!IsRendered) { - Canvas.DrawLocation(LocationName); + Canvas.DrawSection(LocationName); IsRendered = true; } - PageContext.SetLocationPage(LocationName); + PageContext.SetSectionPage(LocationName); base.Draw(availableSpace); } } diff --git a/QuestPDF/Elements/InternalLink.cs b/QuestPDF/Elements/SectionLink.cs similarity index 69% rename from QuestPDF/Elements/InternalLink.cs rename to QuestPDF/Elements/SectionLink.cs index c6e2aba..f19713a 100644 --- a/QuestPDF/Elements/InternalLink.cs +++ b/QuestPDF/Elements/SectionLink.cs @@ -3,9 +3,9 @@ using QuestPDF.Infrastructure; namespace QuestPDF.Elements { - internal class InternalLink : ContainerElement + internal class SectionLink : ContainerElement { - public string LocationName { get; set; } + public string SectionName { get; set; } internal override void Draw(Size availableSpace) { @@ -14,7 +14,7 @@ namespace QuestPDF.Elements if (targetSize.Type == SpacePlanType.Wrap) return; - Canvas.DrawLocationLink(LocationName, targetSize); + Canvas.DrawSectionLink(SectionName, targetSize); base.Draw(availableSpace); } } diff --git a/QuestPDF/Elements/Text/Items/TextBlockExternalLink.cs b/QuestPDF/Elements/Text/Items/TextBlockHyperlink.cs similarity index 78% rename from QuestPDF/Elements/Text/Items/TextBlockExternalLink.cs rename to QuestPDF/Elements/Text/Items/TextBlockHyperlink.cs index 5337fca..bd8228b 100644 --- a/QuestPDF/Elements/Text/Items/TextBlockExternalLink.cs +++ b/QuestPDF/Elements/Text/Items/TextBlockHyperlink.cs @@ -3,7 +3,7 @@ using QuestPDF.Infrastructure; namespace QuestPDF.Elements.Text.Items { - internal class TextBlockExternalLink : TextBlockSpan + internal class TextBlockHyperlink : TextBlockSpan { public string Url { get; set; } @@ -15,7 +15,7 @@ namespace QuestPDF.Elements.Text.Items public override void Draw(TextDrawingRequest request) { request.Canvas.Translate(new Position(0, request.TotalAscent)); - request.Canvas.DrawExternalLink(Url, new Size(request.TextSize.Width, request.TextSize.Height)); + request.Canvas.DrawHyperlink(Url, new Size(request.TextSize.Width, request.TextSize.Height)); request.Canvas.Translate(new Position(0, -request.TotalAscent)); base.Draw(request); diff --git a/QuestPDF/Elements/Text/Items/TextBlockInternalLink.cs b/QuestPDF/Elements/Text/Items/TextBlockSectionlLink.cs similarity index 72% rename from QuestPDF/Elements/Text/Items/TextBlockInternalLink.cs rename to QuestPDF/Elements/Text/Items/TextBlockSectionlLink.cs index 6b28113..5c41ca0 100644 --- a/QuestPDF/Elements/Text/Items/TextBlockInternalLink.cs +++ b/QuestPDF/Elements/Text/Items/TextBlockSectionlLink.cs @@ -3,9 +3,9 @@ using QuestPDF.Infrastructure; namespace QuestPDF.Elements.Text.Items { - internal class TextBlockInternalLink : TextBlockSpan + internal class TextBlockSectionlLink : TextBlockSpan { - public string LocationName { get; set; } + public string SectionName { get; set; } public override TextMeasurementResult? Measure(TextMeasurementRequest request) { @@ -15,7 +15,7 @@ namespace QuestPDF.Elements.Text.Items public override void Draw(TextDrawingRequest request) { request.Canvas.Translate(new Position(0, request.TotalAscent)); - request.Canvas.DrawLocationLink(LocationName, new Size(request.TextSize.Width, request.TextSize.Height)); + request.Canvas.DrawSectionLink(SectionName, new Size(request.TextSize.Width, request.TextSize.Height)); request.Canvas.Translate(new Position(0, -request.TotalAscent)); base.Draw(request); diff --git a/QuestPDF/Fluent/ElementExtensions.cs b/QuestPDF/Fluent/ElementExtensions.cs index d9ce5fb..a85a2e4 100644 --- a/QuestPDF/Fluent/ElementExtensions.cs +++ b/QuestPDF/Fluent/ElementExtensions.cs @@ -60,7 +60,7 @@ namespace QuestPDF.Fluent public static void Placeholder(this IContainer element, string? text = null) { - element.Component(new Elements.Placeholder + element.Component(new Placeholder { Text = text ?? string.Empty }); @@ -99,27 +99,45 @@ namespace QuestPDF.Fluent return element.Element(new Container()); } + [Obsolete("This element has been renamed since version 2022.3. Please use the Hyperlink method.")] public static IContainer ExternalLink(this IContainer element, string url) { - return element.Element(new ExternalLink + return element.Hyperlink(url); + } + + public static IContainer Hyperlink(this IContainer element, string url) + { + return element.Element(new Hyperlink { Url = url }); } + [Obsolete("This element has been renamed since version 2022.3. Please use the Section method.")] public static IContainer Location(this IContainer element, string locationName) { - return element.Element(new InternalLocation + return element.Section(locationName); + } + + public static IContainer Section(this IContainer element, string sectionName) + { + return element.Element(new Section { - LocationName = locationName + LocationName = sectionName }); } + [Obsolete("This element has been renamed since version 2022.3. Please use the SectionLink method.")] public static IContainer InternalLink(this IContainer element, string locationName) { - return element.Element(new InternalLink + return element.SectionLink(locationName); + } + + public static IContainer SectionLink(this IContainer element, string sectionName) + { + return element.Element(new SectionLink { - LocationName = locationName + SectionName = sectionName }); } diff --git a/QuestPDF/Fluent/TextExtensions.cs b/QuestPDF/Fluent/TextExtensions.cs index eb2d680..e069871 100644 --- a/QuestPDF/Fluent/TextExtensions.cs +++ b/QuestPDF/Fluent/TextExtensions.cs @@ -90,81 +90,81 @@ namespace QuestPDF.Fluent Span(Environment.NewLine); } - private void PageNumber(Func source, TextStyle? style = null) + private static Func DefaultTextFormat = x => (x ?? 123).ToString(); + + private void PageNumber(Func pageNumber, TextStyle? style, Func? format) { style ??= TextStyle.Default; + format ??= DefaultTextFormat; AddItemToLastTextBlock(new TextBlockPageNumber() { - Source = source, + Source = context => format(pageNumber(context)), Style = style }); } - - public void CurrentPageNumber(Func format, TextStyle? style = null) + + public void CurrentPageNumber(TextStyle? style = null, Func? format = null) { - PageNumber(context => format(context.CurrentPage), style); + PageNumber(x => x.CurrentPage, style, format); } - public void CurrentPageNumber(TextStyle? style = null) + public void TotalPages(TextStyle? style = null, Func? format = null) { - CurrentPageNumber(x => x.ToString(), style); + PageNumber(x => x.GetLocation(PageContext.DocumentLocation)?.Length, style, format); } - public void TotalPages(Func format, TextStyle? style = null) - { - PageNumber(context => format(context.GetLocation(PageContext.DocumentLocation)?.Length), style); - } - - public void TotalPages(TextStyle? style = null) - { - TotalPages(x => x.ToString(), style); - } - + [Obsolete("This element has been renamed since version 2022.3. Please use the BeginPageNumberOfSection method.")] public void PageNumberOfLocation(string locationName, TextStyle? style = null) { - BeginPageNumberOfLocation(locationName); + BeginPageNumberOfSection(locationName); } - public void BeginPageNumberOfLocation(string locationName, TextStyle? style = null) + public void BeginPageNumberOfSection(string locationName, TextStyle? style = null, Func? format = null) { - PageNumber(context => (context.GetLocation(locationName)?.PageStart ?? 123).ToString(), style); + PageNumber(x => x.GetLocation(locationName)?.PageStart, style, format); } - public void EndPageNumberOfLocation(string locationName, TextStyle? style = null) + public void EndPageNumberOfSection(string locationName, TextStyle? style = null, Func? format = null) { - PageNumber(context => (context.GetLocation(locationName)?.PageEnd ?? 123).ToString(), style); + PageNumber(x => x.GetLocation(locationName)?.PageEnd, style, format); } - public void PageNumberWithinLocation(string locationName, Func format, TextStyle? style = null) + public void PageNumberWithinSection(string locationName, TextStyle? style = null, Func? format = null) { - PageNumber(context => format(context.CurrentPage + 1 - context.GetLocation(locationName)?.PageEnd), style); + PageNumber(x => x.CurrentPage + 1 - x.GetLocation(locationName)?.PageEnd, style, format); } - public void PageLengthOfLocation(string locationName, Func format, TextStyle? style = null) + public void TotalPagesWithinSection(string locationName, TextStyle? style = null, Func? format = null) { - PageNumber(context => format(context.GetLocation(locationName)?.Length), style); + PageNumber(x => x.GetLocation(locationName)?.Length, style, format); } - public void InternalLocation(string? text, string locationName, TextStyle? style = null) + public void SectionLink(string? text, string sectionName, TextStyle? style = null) { if (IsNullOrEmpty(text)) return; - if (IsNullOrEmpty(locationName)) - throw new ArgumentException(nameof(locationName)); + if (IsNullOrEmpty(sectionName)) + throw new ArgumentException(nameof(sectionName)); style ??= TextStyle.Default; - AddItemToLastTextBlock(new TextBlockInternalLink + AddItemToLastTextBlock(new TextBlockSectionlLink { Style = style, Text = text, - LocationName = locationName + SectionName = sectionName }); } - public void ExternalLocation(string? text, string url, TextStyle? style = null) + [Obsolete("This element has been renamed since version 2022.3. Please use the SectionLink method.")] + public void InternalLocation(string? text, string locationName, TextStyle? style = null) + { + SectionLink(text, locationName, style); + } + + public void Hyperlink(string? text, string url, TextStyle? style = null) { if (IsNullOrEmpty(text)) return; @@ -174,7 +174,7 @@ namespace QuestPDF.Fluent style ??= TextStyle.Default; - AddItemToLastTextBlock(new TextBlockExternalLink + AddItemToLastTextBlock(new TextBlockHyperlink { Style = style, Text = text, @@ -182,6 +182,12 @@ namespace QuestPDF.Fluent }); } + [Obsolete("This element has been renamed since version 2022.3. Please use the Hyperlink method.")] + public void ExternalLocation(string? text, string url, TextStyle? style = null) + { + Hyperlink(text, url, style); + } + public IContainer Element() { var container = new Container(); diff --git a/QuestPDF/Infrastructure/ICanvas.cs b/QuestPDF/Infrastructure/ICanvas.cs index e2bb3fb..65b470b 100644 --- a/QuestPDF/Infrastructure/ICanvas.cs +++ b/QuestPDF/Infrastructure/ICanvas.cs @@ -10,9 +10,9 @@ namespace QuestPDF.Infrastructure void DrawText(string text, Position position, TextStyle style); void DrawImage(SKImage image, Position position, Size size); - void DrawExternalLink(string url, Size size); - void DrawLocationLink(string locationName, Size size); - void DrawLocation(string locationName); + void DrawHyperlink(string url, Size size); + void DrawSectionLink(string sectionName, Size size); + void DrawSection(string sectionName); void Rotate(float angle); void Scale(float scaleX, float scaleY); diff --git a/QuestPDF/Infrastructure/IPageContext.cs b/QuestPDF/Infrastructure/IPageContext.cs index b9253bd..73b2c0a 100644 --- a/QuestPDF/Infrastructure/IPageContext.cs +++ b/QuestPDF/Infrastructure/IPageContext.cs @@ -13,7 +13,7 @@ namespace QuestPDF.Infrastructure internal interface IPageContext { int CurrentPage { get; } - void SetLocationPage(string name); + void SetSectionPage(string name); DocumentLocation? GetLocation(string name); } } \ No newline at end of file diff --git a/QuestPDF/Infrastructure/PageContext.cs b/QuestPDF/Infrastructure/PageContext.cs index 1497a3c..385dcfb 100644 --- a/QuestPDF/Infrastructure/PageContext.cs +++ b/QuestPDF/Infrastructure/PageContext.cs @@ -14,10 +14,10 @@ namespace QuestPDF.Infrastructure internal void SetPageNumber(int number) { CurrentPage = number; - SetLocationPage(DocumentLocation); + SetSectionPage(DocumentLocation); } - public void SetLocationPage(string name) + public void SetSectionPage(string name) { var location = GetLocation(name); From 8db283d5593c744ab65f097fa7217e2200fbda38 Mon Sep 17 00:00:00 2001 From: MarcinZiabek Date: Fri, 25 Feb 2022 23:02:42 +0100 Subject: [PATCH 05/12] Code refactorization + added support for multi typeface font types --- QuestPDF.Examples/BarcodeExamples.cs | 6 +- QuestPDF/Drawing/FontManager.cs | 57 ++++++++++--------- QuestPDF/Drawing/FontStyleSet.cs | 84 ++++++++++++++++++---------- 3 files changed, 89 insertions(+), 58 deletions(-) diff --git a/QuestPDF.Examples/BarcodeExamples.cs b/QuestPDF.Examples/BarcodeExamples.cs index 0ce3beb..f4fa0dc 100644 --- a/QuestPDF.Examples/BarcodeExamples.cs +++ b/QuestPDF.Examples/BarcodeExamples.cs @@ -13,11 +13,11 @@ namespace QuestPDF.Examples [Test] public void Example() { - FontManager.RegisterFontType("LibreBarcode39", File.OpenRead("LibreBarcode39-Regular.ttf")); + FontManager.RegisterFontType(File.OpenRead("LibreBarcode39-Regular.ttf")); RenderingTest .Create() - .PageSize(400, 100) + .PageSize(400, 200) .ShowResults() .Render(container => { @@ -25,7 +25,7 @@ namespace QuestPDF.Examples .Background(Colors.White) .AlignCenter() .AlignMiddle() - .Text("*QuestPDF*", TextStyle.Default.FontType("LibreBarcode39").Size(64)); + .Text("*QuestPDF*", TextStyle.Default.FontType("Libre Barcode 39").Size(64)); }); } } diff --git a/QuestPDF/Drawing/FontManager.cs b/QuestPDF/Drawing/FontManager.cs index 131c1bc..fd5cac5 100644 --- a/QuestPDF/Drawing/FontManager.cs +++ b/QuestPDF/Drawing/FontManager.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Concurrent; using System.IO; +using System.Linq; using QuestPDF.Infrastructure; using SkiaSharp; @@ -8,27 +9,39 @@ namespace QuestPDF.Drawing { public static class FontManager { - private static ConcurrentDictionary StyleSets = new ConcurrentDictionary(); - private static ConcurrentDictionary FontMetrics = new ConcurrentDictionary(); - private static ConcurrentDictionary Paints = new ConcurrentDictionary(); - private static ConcurrentDictionary ColorPaint = new ConcurrentDictionary(); + private static ConcurrentDictionary StyleSets = new(); + private static ConcurrentDictionary FontMetrics = new(); + private static ConcurrentDictionary Paints = new(); + private static ConcurrentDictionary ColorPaint = new(); - private static void RegisterFontType(string fontName, SKTypeface typeface) + private static void RegisterFontType(SKData fontData, string? customName = null) { - FontStyleSet set = StyleSets.GetOrAdd(fontName, _ => new FontStyleSet()); - set.Add(typeface); + foreach (var index in Enumerable.Range(0, 256)) + { + var typeface = SKTypeface.FromData(fontData, index); + + if (typeface == null) + break; + + var typefaceName = customName ?? typeface.FamilyName; + + var fontStyleSet = StyleSets.GetOrAdd(typefaceName, _ => new FontStyleSet()); + fontStyleSet.Add(typeface); + } } + [Obsolete("Since version 2022.3, the FontManager class offers better font type matching support. Please use the RegisterFontType(Stream stream) overload.")] public static void RegisterFontType(string fontName, Stream stream) { - SKTypeface typeface = SKTypeface.FromStream(stream); - RegisterFontType(fontName, typeface); + using var fontData = SKData.Create(stream); + RegisterFontType(fontData); + RegisterFontType(fontData, customName: fontName); } public static void RegisterFontType(Stream stream) { - SKTypeface typeface = SKTypeface.FromStream(stream); - RegisterFontType(typeface.FamilyName, typeface); + using var fontData = SKData.Create(stream); + RegisterFontType(fontData); } internal static SKPaint ColorToPaint(this string color) @@ -54,29 +67,23 @@ namespace QuestPDF.Drawing { Color = SKColor.Parse(style.Color), Typeface = GetTypeface(style), - TextSize = (style.Size ?? 12), + TextSize = style.Size ?? 12, TextEncoding = SKTextEncoding.Utf32 }; } static SKTypeface GetTypeface(TextStyle style) { - SKFontStyleWeight weight = (SKFontStyleWeight)(style.FontWeight ?? FontWeight.Normal); - SKFontStyleWidth width = SKFontStyleWidth.Normal; - SKFontStyleSlant slant = (style.IsItalic ?? false) ? SKFontStyleSlant.Italic : SKFontStyleSlant.Upright; + var weight = (SKFontStyleWeight)(style.FontWeight ?? FontWeight.Normal); + var slant = (style.IsItalic ?? false) ? SKFontStyleSlant.Italic : SKFontStyleSlant.Upright; - SKFontStyle skFontStyle = new SKFontStyle(weight, width, slant); + var skFontStyle = new SKFontStyle(weight, SKFontStyleWidth.Normal, slant); - FontStyleSet set; - if (StyleSets.TryGetValue(style.FontType, out set)) - { + if (StyleSets.TryGetValue(style.FontType, out var set)) return set.Match(skFontStyle); - } - else - { - return SKTypeface.FromFamilyName(style.FontType, skFontStyle) - ?? throw new ArgumentException($"The typeface {style.FontType} could not be found."); - } + + return SKTypeface.FromFamilyName(style.FontType, skFontStyle) + ?? throw new ArgumentException($"The typeface {style.FontType} could not be found. Please consider installing the font file on your system or loading it from a file using the FontManager.RegisterFontType() static method."); } } diff --git a/QuestPDF/Drawing/FontStyleSet.cs b/QuestPDF/Drawing/FontStyleSet.cs index a96b633..fd70aa6 100644 --- a/QuestPDF/Drawing/FontStyleSet.cs +++ b/QuestPDF/Drawing/FontStyleSet.cs @@ -7,18 +7,18 @@ namespace QuestPDF.Drawing { internal class FontStyleSet { - private ConcurrentDictionary Styles = new ConcurrentDictionary(); + private ConcurrentDictionary Styles { get; } = new(); public void Add(SKTypeface typeface) { - SKFontStyle style = typeface.FontStyle; - Styles.AddOrUpdate(style, (_) => typeface, (_, _) => typeface); + var style = typeface.FontStyle; + Styles.AddOrUpdate(style, _ => typeface, (_, _) => typeface); } - public SKTypeface Match(SKFontStyle target) + public SKTypeface? Match(SKFontStyle target) { - SKFontStyle bestStyle = null; - SKTypeface bestTypeface = null; + SKFontStyle? bestStyle = null; + SKTypeface? bestTypeface = null; foreach (var entry in Styles) { @@ -40,40 +40,55 @@ namespace QuestPDF.Drawing }; // Checks whether style a is a better match for the target then style b. Uses the CSS font style matching algorithm - internal static bool IsBetterMatch(SKFontStyle target, SKFontStyle a, SKFontStyle b) + internal static bool IsBetterMatch(SKFontStyle? target, SKFontStyle? a, SKFontStyle? b) { // A font is better than no font - if (b == null) return true; - if (a == null) return false; + if (b == null) + return true; + + if (a == null) + return false; // First check font width // For normal and condensed widths prefer smaller widths // For expanded widths prefer larger widths if (target.Width <= (int)SKFontStyleWidth.Normal) { - if (a.Width <= target.Width && b.Width > target.Width) return true; - if (a.Width > target.Width && b.Width <= target.Width) return false; + if (a.Width <= target.Width && b.Width > target.Width) + return true; + + if (a.Width > target.Width && b.Width <= target.Width) + return false; } else { - if (a.Width >= target.Width && b.Width < target.Width) return true; - if (a.Width < target.Width && b.Width >= target.Width) return false; + if (a.Width >= target.Width && b.Width < target.Width) + return true; + + if (a.Width < target.Width && b.Width >= target.Width) + return false; } // Prefer closest match - int widthDifferenceA = Math.Abs(a.Width - target.Width); - int widthDifferenceB = Math.Abs(b.Width - target.Width); + var widthDifferenceA = Math.Abs(a.Width - target.Width); + var widthDifferenceB = Math.Abs(b.Width - target.Width); - if (widthDifferenceA < widthDifferenceB) return true; - if (widthDifferenceB < widthDifferenceA) return false; + if (widthDifferenceA < widthDifferenceB) + return true; + + if (widthDifferenceB < widthDifferenceA) + return false; // Prefer closest slant based on provided fallback list - List slantFallback = SlantFallbacks[target.Slant]; - int slantIndexA = slantFallback.IndexOf(a.Slant); - int slantIndexB = slantFallback.IndexOf(b.Slant); + var slantFallback = SlantFallbacks[target.Slant]; + var slantIndexA = slantFallback.IndexOf(a.Slant); + var slantIndexB = slantFallback.IndexOf(b.Slant); - if (slantIndexA < slantIndexB) return true; - if (slantIndexB < slantIndexA) return false; + if (slantIndexA < slantIndexB) + return true; + + if (slantIndexB < slantIndexA) + return false; // Check weight last // For thin (<400) weights, prefer thinner weights @@ -83,24 +98,33 @@ namespace QuestPDF.Drawing if (target.Weight >= 400 && target.Weight <= 500) { - if ((a.Weight >= 400 && a.Weight <= 500) && !(b.Weight >= 400 && b.Weight <= 500)) return true; - if (!(a.Weight >= 400 && a.Weight <= 500) && (b.Weight >= 400 && b.Weight <= 500)) return false; + if ((a.Weight >= 400 && a.Weight <= 500) && !(b.Weight >= 400 && b.Weight <= 500)) + return true; + + if (!(a.Weight >= 400 && a.Weight <= 500) && (b.Weight >= 400 && b.Weight <= 500)) + return false; } if (target.Weight < 450) { - if (a.Weight <= target.Weight && b.Weight > target.Weight) return true; - if (a.Weight > target.Weight && b.Weight <= target.Weight) return false; + if (a.Weight <= target.Weight && b.Weight > target.Weight) + return true; + + if (a.Weight > target.Weight && b.Weight <= target.Weight) + return false; } else { - if (a.Weight >= target.Weight && b.Weight < target.Weight) return true; - if (a.Weight < target.Weight && b.Weight >= target.Weight) return false; + if (a.Weight >= target.Weight && b.Weight < target.Weight) + return true; + + if (a.Weight < target.Weight && b.Weight >= target.Weight) + return false; } // Prefer closest weight - int weightDifferenceA = Math.Abs(a.Weight - target.Weight); - int weightDifferenceB = Math.Abs(b.Weight - target.Weight); + var weightDifferenceA = Math.Abs(a.Weight - target.Weight); + var weightDifferenceB = Math.Abs(b.Weight - target.Weight); return weightDifferenceA < weightDifferenceB; } From a92cc25fb1ea2d077719b50b55bf13f071675553 Mon Sep 17 00:00:00 2001 From: MarcinZiabek Date: Sun, 27 Feb 2022 15:19:50 +0100 Subject: [PATCH 06/12] Improved font matching and exception message. Refactored tests. --- QuestPDF.UnitTests/FontStyleSetTests.cs | 151 ++++++++++++++---------- QuestPDF/Drawing/FontManager.cs | 29 +++-- QuestPDF/Infrastructure/TextStyle.cs | 6 +- 3 files changed, 109 insertions(+), 77 deletions(-) diff --git a/QuestPDF.UnitTests/FontStyleSetTests.cs b/QuestPDF.UnitTests/FontStyleSetTests.cs index 5f5a47d..9743888 100644 --- a/QuestPDF.UnitTests/FontStyleSetTests.cs +++ b/QuestPDF.UnitTests/FontStyleSetTests.cs @@ -1,123 +1,146 @@ -using NUnit.Framework; +using FluentAssertions; +using NUnit.Framework; using QuestPDF.Drawing; using SkiaSharp; +using static SkiaSharp.SKFontStyleSlant; namespace QuestPDF.UnitTests { [TestFixture] public class FontStyleSetTests { - private void ExpectComparisonOrder(SKFontStyle target, params SKFontStyle[] styles) + private void ExpectComparisonOrder(SKFontStyle target, SKFontStyle[] styles) { - for (int i = 0; i < styles.Length - 1; i++) + for (var i = 0; i < styles.Length - 1; i++) { - Assert.True(FontStyleSet.IsBetterMatch(target, styles[i], styles[i + 1])); - Assert.False(FontStyleSet.IsBetterMatch(target, styles[i + 1], styles[i])); + var currentStyle = styles[i]; + var nextStyle = styles[i + 1]; + + FontStyleSet.IsBetterMatch(target, currentStyle, nextStyle).Should().BeTrue(); + FontStyleSet.IsBetterMatch(target, nextStyle, currentStyle).Should().BeFalse(); } } [Test] public void FontStyleSet_IsBetterMatch_CondensedWidth() { - ExpectComparisonOrder( - new SKFontStyle(500, 5, SKFontStyleSlant.Upright), - new SKFontStyle(500, 5, SKFontStyleSlant.Upright), - new SKFontStyle(500, 4, SKFontStyleSlant.Upright), - new SKFontStyle(500, 3, SKFontStyleSlant.Upright), - new SKFontStyle(500, 6, SKFontStyleSlant.Upright) - ); + var styles = new[] + { + new SKFontStyle(500, 5, Upright), + new SKFontStyle(500, 4, Upright), + new SKFontStyle(500, 3, Upright), + new SKFontStyle(500, 6, Upright) + }; + + ExpectComparisonOrder(new SKFontStyle(500, 5, Upright), styles); } [Test] public void FontStyleSet_IsBetterMatch_ExpandedWidth() { - ExpectComparisonOrder( - new SKFontStyle(500, 6, SKFontStyleSlant.Upright), - new SKFontStyle(500, 6, SKFontStyleSlant.Upright), - new SKFontStyle(500, 7, SKFontStyleSlant.Upright), - new SKFontStyle(500, 8, SKFontStyleSlant.Upright), - new SKFontStyle(500, 5, SKFontStyleSlant.Upright) - ); + var styles = new[] + { + new SKFontStyle(500, 6, Upright), + new SKFontStyle(500, 7, Upright), + new SKFontStyle(500, 8, Upright), + new SKFontStyle(500, 5, Upright) + }; + + ExpectComparisonOrder(new SKFontStyle(500, 6, Upright), styles); } [Test] public void FontStyleSet_IsBetterMatch_ItalicSlant() { - ExpectComparisonOrder( - new SKFontStyle(500, 5, SKFontStyleSlant.Italic), - new SKFontStyle(500, 5, SKFontStyleSlant.Italic), - new SKFontStyle(500, 5, SKFontStyleSlant.Oblique), - new SKFontStyle(500, 5, SKFontStyleSlant.Upright) - ); + var styles = new[] + { + new SKFontStyle(500, 5, Italic), + new SKFontStyle(500, 5, Oblique), + new SKFontStyle(500, 5, Upright) + }; + + ExpectComparisonOrder(new SKFontStyle(500, 5, Italic), styles); } [Test] public void FontStyleSet_IsBetterMatch_ObliqueSlant() { - ExpectComparisonOrder( - new SKFontStyle(500, 5, SKFontStyleSlant.Oblique), - new SKFontStyle(500, 5, SKFontStyleSlant.Oblique), - new SKFontStyle(500, 5, SKFontStyleSlant.Italic), - new SKFontStyle(500, 5, SKFontStyleSlant.Upright) - ); + var styles = new[] + { + new SKFontStyle(500, 5, Oblique), + new SKFontStyle(500, 5, Italic), + new SKFontStyle(500, 5, Upright) + }; + + ExpectComparisonOrder(new SKFontStyle(500, 5, Oblique), styles); } [Test] public void FontStyleSet_IsBetterMatch_UprightSlant() { - ExpectComparisonOrder( - new SKFontStyle(500, 5, SKFontStyleSlant.Upright), - new SKFontStyle(500, 5, SKFontStyleSlant.Upright), - new SKFontStyle(500, 5, SKFontStyleSlant.Oblique), - new SKFontStyle(500, 5, SKFontStyleSlant.Italic) - ); + var styles = new[] + { + new SKFontStyle(500, 5, Upright), + new SKFontStyle(500, 5, Oblique), + new SKFontStyle(500, 5, Italic) + }; + + ExpectComparisonOrder(new SKFontStyle(500, 5, Upright), styles); } [Test] public void FontStyleSet_IsBetterMatch_ThinWeight() { - ExpectComparisonOrder( - new SKFontStyle(300, 5, SKFontStyleSlant.Upright), - new SKFontStyle(300, 5, SKFontStyleSlant.Upright), - new SKFontStyle(200, 5, SKFontStyleSlant.Upright), - new SKFontStyle(100, 5, SKFontStyleSlant.Upright), - new SKFontStyle(400, 5, SKFontStyleSlant.Upright) - ); + var styles = new[] + { + new SKFontStyle(300, 5, Upright), + new SKFontStyle(200, 5, Upright), + new SKFontStyle(100, 5, Upright), + new SKFontStyle(400, 5, Upright) + }; + + ExpectComparisonOrder(new SKFontStyle(300, 5, Upright), styles); } [Test] public void FontStyleSet_IsBetterMatch_RegularWeight() { - ExpectComparisonOrder( - new SKFontStyle(400, 5, SKFontStyleSlant.Upright), - new SKFontStyle(500, 5, SKFontStyleSlant.Upright), - new SKFontStyle(300, 5, SKFontStyleSlant.Upright), - new SKFontStyle(100, 5, SKFontStyleSlant.Upright), - new SKFontStyle(600, 5, SKFontStyleSlant.Upright) - ); + var styles = new[] + { + new SKFontStyle(500, 5, Upright), + new SKFontStyle(300, 5, Upright), + new SKFontStyle(100, 5, Upright), + new SKFontStyle(600, 5, Upright) + }; + + ExpectComparisonOrder(new SKFontStyle(400, 5, Upright), styles); } [Test] public void FontStyleSet_IsBetterMatch_BoldWeight() { - ExpectComparisonOrder( - new SKFontStyle(600, 5, SKFontStyleSlant.Upright), - new SKFontStyle(600, 5, SKFontStyleSlant.Upright), - new SKFontStyle(700, 5, SKFontStyleSlant.Upright), - new SKFontStyle(800, 5, SKFontStyleSlant.Upright), - new SKFontStyle(500, 5, SKFontStyleSlant.Upright) - ); + var styles = new[] + { + new SKFontStyle(600, 5, Upright), + new SKFontStyle(700, 5, Upright), + new SKFontStyle(800, 5, Upright), + new SKFontStyle(500, 5, Upright) + }; + + ExpectComparisonOrder(new SKFontStyle(600, 5, Upright), styles); } [Test] public void FontStyleSet_RespectsPriority() { - ExpectComparisonOrder( - new SKFontStyle(500, 5, SKFontStyleSlant.Upright), - new SKFontStyle(600, 5, SKFontStyleSlant.Italic), - new SKFontStyle(600, 6, SKFontStyleSlant.Upright), - new SKFontStyle(500, 6, SKFontStyleSlant.Italic) - ); + var styles = new[] + { + new SKFontStyle(600, 5, Italic), + new SKFontStyle(600, 6, Upright), + new SKFontStyle(500, 6, Italic) + }; + + ExpectComparisonOrder(new SKFontStyle(500, 5, Upright), styles); } } } \ No newline at end of file diff --git a/QuestPDF/Drawing/FontManager.cs b/QuestPDF/Drawing/FontManager.cs index fd5cac5..1a51bcf 100644 --- a/QuestPDF/Drawing/FontManager.cs +++ b/QuestPDF/Drawing/FontManager.cs @@ -10,8 +10,8 @@ namespace QuestPDF.Drawing public static class FontManager { private static ConcurrentDictionary StyleSets = new(); - private static ConcurrentDictionary FontMetrics = new(); - private static ConcurrentDictionary Paints = new(); + private static ConcurrentDictionary FontMetrics = new(); + private static ConcurrentDictionary Paints = new(); private static ConcurrentDictionary ColorPaint = new(); private static void RegisterFontType(SKData fontData, string? customName = null) @@ -59,7 +59,7 @@ namespace QuestPDF.Drawing internal static SKPaint ToPaint(this TextStyle style) { - return Paints.GetOrAdd(style.Key, key => Convert(style)); + return Paints.GetOrAdd(style.PaintKey, key => Convert(style)); static SKPaint Convert(TextStyle style) { @@ -67,8 +67,7 @@ namespace QuestPDF.Drawing { Color = SKColor.Parse(style.Color), Typeface = GetTypeface(style), - TextSize = style.Size ?? 12, - TextEncoding = SKTextEncoding.Utf32 + TextSize = style.Size ?? 12 }; } @@ -77,19 +76,27 @@ namespace QuestPDF.Drawing var weight = (SKFontStyleWeight)(style.FontWeight ?? FontWeight.Normal); var slant = (style.IsItalic ?? false) ? SKFontStyleSlant.Italic : SKFontStyleSlant.Upright; - var skFontStyle = new SKFontStyle(weight, SKFontStyleWidth.Normal, slant); + var fontStyle = new SKFontStyle(weight, SKFontStyleWidth.Normal, slant); - if (StyleSets.TryGetValue(style.FontType, out var set)) - return set.Match(skFontStyle); + if (StyleSets.TryGetValue(style.FontType, out var fontStyleSet)) + return fontStyleSet.Match(fontStyle); - return SKTypeface.FromFamilyName(style.FontType, skFontStyle) - ?? throw new ArgumentException($"The typeface {style.FontType} could not be found. Please consider installing the font file on your system or loading it from a file using the FontManager.RegisterFontType() static method."); + var fontFromDefaultSource = SKFontManager.Default.MatchFamily(style.FontType, fontStyle); + + if (fontFromDefaultSource != null) + return fontFromDefaultSource; + + throw new ArgumentException( + $"The typeface '{style.FontType}' could not be found. " + + $"Please consider the following options: " + + $"1) install the font on your operating system or execution environment. " + + $"2) load a font file specifically for QuestPDF usage via the QuestPDF.Drawing.FontManager.RegisterFontType(Stream fileContentStream) static method."); } } internal static SKFontMetrics ToFontMetrics(this TextStyle style) { - return FontMetrics.GetOrAdd(style.Key, key => style.ToPaint().FontMetrics); + return FontMetrics.GetOrAdd(style.FontMetricsKey, key => style.ToPaint().FontMetrics); } } } \ No newline at end of file diff --git a/QuestPDF/Infrastructure/TextStyle.cs b/QuestPDF/Infrastructure/TextStyle.cs index 640d9d2..552f54f 100644 --- a/QuestPDF/Infrastructure/TextStyle.cs +++ b/QuestPDF/Infrastructure/TextStyle.cs @@ -17,7 +17,8 @@ namespace QuestPDF.Infrastructure internal bool? HasStrikethrough { get; set; } internal bool? HasUnderline { get; set; } - internal string? Key { get; private set; } + internal object PaintKey { get; private set; } + internal object FontMetricsKey { get; private set; } internal static TextStyle LibraryDefault => new TextStyle { @@ -42,7 +43,8 @@ namespace QuestPDF.Infrastructure HasGlobalStyleApplied = true; ApplyParentStyle(globalStyle); - Key ??= $"{Color}|{BackgroundColor}|{FontType}|{Size}|{LineHeight}|{FontWeight}|{IsItalic}|{HasStrikethrough}|{HasUnderline}"; + PaintKey ??= (FontType, Size, FontWeight, IsItalic, Color); + FontMetricsKey ??= (FontType, Size, FontWeight, IsItalic); } internal void ApplyParentStyle(TextStyle parentStyle) From 054e7a5f205e6d6d1db124f0eb3d49098d6b5931 Mon Sep 17 00:00:00 2001 From: MarcinZiabek Date: Mon, 28 Feb 2022 15:41:10 +0100 Subject: [PATCH 07/12] Added minimal API example --- QuestPDF.Examples/MinimalApiExamples.cs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/QuestPDF.Examples/MinimalApiExamples.cs b/QuestPDF.Examples/MinimalApiExamples.cs index ecea44e..cad0c98 100644 --- a/QuestPDF.Examples/MinimalApiExamples.cs +++ b/QuestPDF.Examples/MinimalApiExamples.cs @@ -19,8 +19,21 @@ namespace QuestPDF.Examples { container.Page(page => { - page.Margin(2, Unit.Centimetre); - page.Content().Text("Hello PDF!"); + page.Size(PageSizes.A5); + page.Margin(1.5f, Unit.Centimetre); + + page.Header() + .Text("Hello PDF!", TextStyle.Default.SemiBold().Size(20)); + + page.Content() + .PaddingVertical(1, Unit.Centimetre) + .Column(x => + { + x.Spacing(10); + + x.Item().Text(Placeholders.LoremIpsum()); + x.Item().Image(Placeholders.Image(200, 100)); + }); }); }) .GeneratePdf("hello.pdf"); From fcff989ab2763bb54ce057b02788fbc87e114be1 Mon Sep 17 00:00:00 2001 From: MarcinZiabek Date: Tue, 8 Mar 2022 17:27:30 +0100 Subject: [PATCH 08/12] Improved text API --- QuestPDF.Examples/BarcodeExamples.cs | 4 +- QuestPDF.Examples/CanvasExamples.cs | 5 +- QuestPDF.Examples/ChartExamples.cs | 5 +- QuestPDF.Examples/ContinousPage.cs | 2 +- QuestPDF.Examples/DefaultTextStyleExample.cs | 2 +- QuestPDF.Examples/DefaultTextStyleExamples.cs | 7 +- QuestPDF.Examples/ElementExamples.cs | 34 ++- QuestPDF.Examples/EnsureSpaceExample.cs | 2 +- QuestPDF.Examples/FrameExample.cs | 4 +- QuestPDF.Examples/InlinedExamples.cs | 3 +- QuestPDF.Examples/MinimalApiExamples.cs | 63 ++++- QuestPDF.Examples/Padding.cs | 3 +- QuestPDF.Examples/ShowOnceExample.cs | 2 +- QuestPDF.Examples/SkipOnceExample.cs | 2 +- QuestPDF.Examples/TextBenchmark.cs | 14 +- QuestPDF.Examples/TextExamples.cs | 22 +- .../Layouts/DifferentHeadersTemplate.cs | 4 +- .../Layouts/SectionTemplate.cs | 5 +- .../Layouts/StandardReport.cs | 8 +- .../Layouts/TableOfContentsTemplate.cs | 9 +- QuestPDF/Elements/DebugArea.cs | 5 +- QuestPDF/Elements/Placeholder.cs | 2 +- QuestPDF/Fluent/TextExtensions.cs | 134 ++++++---- .../Fluent/TextSpanDescriptorExtensions.cs | 126 ++++++++++ QuestPDF/Infrastructure/TextStyle.cs | 13 + readme.md | 237 ++++-------------- 26 files changed, 426 insertions(+), 291 deletions(-) create mode 100644 QuestPDF/Fluent/TextSpanDescriptorExtensions.cs diff --git a/QuestPDF.Examples/BarcodeExamples.cs b/QuestPDF.Examples/BarcodeExamples.cs index f4fa0dc..762ad20 100644 --- a/QuestPDF.Examples/BarcodeExamples.cs +++ b/QuestPDF.Examples/BarcodeExamples.cs @@ -25,7 +25,9 @@ namespace QuestPDF.Examples .Background(Colors.White) .AlignCenter() .AlignMiddle() - .Text("*QuestPDF*", TextStyle.Default.FontType("Libre Barcode 39").Size(64)); + .Text("*QuestPDF*") + .FontType("Libre Barcode 39") + .Size(64); }); } } diff --git a/QuestPDF.Examples/CanvasExamples.cs b/QuestPDF.Examples/CanvasExamples.cs index a37d41f..aff03cf 100644 --- a/QuestPDF.Examples/CanvasExamples.cs +++ b/QuestPDF.Examples/CanvasExamples.cs @@ -49,7 +49,10 @@ namespace QuestPDF.Examples .PrimaryLayer() .PaddingVertical(10) .PaddingHorizontal(20) - .Text("Sample text", TextStyle.Default.Size(16).Color(Colors.Blue.Darken2).SemiBold()); + .Text("Sample text") + .Size(16) + .Color(Colors.Blue.Darken2) + .SemiBold(); }); }); } diff --git a/QuestPDF.Examples/ChartExamples.cs b/QuestPDF.Examples/ChartExamples.cs index a8eac35..6cb8abe 100644 --- a/QuestPDF.Examples/ChartExamples.cs +++ b/QuestPDF.Examples/ChartExamples.cs @@ -57,7 +57,10 @@ namespace QuestPDF.Examples column .Item() .PaddingBottom(10) - .Text("Chart example", TextStyle.Default.Size(20).SemiBold().Color(Colors.Blue.Medium)); + .Text("Chart example") + .Size(20) + .SemiBold() + .Color(Colors.Blue.Medium); column .Item() diff --git a/QuestPDF.Examples/ContinousPage.cs b/QuestPDF.Examples/ContinousPage.cs index 1e5c545..37e3e47 100644 --- a/QuestPDF.Examples/ContinousPage.cs +++ b/QuestPDF.Examples/ContinousPage.cs @@ -25,7 +25,7 @@ namespace QuestPDF.Examples page.Content().PaddingVertical(10).Border(1).Padding(10).Column(column => { foreach (var index in Enumerable.Range(1, 100)) - column.Item().Text($"Line {index}", TextStyle.Default.Color(Placeholders.Color())); + column.Item().Text($"Line {index}").Color(Placeholders.Color()); }); page.Footer().Text("Footer"); diff --git a/QuestPDF.Examples/DefaultTextStyleExample.cs b/QuestPDF.Examples/DefaultTextStyleExample.cs index e7db9eb..acf176e 100644 --- a/QuestPDF.Examples/DefaultTextStyleExample.cs +++ b/QuestPDF.Examples/DefaultTextStyleExample.cs @@ -38,7 +38,7 @@ namespace QuestPDF.Examples text.Line(Placeholders.Sentence()); // this text has size 20 but also semibold and red - text.Span(Placeholders.Sentence(), TextStyle.Default.Color(Colors.Red.Medium)); + text.Span(Placeholders.Sentence()).Color(Colors.Red.Medium); }); }); }); diff --git a/QuestPDF.Examples/DefaultTextStyleExamples.cs b/QuestPDF.Examples/DefaultTextStyleExamples.cs index 4165a39..9db6bc7 100644 --- a/QuestPDF.Examples/DefaultTextStyleExamples.cs +++ b/QuestPDF.Examples/DefaultTextStyleExamples.cs @@ -26,8 +26,8 @@ namespace QuestPDF.Examples .DefaultTextStyle(TextStyle.Default.Bold().Underline()) .Column(column => { - column.Item().Text("Default style applies to all children", TextStyle.Default); - column.Item().Text("You can override certain styles", TextStyle.Default.Underline(false).Color(Colors.Green.Darken2)); + column.Item().Text("Default style applies to all children"); + column.Item().Text("You can override certain styles").Underline(false).Color(Colors.Green.Darken2); column.Item().PaddingTop(10).Border(1).Grid(grid => { @@ -43,7 +43,8 @@ namespace QuestPDF.Examples .Height(50) .AlignCenter() .AlignMiddle() - .Text(i, TextStyle.Default.Size(16 + i / 4)); + .Text(i) + .Size(16 + i / 4); } }); }); diff --git a/QuestPDF.Examples/ElementExamples.cs b/QuestPDF.Examples/ElementExamples.cs index 635961c..3874f78 100644 --- a/QuestPDF.Examples/ElementExamples.cs +++ b/QuestPDF.Examples/ElementExamples.cs @@ -43,7 +43,9 @@ namespace QuestPDF.Examples .Before() .Background(Colors.Grey.Medium) .Padding(10) - .Text("Notes", TextStyle.Default.Size(16).Color("#FFF")); + .Text("Notes") + .Size(16) + .Color("#FFF"); decoration .Content() @@ -280,12 +282,15 @@ namespace QuestPDF.Examples .Layer() .AlignCenter() .AlignMiddle() - .Text("Watermark", TextStyle.Default.Size(48).Bold().Color(Colors.Green.Lighten3)); + .Text("Watermark") + .Size(48) + .Bold() + .Color(Colors.Green.Lighten3); layers .Layer() .AlignBottom() - .Text(text => text.CurrentPageNumber(TextStyle.Default.Size(16).Color(Colors.Green.Medium))); + .Text(text => text.CurrentPageNumber().Size(16).Color(Colors.Green.Medium)); }); }); } @@ -416,7 +421,9 @@ namespace QuestPDF.Examples .Border(1) .BorderColor(Colors.Grey.Medium) .Padding(10) - .Text(font, TextStyle.Default.FontType(font).Size(16)); + .Text(font) + .FontType(font) + .Size(16); } }); }); @@ -456,7 +463,7 @@ namespace QuestPDF.Examples }); layers.Layer().Background("#8F00").Extend(); - layers.Layer().PaddingTop(40).Text("It works!", TextStyle.Default.Size(24)); + layers.Layer().PaddingTop(40).Text("It works!").Size(24); }); }); } @@ -477,7 +484,7 @@ namespace QuestPDF.Examples //.MinimalBox() .Background(Colors.Grey.Lighten2) .Padding(15) - .Text("Test of the \n box element", TextStyle.Default.Size(20)); + .Text("Test of the \n box element").Size(20); }); } @@ -503,7 +510,8 @@ namespace QuestPDF.Examples decoration .Before() .PaddingBottom(10) - .Text("Example: scale component", headerFontStyle); + .Text("Example: scale component") + .Style(headerFontStyle); decoration .Content() @@ -525,7 +533,8 @@ namespace QuestPDF.Examples .Background(fontColor) .Scale(scale) .Padding(5) - .Text($"Content with {scale} scale.", fontStyle); + .Text($"Content with {scale} scale.") + .Style(fontStyle); } }); }); @@ -555,7 +564,8 @@ namespace QuestPDF.Examples .BorderColor(Colors.Green.Darken1) .Padding(50) - .Text("Moved text", TextStyle.Default.Size(25)); + .Text("Moved text") + .Size(25); }); } @@ -591,7 +601,8 @@ namespace QuestPDF.Examples .MinimalBox() .Background(Colors.White) .Padding(10) - .Text($"Rotated {turns * 90}°", TextStyle.Default.Size(16)); + .Text($"Rotated {turns * 90}°") + .Size(16); } }); }); @@ -689,7 +700,8 @@ namespace QuestPDF.Examples .MinimalBox() .Background(Colors.White) .Padding(10) - .Text($"Flipped {turns}", TextStyle.Default.Size(16)); + .Text($"Flipped {turns}") + .Size(16); } }); }); diff --git a/QuestPDF.Examples/EnsureSpaceExample.cs b/QuestPDF.Examples/EnsureSpaceExample.cs index 0425f1b..27de8ad 100644 --- a/QuestPDF.Examples/EnsureSpaceExample.cs +++ b/QuestPDF.Examples/EnsureSpaceExample.cs @@ -23,7 +23,7 @@ namespace QuestPDF.Examples page.Size(PageSizes.A7.Landscape()); page.Background(Colors.White); - page.Header().Text("With ensure space", TextStyle.Default.SemiBold()); + page.Header().Text("With ensure space").SemiBold(); page.Content().Column(column => { diff --git a/QuestPDF.Examples/FrameExample.cs b/QuestPDF.Examples/FrameExample.cs index e8f89b8..95fee4e 100644 --- a/QuestPDF.Examples/FrameExample.cs +++ b/QuestPDF.Examples/FrameExample.cs @@ -17,9 +17,9 @@ namespace QuestPDF.Examples .Padding(5); } - public static void LabelCell(this IContainer container, string text) => container.Cell(true).Text(text, TextStyle.Default.SemiBold()); + public static void LabelCell(this IContainer container, string text) => container.Cell(true).Text(text).SemiBold(); public static IContainer ValueCell(this IContainer container) => container.Cell(false); - public static void ValueCell(this IContainer container, string text) => container.ValueCell().Text(text, TextStyle.Default); + public static void ValueCell(this IContainer container, string text) => container.ValueCell().Text(text); } public class FrameExample diff --git a/QuestPDF.Examples/InlinedExamples.cs b/QuestPDF.Examples/InlinedExamples.cs index fce4c85..cef4d1e 100644 --- a/QuestPDF.Examples/InlinedExamples.cs +++ b/QuestPDF.Examples/InlinedExamples.cs @@ -74,7 +74,8 @@ namespace QuestPDF.Examples .PrimaryLayer() .AlignCenter() .AlignMiddle() - .Text(sizeText, TextStyle.Default.Size(15)); + .Text(sizeText) + .Size(15); }); } }); diff --git a/QuestPDF.Examples/MinimalApiExamples.cs b/QuestPDF.Examples/MinimalApiExamples.cs index cad0c98..0a4112e 100644 --- a/QuestPDF.Examples/MinimalApiExamples.cs +++ b/QuestPDF.Examples/MinimalApiExamples.cs @@ -4,8 +4,6 @@ using QuestPDF.Examples.Engine; using QuestPDF.Fluent; using QuestPDF.Helpers; using QuestPDF.Infrastructure; -using ShimSkiaSharp; -using SKTypeface = SkiaSharp.SKTypeface; namespace QuestPDF.Examples { @@ -19,26 +17,79 @@ namespace QuestPDF.Examples { container.Page(page => { - page.Size(PageSizes.A5); - page.Margin(1.5f, Unit.Centimetre); + page.Size(PageSizes.A4); + page.Margin(2, Unit.Centimetre); + page.Background(Colors.White); + page.DefaultTextStyle(TextStyle.Default.Size(20)); page.Header() - .Text("Hello PDF!", TextStyle.Default.SemiBold().Size(20)); + .Text("Hello PDF!").SemiBold().Size(36).Color(Colors.Blue.Medium); page.Content() .PaddingVertical(1, Unit.Centimetre) .Column(x => { - x.Spacing(10); + x.Spacing(20); x.Item().Text(Placeholders.LoremIpsum()); x.Item().Image(Placeholders.Image(200, 100)); }); + + page.Footer() + .AlignCenter() + .Text(x => + { + x.Span("Page "); + x.CurrentPageNumber(); + }); }); }) .GeneratePdf("hello.pdf"); Process.Start("explorer.exe", "hello.pdf"); } + + [Test] + public void MinimalApi2() + { + RenderingTest + .Create() + .ProduceImages() + .ShowResults() + .RenderDocument(container => + { + container.Page(page => + { + page.Size(PageSizes.A4); + page.Margin(2, Unit.Centimetre); + page.Background(Colors.White); + page.DefaultTextStyle(TextStyle.Default.Size(20)); + + page.Header() + .Text("Hello PDF!") + .SemiBold() + .Size(36) + .Color(Colors.Blue.Medium); + + page.Content() + .PaddingVertical(1, Unit.Centimetre) + .Column(x => + { + x.Spacing(20); + + x.Item().Text(Placeholders.LoremIpsum()); + x.Item().Image(Placeholders.Image(200, 100)); + }); + + page.Footer() + .AlignCenter() + .Text(x => + { + x.Span("Page "); + x.CurrentPageNumber(); + }); + }); + }); + } } } \ No newline at end of file diff --git a/QuestPDF.Examples/Padding.cs b/QuestPDF.Examples/Padding.cs index a544765..68f5576 100644 --- a/QuestPDF.Examples/Padding.cs +++ b/QuestPDF.Examples/Padding.cs @@ -50,7 +50,8 @@ namespace QuestPDF.Examples .Background("FFF") .Padding(5) .AlignCenter() - .Text("Sample text", TextStyle.Default.FontType("Segoe UI emoji")); + .Text("Sample text") + .FontType("Segoe UI emoji"); }); } diff --git a/QuestPDF.Examples/ShowOnceExample.cs b/QuestPDF.Examples/ShowOnceExample.cs index 8d88fe8..2fa5f29 100644 --- a/QuestPDF.Examples/ShowOnceExample.cs +++ b/QuestPDF.Examples/ShowOnceExample.cs @@ -23,7 +23,7 @@ namespace QuestPDF.Examples page.Size(PageSizes.A7.Landscape()); page.Background(Colors.White); - page.Header().Text("With show once", TextStyle.Default.SemiBold()); + page.Header().Text("With show once").SemiBold(); page.Content().PaddingVertical(5).Row(row => { diff --git a/QuestPDF.Examples/SkipOnceExample.cs b/QuestPDF.Examples/SkipOnceExample.cs index 634fee5..942e730 100644 --- a/QuestPDF.Examples/SkipOnceExample.cs +++ b/QuestPDF.Examples/SkipOnceExample.cs @@ -31,7 +31,7 @@ namespace QuestPDF.Examples page.Content() .PaddingVertical(10) - .Text(Placeholders.Paragraphs(), TextStyle.Default.Color(Colors.Grey.Medium)); + .Text(Placeholders.Paragraphs()).Color(Colors.Grey.Medium); page.Footer().Text(text => { diff --git a/QuestPDF.Examples/TextBenchmark.cs b/QuestPDF.Examples/TextBenchmark.cs index 47645a3..5470664 100644 --- a/QuestPDF.Examples/TextBenchmark.cs +++ b/QuestPDF.Examples/TextBenchmark.cs @@ -141,8 +141,8 @@ namespace QuestPDF.Examples .AlignBottom() .Column(column => { - column.Item().Text("Quo Vadis", TextStyle.Default.Size(72).Bold().Color(Colors.Blue.Darken2)); - column.Item().Text("Henryk Sienkiewicz", TextStyle.Default.Size(24).Color(Colors.Grey.Darken2)); + column.Item().Text("Quo Vadis").Size(72).Bold().Color(Colors.Blue.Darken2); + column.Item().Text("Henryk Sienkiewicz").Size(24).Color(Colors.Grey.Darken2); }); } @@ -156,8 +156,8 @@ namespace QuestPDF.Examples { column.Item().InternalLink(chapter.Title).Row(row => { - row.RelativeItem().Text(chapter.Title, normalStyle); - row.ConstantItem(100).AlignRight().Text(text => text.PageNumberOfLocation(chapter.Title, normalStyle)); + row.RelativeItem().Text(chapter.Title).Style(normalStyle); + row.ConstantItem(100).AlignRight().Text(text => text.BeginPageNumberOfSection(chapter.Title).Style(normalStyle)); }); } }); @@ -180,7 +180,7 @@ namespace QuestPDF.Examples column.Item().Text(text => { text.ParagraphSpacing(5); - text.Span(content, normalStyle); + text.Span(content).Style(normalStyle); }); column.Item().PageBreak(); @@ -198,7 +198,7 @@ namespace QuestPDF.Examples text.DefaultTextStyle(normalStyle); text.Span("Ten dokument został wygenerowany na podstawie książki w formacie TXT opublikowanej w serwisie "); - text.ExternalLocation("wolnelektury.pl", "https://wolnelektury.pl/", normalStyle.Color(Colors.Blue.Medium).Underline()); + text.Hyperlink("wolnelektury.pl", "https://wolnelektury.pl/").Color(Colors.Blue.Medium).Underline(); text.Span(". Dziękuję za wspieranie polskiego czytelnictwa!"); }); }); @@ -206,7 +206,7 @@ namespace QuestPDF.Examples void SectionTitle(ColumnDescriptor column, string text) { - column.Item().Location(text).Text(text, subtitleStyle); + column.Item().Location(text).Text(text).Style(subtitleStyle); column.Item().PaddingTop(10).PaddingBottom(50).BorderBottom(1).BorderColor(Colors.Grey.Lighten2).ExtendHorizontal(); } diff --git a/QuestPDF.Examples/TextExamples.cs b/QuestPDF.Examples/TextExamples.cs index ef12e96..347996f 100644 --- a/QuestPDF.Examples/TextExamples.cs +++ b/QuestPDF.Examples/TextExamples.cs @@ -31,7 +31,7 @@ namespace QuestPDF.Examples { text.DefaultTextStyle(TextStyle.Default.Size(20)); text.Span("This is a normal text, followed by an "); - text.Span("underlined red text.", TextStyle.Default.Size(20).Color(Colors.Red.Medium).Underline()); + text.Span("underlined red text.").Size(20).Color(Colors.Red.Medium).Underline(); }); }); } @@ -57,7 +57,7 @@ namespace QuestPDF.Examples foreach (var i in Enumerable.Range(1, 3)) { - text.Span($"Paragraph {i}: ", TextStyle.Default.SemiBold()); + text.Span($"Paragraph {i}: ").SemiBold(); text.Line(Placeholders.Paragraph()); } }); @@ -120,7 +120,7 @@ namespace QuestPDF.Examples text.Line(Placeholders.LoremIpsum()); - text.Span($"This is target text that should show up. {DateTime.UtcNow:T} > This is a short sentence that will be wrapped into second line hopefully, right? <", TextStyle.Default.Underline()); + text.Span($"This is target text that should show up. {DateTime.UtcNow:T} > This is a short sentence that will be wrapped into second line hopefully, right? <").Underline(); }); }); } @@ -173,6 +173,8 @@ namespace QuestPDF.Examples .Padding(10) .Text(text => { + text.DefaultTextStyle(x => x.Bold()); + text.DefaultTextStyle(TextStyle.Default); text.AlignLeft(); text.ParagraphSpacing(10); @@ -182,9 +184,9 @@ namespace QuestPDF.Examples text.EmptyLine(); text.Span("This text is a normal text, "); - text.Span("this is a bold text, ", TextStyle.Default.Bold()); - text.Span("this is a red and underlined text, ", TextStyle.Default.Color(Colors.Red.Medium).Underline()); - text.Span("and this is slightly bigger text.", TextStyle.Default.Size(16)); + text.Span("this is a bold text, ").Bold(); + text.Span("this is a red and underlined text, ").Color(Colors.Red.Medium).Underline(); + text.Span("and this is slightly bigger text.").Size(16); text.EmptyLine(); @@ -210,7 +212,7 @@ namespace QuestPDF.Examples text.EmptyLine(); - text.Span(Placeholders.Paragraphs(), TextStyle.Default.Italic()); + text.Span(Placeholders.Paragraphs()).Italic(); text.Line("This is target text that does not show up. " + Placeholders.Paragraph()); }); @@ -241,9 +243,9 @@ namespace QuestPDF.Examples text.ParagraphSpacing(10); text.Span("This text is a normal text, "); - text.Span("this is a bold text, ", TextStyle.Default.Bold()); - text.Span("this is a red and underlined text, ", TextStyle.Default.Color(Colors.Red.Medium).Underline()); - text.Span("and this is slightly bigger text.", TextStyle.Default.Size(16)); + text.Span("this is a bold text, ").Bold(); + text.Span("this is a red and underlined text, ").Color(Colors.Red.Medium).Underline(); + text.Span("and this is slightly bigger text.").Size(16); text.Span("The new text element also supports injecting custom content between words: "); text.Element().PaddingBottom(-10).Height(16).Width(32).Image(Placeholders.Image); diff --git a/QuestPDF.ReportSample/Layouts/DifferentHeadersTemplate.cs b/QuestPDF.ReportSample/Layouts/DifferentHeadersTemplate.cs index e67fb58..a70c3bb 100644 --- a/QuestPDF.ReportSample/Layouts/DifferentHeadersTemplate.cs +++ b/QuestPDF.ReportSample/Layouts/DifferentHeadersTemplate.cs @@ -30,12 +30,12 @@ namespace QuestPDF.ReportSample.Layouts { column.Item().ShowOnce().Padding(5).AlignMiddle().Row(row => { - row.RelativeItem(2).AlignMiddle().Text("PRIMARY HEADER", TextStyle.Default.Color(Colors.Grey.Darken3).Size(30).Bold()); + row.RelativeItem(2).AlignMiddle().Text("PRIMARY HEADER").Color(Colors.Grey.Darken3).Size(30).Bold(); row.RelativeItem(1).AlignRight().MinimalBox().AlignMiddle().Background(Colors.Blue.Darken2).Padding(30); }); column.Item().SkipOnce().Padding(5).Row(row => { - row.RelativeItem(2).Text("SECONDARY HEADER", TextStyle.Default.Color(Colors.Grey.Darken3).Size(30).Bold()); + row.RelativeItem(2).Text("SECONDARY HEADER").Color(Colors.Grey.Darken3).Size(30).Bold(); row.RelativeItem(1).AlignRight().MinimalBox().Background(Colors.Blue.Lighten4).Padding(15); }); }); diff --git a/QuestPDF.ReportSample/Layouts/SectionTemplate.cs b/QuestPDF.ReportSample/Layouts/SectionTemplate.cs index 2e37ec3..83c55cf 100644 --- a/QuestPDF.ReportSample/Layouts/SectionTemplate.cs +++ b/QuestPDF.ReportSample/Layouts/SectionTemplate.cs @@ -23,7 +23,8 @@ namespace QuestPDF.ReportSample.Layouts decoration .Before() .PaddingBottom(5) - .Text(Model.Title, Typography.Headline); + .Text(Model.Title) + .Style(Typography.Headline); decoration.Content().Border(0.75f).BorderColor(Colors.Grey.Medium).Column(column => { @@ -69,7 +70,7 @@ namespace QuestPDF.ReportSample.Layouts { if (model.PhotoCount == 0) { - container.Text("No photos", Typography.Normal); + container.Text("No photos").Style(Typography.Normal); return; } diff --git a/QuestPDF.ReportSample/Layouts/StandardReport.cs b/QuestPDF.ReportSample/Layouts/StandardReport.cs index 143d5c9..87adf0f 100644 --- a/QuestPDF.ReportSample/Layouts/StandardReport.cs +++ b/QuestPDF.ReportSample/Layouts/StandardReport.cs @@ -39,9 +39,9 @@ namespace QuestPDF.ReportSample.Layouts page.Footer().AlignCenter().Text(text => { - text.CurrentPageNumber(format: x => x?.FormatAsRomanNumeral() ?? "-----"); + text.CurrentPageNumber().Format(x => x?.FormatAsRomanNumeral() ?? "-----"); text.Span(" / "); - text.TotalPages(format: x => x?.FormatAsRomanNumeral() ?? "-----"); + text.TotalPages().Format(x => x?.FormatAsRomanNumeral() ?? "-----"); }); }); } @@ -54,7 +54,7 @@ namespace QuestPDF.ReportSample.Layouts { row.Spacing(50); - row.RelativeItem().PaddingTop(-10).Text(Model.Title, Typography.Title); + row.RelativeItem().PaddingTop(-10).Text(Model.Title).Style(Typography.Title); row.ConstantItem(90).ExternalLink("https://www.questpdf.com").MaxHeight(30).Component(); }); @@ -69,7 +69,7 @@ namespace QuestPDF.ReportSample.Layouts { grid.Item().Text(text => { - text.Span($"{field.Label}: ", TextStyle.Default.SemiBold()); + text.Span($"{field.Label}: ").SemiBold(); text.Span(field.Value); }); } diff --git a/QuestPDF.ReportSample/Layouts/TableOfContentsTemplate.cs b/QuestPDF.ReportSample/Layouts/TableOfContentsTemplate.cs index 62c02e3..8788edd 100644 --- a/QuestPDF.ReportSample/Layouts/TableOfContentsTemplate.cs +++ b/QuestPDF.ReportSample/Layouts/TableOfContentsTemplate.cs @@ -23,7 +23,8 @@ namespace QuestPDF.ReportSample.Layouts decoration .Before() .PaddingBottom(5) - .Text("Table of contents", Typography.Headline); + .Text("Table of contents") + .Style(Typography.Headline); decoration.Content().Column(column => { @@ -53,9 +54,9 @@ namespace QuestPDF.ReportSample.Layouts var lengthStyle = TextStyle.Default.Color(Colors.Grey.Medium); - text.Span(" (", lengthStyle); - text.TotalPagesWithinSection(locationName, lengthStyle, x => x == 1 ? "1 page long" : $"{x} pages long"); - text.Span(")", lengthStyle); + text.Span(" (").Style(lengthStyle); + text.TotalPagesWithinSection(locationName).Style(lengthStyle).Format(x => x == 1 ? "1 page long" : $"{x} pages long"); + text.Span(")").Style(lengthStyle); }); }); } diff --git a/QuestPDF/Elements/DebugArea.cs b/QuestPDF/Elements/DebugArea.cs index cd93da2..be9b2a5 100644 --- a/QuestPDF/Elements/DebugArea.cs +++ b/QuestPDF/Elements/DebugArea.cs @@ -30,7 +30,10 @@ namespace QuestPDF.Elements .MinimalBox() .Background(Colors.White) .Padding(2) - .Text(Text, TextStyle.Default.Color(Color).FontType(Fonts.Consolas).Size(8)); + .Text(Text) + .Color(Color) + .FontType(Fonts.Consolas) + .Size(8); }); } } diff --git a/QuestPDF/Elements/Placeholder.cs b/QuestPDF/Elements/Placeholder.cs index cf33087..943ecc0 100644 --- a/QuestPDF/Elements/Placeholder.cs +++ b/QuestPDF/Elements/Placeholder.cs @@ -26,7 +26,7 @@ namespace QuestPDF.Elements if (string.IsNullOrWhiteSpace(Text)) x.MaxHeight(32).Image(ImageData, ImageScaling.FitArea); else - x.Text(Text, TextStyle.Default.Size(14)); + x.Text(Text).Size(14); }); } } diff --git a/QuestPDF/Fluent/TextExtensions.cs b/QuestPDF/Fluent/TextExtensions.cs index e069871..4e510d5 100644 --- a/QuestPDF/Fluent/TextExtensions.cs +++ b/QuestPDF/Fluent/TextExtensions.cs @@ -10,6 +10,34 @@ using static System.String; namespace QuestPDF.Fluent { + public class TextSpanDescriptor + { + internal TextStyle TextStyle { get; } + + internal TextSpanDescriptor(TextStyle textStyle) + { + TextStyle = textStyle; + } + } + + public delegate string PageNumberFormatter(int? pageNumber); + + public class TextPageNumberDescriptor : TextSpanDescriptor + { + internal PageNumberFormatter FormatFunction { get; private set; } = x => (x ?? 123).ToString(); + + internal TextPageNumberDescriptor(TextStyle textStyle) : base(textStyle) + { + + } + + public TextPageNumberDescriptor Format(PageNumberFormatter formatter) + { + FormatFunction = formatter ?? FormatFunction; + return this; + } + } + public class TextDescriptor { private ICollection TextBlocks { get; } = new List(); @@ -22,6 +50,11 @@ namespace QuestPDF.Fluent DefaultStyle = style; } + public void DefaultTextStyle(Func style) + { + DefaultStyle = style(TextStyle.Default); + } + public void AlignLeft() { Alignment = HorizontalAlignment.Left; @@ -50,12 +83,9 @@ namespace QuestPDF.Fluent TextBlocks.Last().Items.Add(item); } - public void Span(string? text, TextStyle? style = null) + public TextSpanDescriptor Span(string? text) { - if (IsNullOrEmpty(text)) - return; - - style ??= TextStyle.Default; + var style = DefaultStyle.Clone(); var items = text .Replace("\r", string.Empty) @@ -77,78 +107,81 @@ namespace QuestPDF.Fluent }) .ToList() .ForEach(TextBlocks.Add); + + return new TextSpanDescriptor(style); } - public void Line(string? text, TextStyle? style = null) + public TextSpanDescriptor Line(string? text) { text ??= string.Empty; - Span(text + Environment.NewLine, style); + return Span(text + Environment.NewLine); } - public void EmptyLine() + public TextSpanDescriptor EmptyLine() { - Span(Environment.NewLine); + return Span(Environment.NewLine); } - - private static Func DefaultTextFormat = x => (x ?? 123).ToString(); - private void PageNumber(Func pageNumber, TextStyle? style, Func? format) + private TextPageNumberDescriptor PageNumber(Func pageNumber) { - style ??= TextStyle.Default; - format ??= DefaultTextFormat; + var style = DefaultStyle.Clone(); + var descriptor = new TextPageNumberDescriptor(DefaultStyle); - AddItemToLastTextBlock(new TextBlockPageNumber() + AddItemToLastTextBlock(new TextBlockPageNumber { - Source = context => format(pageNumber(context)), + Source = context => descriptor.FormatFunction(pageNumber(context)), Style = style }); + + return descriptor; } - public void CurrentPageNumber(TextStyle? style = null, Func? format = null) + public TextPageNumberDescriptor CurrentPageNumber() { - PageNumber(x => x.CurrentPage, style, format); + return PageNumber(x => x.CurrentPage); } - public void TotalPages(TextStyle? style = null, Func? format = null) + public TextPageNumberDescriptor TotalPages() { - PageNumber(x => x.GetLocation(PageContext.DocumentLocation)?.Length, style, format); + return PageNumber(x => x.GetLocation(PageContext.DocumentLocation)?.Length); } [Obsolete("This element has been renamed since version 2022.3. Please use the BeginPageNumberOfSection method.")] public void PageNumberOfLocation(string locationName, TextStyle? style = null) { - BeginPageNumberOfSection(locationName); + BeginPageNumberOfSection(locationName).Style(style); } - public void BeginPageNumberOfSection(string locationName, TextStyle? style = null, Func? format = null) + public TextPageNumberDescriptor BeginPageNumberOfSection(string locationName) { - PageNumber(x => x.GetLocation(locationName)?.PageStart, style, format); + return PageNumber(x => x.GetLocation(locationName)?.PageStart); } - public void EndPageNumberOfSection(string locationName, TextStyle? style = null, Func? format = null) + public TextPageNumberDescriptor EndPageNumberOfSection(string locationName) { - PageNumber(x => x.GetLocation(locationName)?.PageEnd, style, format); + return PageNumber(x => x.GetLocation(locationName)?.PageEnd); } - public void PageNumberWithinSection(string locationName, TextStyle? style = null, Func? format = null) + public TextPageNumberDescriptor PageNumberWithinSection(string locationName) { - PageNumber(x => x.CurrentPage + 1 - x.GetLocation(locationName)?.PageEnd, style, format); + return PageNumber(x => x.CurrentPage + 1 - x.GetLocation(locationName)?.PageEnd); } - public void TotalPagesWithinSection(string locationName, TextStyle? style = null, Func? format = null) + public TextPageNumberDescriptor TotalPagesWithinSection(string locationName) { - PageNumber(x => x.GetLocation(locationName)?.Length, style, format); + return PageNumber(x => x.GetLocation(locationName)?.Length); } - public void SectionLink(string? text, string sectionName, TextStyle? style = null) + public TextSpanDescriptor SectionLink(string? text, string sectionName) { - if (IsNullOrEmpty(text)) - return; - if (IsNullOrEmpty(sectionName)) throw new ArgumentException(nameof(sectionName)); - style ??= TextStyle.Default; + var style = DefaultStyle.Clone(); + var descriptor = new TextSpanDescriptor(style); + + if (IsNullOrEmpty(text)) + return descriptor; AddItemToLastTextBlock(new TextBlockSectionlLink { @@ -156,23 +189,26 @@ namespace QuestPDF.Fluent Text = text, SectionName = sectionName }); + + return descriptor; } [Obsolete("This element has been renamed since version 2022.3. Please use the SectionLink method.")] public void InternalLocation(string? text, string locationName, TextStyle? style = null) { - SectionLink(text, locationName, style); + SectionLink(text, locationName).Style(style); } - public void Hyperlink(string? text, string url, TextStyle? style = null) + public TextSpanDescriptor Hyperlink(string? text, string url) { - if (IsNullOrEmpty(text)) - return; - if (IsNullOrEmpty(url)) throw new ArgumentException(nameof(url)); - - style ??= TextStyle.Default; + + var style = DefaultStyle.Clone(); + var descriptor = new TextSpanDescriptor(style); + + if (IsNullOrEmpty(text)) + return descriptor; AddItemToLastTextBlock(new TextBlockHyperlink { @@ -180,12 +216,14 @@ namespace QuestPDF.Fluent Text = text, Url = url }); + + return descriptor; } [Obsolete("This element has been renamed since version 2022.3. Please use the Hyperlink method.")] public void ExternalLocation(string? text, string url, TextStyle? style = null) { - Hyperlink(text, url, style); + Hyperlink(text, url).Style(style); } public IContainer Element() @@ -227,9 +265,17 @@ namespace QuestPDF.Fluent descriptor.Compose(element); } - public static void Text(this IContainer element, object? text, TextStyle? style = null) + //[Obsolete("This element has been renamed since version 2022.3. Please use the ")] + // public static void Text(this IContainer element, object? text, TextStyle style) + // { + // element.Text(text).Style(style); + // } + + public static TextSpanDescriptor Text(this IContainer element, object? text) { - element.Text(x => x.Span(text?.ToString(), style)); + var descriptor = (TextSpanDescriptor) null; + element.Text(x => descriptor = x.Span(text?.ToString())); + return descriptor; } } } \ No newline at end of file diff --git a/QuestPDF/Fluent/TextSpanDescriptorExtensions.cs b/QuestPDF/Fluent/TextSpanDescriptorExtensions.cs new file mode 100644 index 0000000..f04e207 --- /dev/null +++ b/QuestPDF/Fluent/TextSpanDescriptorExtensions.cs @@ -0,0 +1,126 @@ +using System; +using System.Runtime.CompilerServices; +using QuestPDF.Infrastructure; + +namespace QuestPDF.Fluent +{ + public static class TextSpanDescriptorExtensions + { + public static T Style(this T descriptor, TextStyle style) where T : TextSpanDescriptor + { + if (style == null) + return descriptor; + + descriptor.TextStyle.OverrideStyle(style); + return descriptor; + } + + public static T Color(this T descriptor, string value) where T : TextSpanDescriptor + { + descriptor.TextStyle.Color = value; + return descriptor; + } + + public static T BackgroundColor(this T descriptor, string value) where T : TextSpanDescriptor + { + descriptor.TextStyle.BackgroundColor = value; + return descriptor; + } + + public static T FontType(this T descriptor, string value) where T : TextSpanDescriptor + { + descriptor.TextStyle.FontType = value; + return descriptor; + } + + public static T Size(this T descriptor, float value) where T : TextSpanDescriptor + { + descriptor.TextStyle.Size = value; + return descriptor; + } + + public static T LineHeight(this T descriptor, float value) where T : TextSpanDescriptor + { + descriptor.TextStyle.LineHeight = value; + return descriptor; + } + + public static T Italic(this T descriptor, bool value = true) where T : TextSpanDescriptor + { + descriptor.TextStyle.IsItalic = value; + return descriptor; + } + + public static T Strikethrough(this T descriptor, bool value = true) where T : TextSpanDescriptor + { + descriptor.TextStyle.HasStrikethrough = value; + return descriptor; + } + + public static T Underline(this T descriptor, bool value = true) where T : TextSpanDescriptor + { + descriptor.TextStyle.HasUnderline = value; + return descriptor; + } + + #region Weight + + public static T Weight(this T descriptor, FontWeight weight) where T : TextSpanDescriptor + { + descriptor.TextStyle.FontWeight = weight; + return descriptor; + } + + public static T Thin(this T descriptor) where T : TextSpanDescriptor + { + return descriptor.Weight(FontWeight.Thin); + } + + public static T ExtraLight(this T descriptor) where T : TextSpanDescriptor + { + return descriptor.Weight(FontWeight.ExtraLight); + } + + public static T Light(this T descriptor) where T : TextSpanDescriptor + { + return descriptor.Weight(FontWeight.Light); + } + + public static T NormalWeight(this T descriptor) where T : TextSpanDescriptor + { + return descriptor.Weight(FontWeight.Normal); + } + + public static T Medium(this T descriptor) where T : TextSpanDescriptor + { + return descriptor.Weight(FontWeight.Medium); + } + + public static T SemiBold(this T descriptor) where T : TextSpanDescriptor + { + return descriptor.Weight(FontWeight.SemiBold); + } + + public static T Bold(this T descriptor) where T : TextSpanDescriptor + { + return descriptor.Weight(FontWeight.Bold); + } + + public static T ExtraBold(this T descriptor) where T : TextSpanDescriptor + { + return descriptor.Weight(FontWeight.ExtraBold); + } + + public static T Black(this T descriptor) where T : TextSpanDescriptor + { + return descriptor.Weight(FontWeight.Black); + } + + public static T ExtraBlack(this T descriptor) where T : TextSpanDescriptor + { + return descriptor.Weight(FontWeight.ExtraBlack); + } + + #endregion + } +} \ No newline at end of file diff --git a/QuestPDF/Infrastructure/TextStyle.cs b/QuestPDF/Infrastructure/TextStyle.cs index 552f54f..13dde4e 100644 --- a/QuestPDF/Infrastructure/TextStyle.cs +++ b/QuestPDF/Infrastructure/TextStyle.cs @@ -60,6 +60,19 @@ namespace QuestPDF.Infrastructure HasUnderline ??= parentStyle.HasUnderline; } + internal void OverrideStyle(TextStyle parentStyle) + { + Color = parentStyle.Color ?? Color; + BackgroundColor = parentStyle.BackgroundColor ?? BackgroundColor; + FontType = parentStyle.FontType ?? FontType; + Size = parentStyle.Size ?? Size; + LineHeight = parentStyle.LineHeight ?? LineHeight; + FontWeight = parentStyle.FontWeight ?? FontWeight; + IsItalic = parentStyle.IsItalic ?? IsItalic; + HasStrikethrough = parentStyle.HasStrikethrough ?? HasStrikethrough; + HasUnderline = parentStyle.HasUnderline ?? HasUnderline; + } + internal TextStyle Clone() { var clone = (TextStyle)MemberwiseClone(); diff --git a/readme.md b/readme.md index 187a195..494e112 100644 --- a/readme.md +++ b/readme.md @@ -8,215 +8,84 @@ I have designed this layouting engine with full paging support in mind. The docu ## Support QuestPDF -All great frameworks and libraries started from zero. Please help me make QuestPDF a commonly known library and an obvious choice in case of generating PDF documents. Please give it a start ⭐ and share with your colleagues 💬👨‍💻. +All great frameworks and libraries started from zero. Please help me make QuestPDF a commonly known library and an obvious choice for generating PDF documents. + +- ⭐ Give this repository a star, +- 💬 Share it with your team members. ## Installation The library is available as a nuget package. You can install it as any other nuget package from your IDE, try to search by `QuestPDF`. You can find package details [on this webpage](https://www.nuget.org/packages/QuestPDF/). -``` +```c# +// Package Manager Install-Package QuestPDF + +// .NET CLI +dotnet add package QuestPDF + +// Package reference in .csproj file + ``` ## Documentation -**[Release notes and roadmap](https://www.questpdf.com/documentation/releases.html)** - everything that is planned for future library iterations, description of new features and information about potential breaking changes. +**[🚀 Getting started tutorial](https://www.questpdf.com/documentation/getting-started.html)** - a short and easy to follow tutorial showing how to design an invoice document under 200 lines of code. -**[Getting started tutorial](https://www.questpdf.com/documentation/getting-started.html)** - a short and easy to follow tutorial showing how to design an invoice document under 200 lines of code. +**[📖 API Reference](https://www.questpdf.com/documentation/api-reference.html)** - a detailed description of behavior of all available components and how to use them with C# Fluent API. -**[API Reference](https://www.questpdf.com/documentation/api-reference.html)** - a detailed description of behavior of all available components and how to use them with C# Fluent API. +**[ℹ️ Patterns and practices](https://www.questpdf.com/documentation/patterns-and-practices.html#document-metadata)** - everything that may help you designing great reports and create reusable code that is easy to maintain. -**[Patterns and practices](https://www.questpdf.com/documentation/patterns-and-practices.html#document-metadata)** - everything that may help you designing great reports and reusable code that is easy to maintain. +## Simplicity is the key -## Example invoice - -Do you believe that creating a complete invoice document can take less than 200 lines of code? We have prepared for you a step-by-step instruction that shows every detail of this implementation and describes the best patterns and practices. - -For tutorial, documentation and API reference, please visit [the QuestPDF documentation](https://www.questpdf.com/documentation/getting-started.html). - - - - - -Here you can find an example code showing how easy is to write and understand the fluent API. - -**General document structure** with header, content and footer: +How easy it is to start and prototype with QuestPDF? Really easy thanks to its minimal API! Please analyse the code below: ```csharp -public void Compose(IDocumentContainer container) +using QuestPDF.Fluent; +using QuestPDF.Helpers; +using QuestPDF.Infrastructure; + +// code in your main method +Document.Create(container => { - container - .Page(page => - { - page.Margin(50); - - page.Header().Element(ComposeHeader); - page.Content().Element(ComposeContent); - - page.Footer().AlignCenter().Text(x => + container.Page(page => + { + page.Size(PageSizes.A4); + page.Margin(2, Unit.Centimetre); + page.Background(Colors.White); + page.DefaultTextStyle(TextStyle.Default.Size(20)); + + page.Header() + .Text("Hello PDF!", TextStyle.Default.SemiBold().Size(36).Color(Colors.Blue.Medium)); + + page.Content() + .PaddingVertical(1, Unit.Centimetre) + .Column(x => { + x.Spacing(20); + + x.Item().Text(Placeholders.LoremIpsum()); + x.Item().Image(Placeholders.Image(200, 100)); + }); + + page.Footer() + .AlignCenter() + .Text(x => + { + x.Span("Page "); x.CurrentPageNumber(); - x.Span(" / "); - x.TotalPages(); }); - }); -} -``` - -**The header area** consists of basic invoice information along with a logo placeholder. - -```csharp -void ComposeHeader(IContainer container) -{ - var titleTextStyle = TextStyle.Default.Size(20).SemiBold().Color(Colors.Blue.Medium); - - container.Row(row => - { - row.RelativeColumn().Stack(stack => - { - stack.Item().Text($"Invoice #{Model.InvoiceNumber}", titleStyle); - - stack.Item().Text(text => - { - text.Span("Issue date: ", TextStyle.Default.SemiBold()); - text.Span($"{Model.IssueDate:d}"); - }); - - stack.Item().Text(text => - { - text.Span("Due date: ", TextStyle.Default.SemiBold()); - text.Span($"{Model.DueDate:d}"); - }); - }); - - row.ConstantColumn(100).Height(50).Placeholder(); }); -} +}) +.GeneratePdf("hello.pdf"); ``` -Implementation of **the content area** that contains seller and customer details, then listing of all bought products, then a comments section. +And compare it to the produced PDF file: -```csharp -void ComposeContent(IContainer container) -{ - container.PaddingVertical(40).Stack(column => - { - column.Spacing(20); - - column.Item().Row(row => - { - row.RelativeColumn().Component(new AddressComponent("From", Model.SellerAddress)); - row.ConstantColumn(50); - row.RelativeColumn().Component(new AddressComponent("For", Model.CustomerAddress)); - }); - column.Item().Element(ComposeTable); - var totalPrice = Model.Items.Sum(x => x.Price * x.Quantity); - - column - .Item() - .PaddingRight(5) - .AlignRight() - .Text($"Grand total: {totalPrice}$", TextStyle.Default.SemiBold()); +## Are you ready for more? - if (!string.IsNullOrWhiteSpace(Model.Comments)) - column.Item().PaddingTop(25).Element(ComposeComments); - }); -} -``` +The Fluent API of QuestPDF scales really well. It is easy to create and maintain even most complex documents. Read [the Getting started tutorial](https://www.questpdf.com/documentation/getting-started.html) to learn QuestPDF basics and implement an invoice under 200 lines of code. You can also investigate and play with the code from [the example repository](https://github.com/QuestPDF/example-invoice). -**The table and comments** codes are extracted into separate methods to increase clarity: - -```csharp -void ComposeTable(IContainer container) -{ - var headerStyle = TextStyle.Default.SemiBold(); - - container.Table(table => - { - table.ColumnsDefinition(columns => - { - columns.ConstantColumn(25); - columns.RelativeColumn(3); - columns.RelativeColumn(); - columns.RelativeColumn(); - columns.RelativeColumn(); - }); - - table.Header(header => - { - header.Cell().Text("#", headerStyle); - header.Cell().Text("Product", headerStyle); - header.Cell().AlignRight().Text("Unit price", headerStyle); - header.Cell().AlignRight().Text("Quantity", headerStyle); - header.Cell().AlignRight().Text("Total", headerStyle); - - header.Cell().ColumnSpan(5) - .PaddingVertical(5).BorderBottom(1).BorderColor(Colors.Black); - }); - - foreach (var item in Model.Items) - { - table.Cell().Element(CellStyle).Text(Model.Items.IndexOf(item) + 1); - table.Cell().Element(CellStyle).Text(item.Name); - table.Cell().Element(CellStyle).AlignRight().Text($"{item.Price}$"); - table.Cell().Element(CellStyle).AlignRight().Text(item.Quantity); - table.Cell().Element(CellStyle).AlignRight().Text($"{item.Price * item.Quantity}$"); - - static IContainer CellStyle(IContainer container) - { - container.BorderBottom(1).BorderColor(Colors.Grey.Lighten2).PaddingVertical(5); - } - } - }); -} -``` - -```csharp -void ComposeComments(IContainer container) -{ - container.ShowEntire().Background(Colors.Grey.Lighten3).Padding(10).Stack(message => - { - message.Spacing(5); - message.Item().Text("Comments", TextStyle.Default.Size(14).SemiBold()); - message.Item().Text(Model.Comments); - }); -} -``` - -**The address details section** is implemented using components. This way the code can be easily reused for both seller and customer: - -```csharp -public class AddressComponent : IComponent -{ - private string Title { get; } - private Address Address { get; } - - public AddressComponent(string title, Address address) - { - Title = title; - Address = address; - } - - public void Compose(IContainer container) - { - container.ShowEntire().Stack(column => - { - column.Spacing(5); - - column - .Item() - .BorderBottom(1) - .PaddingBottom(5) - .Text(Title, TextStyle.Default.SemiBold()); - - column.Item().Text(Address.CompanyName); - column.Item().Text(Address.Street); - column.Item().Text($"{Address.City}, {Address.State}"); - column.Item().Text(Address.Email); - column.Item().Text(Address.Phone); - }); - } -} -``` + \ No newline at end of file From 262bd3c18256738f699664b9c92f484f1dd34f98 Mon Sep 17 00:00:00 2001 From: MarcinZiabek Date: Wed, 9 Mar 2022 22:13:51 +0100 Subject: [PATCH 09/12] Improved backwards compatibility --- QuestPDF.Examples/SkipOnceExample.cs | 3 ++- QuestPDF/Fluent/TextExtensions.cs | 16 +++++++++++----- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/QuestPDF.Examples/SkipOnceExample.cs b/QuestPDF.Examples/SkipOnceExample.cs index 942e730..1c3323e 100644 --- a/QuestPDF.Examples/SkipOnceExample.cs +++ b/QuestPDF.Examples/SkipOnceExample.cs @@ -31,7 +31,8 @@ namespace QuestPDF.Examples page.Content() .PaddingVertical(10) - .Text(Placeholders.Paragraphs()).Color(Colors.Grey.Medium); + .Text(Placeholders.Paragraphs()) + .Color(Colors.Grey.Medium); page.Footer().Text(text => { diff --git a/QuestPDF/Fluent/TextExtensions.cs b/QuestPDF/Fluent/TextExtensions.cs index 4e510d5..3819d40 100644 --- a/QuestPDF/Fluent/TextExtensions.cs +++ b/QuestPDF/Fluent/TextExtensions.cs @@ -83,6 +83,12 @@ namespace QuestPDF.Fluent TextBlocks.Last().Items.Add(item); } + [Obsolete("This element has been renamed since version 2022.3. Please use the overload that returns a TextSpanDescriptor object which allows to specify text style.")] + public void Span(string? text, TextStyle style) + { + Span(text).Style(style); + } + public TextSpanDescriptor Span(string? text) { var style = DefaultStyle.Clone(); @@ -265,11 +271,11 @@ namespace QuestPDF.Fluent descriptor.Compose(element); } - //[Obsolete("This element has been renamed since version 2022.3. Please use the ")] - // public static void Text(this IContainer element, object? text, TextStyle style) - // { - // element.Text(text).Style(style); - // } + [Obsolete("This element has been renamed since version 2022.3. Please use the overload that returns a TextSpanDescriptor object which allows to specify text style.")] + public static void Text(this IContainer element, object? text, TextStyle style) + { + element.Text(text).Style(style); + } public static TextSpanDescriptor Text(this IContainer element, object? text) { From b533a32b6140749a48b6afd068804e5f736370d6 Mon Sep 17 00:00:00 2001 From: MarcinZiabek Date: Fri, 11 Mar 2022 13:23:36 +0100 Subject: [PATCH 10/12] Text API naming adjustments --- QuestPDF.Examples/BarcodeExamples.cs | 6 ++--- QuestPDF.Examples/CanvasExamples.cs | 4 ++-- QuestPDF.Examples/ChartExamples.cs | 4 ++-- QuestPDF.Examples/ContinousPage.cs | 2 +- QuestPDF.Examples/DefaultTextStyleExample.cs | 2 +- QuestPDF.Examples/DefaultTextStyleExamples.cs | 4 ++-- QuestPDF.Examples/ElementExamples.cs | 24 +++++++++---------- QuestPDF.Examples/InlinedExamples.cs | 2 +- QuestPDF.Examples/MinimalApiExamples.cs | 6 ++--- QuestPDF.Examples/Padding.cs | 2 +- QuestPDF.Examples/SkipOnceExample.cs | 2 +- QuestPDF.Examples/TextBenchmark.cs | 6 ++--- QuestPDF.Examples/TextExamples.cs | 10 ++++---- .../Layouts/DifferentHeadersTemplate.cs | 4 ++-- QuestPDF.UnitTests/TestEngine/TestPlan.cs | 2 +- QuestPDF/Drawing/FontManager.cs | 10 ++++---- QuestPDF/Elements/DebugArea.cs | 6 ++--- QuestPDF/Elements/Placeholder.cs | 2 +- .../Fluent/TextSpanDescriptorExtensions.cs | 8 +++---- QuestPDF/Fluent/TextStyleExtensions.cs | 20 +++++++++++++++- QuestPDF/Infrastructure/TextStyle.cs | 12 +++++----- QuestPDF/QuestPDF.csproj | 2 +- 22 files changed, 79 insertions(+), 61 deletions(-) diff --git a/QuestPDF.Examples/BarcodeExamples.cs b/QuestPDF.Examples/BarcodeExamples.cs index 762ad20..ad43734 100644 --- a/QuestPDF.Examples/BarcodeExamples.cs +++ b/QuestPDF.Examples/BarcodeExamples.cs @@ -13,7 +13,7 @@ namespace QuestPDF.Examples [Test] public void Example() { - FontManager.RegisterFontType(File.OpenRead("LibreBarcode39-Regular.ttf")); + FontManager.RegisterFont(File.OpenRead("LibreBarcode39-Regular.ttf")); RenderingTest .Create() @@ -26,8 +26,8 @@ namespace QuestPDF.Examples .AlignCenter() .AlignMiddle() .Text("*QuestPDF*") - .FontType("Libre Barcode 39") - .Size(64); + .FontFamily("Libre Barcode 39") + .FontSize(64); }); } } diff --git a/QuestPDF.Examples/CanvasExamples.cs b/QuestPDF.Examples/CanvasExamples.cs index aff03cf..cb9bd2e 100644 --- a/QuestPDF.Examples/CanvasExamples.cs +++ b/QuestPDF.Examples/CanvasExamples.cs @@ -50,8 +50,8 @@ namespace QuestPDF.Examples .PaddingVertical(10) .PaddingHorizontal(20) .Text("Sample text") - .Size(16) - .Color(Colors.Blue.Darken2) + .FontSize(16) + .FontColor(Colors.Blue.Darken2) .SemiBold(); }); }); diff --git a/QuestPDF.Examples/ChartExamples.cs b/QuestPDF.Examples/ChartExamples.cs index 6cb8abe..beae613 100644 --- a/QuestPDF.Examples/ChartExamples.cs +++ b/QuestPDF.Examples/ChartExamples.cs @@ -58,9 +58,9 @@ namespace QuestPDF.Examples .Item() .PaddingBottom(10) .Text("Chart example") - .Size(20) + .FontSize(20) .SemiBold() - .Color(Colors.Blue.Medium); + .FontColor(Colors.Blue.Medium); column .Item() diff --git a/QuestPDF.Examples/ContinousPage.cs b/QuestPDF.Examples/ContinousPage.cs index 37e3e47..eb73ed9 100644 --- a/QuestPDF.Examples/ContinousPage.cs +++ b/QuestPDF.Examples/ContinousPage.cs @@ -25,7 +25,7 @@ namespace QuestPDF.Examples page.Content().PaddingVertical(10).Border(1).Padding(10).Column(column => { foreach (var index in Enumerable.Range(1, 100)) - column.Item().Text($"Line {index}").Color(Placeholders.Color()); + column.Item().Text($"Line {index}").FontColor(Placeholders.Color()); }); page.Footer().Text("Footer"); diff --git a/QuestPDF.Examples/DefaultTextStyleExample.cs b/QuestPDF.Examples/DefaultTextStyleExample.cs index acf176e..9efce3f 100644 --- a/QuestPDF.Examples/DefaultTextStyleExample.cs +++ b/QuestPDF.Examples/DefaultTextStyleExample.cs @@ -38,7 +38,7 @@ namespace QuestPDF.Examples text.Line(Placeholders.Sentence()); // this text has size 20 but also semibold and red - text.Span(Placeholders.Sentence()).Color(Colors.Red.Medium); + text.Span(Placeholders.Sentence()).FontColor(Colors.Red.Medium); }); }); }); diff --git a/QuestPDF.Examples/DefaultTextStyleExamples.cs b/QuestPDF.Examples/DefaultTextStyleExamples.cs index 9db6bc7..c02536b 100644 --- a/QuestPDF.Examples/DefaultTextStyleExamples.cs +++ b/QuestPDF.Examples/DefaultTextStyleExamples.cs @@ -27,7 +27,7 @@ namespace QuestPDF.Examples .Column(column => { column.Item().Text("Default style applies to all children"); - column.Item().Text("You can override certain styles").Underline(false).Color(Colors.Green.Darken2); + column.Item().Text("You can override certain styles").Underline(false).FontColor(Colors.Green.Darken2); column.Item().PaddingTop(10).Border(1).Grid(grid => { @@ -44,7 +44,7 @@ namespace QuestPDF.Examples .AlignCenter() .AlignMiddle() .Text(i) - .Size(16 + i / 4); + .FontSize(16 + i / 4); } }); }); diff --git a/QuestPDF.Examples/ElementExamples.cs b/QuestPDF.Examples/ElementExamples.cs index 3874f78..45d67ea 100644 --- a/QuestPDF.Examples/ElementExamples.cs +++ b/QuestPDF.Examples/ElementExamples.cs @@ -44,8 +44,8 @@ namespace QuestPDF.Examples .Background(Colors.Grey.Medium) .Padding(10) .Text("Notes") - .Size(16) - .Color("#FFF"); + .FontSize(16) + .FontColor("#FFF"); decoration .Content() @@ -283,14 +283,14 @@ namespace QuestPDF.Examples .AlignCenter() .AlignMiddle() .Text("Watermark") - .Size(48) + .FontSize(48) .Bold() - .Color(Colors.Green.Lighten3); + .FontColor(Colors.Green.Lighten3); layers .Layer() .AlignBottom() - .Text(text => text.CurrentPageNumber().Size(16).Color(Colors.Green.Medium)); + .Text(text => text.CurrentPageNumber().FontSize(16).FontColor(Colors.Green.Medium)); }); }); } @@ -422,8 +422,8 @@ namespace QuestPDF.Examples .BorderColor(Colors.Grey.Medium) .Padding(10) .Text(font) - .FontType(font) - .Size(16); + .FontFamily(font) + .FontSize(16); } }); }); @@ -463,7 +463,7 @@ namespace QuestPDF.Examples }); layers.Layer().Background("#8F00").Extend(); - layers.Layer().PaddingTop(40).Text("It works!").Size(24); + layers.Layer().PaddingTop(40).Text("It works!").FontSize(24); }); }); } @@ -484,7 +484,7 @@ namespace QuestPDF.Examples //.MinimalBox() .Background(Colors.Grey.Lighten2) .Padding(15) - .Text("Test of the \n box element").Size(20); + .Text("Test of the \n box element").FontSize(20); }); } @@ -565,7 +565,7 @@ namespace QuestPDF.Examples .Padding(50) .Text("Moved text") - .Size(25); + .FontSize(25); }); } @@ -602,7 +602,7 @@ namespace QuestPDF.Examples .Background(Colors.White) .Padding(10) .Text($"Rotated {turns * 90}°") - .Size(16); + .FontSize(16); } }); }); @@ -701,7 +701,7 @@ namespace QuestPDF.Examples .Background(Colors.White) .Padding(10) .Text($"Flipped {turns}") - .Size(16); + .FontSize(16); } }); }); diff --git a/QuestPDF.Examples/InlinedExamples.cs b/QuestPDF.Examples/InlinedExamples.cs index cef4d1e..8f4adda 100644 --- a/QuestPDF.Examples/InlinedExamples.cs +++ b/QuestPDF.Examples/InlinedExamples.cs @@ -75,7 +75,7 @@ namespace QuestPDF.Examples .AlignCenter() .AlignMiddle() .Text(sizeText) - .Size(15); + .FontSize(15); }); } }); diff --git a/QuestPDF.Examples/MinimalApiExamples.cs b/QuestPDF.Examples/MinimalApiExamples.cs index 0a4112e..4d7e3ba 100644 --- a/QuestPDF.Examples/MinimalApiExamples.cs +++ b/QuestPDF.Examples/MinimalApiExamples.cs @@ -23,7 +23,7 @@ namespace QuestPDF.Examples page.DefaultTextStyle(TextStyle.Default.Size(20)); page.Header() - .Text("Hello PDF!").SemiBold().Size(36).Color(Colors.Blue.Medium); + .Text("Hello PDF!").SemiBold().FontSize(36).FontColor(Colors.Blue.Medium); page.Content() .PaddingVertical(1, Unit.Centimetre) @@ -68,8 +68,8 @@ namespace QuestPDF.Examples page.Header() .Text("Hello PDF!") .SemiBold() - .Size(36) - .Color(Colors.Blue.Medium); + .FontSize(36) + .FontColor(Colors.Blue.Medium); page.Content() .PaddingVertical(1, Unit.Centimetre) diff --git a/QuestPDF.Examples/Padding.cs b/QuestPDF.Examples/Padding.cs index 68f5576..d8dddcd 100644 --- a/QuestPDF.Examples/Padding.cs +++ b/QuestPDF.Examples/Padding.cs @@ -51,7 +51,7 @@ namespace QuestPDF.Examples .Padding(5) .AlignCenter() .Text("Sample text") - .FontType("Segoe UI emoji"); + .FontFamily("Segoe UI emoji"); }); } diff --git a/QuestPDF.Examples/SkipOnceExample.cs b/QuestPDF.Examples/SkipOnceExample.cs index 1c3323e..182a65e 100644 --- a/QuestPDF.Examples/SkipOnceExample.cs +++ b/QuestPDF.Examples/SkipOnceExample.cs @@ -32,7 +32,7 @@ namespace QuestPDF.Examples page.Content() .PaddingVertical(10) .Text(Placeholders.Paragraphs()) - .Color(Colors.Grey.Medium); + .FontColor(Colors.Grey.Medium); page.Footer().Text(text => { diff --git a/QuestPDF.Examples/TextBenchmark.cs b/QuestPDF.Examples/TextBenchmark.cs index 5470664..33246ec 100644 --- a/QuestPDF.Examples/TextBenchmark.cs +++ b/QuestPDF.Examples/TextBenchmark.cs @@ -141,8 +141,8 @@ namespace QuestPDF.Examples .AlignBottom() .Column(column => { - column.Item().Text("Quo Vadis").Size(72).Bold().Color(Colors.Blue.Darken2); - column.Item().Text("Henryk Sienkiewicz").Size(24).Color(Colors.Grey.Darken2); + column.Item().Text("Quo Vadis").FontSize(72).Bold().FontColor(Colors.Blue.Darken2); + column.Item().Text("Henryk Sienkiewicz").FontSize(24).FontColor(Colors.Grey.Darken2); }); } @@ -198,7 +198,7 @@ namespace QuestPDF.Examples text.DefaultTextStyle(normalStyle); text.Span("Ten dokument został wygenerowany na podstawie książki w formacie TXT opublikowanej w serwisie "); - text.Hyperlink("wolnelektury.pl", "https://wolnelektury.pl/").Color(Colors.Blue.Medium).Underline(); + text.Hyperlink("wolnelektury.pl", "https://wolnelektury.pl/").FontColor(Colors.Blue.Medium).Underline(); text.Span(". Dziękuję za wspieranie polskiego czytelnictwa!"); }); }); diff --git a/QuestPDF.Examples/TextExamples.cs b/QuestPDF.Examples/TextExamples.cs index 347996f..4b938bb 100644 --- a/QuestPDF.Examples/TextExamples.cs +++ b/QuestPDF.Examples/TextExamples.cs @@ -31,7 +31,7 @@ namespace QuestPDF.Examples { text.DefaultTextStyle(TextStyle.Default.Size(20)); text.Span("This is a normal text, followed by an "); - text.Span("underlined red text.").Size(20).Color(Colors.Red.Medium).Underline(); + text.Span("underlined red text.").FontSize(20).FontColor(Colors.Red.Medium).Underline(); }); }); } @@ -185,8 +185,8 @@ namespace QuestPDF.Examples text.Span("This text is a normal text, "); text.Span("this is a bold text, ").Bold(); - text.Span("this is a red and underlined text, ").Color(Colors.Red.Medium).Underline(); - text.Span("and this is slightly bigger text.").Size(16); + text.Span("this is a red and underlined text, ").FontColor(Colors.Red.Medium).Underline(); + text.Span("and this is slightly bigger text.").FontSize(16); text.EmptyLine(); @@ -244,8 +244,8 @@ namespace QuestPDF.Examples text.Span("This text is a normal text, "); text.Span("this is a bold text, ").Bold(); - text.Span("this is a red and underlined text, ").Color(Colors.Red.Medium).Underline(); - text.Span("and this is slightly bigger text.").Size(16); + text.Span("this is a red and underlined text, ").FontColor(Colors.Red.Medium).Underline(); + text.Span("and this is slightly bigger text.").FontSize(16); text.Span("The new text element also supports injecting custom content between words: "); text.Element().PaddingBottom(-10).Height(16).Width(32).Image(Placeholders.Image); diff --git a/QuestPDF.ReportSample/Layouts/DifferentHeadersTemplate.cs b/QuestPDF.ReportSample/Layouts/DifferentHeadersTemplate.cs index a70c3bb..9b72d86 100644 --- a/QuestPDF.ReportSample/Layouts/DifferentHeadersTemplate.cs +++ b/QuestPDF.ReportSample/Layouts/DifferentHeadersTemplate.cs @@ -30,12 +30,12 @@ namespace QuestPDF.ReportSample.Layouts { column.Item().ShowOnce().Padding(5).AlignMiddle().Row(row => { - row.RelativeItem(2).AlignMiddle().Text("PRIMARY HEADER").Color(Colors.Grey.Darken3).Size(30).Bold(); + row.RelativeItem(2).AlignMiddle().Text("PRIMARY HEADER").FontColor(Colors.Grey.Darken3).FontSize(30).Bold(); row.RelativeItem(1).AlignRight().MinimalBox().AlignMiddle().Background(Colors.Blue.Darken2).Padding(30); }); column.Item().SkipOnce().Padding(5).Row(row => { - row.RelativeItem(2).Text("SECONDARY HEADER").Color(Colors.Grey.Darken3).Size(30).Bold(); + row.RelativeItem(2).Text("SECONDARY HEADER").FontColor(Colors.Grey.Darken3).FontSize(30).Bold(); row.RelativeItem(1).AlignRight().MinimalBox().Background(Colors.Blue.Lighten4).Padding(15); }); }); diff --git a/QuestPDF.UnitTests/TestEngine/TestPlan.cs b/QuestPDF.UnitTests/TestEngine/TestPlan.cs index 00136fc..1ec5fe9 100644 --- a/QuestPDF.UnitTests/TestEngine/TestPlan.cs +++ b/QuestPDF.UnitTests/TestEngine/TestPlan.cs @@ -92,7 +92,7 @@ namespace QuestPDF.UnitTests.TestEngine Assert.AreEqual(expected.Position.Y, position.Y, "Draw text: Y"); Assert.AreEqual(expected.Style.Color, style.Color, "Draw text: color"); - Assert.AreEqual(expected.Style.FontType, style.FontType, "Draw text: font"); + Assert.AreEqual(expected.Style.FontFamily, style.FontFamily, "Draw text: font"); Assert.AreEqual(expected.Style.Size, style.Size, "Draw text: size"); }, DrawImageFunc = (image, position, size) => diff --git a/QuestPDF/Drawing/FontManager.cs b/QuestPDF/Drawing/FontManager.cs index 1a51bcf..e4d2fca 100644 --- a/QuestPDF/Drawing/FontManager.cs +++ b/QuestPDF/Drawing/FontManager.cs @@ -30,7 +30,7 @@ namespace QuestPDF.Drawing } } - [Obsolete("Since version 2022.3, the FontManager class offers better font type matching support. Please use the RegisterFontType(Stream stream) overload.")] + [Obsolete("Since version 2022.3, the FontManager class offers better font type matching support. Please use the RegisterFont(Stream stream) method.")] public static void RegisterFontType(string fontName, Stream stream) { using var fontData = SKData.Create(stream); @@ -38,7 +38,7 @@ namespace QuestPDF.Drawing RegisterFontType(fontData, customName: fontName); } - public static void RegisterFontType(Stream stream) + public static void RegisterFont(Stream stream) { using var fontData = SKData.Create(stream); RegisterFontType(fontData); @@ -78,16 +78,16 @@ namespace QuestPDF.Drawing var fontStyle = new SKFontStyle(weight, SKFontStyleWidth.Normal, slant); - if (StyleSets.TryGetValue(style.FontType, out var fontStyleSet)) + if (StyleSets.TryGetValue(style.FontFamily, out var fontStyleSet)) return fontStyleSet.Match(fontStyle); - var fontFromDefaultSource = SKFontManager.Default.MatchFamily(style.FontType, fontStyle); + var fontFromDefaultSource = SKFontManager.Default.MatchFamily(style.FontFamily, fontStyle); if (fontFromDefaultSource != null) return fontFromDefaultSource; throw new ArgumentException( - $"The typeface '{style.FontType}' could not be found. " + + $"The typeface '{style.FontFamily}' could not be found. " + $"Please consider the following options: " + $"1) install the font on your operating system or execution environment. " + $"2) load a font file specifically for QuestPDF usage via the QuestPDF.Drawing.FontManager.RegisterFontType(Stream fileContentStream) static method."); diff --git a/QuestPDF/Elements/DebugArea.cs b/QuestPDF/Elements/DebugArea.cs index be9b2a5..7b20236 100644 --- a/QuestPDF/Elements/DebugArea.cs +++ b/QuestPDF/Elements/DebugArea.cs @@ -31,9 +31,9 @@ namespace QuestPDF.Elements .Background(Colors.White) .Padding(2) .Text(Text) - .Color(Color) - .FontType(Fonts.Consolas) - .Size(8); + .FontColor(Color) + .FontFamily(Fonts.Consolas) + .FontSize(8); }); } } diff --git a/QuestPDF/Elements/Placeholder.cs b/QuestPDF/Elements/Placeholder.cs index 943ecc0..b1020d0 100644 --- a/QuestPDF/Elements/Placeholder.cs +++ b/QuestPDF/Elements/Placeholder.cs @@ -26,7 +26,7 @@ namespace QuestPDF.Elements if (string.IsNullOrWhiteSpace(Text)) x.MaxHeight(32).Image(ImageData, ImageScaling.FitArea); else - x.Text(Text).Size(14); + x.Text(Text).FontSize(14); }); } } diff --git a/QuestPDF/Fluent/TextSpanDescriptorExtensions.cs b/QuestPDF/Fluent/TextSpanDescriptorExtensions.cs index f04e207..9af8e9a 100644 --- a/QuestPDF/Fluent/TextSpanDescriptorExtensions.cs +++ b/QuestPDF/Fluent/TextSpanDescriptorExtensions.cs @@ -15,7 +15,7 @@ namespace QuestPDF.Fluent return descriptor; } - public static T Color(this T descriptor, string value) where T : TextSpanDescriptor + public static T FontColor(this T descriptor, string value) where T : TextSpanDescriptor { descriptor.TextStyle.Color = value; return descriptor; @@ -27,13 +27,13 @@ namespace QuestPDF.Fluent return descriptor; } - public static T FontType(this T descriptor, string value) where T : TextSpanDescriptor + public static T FontFamily(this T descriptor, string value) where T : TextSpanDescriptor { - descriptor.TextStyle.FontType = value; + descriptor.TextStyle.FontFamily = value; return descriptor; } - public static T Size(this T descriptor, float value) where T : TextSpanDescriptor + public static T FontSize(this T descriptor, float value) where T : TextSpanDescriptor { descriptor.TextStyle.Size = value; return descriptor; diff --git a/QuestPDF/Fluent/TextStyleExtensions.cs b/QuestPDF/Fluent/TextStyleExtensions.cs index 64cae02..a748225 100644 --- a/QuestPDF/Fluent/TextStyleExtensions.cs +++ b/QuestPDF/Fluent/TextStyleExtensions.cs @@ -13,7 +13,13 @@ namespace QuestPDF.Fluent return style; } + [Obsolete("This element has been renamed since version 2022.3. Please use the FontColor method.")] public static TextStyle Color(this TextStyle style, string value) + { + return style.FontColor(value); + } + + public static TextStyle FontColor(this TextStyle style, string value) { return style.Mutate(x => x.Color = value); } @@ -23,12 +29,24 @@ namespace QuestPDF.Fluent return style.Mutate(x => x.BackgroundColor = value); } + [Obsolete("This element has been renamed since version 2022.3. Please use the FontFamily method.")] public static TextStyle FontType(this TextStyle style, string value) { - return style.Mutate(x => x.FontType = value); + return style.FontFamily(value); } + public static TextStyle FontFamily(this TextStyle style, string value) + { + return style.Mutate(x => x.FontFamily = value); + } + + [Obsolete("This element has been renamed since version 2022.3. Please use the FontSize method.")] public static TextStyle Size(this TextStyle style, float value) + { + return style.FontSize(value); + } + + public static TextStyle FontSize(this TextStyle style, float value) { return style.Mutate(x => x.Size = value); } diff --git a/QuestPDF/Infrastructure/TextStyle.cs b/QuestPDF/Infrastructure/TextStyle.cs index 13dde4e..4556648 100644 --- a/QuestPDF/Infrastructure/TextStyle.cs +++ b/QuestPDF/Infrastructure/TextStyle.cs @@ -9,7 +9,7 @@ namespace QuestPDF.Infrastructure internal string? Color { get; set; } internal string? BackgroundColor { get; set; } - internal string? FontType { get; set; } + internal string? FontFamily { get; set; } internal float? Size { get; set; } internal float? LineHeight { get; set; } internal FontWeight? FontWeight { get; set; } @@ -24,7 +24,7 @@ namespace QuestPDF.Infrastructure { Color = Colors.Black, BackgroundColor = Colors.Transparent, - FontType = Fonts.Calibri, + FontFamily = Fonts.Calibri, Size = 12, LineHeight = 1.2f, FontWeight = Infrastructure.FontWeight.Normal, @@ -43,15 +43,15 @@ namespace QuestPDF.Infrastructure HasGlobalStyleApplied = true; ApplyParentStyle(globalStyle); - PaintKey ??= (FontType, Size, FontWeight, IsItalic, Color); - FontMetricsKey ??= (FontType, Size, FontWeight, IsItalic); + PaintKey ??= (FontFamily, Size, FontWeight, IsItalic, Color); + FontMetricsKey ??= (FontFamily, Size, FontWeight, IsItalic); } internal void ApplyParentStyle(TextStyle parentStyle) { Color ??= parentStyle.Color; BackgroundColor ??= parentStyle.BackgroundColor; - FontType ??= parentStyle.FontType; + FontFamily ??= parentStyle.FontFamily; Size ??= parentStyle.Size; LineHeight ??= parentStyle.LineHeight; FontWeight ??= parentStyle.FontWeight; @@ -64,7 +64,7 @@ namespace QuestPDF.Infrastructure { Color = parentStyle.Color ?? Color; BackgroundColor = parentStyle.BackgroundColor ?? BackgroundColor; - FontType = parentStyle.FontType ?? FontType; + FontFamily = parentStyle.FontFamily ?? FontFamily; Size = parentStyle.Size ?? Size; LineHeight = parentStyle.LineHeight ?? LineHeight; FontWeight = parentStyle.FontWeight ?? FontWeight; diff --git a/QuestPDF/QuestPDF.csproj b/QuestPDF/QuestPDF.csproj index 932b8b3..0801a9b 100644 --- a/QuestPDF/QuestPDF.csproj +++ b/QuestPDF/QuestPDF.csproj @@ -4,7 +4,7 @@ MarcinZiabek CodeFlint QuestPDF - 2022.2.3 + 2022.3.0 QuestPDF is an open-source, modern and battle-tested library that can help you with generating PDF documents by offering friendly, discoverable and predictable C# fluent API. $([System.IO.File]::ReadAllText("$(MSBuildProjectDirectory)/Resources/ReleaseNotes.txt")) 9 From 2c14f91ebe9db8c2ab4dc96f2eaee763c0f90705 Mon Sep 17 00:00:00 2001 From: MarcinZiabek Date: Fri, 11 Mar 2022 18:04:30 +0100 Subject: [PATCH 11/12] Added support for background and foreground in page API --- QuestPDF.Examples/PageBackgroundForeground.cs | 57 ++++++++++++++ QuestPDF/Elements/Page.cs | 78 ++++++++++++------- QuestPDF/Fluent/PageExtensions.cs | 14 ++++ 3 files changed, 119 insertions(+), 30 deletions(-) create mode 100644 QuestPDF.Examples/PageBackgroundForeground.cs diff --git a/QuestPDF.Examples/PageBackgroundForeground.cs b/QuestPDF.Examples/PageBackgroundForeground.cs new file mode 100644 index 0000000..ac36a67 --- /dev/null +++ b/QuestPDF.Examples/PageBackgroundForeground.cs @@ -0,0 +1,57 @@ +using System; +using System.Linq; +using NUnit.Framework; +using QuestPDF.Examples.Engine; +using QuestPDF.Fluent; +using QuestPDF.Helpers; +using QuestPDF.Infrastructure; + +namespace QuestPDF.Examples +{ + public class PageBackgroundForeground + { + [Test] + public void Frame() + { + RenderingTest + .Create() + .PageSize(550, 400) + .ProducePdf() + .ShowResults() + .RenderDocument(document => + { + document.Page(page => + { + page.Size(PageSizes.A4); + page.Margin(1, Unit.Inch); + page.DefaultTextStyle(TextStyle.Default.FontSize(16)); + + page.Foreground() + .AlignMiddle() + .AlignCenter() + .Text("Watermark") + .FontSize(64) + .FontColor(Colors.Blue.Lighten4); + + page.Header().Text("Background and foreground").Bold().FontColor(Colors.Blue.Medium).FontSize(24); + + page.Content().PaddingVertical(25).Column(column => + { + column.Spacing(25); + + foreach (var i in Enumerable.Range(0, 100)) + column.Item().Background(Colors.Grey.Lighten2).Height(75); + }); + + page.Footer() + .AlignCenter() + .Text(x => + { + x.Span("Page "); + x.CurrentPageNumber(); + }); + }); + }); + } + } +} \ No newline at end of file diff --git a/QuestPDF/Elements/Page.cs b/QuestPDF/Elements/Page.cs index 7d06bbe..be8e678 100644 --- a/QuestPDF/Elements/Page.cs +++ b/QuestPDF/Elements/Page.cs @@ -20,6 +20,9 @@ namespace QuestPDF.Elements public string BackgroundColor { get; set; } = Colors.Transparent; + public Element Background { get; set; } = Empty.Instance; + public Element Foreground { get; set; } = Empty.Instance; + public Element Header { get; set; } = Empty.Instance; public Element Content { get; set; } = Empty.Instance; public Element Footer { get; set; } = Empty.Instance; @@ -27,39 +30,54 @@ namespace QuestPDF.Elements public void Compose(IContainer container) { container - .MinWidth(MinSize.Width) - .MinHeight(MinSize.Height) - - .MaxWidth(MaxSize.Width) - .MaxHeight(MaxSize.Height) - - .Background(BackgroundColor) - - .PaddingLeft(MarginLeft) - .PaddingRight(MarginRight) - .PaddingTop(MarginTop) - .PaddingBottom(MarginBottom) - - .DefaultTextStyle(DefaultTextStyle) - - .Decoration(decoration => + .Layers(layers => { - decoration - .Before() - .DebugPointer("Page header") - .Element(Header); + layers + .Layer() + .DebugPointer("Page background layer") + .Element(Background); - decoration - .Content() - .Element(x => IsClose(MinSize.Width, MaxSize.Width) ? x.ExtendHorizontal() : x) - .Element(x => IsClose(MinSize.Height, MaxSize.Height) ? x.ExtendVertical() : x) - .DebugPointer("Page content") - .Element(Content); + layers + .PrimaryLayer() + .MinWidth(MinSize.Width) + .MinHeight(MinSize.Height) + + .MaxWidth(MaxSize.Width) + .MaxHeight(MaxSize.Height) + + .Background(BackgroundColor) + + .PaddingLeft(MarginLeft) + .PaddingRight(MarginRight) + .PaddingTop(MarginTop) + .PaddingBottom(MarginBottom) + + .DefaultTextStyle(DefaultTextStyle) + + .Decoration(decoration => + { + decoration + .Before() + .DebugPointer("Page header") + .Element(Header); + + decoration + .Content() + .Element(x => IsClose(MinSize.Width, MaxSize.Width) ? x.ExtendHorizontal() : x) + .Element(x => IsClose(MinSize.Height, MaxSize.Height) ? x.ExtendVertical() : x) + .DebugPointer("Page content") + .Element(Content); + + decoration + .After() + .DebugPointer("Page footer") + .Element(Footer); + }); - decoration - .After() - .DebugPointer("Page footer") - .Element(Footer); + layers + .Layer() + .DebugPointer("Page foreground layer") + .Element(Foreground); }); bool IsClose(float x, float y) diff --git a/QuestPDF/Fluent/PageExtensions.cs b/QuestPDF/Fluent/PageExtensions.cs index ab968ee..c336160 100644 --- a/QuestPDF/Fluent/PageExtensions.cs +++ b/QuestPDF/Fluent/PageExtensions.cs @@ -88,6 +88,20 @@ namespace QuestPDF.Fluent Page.BackgroundColor = color; } + public IContainer Background() + { + var container = new Container(); + Page.Background = container; + return container; + } + + public IContainer Foreground() + { + var container = new Container(); + Page.Foreground = container; + return container; + } + public IContainer Header() { var container = new Container(); From 1b9b1645833da1a6b8e14076555464e559b31604 Mon Sep 17 00:00:00 2001 From: MarcinZiabek Date: Sat, 12 Mar 2022 16:11:03 +0100 Subject: [PATCH 12/12] DefaultTextStyle API improvements --- QuestPDF.Examples/PageBackgroundForeground.cs | 12 ++---------- QuestPDF/Fluent/ElementExtensions.cs | 10 +++++++++- QuestPDF/Fluent/PageExtensions.cs | 5 +++++ 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/QuestPDF.Examples/PageBackgroundForeground.cs b/QuestPDF.Examples/PageBackgroundForeground.cs index ac36a67..23a3fd0 100644 --- a/QuestPDF.Examples/PageBackgroundForeground.cs +++ b/QuestPDF.Examples/PageBackgroundForeground.cs @@ -11,7 +11,7 @@ namespace QuestPDF.Examples public class PageBackgroundForeground { [Test] - public void Frame() + public void Test() { RenderingTest .Create() @@ -31,7 +31,7 @@ namespace QuestPDF.Examples .AlignCenter() .Text("Watermark") .FontSize(64) - .FontColor(Colors.Blue.Lighten4); + .FontColor(Colors.Blue.Lighten3); page.Header().Text("Background and foreground").Bold().FontColor(Colors.Blue.Medium).FontSize(24); @@ -42,14 +42,6 @@ namespace QuestPDF.Examples foreach (var i in Enumerable.Range(0, 100)) column.Item().Background(Colors.Grey.Lighten2).Height(75); }); - - page.Footer() - .AlignCenter() - .Text(x => - { - x.Span("Page "); - x.CurrentPageNumber(); - }); }); }); } diff --git a/QuestPDF/Fluent/ElementExtensions.cs b/QuestPDF/Fluent/ElementExtensions.cs index a85a2e4..804dc84 100644 --- a/QuestPDF/Fluent/ElementExtensions.cs +++ b/QuestPDF/Fluent/ElementExtensions.cs @@ -177,7 +177,15 @@ namespace QuestPDF.Fluent TextStyle = textStyle }); } - + + public static IContainer DefaultTextStyle(this IContainer element, Func handler) + { + return element.Element(new DefaultTextStyle + { + TextStyle = handler(TextStyle.Default) + }); + } + public static IContainer StopPaging(this IContainer element) { return element.Element(new StopPaging()); diff --git a/QuestPDF/Fluent/PageExtensions.cs b/QuestPDF/Fluent/PageExtensions.cs index c336160..e043ed3 100644 --- a/QuestPDF/Fluent/PageExtensions.cs +++ b/QuestPDF/Fluent/PageExtensions.cs @@ -83,6 +83,11 @@ namespace QuestPDF.Fluent Page.DefaultTextStyle = textStyle; } + public void DefaultTextStyle(Func handler) + { + DefaultTextStyle(handler(TextStyle.Default)); + } + public void Background(string color) { Page.BackgroundColor = color;