From 4938e97e6d630f96c40e2bd46bde3e4884a605e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Zi=C4=85bek?= Date: Fri, 1 Oct 2021 14:09:25 +0200 Subject: [PATCH 01/21] Update readme.md --- readme.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/readme.md b/readme.md index 1b6effb..457fe44 100644 --- a/readme.md +++ b/readme.md @@ -37,8 +37,11 @@ The library is available as a nuget package. You can install it as any other nug ## 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. + **[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 reusable code that is easy to maintain. ## Example invoice From caa779e1e0d8b901d754a80f755e03260d2e13f7 Mon Sep 17 00:00:00 2001 From: jcl-aadlab Date: Tue, 5 Oct 2021 10:47:22 +0200 Subject: [PATCH 02/21] Added skip element for headers and footers --- .../Layouts/DifferentHeadersTemplate.cs | 85 +++++++++++++++++++ QuestPDF/Elements/SkipOnce.cs | 34 ++++++++ QuestPDF/Fluent/ElementExtensions.cs | 7 +- 3 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 QuestPDF.ReportSample/Layouts/DifferentHeadersTemplate.cs create mode 100644 QuestPDF/Elements/SkipOnce.cs diff --git a/QuestPDF.ReportSample/Layouts/DifferentHeadersTemplate.cs b/QuestPDF.ReportSample/Layouts/DifferentHeadersTemplate.cs new file mode 100644 index 0000000..2b3898f --- /dev/null +++ b/QuestPDF.ReportSample/Layouts/DifferentHeadersTemplate.cs @@ -0,0 +1,85 @@ +using QuestPDF.Drawing; +using QuestPDF.Fluent; +using QuestPDF.Helpers; +using QuestPDF.Infrastructure; + +namespace QuestPDF.ReportSample.Layouts +{ + public class DifferentHeadersTemplate : IDocument + { + public DocumentMetadata GetMetadata() => DocumentMetadata.Default; + + public void Compose(IDocumentContainer container) + { + container + .Page(page => + { + page.Margin(40); + + page.Size(PageSizes.A4); + + page.Header().Element(ComposeHeader); + page.Content().Element(ComposeContent); + page.Footer().Element(ComposeFooter); + }); + } + + private void ComposeHeader(IContainer container) + { + container.Background(Colors.Grey.Lighten3).Border(1).Stack(stack => + { + stack.Item().ShowOnce().Padding(5).AlignMiddle().Row(row => + { + row.RelativeColumn(2).AlignMiddle().Text("PRIMARY HEADER", TextStyle.Default.Color(Colors.Grey.Darken3).Size(30).Bold()); + row.RelativeColumn(1).AlignRight().Box().AlignMiddle().Background(Colors.Blue.Darken2).Padding(30); + }); + stack.Item().SkipOnce().Padding(5).Row(row => + { + row.RelativeColumn(2).Text("SECONDARY HEADER", TextStyle.Default.Color(Colors.Grey.Darken3).Size(30).Bold()); + row.RelativeColumn(1).AlignRight().Box().Background(Colors.Blue.Lighten4).Padding(15); + }); + }); + } + + private void ComposeContent(IContainer container) + { + container.Stack(stack => + { + stack.Item().PaddingVertical(80).Text("First"); + stack.Item().PageBreak(); + stack.Item().PaddingVertical(80).Text("Second"); + stack.Item().PageBreak(); + stack.Item().PaddingVertical(80).Text("Third"); + stack.Item().PageBreak(); + }); + } + + private void ComposeFooter(IContainer container) + { + container.Background(Colors.Grey.Lighten3).Stack(stack => + { + stack.Item().ShowOnce().Background(Colors.Grey.Lighten3).Row(row => + { + row.RelativeColumn().Text(x => + { + x.CurrentPageNumber(); + x.Span(" / "); + x.TotalPages(); + }); + row.RelativeColumn().AlignRight().Text("Footer for header"); + }); + + stack.Item().SkipOnce().Background(Colors.Grey.Lighten3).Row(row => + { + row.RelativeColumn().Text(x => + { + x.CurrentPageNumber(); + x.Span(" / "); + x.TotalPages(); + }); + row.RelativeColumn().AlignRight().Text("Footer for every page except header"); + }); + }); + } + } +} \ No newline at end of file diff --git a/QuestPDF/Elements/SkipOnce.cs b/QuestPDF/Elements/SkipOnce.cs new file mode 100644 index 0000000..cf5ee5a --- /dev/null +++ b/QuestPDF/Elements/SkipOnce.cs @@ -0,0 +1,34 @@ +using QuestPDF.Drawing.SpacePlan; +using QuestPDF.Infrastructure; + +namespace QuestPDF.Elements +{ + internal class SkipOnce : ContainerElement, IStateResettable + { + private bool firstPageWasSkiped; + + public void ResetState() + { + firstPageWasSkiped = false; + } + + internal override ISpacePlan Measure(Size availableSpace) + { + if (Child == null || !firstPageWasSkiped) + return new FullRender(Size.Zero); + + return Child.Measure(availableSpace); + } + + internal override void Draw(Size availableSpace) + { + if (Child == null) + return; + + if (firstPageWasSkiped) + Child.Draw(availableSpace); + + firstPageWasSkiped = true; + } + } +} \ No newline at end of file diff --git a/QuestPDF/Fluent/ElementExtensions.cs b/QuestPDF/Fluent/ElementExtensions.cs index f41d050..895cf6e 100644 --- a/QuestPDF/Fluent/ElementExtensions.cs +++ b/QuestPDF/Fluent/ElementExtensions.cs @@ -70,7 +70,12 @@ namespace QuestPDF.Fluent { return element.Element(new ShowOnce()); } - + + public static IContainer SkipOnce(this IContainer element) + { + return element.Element(new SkipOnce()); + } + public static IContainer ShowEntire(this IContainer element) { return element.Element(new ShowEntire()); From e379502a64cab1ede91bfcbc1a0269c8737fafd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Zi=C4=85bek?= Date: Thu, 7 Oct 2021 22:38:41 +0200 Subject: [PATCH 03/21] Implemented prototype of global, document-wide text style --- .../Layouts/StandardReport.cs | 3 +- QuestPDF.ReportSample/Typography.cs | 2 +- QuestPDF/Drawing/DocumentContainer.cs | 1 + QuestPDF/Drawing/DocumentGenerator.cs | 21 ++++++ QuestPDF/Drawing/FontManager.cs | 10 +-- .../Elements/Text/Items/ITextBlockItem.cs | 1 + .../Text/Items/TextBlockExternalLink.cs | 21 ++---- .../Text/Items/TextBlockInternalLink.cs | 21 ++---- .../Text/Items/TextBlockPageNumber.cs | 23 +++---- QuestPDF/Elements/Text/Items/TextBlockSpan.cs | 16 ++--- QuestPDF/Fluent/PageExtensions.cs | 6 ++ QuestPDF/Infrastructure/TextStyle.cs | 69 ++++++++++++++----- 12 files changed, 116 insertions(+), 78 deletions(-) diff --git a/QuestPDF.ReportSample/Layouts/StandardReport.cs b/QuestPDF.ReportSample/Layouts/StandardReport.cs index c130b82..f31ee26 100644 --- a/QuestPDF.ReportSample/Layouts/StandardReport.cs +++ b/QuestPDF.ReportSample/Layouts/StandardReport.cs @@ -25,6 +25,7 @@ namespace QuestPDF.ReportSample.Layouts public void Compose(IDocumentContainer container) { container + .DefaultTextStyle(Typography.Normal) .Page(page => { page.MarginVertical(40); @@ -37,8 +38,6 @@ namespace QuestPDF.ReportSample.Layouts page.Footer().AlignCenter().Text(text => { - text.DefaultTextStyle(Typography.Normal); - text.CurrentPageNumber(); text.Span(" / "); text.TotalPages(); diff --git a/QuestPDF.ReportSample/Typography.cs b/QuestPDF.ReportSample/Typography.cs index 1d8992c..434bf37 100644 --- a/QuestPDF.ReportSample/Typography.cs +++ b/QuestPDF.ReportSample/Typography.cs @@ -8,6 +8,6 @@ namespace QuestPDF.ReportSample { public static TextStyle Title => TextStyle.Default.FontType(Fonts.Calibri).Color(Colors.Blue.Darken3).Size(26).Black(); public static TextStyle Headline => TextStyle.Default.FontType(Fonts.Calibri).Color(Colors.Blue.Medium).Size(16).SemiBold(); - public static TextStyle Normal => TextStyle.Default.FontType(Fonts.Calibri).Color(Colors.Black).Size(11).LineHeight(1.1f); + public static TextStyle Normal => TextStyle.Default.FontType(Fonts.Verdana).Color(Colors.Black).Size(10).LineHeight(1.2f); } } \ No newline at end of file diff --git a/QuestPDF/Drawing/DocumentContainer.cs b/QuestPDF/Drawing/DocumentContainer.cs index e813839..9f593e2 100644 --- a/QuestPDF/Drawing/DocumentContainer.cs +++ b/QuestPDF/Drawing/DocumentContainer.cs @@ -9,6 +9,7 @@ namespace QuestPDF.Drawing { internal class DocumentContainer : IDocumentContainer { + internal TextStyle DefaultTextStyle { get; set; } = TextStyle.Default; internal List Pages { get; set; } = new List(); internal Container Compose() diff --git a/QuestPDF/Drawing/DocumentGenerator.cs b/QuestPDF/Drawing/DocumentGenerator.cs index f7fbb59..8b53173 100644 --- a/QuestPDF/Drawing/DocumentGenerator.cs +++ b/QuestPDF/Drawing/DocumentGenerator.cs @@ -1,9 +1,12 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using QuestPDF.Drawing.Exceptions; using QuestPDF.Drawing.SpacePlan; using QuestPDF.Elements; +using QuestPDF.Elements.Text; +using QuestPDF.Elements.Text.Items; using QuestPDF.Infrastructure; namespace QuestPDF.Drawing @@ -36,6 +39,7 @@ namespace QuestPDF.Drawing var metadata = document.GetMetadata(); var pageContext = new PageContext(); + ApplyDefaultTextStyle(content, container.DefaultTextStyle); RenderPass(pageContext, new FreeCanvas(), content, metadata); RenderPass(pageContext, canvas, content, metadata); } @@ -93,5 +97,22 @@ namespace QuestPDF.Drawing throw new DocumentLayoutException("Composed layout generates infinite document."); } } + + private static void ApplyDefaultTextStyle(Container content, TextStyle documentDefaultTextStyle) + { + documentDefaultTextStyle.ApplyGlobalStyle(TextStyle.LibraryDefault); + + content.HandleVisitor(element => + { + var text = element as TextBlock; + + if (text == null) + return; + + foreach (var child in text.Children) + if (child is TextBlockSpan span) + span.Style.ApplyGlobalStyle(documentDefaultTextStyle); + }); + } } } \ No newline at end of file diff --git a/QuestPDF/Drawing/FontManager.cs b/QuestPDF/Drawing/FontManager.cs index 319eec7..19d8f5d 100644 --- a/QuestPDF/Drawing/FontManager.cs +++ b/QuestPDF/Drawing/FontManager.cs @@ -34,7 +34,7 @@ namespace QuestPDF.Drawing internal static SKPaint ToPaint(this TextStyle style) { - return Paints.GetOrAdd(style.ToString(), key => Convert(style)); + return Paints.GetOrAdd(style.Key, key => Convert(style)); static SKPaint Convert(TextStyle style) { @@ -42,7 +42,7 @@ namespace QuestPDF.Drawing { Color = SKColor.Parse(style.Color), Typeface = GetTypeface(style), - TextSize = style.Size, + TextSize = (style.Size ?? 12), TextEncoding = SKTextEncoding.Utf32 }; } @@ -52,16 +52,16 @@ namespace QuestPDF.Drawing if (Typefaces.TryGetValue(style.FontType, out var result)) return result; - var slant = style.IsItalic ? SKFontStyleSlant.Italic : SKFontStyleSlant.Upright; + var slant = (style.IsItalic ?? false) ? SKFontStyleSlant.Italic : SKFontStyleSlant.Upright; - return SKTypeface.FromFamilyName(style.FontType, (int)style.FontWeight, (int)SKFontStyleWidth.Normal, slant) + 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."); } } internal static SKFontMetrics ToFontMetrics(this TextStyle style) { - return FontMetrics.GetOrAdd(style.ToString(), key => style.ToPaint().FontMetrics); + return FontMetrics.GetOrAdd(style.Key, key => style.ToPaint().FontMetrics); } } } \ No newline at end of file diff --git a/QuestPDF/Elements/Text/Items/ITextBlockItem.cs b/QuestPDF/Elements/Text/Items/ITextBlockItem.cs index 581dcc9..570087b 100644 --- a/QuestPDF/Elements/Text/Items/ITextBlockItem.cs +++ b/QuestPDF/Elements/Text/Items/ITextBlockItem.cs @@ -1,4 +1,5 @@ using QuestPDF.Elements.Text.Calculation; +using QuestPDF.Infrastructure; namespace QuestPDF.Elements.Text.Items { diff --git a/QuestPDF/Elements/Text/Items/TextBlockExternalLink.cs b/QuestPDF/Elements/Text/Items/TextBlockExternalLink.cs index 9eac125..5337fca 100644 --- a/QuestPDF/Elements/Text/Items/TextBlockExternalLink.cs +++ b/QuestPDF/Elements/Text/Items/TextBlockExternalLink.cs @@ -3,33 +3,22 @@ using QuestPDF.Infrastructure; namespace QuestPDF.Elements.Text.Items { - internal class TextBlockExternalLink : ITextBlockItem + internal class TextBlockExternalLink : TextBlockSpan { - public TextStyle Style { get; set; } = new TextStyle(); - public string Text { get; set; } public string Url { get; set; } - public TextMeasurementResult? Measure(TextMeasurementRequest request) + public override TextMeasurementResult? Measure(TextMeasurementRequest request) { - return GetItem().MeasureWithoutCache(request); + return MeasureWithoutCache(request); } - public void Draw(TextDrawingRequest request) + 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.Translate(new Position(0, -request.TotalAscent)); - GetItem().Draw(request); - } - - private TextBlockSpan GetItem() - { - return new TextBlockSpan - { - Style = Style, - Text = Text - }; + base.Draw(request); } } } \ No newline at end of file diff --git a/QuestPDF/Elements/Text/Items/TextBlockInternalLink.cs b/QuestPDF/Elements/Text/Items/TextBlockInternalLink.cs index 914cd61..6b28113 100644 --- a/QuestPDF/Elements/Text/Items/TextBlockInternalLink.cs +++ b/QuestPDF/Elements/Text/Items/TextBlockInternalLink.cs @@ -3,33 +3,22 @@ using QuestPDF.Infrastructure; namespace QuestPDF.Elements.Text.Items { - internal class TextBlockInternalLink : ITextBlockItem + internal class TextBlockInternalLink : TextBlockSpan { - public TextStyle Style { get; set; } = new TextStyle(); - public string Text { get; set; } public string LocationName { get; set; } - public TextMeasurementResult? Measure(TextMeasurementRequest request) + public override TextMeasurementResult? Measure(TextMeasurementRequest request) { - return GetItem().MeasureWithoutCache(request); + return MeasureWithoutCache(request); } - public void Draw(TextDrawingRequest request) + 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.Translate(new Position(0, -request.TotalAscent)); - GetItem().Draw(request); - } - - private TextBlockSpan GetItem() - { - return new TextBlockSpan - { - Style = Style, - Text = Text - }; + base.Draw(request); } } } \ No newline at end of file diff --git a/QuestPDF/Elements/Text/Items/TextBlockPageNumber.cs b/QuestPDF/Elements/Text/Items/TextBlockPageNumber.cs index fda65ac..85a829d 100644 --- a/QuestPDF/Elements/Text/Items/TextBlockPageNumber.cs +++ b/QuestPDF/Elements/Text/Items/TextBlockPageNumber.cs @@ -3,34 +3,31 @@ using QuestPDF.Infrastructure; namespace QuestPDF.Elements.Text.Items { - internal class TextBlockPageNumber : ITextBlockItem + internal class TextBlockPageNumber : TextBlockSpan { - public TextStyle Style { get; set; } = new TextStyle(); public string SlotName { get; set; } - public TextMeasurementResult? Measure(TextMeasurementRequest request) + public override TextMeasurementResult? Measure(TextMeasurementRequest request) { - return GetItem(request.PageContext).MeasureWithoutCache(request); + SetPageNumber(request.PageContext); + return MeasureWithoutCache(request); } - public void Draw(TextDrawingRequest request) + public override void Draw(TextDrawingRequest request) { - GetItem(request.PageContext).Draw(request); + SetPageNumber(request.PageContext); + base.Draw(request); } - private TextBlockSpan GetItem(IPageContext context) + private void SetPageNumber(IPageContext context) { var pageNumberPlaceholder = 123; var pageNumber = context.GetRegisteredLocations().Contains(SlotName) ? context.GetLocationPage(SlotName) : pageNumberPlaceholder; - - return new TextBlockSpan - { - Style = Style, - Text = pageNumber.ToString() - }; + + Text = pageNumber.ToString(); } } } \ No newline at end of file diff --git a/QuestPDF/Elements/Text/Items/TextBlockSpan.cs b/QuestPDF/Elements/Text/Items/TextBlockSpan.cs index 2c0e4b2..a8d0436 100644 --- a/QuestPDF/Elements/Text/Items/TextBlockSpan.cs +++ b/QuestPDF/Elements/Text/Items/TextBlockSpan.cs @@ -14,7 +14,7 @@ namespace QuestPDF.Elements.Text.Items private Dictionary<(int startIndex, float availableWidth), TextMeasurementResult?> MeasureCache = new Dictionary<(int startIndex, float availableWidth), TextMeasurementResult?>(); - public TextMeasurementResult? Measure(TextMeasurementRequest request) + public virtual TextMeasurementResult? Measure(TextMeasurementRequest request) { var cacheKey = (request.StartIndex, request.AvailableWidth); @@ -45,7 +45,7 @@ namespace QuestPDF.Elements.Text.Items { Width = 0, - LineHeight = Style.LineHeight, + LineHeight = Style.LineHeight ?? 1, Ascent = fontMetrics.Ascent, Descent = fontMetrics.Descent }; @@ -96,7 +96,7 @@ namespace QuestPDF.Elements.Text.Items Ascent = fontMetrics.Ascent, Descent = fontMetrics.Descent, - LineHeight = Style.LineHeight, + LineHeight = Style.LineHeight ?? 1, StartIndex = startIndex, EndIndex = endIndex, @@ -105,7 +105,7 @@ namespace QuestPDF.Elements.Text.Items }; } - public void Draw(TextDrawingRequest request) + public virtual void Draw(TextDrawingRequest request) { var fontMetrics = Style.ToFontMetrics(); @@ -115,12 +115,12 @@ namespace QuestPDF.Elements.Text.Items request.Canvas.DrawText(text, Position.Zero, Style); // draw underline - if (Style.HasUnderline && fontMetrics.UnderlinePosition.HasValue) - DrawLine(fontMetrics.UnderlinePosition.Value, fontMetrics.UnderlineThickness.Value); + if ((Style.HasUnderline ?? false) && fontMetrics.UnderlinePosition.HasValue) + DrawLine(fontMetrics.UnderlinePosition.Value, fontMetrics.UnderlineThickness ?? 1); // draw stroke - if (Style.HasStrikethrough && fontMetrics.StrikeoutPosition.HasValue) - DrawLine(fontMetrics.StrikeoutPosition.Value, fontMetrics.StrikeoutThickness.Value); + if ((Style.HasStrikethrough ?? false) && fontMetrics.StrikeoutPosition.HasValue) + DrawLine(fontMetrics.StrikeoutPosition.Value, fontMetrics.StrikeoutThickness ?? 1); void DrawLine(float offset, float thickness) { diff --git a/QuestPDF/Fluent/PageExtensions.cs b/QuestPDF/Fluent/PageExtensions.cs index 24a35a8..f2c530c 100644 --- a/QuestPDF/Fluent/PageExtensions.cs +++ b/QuestPDF/Fluent/PageExtensions.cs @@ -94,6 +94,12 @@ namespace QuestPDF.Fluent public static class PageExtensions { + public static IDocumentContainer DefaultTextStyle(this IDocumentContainer document, TextStyle textStyle) + { + (document as DocumentContainer).DefaultTextStyle = textStyle; + return document; + } + public static IDocumentContainer Page(this IDocumentContainer document, Action handler) { var descriptor = new PageDescriptor(); diff --git a/QuestPDF/Infrastructure/TextStyle.cs b/QuestPDF/Infrastructure/TextStyle.cs index 76bbd99..512e1af 100644 --- a/QuestPDF/Infrastructure/TextStyle.cs +++ b/QuestPDF/Infrastructure/TextStyle.cs @@ -4,26 +4,61 @@ namespace QuestPDF.Infrastructure { public class TextStyle { - internal string Color { get; set; } = Colors.Black; - internal string BackgroundColor { get; set; } = Colors.Transparent; - internal string FontType { get; set; } = "Calibri"; - internal float Size { get; set; } = 12; - internal float LineHeight { get; set; } = 1.2f; - internal FontWeight FontWeight { get; set; } = FontWeight.Normal; - internal bool IsItalic { get; set; } = false; - internal bool HasStrikethrough { get; set; } = false; - internal bool HasUnderline { get; set; } = false; - - public static TextStyle Default => new TextStyle(); - - private string? KeyCache { get; set; } + internal bool HasGlobalStyleApplied { get; private set; } - public override string ToString() + internal string? Color { get; set; } + internal string? BackgroundColor { get; set; } + internal string? FontType { get; set; } + internal float? Size { get; set; } + internal float? LineHeight { get; set; } + internal FontWeight? FontWeight { get; set; } + internal bool? IsItalic { get; set; } + internal bool? HasStrikethrough { get; set; } + internal bool? HasUnderline { get; set; } + + internal string? Key { get; private set; } + + internal static TextStyle LibraryDefault => new TextStyle { - KeyCache ??= $"{Color}|{BackgroundColor}|{FontType}|{Size}|{LineHeight}|{FontWeight}|{IsItalic}|{HasStrikethrough}|{HasUnderline}"; - return KeyCache; + Color = Colors.Black, + BackgroundColor = Colors.Transparent, + FontType = Fonts.Calibri, + Size = 12, + LineHeight = 1.2f, + FontWeight = Infrastructure.FontWeight.Normal, + IsItalic = false, + HasStrikethrough = false, + HasUnderline = false + }; + + private static TextStyle DefaultTextStyleCache = new TextStyle(); + public static TextStyle Default => DefaultTextStyleCache; + + internal void ApplyGlobalStyle(TextStyle global) + { + if (HasGlobalStyleApplied) + return; + + HasGlobalStyleApplied = true; + + Color ??= global.Color; + BackgroundColor ??= global.BackgroundColor; + FontType ??= global.FontType; + Size ??= global.Size; + LineHeight ??= global.LineHeight; + FontWeight ??= global.FontWeight; + IsItalic ??= global.IsItalic; + HasStrikethrough ??= global.HasStrikethrough; + HasUnderline ??= global.HasUnderline; + + Key ??= $"{Color}|{BackgroundColor}|{FontType}|{Size}|{LineHeight}|{FontWeight}|{IsItalic}|{HasStrikethrough}|{HasUnderline}"; } - internal TextStyle Clone() => (TextStyle)MemberwiseClone(); + internal TextStyle Clone() + { + var clone = (TextStyle)MemberwiseClone(); + clone.HasGlobalStyleApplied = false; + return clone; + } } } \ No newline at end of file From c2ba2addfc27086ebdcc3668b4f2e55db98ae504 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Zi=C4=85bek?= Date: Thu, 7 Oct 2021 22:56:51 +0200 Subject: [PATCH 04/21] Fixed handling global text style when setting block-wide style --- QuestPDF/Drawing/DocumentGenerator.cs | 2 +- QuestPDF/Fluent/TextExtensions.cs | 23 +++++++++++++---------- QuestPDF/Infrastructure/TextStyle.cs | 25 +++++++++++++++---------- 3 files changed, 29 insertions(+), 21 deletions(-) diff --git a/QuestPDF/Drawing/DocumentGenerator.cs b/QuestPDF/Drawing/DocumentGenerator.cs index 8b53173..9b0d83b 100644 --- a/QuestPDF/Drawing/DocumentGenerator.cs +++ b/QuestPDF/Drawing/DocumentGenerator.cs @@ -98,7 +98,7 @@ namespace QuestPDF.Drawing } } - private static void ApplyDefaultTextStyle(Container content, TextStyle documentDefaultTextStyle) + private static void ApplyDefaultTextStyle(Element content, TextStyle documentDefaultTextStyle) { documentDefaultTextStyle.ApplyGlobalStyle(TextStyle.LibraryDefault); diff --git a/QuestPDF/Fluent/TextExtensions.cs b/QuestPDF/Fluent/TextExtensions.cs index dd8eb3c..71036ab 100644 --- a/QuestPDF/Fluent/TextExtensions.cs +++ b/QuestPDF/Fluent/TextExtensions.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using QuestPDF.Drawing; using QuestPDF.Elements; using QuestPDF.Elements.Text; using QuestPDF.Elements.Text.Items; @@ -13,7 +14,7 @@ namespace QuestPDF.Fluent { private ICollection TextBlocks { get; } = new List(); private TextStyle DefaultStyle { get; set; } = TextStyle.Default; - private HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left; + internal HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left; private float Spacing { get; set; } = 0f; public void DefaultTextStyle(TextStyle style) @@ -51,7 +52,7 @@ namespace QuestPDF.Fluent public void Span(string text, TextStyle? style = null) { - style ??= DefaultStyle; + style ??= TextStyle.Default; var items = text .Replace("\r", string.Empty) @@ -92,7 +93,7 @@ namespace QuestPDF.Fluent private void PageNumber(string slotName, TextStyle? style = null) { - style ??= DefaultStyle; + style ??= TextStyle.Default; AddItemToLastTextBlock(new TextBlockPageNumber() { @@ -118,7 +119,7 @@ namespace QuestPDF.Fluent public void InternalLocation(string text, string locationName, TextStyle? style = null) { - style ??= DefaultStyle; + style ??= TextStyle.Default; AddItemToLastTextBlock(new TextBlockInternalLink { @@ -130,7 +131,7 @@ namespace QuestPDF.Fluent public void ExternalLocation(string text, string url, TextStyle? style = null) { - style ??= DefaultStyle; + style ??= TextStyle.Default; AddItemToLastTextBlock(new TextBlockExternalLink { @@ -156,6 +157,9 @@ namespace QuestPDF.Fluent { TextBlocks.ToList().ForEach(x => x.Alignment = Alignment); + foreach (var textBlockSpan in TextBlocks.SelectMany(x => x.Children).Where(x => x is TextBlockSpan).Cast()) + textBlockSpan.Style.ApplyParentStyle(DefaultStyle); + container.Stack(stack => { stack.Spacing(Spacing); @@ -170,12 +174,11 @@ namespace QuestPDF.Fluent { public static void Text(this IContainer element, Action content) { - var textBlock = new TextBlock(); - - if (element is Alignment alignment) - textBlock.Alignment = alignment.Horizontal; - var descriptor = new TextDescriptor(); + + if (element is Alignment alignment) + descriptor.Alignment = alignment.Horizontal; + content?.Invoke(descriptor); descriptor.Compose(element); } diff --git a/QuestPDF/Infrastructure/TextStyle.cs b/QuestPDF/Infrastructure/TextStyle.cs index 512e1af..ee8aa5d 100644 --- a/QuestPDF/Infrastructure/TextStyle.cs +++ b/QuestPDF/Infrastructure/TextStyle.cs @@ -34,25 +34,30 @@ namespace QuestPDF.Infrastructure private static TextStyle DefaultTextStyleCache = new TextStyle(); public static TextStyle Default => DefaultTextStyleCache; - internal void ApplyGlobalStyle(TextStyle global) + internal void ApplyGlobalStyle(TextStyle globalStyle) { if (HasGlobalStyleApplied) return; HasGlobalStyleApplied = true; - Color ??= global.Color; - BackgroundColor ??= global.BackgroundColor; - FontType ??= global.FontType; - Size ??= global.Size; - LineHeight ??= global.LineHeight; - FontWeight ??= global.FontWeight; - IsItalic ??= global.IsItalic; - HasStrikethrough ??= global.HasStrikethrough; - HasUnderline ??= global.HasUnderline; + ApplyParentStyle(globalStyle); Key ??= $"{Color}|{BackgroundColor}|{FontType}|{Size}|{LineHeight}|{FontWeight}|{IsItalic}|{HasStrikethrough}|{HasUnderline}"; } + + internal void ApplyParentStyle(TextStyle parentStyle) + { + Color ??= parentStyle.Color; + BackgroundColor ??= parentStyle.BackgroundColor; + FontType ??= parentStyle.FontType; + Size ??= parentStyle.Size; + LineHeight ??= parentStyle.LineHeight; + FontWeight ??= parentStyle.FontWeight; + IsItalic ??= parentStyle.IsItalic; + HasStrikethrough ??= parentStyle.HasStrikethrough; + HasUnderline ??= parentStyle.HasUnderline; + } internal TextStyle Clone() { From e8b7f38187506dd9b1dbb3ddb068add0d39557bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Zi=C4=85bek?= Date: Thu, 7 Oct 2021 23:10:12 +0200 Subject: [PATCH 05/21] Fixed setting text style in the text-block-elements --- QuestPDF/Drawing/DocumentGenerator.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/QuestPDF/Drawing/DocumentGenerator.cs b/QuestPDF/Drawing/DocumentGenerator.cs index 9b0d83b..364c98c 100644 --- a/QuestPDF/Drawing/DocumentGenerator.cs +++ b/QuestPDF/Drawing/DocumentGenerator.cs @@ -108,10 +108,15 @@ namespace QuestPDF.Drawing if (text == null) return; - + foreach (var child in text.Children) - if (child is TextBlockSpan span) - span.Style.ApplyGlobalStyle(documentDefaultTextStyle); + { + if (child is TextBlockSpan textSpan) + textSpan.Style.ApplyGlobalStyle(documentDefaultTextStyle); + + if (child is TextBlockElement textElement) + ApplyDefaultTextStyle(textElement.Element, documentDefaultTextStyle); + } }); } } From 70e5783f0ef4988e28d1eb2f5917db90754dd7c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Zi=C4=85bek?= Date: Thu, 7 Oct 2021 23:23:07 +0200 Subject: [PATCH 06/21] Updated report sample to follow new text style approach --- QuestPDF.ReportSample/Layouts/ImageTemplate.cs | 3 ++- QuestPDF.ReportSample/Layouts/PhotoTemplate.cs | 12 ++++++------ QuestPDF.ReportSample/Layouts/SectionTemplate.cs | 10 +++++----- QuestPDF.ReportSample/Layouts/StandardReport.cs | 4 ++-- .../Layouts/TableOfContentsTemplate.cs | 6 +++--- QuestPDF/Infrastructure/TextStyle.cs | 3 ++- 6 files changed, 20 insertions(+), 18 deletions(-) diff --git a/QuestPDF.ReportSample/Layouts/ImageTemplate.cs b/QuestPDF.ReportSample/Layouts/ImageTemplate.cs index 673d470..0e3eca8 100644 --- a/QuestPDF.ReportSample/Layouts/ImageTemplate.cs +++ b/QuestPDF.ReportSample/Layouts/ImageTemplate.cs @@ -1,5 +1,6 @@ using System; using QuestPDF.Fluent; +using QuestPDF.Helpers; using QuestPDF.Infrastructure; namespace QuestPDF.ReportSample.Layouts @@ -24,7 +25,7 @@ namespace QuestPDF.ReportSample.Layouts { container .AspectRatio(AspectRatio) - .Background("#EEEEEE") + .Background(Colors.Grey.Lighten3) .Image(Source(Size.Zero)); } } diff --git a/QuestPDF.ReportSample/Layouts/PhotoTemplate.cs b/QuestPDF.ReportSample/Layouts/PhotoTemplate.cs index 8384147..f2a4c7d 100644 --- a/QuestPDF.ReportSample/Layouts/PhotoTemplate.cs +++ b/QuestPDF.ReportSample/Layouts/PhotoTemplate.cs @@ -48,13 +48,13 @@ namespace QuestPDF.ReportSample.Layouts { grid.Columns(6); - grid.Item().LabelCell().Text("Date", Typography.Normal); - grid.Item(2).ValueCell().Text(Model.Date?.ToString("g") ?? string.Empty, Typography.Normal); - grid.Item().LabelCell().Text("Location", Typography.Normal); - grid.Item(2).ValueCell().Text(Model.Location.Format(), Typography.Normal); + grid.Item().LabelCell().Text("Date"); + grid.Item(2).ValueCell().Text(Model.Date?.ToString("g") ?? string.Empty); + grid.Item().LabelCell().Text("Location"); + grid.Item(2).ValueCell().Text(Model.Location.Format()); - grid.Item().LabelCell().Text("Comments", Typography.Normal); - grid.Item(5).ValueCell().Text(Model.Comments, Typography.Normal); + grid.Item().LabelCell().Text("Comments"); + grid.Item(5).ValueCell().Text(Model.Comments); }); } } diff --git a/QuestPDF.ReportSample/Layouts/SectionTemplate.cs b/QuestPDF.ReportSample/Layouts/SectionTemplate.cs index f8da682..8142da9 100644 --- a/QuestPDF.ReportSample/Layouts/SectionTemplate.cs +++ b/QuestPDF.ReportSample/Layouts/SectionTemplate.cs @@ -30,11 +30,11 @@ namespace QuestPDF.ReportSample.Layouts { stack.Item().EnsureSpace(25).Row(row => { - row.ConstantColumn(150).LabelCell().Text(part.Label, Typography.Normal); + row.ConstantColumn(150).LabelCell().Text(part.Label); var frame = row.RelativeColumn().ValueCell(); if (part is ReportSectionText text) - frame.ShowEntire().Text(text.Text, Typography.Normal); + frame.ShowEntire().Text(text.Text); if (part is ReportSectionMap map) frame.Element(x => MapElement(x, map)); @@ -51,7 +51,7 @@ namespace QuestPDF.ReportSample.Layouts { if (model.ImageSource == null || model.Location == null) { - container.Text("No location provided", Typography.Normal); + container.Text("No location provided"); return; } @@ -60,7 +60,7 @@ namespace QuestPDF.ReportSample.Layouts stack.Spacing(5); stack.Item().MaxWidth(250).AspectRatio(4 / 3f).Image(Placeholders.Image); - stack.Item().Text(model.Location.Format(), Typography.Normal); + stack.Item().Text(model.Location.Format()); }); } @@ -68,7 +68,7 @@ namespace QuestPDF.ReportSample.Layouts { if (model.Photos.Count == 0) { - container.Text("No photos", Typography.Normal); + container.Text("No photos"); return; } diff --git a/QuestPDF.ReportSample/Layouts/StandardReport.cs b/QuestPDF.ReportSample/Layouts/StandardReport.cs index f31ee26..61a9150 100644 --- a/QuestPDF.ReportSample/Layouts/StandardReport.cs +++ b/QuestPDF.ReportSample/Layouts/StandardReport.cs @@ -68,8 +68,8 @@ namespace QuestPDF.ReportSample.Layouts { grid.Item().Text(text => { - text.Span($"{field.Label}: ", Typography.Normal.SemiBold()); - text.Span(field.Value, Typography.Normal); + text.Span($"{field.Label}: ", TextStyle.Default.SemiBold()); + text.Span(field.Value); }); } }); diff --git a/QuestPDF.ReportSample/Layouts/TableOfContentsTemplate.cs b/QuestPDF.ReportSample/Layouts/TableOfContentsTemplate.cs index 087cfff..64ff8f2 100644 --- a/QuestPDF.ReportSample/Layouts/TableOfContentsTemplate.cs +++ b/QuestPDF.ReportSample/Layouts/TableOfContentsTemplate.cs @@ -41,9 +41,9 @@ namespace QuestPDF.ReportSample.Layouts .InternalLink(locationName) .Row(row => { - row.ConstantColumn(25).Text($"{number}.", Typography.Normal); - row.RelativeColumn().Text(locationName, Typography.Normal); - row.ConstantColumn(150).AlignRight().Text(text => text.PageNumberOfLocation(locationName, Typography.Normal)); + row.ConstantColumn(25).Text($"{number}."); + row.RelativeColumn().Text(locationName); + row.ConstantColumn(150).AlignRight().Text(text => text.PageNumberOfLocation(locationName)); }); } } diff --git a/QuestPDF/Infrastructure/TextStyle.cs b/QuestPDF/Infrastructure/TextStyle.cs index ee8aa5d..545d7db 100644 --- a/QuestPDF/Infrastructure/TextStyle.cs +++ b/QuestPDF/Infrastructure/TextStyle.cs @@ -1,4 +1,5 @@ -using QuestPDF.Helpers; +using System; +using QuestPDF.Helpers; namespace QuestPDF.Infrastructure { From 7174a2afa174c18135f4e402d8397974739ceff5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Zi=C4=85bek?= Date: Sun, 10 Oct 2021 19:17:31 +0200 Subject: [PATCH 07/21] Code style improvements --- QuestPDF/Elements/SkipOnce.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/QuestPDF/Elements/SkipOnce.cs b/QuestPDF/Elements/SkipOnce.cs index cf5ee5a..b1bb9c2 100644 --- a/QuestPDF/Elements/SkipOnce.cs +++ b/QuestPDF/Elements/SkipOnce.cs @@ -5,16 +5,16 @@ namespace QuestPDF.Elements { internal class SkipOnce : ContainerElement, IStateResettable { - private bool firstPageWasSkiped; + private bool FirstPageWasSkipped { get; set; } public void ResetState() { - firstPageWasSkiped = false; + FirstPageWasSkipped = false; } internal override ISpacePlan Measure(Size availableSpace) { - if (Child == null || !firstPageWasSkiped) + if (Child == null || !FirstPageWasSkipped) return new FullRender(Size.Zero); return Child.Measure(availableSpace); @@ -25,10 +25,10 @@ namespace QuestPDF.Elements if (Child == null) return; - if (firstPageWasSkiped) + if (FirstPageWasSkipped) Child.Draw(availableSpace); - firstPageWasSkiped = true; + FirstPageWasSkipped = true; } } } \ No newline at end of file From 5deea91fe4c608de9451d25492fabfaec6d6fa29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Zi=C4=85bek?= Date: Sun, 10 Oct 2021 23:21:11 +0200 Subject: [PATCH 08/21] Code refactorization --- QuestPDF.ReportSample/DataSource.cs | 3 --- QuestPDF.ReportSample/Tests.cs | 4 ++-- QuestPDF/Elements/Constrained.cs | 25 +++++++++++-------------- QuestPDF/Infrastructure/Position.cs | 6 +++--- QuestPDF/Infrastructure/Size.cs | 25 ++----------------------- QuestPDF/Infrastructure/TextStyle.cs | 6 ++---- 6 files changed, 20 insertions(+), 49 deletions(-) diff --git a/QuestPDF.ReportSample/DataSource.cs b/QuestPDF.ReportSample/DataSource.cs index 1b278c2..062cea3 100644 --- a/QuestPDF.ReportSample/DataSource.cs +++ b/QuestPDF.ReportSample/DataSource.cs @@ -7,9 +7,6 @@ namespace QuestPDF.ReportSample { public static class DataSource { - public static int SectionCounter { get; set; } - public static int FieldCounter { get; set; } - public static ReportModel GetReport() { return new ReportModel diff --git a/QuestPDF.ReportSample/Tests.cs b/QuestPDF.ReportSample/Tests.cs index 08b9995..089ad2e 100644 --- a/QuestPDF.ReportSample/Tests.cs +++ b/QuestPDF.ReportSample/Tests.cs @@ -57,8 +57,8 @@ namespace QuestPDF.ReportSample Console.WriteLine($"Time per document: {performance:N} ms"); Console.WriteLine($"Documents per second: {speed:N} d/s"); - //if (speed < performanceTarget) - // throw new Exception("Rendering algorithm is too slow."); + if (speed < performanceTarget) + throw new Exception("Rendering algorithm is too slow."); } } } \ No newline at end of file diff --git a/QuestPDF/Elements/Constrained.cs b/QuestPDF/Elements/Constrained.cs index d18def5..8a58ff2 100644 --- a/QuestPDF/Elements/Constrained.cs +++ b/QuestPDF/Elements/Constrained.cs @@ -22,8 +22,8 @@ namespace QuestPDF.Elements return new Wrap(); var available = new Size( - MathHelpers.Min(MaxWidth, availableSpace.Width), - MathHelpers.Min(MaxHeight, availableSpace.Height)); + Min(MaxWidth, availableSpace.Width), + Min(MaxHeight, availableSpace.Height)); var measurement = Child?.Measure(available) ?? new FullRender(Size.Zero); var size = measurement as Size; @@ -32,8 +32,8 @@ namespace QuestPDF.Elements return new Wrap(); var actualSize = new Size( - MathHelpers.Max(MinWidth, size.Width), - MathHelpers.Max(MinHeight, size.Height)); + Max(MinWidth, size.Width), + Max(MinHeight, size.Height)); if (size is FullRender) return new FullRender(actualSize); @@ -47,23 +47,20 @@ namespace QuestPDF.Elements internal override void Draw(Size availableSpace) { var available = new Size( - MathHelpers.Min(MaxWidth, availableSpace.Width), - MathHelpers.Min(MaxHeight, availableSpace.Height)); + Min(MaxWidth, availableSpace.Width), + Min(MaxHeight, availableSpace.Height)); Child?.Draw(available); } - } - - static class MathHelpers - { - public static float Min(params float?[] values) + + private static float Min(float? x, float y) { - return values.Where(x => x.HasValue).Min().Value; + return x.HasValue ? Math.Min(x.Value, y) : y; } - public static float Max(params float?[] values) + private static float Max(float? x, float y) { - return values.Where(x => x.HasValue).Max().Value; + return x.HasValue ? Math.Max(x.Value, y) : y; } } } \ No newline at end of file diff --git a/QuestPDF/Infrastructure/Position.cs b/QuestPDF/Infrastructure/Position.cs index f71dd38..358c602 100644 --- a/QuestPDF/Infrastructure/Position.cs +++ b/QuestPDF/Infrastructure/Position.cs @@ -2,10 +2,10 @@ { internal class Position { - public float X { get; set; } - public float Y { get; set; } + public float X { get; } + public float Y { get; } - public static Position Zero => new Position(0, 0); + public static Position Zero { get; } = new Position(0, 0); public Position(float x, float y) { diff --git a/QuestPDF/Infrastructure/Size.cs b/QuestPDF/Infrastructure/Size.cs index 196d58e..5ccceac 100644 --- a/QuestPDF/Infrastructure/Size.cs +++ b/QuestPDF/Infrastructure/Size.cs @@ -7,8 +7,8 @@ public float Width { get; } public float Height { get; } - public static Size Zero => new Size(0, 0); - public static Size Max => new Size(14_400, 14_400); + public static Size Zero { get; } = new Size(0, 0); + public static Size Max { get; } = new Size(14_400, 14_400); public Size(float width, float height) { @@ -17,26 +17,5 @@ } public override string ToString() => $"(W: {Width}, H: {Height})"; - - protected bool Equals(Size other) - { - return Width.Equals(other.Width) && Height.Equals(other.Height); - } - - public override bool Equals(object? obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((Size) obj); - } - - public override int GetHashCode() - { - unchecked - { - return (Width.GetHashCode() * 397) ^ Height.GetHashCode(); - } - } } } \ No newline at end of file diff --git a/QuestPDF/Infrastructure/TextStyle.cs b/QuestPDF/Infrastructure/TextStyle.cs index 545d7db..640d9d2 100644 --- a/QuestPDF/Infrastructure/TextStyle.cs +++ b/QuestPDF/Infrastructure/TextStyle.cs @@ -32,9 +32,8 @@ namespace QuestPDF.Infrastructure HasUnderline = false }; - private static TextStyle DefaultTextStyleCache = new TextStyle(); - public static TextStyle Default => DefaultTextStyleCache; - + public static TextStyle Default => new TextStyle(); + internal void ApplyGlobalStyle(TextStyle globalStyle) { if (HasGlobalStyleApplied) @@ -43,7 +42,6 @@ namespace QuestPDF.Infrastructure HasGlobalStyleApplied = true; ApplyParentStyle(globalStyle); - Key ??= $"{Color}|{BackgroundColor}|{FontType}|{Size}|{LineHeight}|{FontWeight}|{IsItalic}|{HasStrikethrough}|{HasUnderline}"; } From 479915b68f7a91dbb80c82b92a4f988b990ecf7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Zi=C4=85bek?= Date: Mon, 11 Oct 2021 01:48:12 +0200 Subject: [PATCH 09/21] Implemented the Inlined element --- QuestPDF.Examples/InlinedExamples.cs | 49 +++++++ QuestPDF/Elements/Grid.cs | 9 +- QuestPDF/Elements/Inlined.cs | 195 +++++++++++++++++++++++++++ QuestPDF/Fluent/InlinedExtensions.cs | 81 +++++++++++ 4 files changed, 329 insertions(+), 5 deletions(-) create mode 100644 QuestPDF.Examples/InlinedExamples.cs create mode 100644 QuestPDF/Elements/Inlined.cs create mode 100644 QuestPDF/Fluent/InlinedExtensions.cs diff --git a/QuestPDF.Examples/InlinedExamples.cs b/QuestPDF.Examples/InlinedExamples.cs new file mode 100644 index 0000000..a9e3a63 --- /dev/null +++ b/QuestPDF.Examples/InlinedExamples.cs @@ -0,0 +1,49 @@ +using System; +using System.Linq; +using NUnit.Framework; +using QuestPDF.Examples.Engine; +using QuestPDF.Fluent; +using QuestPDF.Helpers; + +namespace QuestPDF.Examples +{ + public class InlinedExamples + { + [Test] + public void Inlined() + { + RenderingTest + .Create() + .PageSize(800, 575) + .FileName() + .ProduceImages() + .ShowResults() + .Render(container => + { + container + .Padding(25) + .Border(1) + .Background(Colors.Grey.Lighten2) + .Inlined(inlined => + { + inlined.Spacing(25); + + inlined.AlignCenter(); + inlined.BaselineMiddle(); + + var random = new Random(); + + foreach (var _ in Enumerable.Range(0, 50)) + { + inlined + .Item() + .Border(1) + .Width(random.Next(1, 5) * 25) + .Height(random.Next(1, 5) * 25) + .Background(Placeholders.BackgroundColor()); + } + }); + }); + } + } +} \ No newline at end of file diff --git a/QuestPDF/Elements/Grid.cs b/QuestPDF/Elements/Grid.cs index 4649ac4..7014bf6 100644 --- a/QuestPDF/Elements/Grid.cs +++ b/QuestPDF/Elements/Grid.cs @@ -2,7 +2,6 @@ using System.Linq; using QuestPDF.Fluent; using QuestPDF.Infrastructure; -using static QuestPDF.Infrastructure.HorizontalAlignment; namespace QuestPDF.Elements { @@ -20,7 +19,7 @@ namespace QuestPDF.Elements public Queue ChildrenQueue { get; set; } = new Queue(); public int ColumnsCount { get; set; } = DefaultColumnsCount; - public HorizontalAlignment Alignment { get; set; } = Left; + public HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left; public float VerticalSpacing { get; set; } = 0; public float HorizontalSpacing { get; set; } = 0; @@ -62,15 +61,15 @@ namespace QuestPDF.Elements var emptySpace = ColumnsCount - columnsWidth; var hasEmptySpace = emptySpace >= Size.Epsilon; - if (Alignment == Center) + if (Alignment == HorizontalAlignment.Center) emptySpace /= 2; - if (hasEmptySpace && Alignment != Left) + if (hasEmptySpace && Alignment != HorizontalAlignment.Left) row.RelativeColumn(emptySpace); elements.ForEach(x => row.RelativeColumn(x.Columns).Element(x.Child)); - if (hasEmptySpace && Alignment != Right) + if (hasEmptySpace && Alignment != HorizontalAlignment.Right) row.RelativeColumn(emptySpace); } } diff --git a/QuestPDF/Elements/Inlined.cs b/QuestPDF/Elements/Inlined.cs new file mode 100644 index 0000000..0ab3352 --- /dev/null +++ b/QuestPDF/Elements/Inlined.cs @@ -0,0 +1,195 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using QuestPDF.Drawing.SpacePlan; +using QuestPDF.Infrastructure; + +namespace QuestPDF.Elements +{ + internal class InlinedElement : Container + { + public ISpacePlan? Size { get; set; } + } + + internal class Inlined : Element, IStateResettable + { + public List Elements { get; internal set; } = new List(); + private Queue ChildrenQueue { get; set; } + + internal HorizontalAlignment HorizontalAlignment { get; set; } + internal VerticalAlignment BaselineAlignment { get; set; } + + public void ResetState() + { + Elements.ForEach(x => x.Size ??= x.Measure(Size.Max)); + ChildrenQueue = new Queue(Elements); + } + + internal override void HandleVisitor(Action visit) + { + Elements.ForEach(x => x.HandleVisitor(visit)); + base.HandleVisitor(visit); + } + + internal override ISpacePlan Measure(Size availableSpace) + { + if (!ChildrenQueue.Any()) + return new FullRender(Size.Zero); + + var lines = Compose(availableSpace); + + if (!lines.Any()) + return new Wrap(); + + var lineSizes = lines.Select(GetLineSize).ToList(); + + var width = lineSizes.Max(x => x.Width); + var height = lineSizes.Sum(x => x.Height); + var targetSize = new Size(width, height); + + var isPartiallyRendered = lines.Sum(x => x.Count) != ChildrenQueue.Count; + + if (isPartiallyRendered) + return new PartialRender(targetSize); + + return new FullRender(targetSize); + } + + internal override void Draw(Size availableSpace) + { + var lines = Compose(availableSpace); + var topOffset = 0f; + + foreach (var line in lines) + { + var height = line.Select(x => x.Size as Size).Where(x => x != null).Max(x => x.Height); + DrawLine(line); + + topOffset += height; + Canvas.Translate(new Position(0, height)); + } + + Canvas.Translate(new Position(0, -topOffset)); + lines.SelectMany(x => x).ToList().ForEach(x => ChildrenQueue.Dequeue()); + + void DrawLine(ICollection elements) + { + var lineSize = GetLineSize(elements); + + var leftOffset = AlignOffset(); + Canvas.Translate(new Position(leftOffset, 0)); + + foreach (var element in elements) + { + var size = element.Size as Size; + var baselineOffset = BaselineOffset(size, lineSize.Height); + + Canvas.Translate(new Position(0, baselineOffset)); + element.Draw(size); + Canvas.Translate(new Position(0, -baselineOffset)); + + leftOffset += size.Width; + Canvas.Translate(new Position(size.Width, 0)); + } + + Canvas.Translate(new Position(-leftOffset, 0)); + + float AlignOffset() + { + if (HorizontalAlignment == HorizontalAlignment.Left) + return 0; + + var difference = availableSpace.Width - lineSize.Width; + + if (HorizontalAlignment == HorizontalAlignment.Center) + return difference / 2; + + return difference; + } + + float BaselineOffset(Size elementSize, float lineHeight) + { + if (BaselineAlignment == VerticalAlignment.Top) + return 0; + + var difference = lineHeight - elementSize.Height; + + if (BaselineAlignment == VerticalAlignment.Middle) + return difference / 2; + + return difference; + } + } + } + + Size GetLineSize(ICollection elements) + { + var sizes = elements + .Select(x => x.Size as Size) + .Where(x => x != null) + .ToList(); + + var width = sizes.Sum(x => x.Width); + var height = sizes.Max(x => x.Height); + + return new Size(width, height); + } + + // list of lines, each line is a list of elements + private ICollection> Compose(Size availableSize) + { + var queue = new Queue(ChildrenQueue); + var result = new List>(); + + var topOffset = 0f; + + while (true) + { + var line = GetNextLine(); + + if (!line.Any()) + break; + + var height = line + .Select(x => x.Size as Size) + .Where(x => x != null) + .Max(x => x.Height); + + if (topOffset + height > availableSize.Height) + break; + + topOffset += height; + result.Add(line); + } + + return result; + + ICollection GetNextLine() + { + var result = new List(); + var leftOffset = 0f; + + while (true) + { + if (!queue.Any()) + break; + + var element = queue.Peek(); + var size = element.Size as Size; + + if (size == null) + break; + + if (leftOffset + size.Width > availableSize.Width) + break; + + queue.Dequeue(); + leftOffset += size.Width; + result.Add(element); + } + + return result; + } + } + } +} \ No newline at end of file diff --git a/QuestPDF/Fluent/InlinedExtensions.cs b/QuestPDF/Fluent/InlinedExtensions.cs new file mode 100644 index 0000000..7075826 --- /dev/null +++ b/QuestPDF/Fluent/InlinedExtensions.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using QuestPDF.Elements; +using QuestPDF.Infrastructure; + +namespace QuestPDF.Fluent +{ + public class InlinedDescriptor + { + private ICollection Children = new List(); + private float VerticalSpacingValue { get; set; } + private VerticalAlignment BaselineAlignmentValue { get; set; } + private float HorizontalSpacingValue { get; set; } + private HorizontalAlignment HorizontalAlignmentValue { get; set; } + + public void Spacing(float value) + { + VerticalSpacing(value); + HorizontalSpacing(value); + } + + public void VerticalSpacing(float value) => VerticalSpacingValue = value; + public void HorizontalSpacing(float value) => HorizontalSpacingValue = value; + + public void BaselineTop() => BaselineAlignmentValue = VerticalAlignment.Top; + public void BaselineMiddle() => BaselineAlignmentValue = VerticalAlignment.Middle; + public void BaselineBottom() => BaselineAlignmentValue = VerticalAlignment.Bottom; + + public void AlignLeft() => HorizontalAlignmentValue = HorizontalAlignment.Left; + public void AlignCenter() => HorizontalAlignmentValue = HorizontalAlignment.Center; + public void AlignRight() => HorizontalAlignmentValue = HorizontalAlignment.Right; + + public IContainer Item() + { + var container = new Container(); + Children.Add(container); + return container; + } + + internal Element Compose() + { + var elements = Children + .Select(x => new InlinedElement + { + Child = new Padding + { + Left = HorizontalSpacingValue, + Top = VerticalSpacingValue, + Child = x + } + }) + .ToList(); + + return new Padding + { + Left = -HorizontalSpacingValue, + Top = -VerticalSpacingValue, + + Child = new Inlined + { + Elements = elements, + + HorizontalAlignment = HorizontalAlignmentValue, + BaselineAlignment = BaselineAlignmentValue + } + }; + } + } + + public static class InlinedExtensions + { + public static void Inlined(this IContainer element, Action handler) + { + var descriptor = new InlinedDescriptor(); + handler(descriptor); + + element.Element(descriptor.Compose()); + } + } +} \ No newline at end of file From 9ef444462aeaf2f8e8d18bf1ccd557480d5136be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Zi=C4=85bek?= Date: Mon, 11 Oct 2021 22:48:44 +0200 Subject: [PATCH 10/21] Implemented the Inlined document --- QuestPDF.Examples/InlinedExamples.cs | 74 +++++++++++++----- QuestPDF/Elements/Inlined.cs | 112 +++++++++++++++++++++------ QuestPDF/Fluent/InlinedExtensions.cs | 59 ++++---------- 3 files changed, 156 insertions(+), 89 deletions(-) diff --git a/QuestPDF.Examples/InlinedExamples.cs b/QuestPDF.Examples/InlinedExamples.cs index a9e3a63..5b8f4a2 100644 --- a/QuestPDF.Examples/InlinedExamples.cs +++ b/QuestPDF.Examples/InlinedExamples.cs @@ -4,6 +4,7 @@ using NUnit.Framework; using QuestPDF.Examples.Engine; using QuestPDF.Fluent; using QuestPDF.Helpers; +using QuestPDF.Infrastructure; namespace QuestPDF.Examples { @@ -14,7 +15,7 @@ namespace QuestPDF.Examples { RenderingTest .Create() - .PageSize(800, 575) + .PageSize(800, 650) .FileName() .ProduceImages() .ShowResults() @@ -22,26 +23,61 @@ namespace QuestPDF.Examples { container .Padding(25) - .Border(1) - .Background(Colors.Grey.Lighten2) - .Inlined(inlined => + .Decoration(decoration => { - inlined.Spacing(25); - - inlined.AlignCenter(); - inlined.BaselineMiddle(); - - var random = new Random(); - - foreach (var _ in Enumerable.Range(0, 50)) + decoration.Header().Text(text => { - inlined - .Item() - .Border(1) - .Width(random.Next(1, 5) * 25) - .Height(random.Next(1, 5) * 25) - .Background(Placeholders.BackgroundColor()); - } + text.DefaultTextStyle(TextStyle.Default.Size(20)); + + text.CurrentPageNumber(); + text.Span(" / "); + text.TotalPages(); + }); + + decoration + .Content() + .PaddingTop(25) + //.Box() + .Border(1) + .Background(Colors.Grey.Lighten2) + .Inlined(inlined => + { + inlined.Spacing(25); + + inlined.AlignSpaceAround(); + inlined.BaselineMiddle(); + + var random = new Random(123); + + foreach (var _ in Enumerable.Range(0, 50)) + { + var width = random.Next(2, 7); + var height = random.Next(2, 7); + + var sizeText = $"{width}×{height}"; + + inlined + .Item() + .Border(1) + .Width(width * 25) + .Height(height * 25) + .Background(Placeholders.BackgroundColor()) + .Layers(layers => + { + layers.Layer().Grid(grid => + { + grid.Columns(width); + Enumerable.Range(0, width * height).ToList().ForEach(x => grid.Item().Border(1).BorderColor(Colors.White).Width(25).Height(25)); + }); + + layers + .PrimaryLayer() + .AlignCenter() + .AlignMiddle() + .Text(sizeText, TextStyle.Default.Size(15)); + }); + } + }); }); }); } diff --git a/QuestPDF/Elements/Inlined.cs b/QuestPDF/Elements/Inlined.cs index 0ab3352..8f0cb0b 100644 --- a/QuestPDF/Elements/Inlined.cs +++ b/QuestPDF/Elements/Inlined.cs @@ -8,7 +8,24 @@ namespace QuestPDF.Elements { internal class InlinedElement : Container { - public ISpacePlan? Size { get; set; } + public ISpacePlan? MeasureCache { get; set; } + + internal override ISpacePlan Measure(Size availableSpace) + { + // TODO: once element caching proxy is introduces, this can be removed + + MeasureCache ??= Child.Measure(Size.Max); + return MeasureCache; + } + } + + internal enum InlinedAlignment + { + Left, + Center, + Right, + Justify, + SpaceAround } internal class Inlined : Element, IStateResettable @@ -16,12 +33,14 @@ namespace QuestPDF.Elements public List Elements { get; internal set; } = new List(); private Queue ChildrenQueue { get; set; } - internal HorizontalAlignment HorizontalAlignment { get; set; } + internal float VerticalSpacing { get; set; } + internal float HorizontalSpacing { get; set; } + + internal InlinedAlignment ElementsAlignment { get; set; } internal VerticalAlignment BaselineAlignment { get; set; } public void ResetState() { - Elements.ForEach(x => x.Size ??= x.Measure(Size.Max)); ChildrenQueue = new Queue(Elements); } @@ -41,10 +60,17 @@ namespace QuestPDF.Elements if (!lines.Any()) return new Wrap(); - var lineSizes = lines.Select(GetLineSize).ToList(); + var lineSizes = lines + .Select(line => + { + var size = GetLineSize(line); + var heightWithSpacing = size.Height + (line.Count - 1) * HorizontalSpacing; + return new Size(size.Width, heightWithSpacing); + }) + .ToList(); var width = lineSizes.Max(x => x.Width); - var height = lineSizes.Sum(x => x.Height); + var height = lineSizes.Sum(x => x.Height) + (lines.Count - 1) * VerticalSpacing; var targetSize = new Size(width, height); var isPartiallyRendered = lines.Sum(x => x.Count) != ChildrenQueue.Count; @@ -62,11 +88,11 @@ namespace QuestPDF.Elements foreach (var line in lines) { - var height = line.Select(x => x.Size as Size).Where(x => x != null).Max(x => x.Height); + var height = line.Select(x => x.Measure(Size.Max) as Size).Where(x => x != null).Max(x => x.Height); DrawLine(line); - topOffset += height; - Canvas.Translate(new Position(0, height)); + topOffset += height + VerticalSpacing; + Canvas.Translate(new Position(0, height + VerticalSpacing)); } Canvas.Translate(new Position(0, -topOffset)); @@ -75,36 +101,62 @@ namespace QuestPDF.Elements void DrawLine(ICollection elements) { var lineSize = GetLineSize(elements); - + + var elementOffset = ElementOffset(); var leftOffset = AlignOffset(); Canvas.Translate(new Position(leftOffset, 0)); foreach (var element in elements) { - var size = element.Size as Size; + var size = element.Measure(Size.Max) as Size; var baselineOffset = BaselineOffset(size, lineSize.Height); Canvas.Translate(new Position(0, baselineOffset)); element.Draw(size); Canvas.Translate(new Position(0, -baselineOffset)); - leftOffset += size.Width; - Canvas.Translate(new Position(size.Width, 0)); + leftOffset += size.Width + elementOffset; + Canvas.Translate(new Position(size.Width + elementOffset, 0)); } Canvas.Translate(new Position(-leftOffset, 0)); - float AlignOffset() + float ElementOffset() { - if (HorizontalAlignment == HorizontalAlignment.Left) + var difference = availableSpace.Width - lineSize.Width; + + if (elements.Count == 1) return 0; - var difference = availableSpace.Width - lineSize.Width; + if (ElementsAlignment == InlinedAlignment.Justify) + return difference / (elements.Count - 1); - if (HorizontalAlignment == HorizontalAlignment.Center) + if (ElementsAlignment == InlinedAlignment.SpaceAround) + return difference / (elements.Count + 1); + + return HorizontalSpacing; + } + + float AlignOffset() + { + if (ElementsAlignment == InlinedAlignment.Left) + return 0; + + if (ElementsAlignment == InlinedAlignment.Justify) + return 0; + + if (ElementsAlignment == InlinedAlignment.SpaceAround) + return elementOffset; + + var difference = availableSpace.Width - lineSize.Width - (elements.Count - 1) * HorizontalSpacing; + + if (ElementsAlignment == InlinedAlignment.Center) return difference / 2; - return difference; + if (ElementsAlignment == InlinedAlignment.Right) + return difference; + + return 0; } float BaselineOffset(Size elementSize, float lineHeight) @@ -125,7 +177,7 @@ namespace QuestPDF.Elements Size GetLineSize(ICollection elements) { var sizes = elements - .Select(x => x.Size as Size) + .Select(x => x.Measure(Size.Max) as Size) .Where(x => x != null) .ToList(); @@ -151,14 +203,14 @@ namespace QuestPDF.Elements break; var height = line - .Select(x => x.Size as Size) + .Select(x => x.Measure(availableSize) as Size) .Where(x => x != null) .Max(x => x.Height); - if (topOffset + height > availableSize.Height) + if (topOffset + height > availableSize.Height + Size.Epsilon) break; - topOffset += height; + topOffset += height + VerticalSpacing; result.Add(line); } @@ -167,7 +219,7 @@ namespace QuestPDF.Elements ICollection GetNextLine() { var result = new List(); - var leftOffset = 0f; + var leftOffset = GetInitialAlignmentOffset(); while (true) { @@ -175,21 +227,31 @@ namespace QuestPDF.Elements break; var element = queue.Peek(); - var size = element.Size as Size; + var size = element.Measure(Size.Max) as Size; if (size == null) break; - if (leftOffset + size.Width > availableSize.Width) + if (leftOffset + size.Width > availableSize.Width + Size.Epsilon) break; queue.Dequeue(); - leftOffset += size.Width; + leftOffset += size.Width + HorizontalSpacing; result.Add(element); } return result; } + + float GetInitialAlignmentOffset() + { + // this method makes sure that the spacing between elements is no lesser than configured + + if (ElementsAlignment == InlinedAlignment.SpaceAround) + return HorizontalSpacing * 2; + + return 0; + } } } } \ No newline at end of file diff --git a/QuestPDF/Fluent/InlinedExtensions.cs b/QuestPDF/Fluent/InlinedExtensions.cs index 7075826..b250c49 100644 --- a/QuestPDF/Fluent/InlinedExtensions.cs +++ b/QuestPDF/Fluent/InlinedExtensions.cs @@ -8,11 +8,7 @@ namespace QuestPDF.Fluent { public class InlinedDescriptor { - private ICollection Children = new List(); - private float VerticalSpacingValue { get; set; } - private VerticalAlignment BaselineAlignmentValue { get; set; } - private float HorizontalSpacingValue { get; set; } - private HorizontalAlignment HorizontalAlignmentValue { get; set; } + internal Inlined Inlined { get; } = new Inlined(); public void Spacing(float value) { @@ -20,52 +16,25 @@ namespace QuestPDF.Fluent HorizontalSpacing(value); } - public void VerticalSpacing(float value) => VerticalSpacingValue = value; - public void HorizontalSpacing(float value) => HorizontalSpacingValue = value; + public void VerticalSpacing(float value) => Inlined.VerticalSpacing = value; + public void HorizontalSpacing(float value) => Inlined.HorizontalSpacing = value; - public void BaselineTop() => BaselineAlignmentValue = VerticalAlignment.Top; - public void BaselineMiddle() => BaselineAlignmentValue = VerticalAlignment.Middle; - public void BaselineBottom() => BaselineAlignmentValue = VerticalAlignment.Bottom; + public void BaselineTop() => Inlined.BaselineAlignment = VerticalAlignment.Top; + public void BaselineMiddle() => Inlined.BaselineAlignment = VerticalAlignment.Middle; + public void BaselineBottom() => Inlined.BaselineAlignment = VerticalAlignment.Bottom; - public void AlignLeft() => HorizontalAlignmentValue = HorizontalAlignment.Left; - public void AlignCenter() => HorizontalAlignmentValue = HorizontalAlignment.Center; - public void AlignRight() => HorizontalAlignmentValue = HorizontalAlignment.Right; + public void AlignLeft() => Inlined.ElementsAlignment = InlinedAlignment.Left; + public void AlignCenter() => Inlined.ElementsAlignment = InlinedAlignment.Center; + public void AlignRight() => Inlined.ElementsAlignment = InlinedAlignment.Right; + public void AlignJustify() => Inlined.ElementsAlignment = InlinedAlignment.Justify; + public void AlignSpaceAround() => Inlined.ElementsAlignment = InlinedAlignment.SpaceAround; public IContainer Item() { - var container = new Container(); - Children.Add(container); + var container = new InlinedElement(); + Inlined.Elements.Add(container); return container; } - - internal Element Compose() - { - var elements = Children - .Select(x => new InlinedElement - { - Child = new Padding - { - Left = HorizontalSpacingValue, - Top = VerticalSpacingValue, - Child = x - } - }) - .ToList(); - - return new Padding - { - Left = -HorizontalSpacingValue, - Top = -VerticalSpacingValue, - - Child = new Inlined - { - Elements = elements, - - HorizontalAlignment = HorizontalAlignmentValue, - BaselineAlignment = BaselineAlignmentValue - } - }; - } } public static class InlinedExtensions @@ -75,7 +44,7 @@ namespace QuestPDF.Fluent var descriptor = new InlinedDescriptor(); handler(descriptor); - element.Element(descriptor.Compose()); + element.Element(descriptor.Inlined); } } } \ No newline at end of file From 0ccc8cc2366a5f49100e32c71a963e10a81aef14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Zi=C4=85bek?= Date: Mon, 11 Oct 2021 23:01:26 +0200 Subject: [PATCH 11/21] Improved the DocumentLayoutException message --- QuestPDF/Drawing/DocumentGenerator.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/QuestPDF/Drawing/DocumentGenerator.cs b/QuestPDF/Drawing/DocumentGenerator.cs index 364c98c..cd1718d 100644 --- a/QuestPDF/Drawing/DocumentGenerator.cs +++ b/QuestPDF/Drawing/DocumentGenerator.cs @@ -81,7 +81,7 @@ namespace QuestPDF.Drawing if (currentPage >= documentMetadata.DocumentLayoutExceptionThreshold) { canvas.EndDocument(); - throw new DocumentLayoutException("Composed layout generates infinite document."); + ThrowLayoutException(); } if (spacePlan is FullRender) @@ -94,7 +94,12 @@ namespace QuestPDF.Drawing void ThrowLayoutException() { - throw new DocumentLayoutException("Composed layout generates infinite document."); + throw new DocumentLayoutException( + $"Composed layout generates infinite document. This may happen in two cases. " + + $"1) Your document and its layout configuration is correct but the content takes more than {documentMetadata.DocumentLayoutExceptionThreshold} pages. " + + $"In this case, please increase the value {nameof(DocumentMetadata)}.{nameof(DocumentMetadata.DocumentLayoutExceptionThreshold)} property configured in the {nameof(IDocument.GetMetadata)} method. " + + $"2) The layout configuration of your document is invalid. Some of the elements require more space than is provided." + + $"Please analyze your documents structure to detect this element and fix its size constraints."); } } From fd696c5b66e4e7609782072a4f5cac6d8c802ea8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Zi=C4=85bek?= Date: Tue, 12 Oct 2021 23:58:28 +0200 Subject: [PATCH 12/21] Added missing tests to the constrained element --- QuestPDF.UnitTests/ConstrainedTests.cs | 88 +++++++++++++++++++++++++ QuestPDF.UnitTests/DynamicImageTests.cs | 2 +- 2 files changed, 89 insertions(+), 1 deletion(-) diff --git a/QuestPDF.UnitTests/ConstrainedTests.cs b/QuestPDF.UnitTests/ConstrainedTests.cs index 8ffb806..e51f07d 100644 --- a/QuestPDF.UnitTests/ConstrainedTests.cs +++ b/QuestPDF.UnitTests/ConstrainedTests.cs @@ -9,6 +9,8 @@ namespace QuestPDF.UnitTests [TestFixture] public class ConstrainedTests { + #region Height + [Test] public void Measure_MinHeight_ExpectWrap() { @@ -88,5 +90,91 @@ namespace QuestPDF.UnitTests .ExpectChildMeasure(new Size(400, 100), new Wrap()) .CheckMeasureResult(new Wrap()); } + + #endregion + + #region Width + + [Test] + public void Measure_MinWidth_ExpectWrap() + { + TestPlan + .For(x => new Constrained + { + MinWidth = 100 + }) + .MeasureElement(new Size(50, 400)) + .CheckMeasureResult(new Wrap()); + } + + [Test] + public void Measure_MinWidth_ExtendHeight() + { + TestPlan + .For(x => new Constrained + { + MinWidth = 100, + Child = x.CreateChild() + }) + .MeasureElement(new Size(200, 400)) + .ExpectChildMeasure(new Size(200, 400), new FullRender(50, 400)) + .CheckMeasureResult(new FullRender(100, 400)); + } + + [Test] + public void Measure_MinWidth_PassHeight() + { + TestPlan + .For(x => new Constrained + { + MinWidth = 100, + Child = x.CreateChild() + }) + .MeasureElement(new Size(200, 400)) + .ExpectChildMeasure(new Size(200, 400), new FullRender(150, 400)) + .CheckMeasureResult(new FullRender(150, 400)); + } + + [Test] + public void Measure_MaxWidth_Empty() + { + TestPlan + .For(x => new Constrained + { + MaxWidth = 100 + }) + .MeasureElement(new Size(150, 400)) + .CheckMeasureResult(new FullRender(0, 0)); + } + + [Test] + public void Measure_MaxWidth_PartialRender() + { + TestPlan + .For(x => new Constrained + { + MaxWidth = 100, + Child = x.CreateChild() + }) + .MeasureElement(new Size(200, 400)) + .ExpectChildMeasure(new Size(100, 400), new PartialRender(75, 400)) + .CheckMeasureResult(new PartialRender(75, 400)); + } + + [Test] + public void Measure_MaxWidth_ExpectWrap() + { + TestPlan + .For(x => new Constrained + { + MaxWidth = 100, + Child = x.CreateChild() + }) + .MeasureElement(new Size(200, 400)) + .ExpectChildMeasure(new Size(100, 400), new Wrap()) + .CheckMeasureResult(new Wrap()); + } + + #endregion } } \ No newline at end of file diff --git a/QuestPDF.UnitTests/DynamicImageTests.cs b/QuestPDF.UnitTests/DynamicImageTests.cs index d5b3cd8..eff1994 100644 --- a/QuestPDF.UnitTests/DynamicImageTests.cs +++ b/QuestPDF.UnitTests/DynamicImageTests.cs @@ -66,7 +66,7 @@ namespace QuestPDF.UnitTests .ExpectCanvasDrawImage(Position.Zero, new Size(400, 300)) .CheckDrawResult(); - passedSize.Should().Be(new Size(400, 300)); + passedSize.Should().BeEquivalentTo(new Size(400, 300)); } byte[] GenerateImage(Size size) From 0a6673dd4f498494df7de4ed742dfb73fc53aaf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Zi=C4=85bek?= Date: Thu, 21 Oct 2021 15:46:31 +0200 Subject: [PATCH 13/21] Update QuestPDF.csproj --- QuestPDF/QuestPDF.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/QuestPDF/QuestPDF.csproj b/QuestPDF/QuestPDF.csproj index fcf853c..b2f0ed5 100644 --- a/QuestPDF/QuestPDF.csproj +++ b/QuestPDF/QuestPDF.csproj @@ -15,7 +15,7 @@ https://github.com/QuestPDF/library.git git Marcin Ziąbek, QuestPDF contributors - pdf file export generate generation tool create creation render portable document format quest html library converter open source free standard core + pdf report file export generate generation tool create creation render portable document format quest html library converter open source free standard core MIT enable net462;netstandard2.0;netcoreapp2.0;netcoreapp3.0 From 11f0809efc0e68a8381995ebc298a6fd5c44c24f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Zi=C4=85bek?= Date: Sat, 23 Oct 2021 02:59:29 +0200 Subject: [PATCH 14/21] Improved default text style --- QuestPDF.ReportSample/Layouts/StandardReport.cs | 3 ++- QuestPDF/Drawing/DocumentContainer.cs | 1 - QuestPDF/Drawing/DocumentGenerator.cs | 3 +-- QuestPDF/Elements/Page.cs | 10 +++++++++- QuestPDF/Fluent/PageExtensions.cs | 16 ++++++++++------ QuestPDF/Helpers/Placeholders.cs | 2 +- 6 files changed, 23 insertions(+), 12 deletions(-) diff --git a/QuestPDF.ReportSample/Layouts/StandardReport.cs b/QuestPDF.ReportSample/Layouts/StandardReport.cs index 61a9150..08ada55 100644 --- a/QuestPDF.ReportSample/Layouts/StandardReport.cs +++ b/QuestPDF.ReportSample/Layouts/StandardReport.cs @@ -25,9 +25,10 @@ namespace QuestPDF.ReportSample.Layouts public void Compose(IDocumentContainer container) { container - .DefaultTextStyle(Typography.Normal) .Page(page => { + page.DefaultTextStyle(Typography.Normal); + page.MarginVertical(40); page.MarginHorizontal(50); diff --git a/QuestPDF/Drawing/DocumentContainer.cs b/QuestPDF/Drawing/DocumentContainer.cs index 9f593e2..e813839 100644 --- a/QuestPDF/Drawing/DocumentContainer.cs +++ b/QuestPDF/Drawing/DocumentContainer.cs @@ -9,7 +9,6 @@ namespace QuestPDF.Drawing { internal class DocumentContainer : IDocumentContainer { - internal TextStyle DefaultTextStyle { get; set; } = TextStyle.Default; internal List Pages { get; set; } = new List(); internal Container Compose() diff --git a/QuestPDF/Drawing/DocumentGenerator.cs b/QuestPDF/Drawing/DocumentGenerator.cs index cd1718d..3d33328 100644 --- a/QuestPDF/Drawing/DocumentGenerator.cs +++ b/QuestPDF/Drawing/DocumentGenerator.cs @@ -39,7 +39,6 @@ namespace QuestPDF.Drawing var metadata = document.GetMetadata(); var pageContext = new PageContext(); - ApplyDefaultTextStyle(content, container.DefaultTextStyle); RenderPass(pageContext, new FreeCanvas(), content, metadata); RenderPass(pageContext, canvas, content, metadata); } @@ -103,7 +102,7 @@ namespace QuestPDF.Drawing } } - private static void ApplyDefaultTextStyle(Element content, TextStyle documentDefaultTextStyle) + internal static void ApplyDefaultTextStyle(this Element content, TextStyle documentDefaultTextStyle) { documentDefaultTextStyle.ApplyGlobalStyle(TextStyle.LibraryDefault); diff --git a/QuestPDF/Elements/Page.cs b/QuestPDF/Elements/Page.cs index 792638b..e47e285 100644 --- a/QuestPDF/Elements/Page.cs +++ b/QuestPDF/Elements/Page.cs @@ -1,4 +1,5 @@ using System; +using QuestPDF.Drawing; using QuestPDF.Fluent; using QuestPDF.Helpers; using QuestPDF.Infrastructure; @@ -7,6 +8,8 @@ namespace QuestPDF.Elements { internal class Page : IComponent { + public TextStyle DefaultTextStyle { get; set; } = new TextStyle(); + public Size MinSize { get; set; } = PageSizes.A4; public Size MaxSize { get; set; } = PageSizes.A4; @@ -15,6 +18,8 @@ namespace QuestPDF.Elements public float MarginTop { get; set; } public float MarginBottom { get; set; } + public string BackgroundColor { get; set; } = Colors.Transparent; + public Element Header { get; set; } = Empty.Instance; public Element Content { get; set; } = Empty.Instance; public Element Footer { get; set; } = Empty.Instance; @@ -22,12 +27,13 @@ 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) @@ -47,6 +53,8 @@ namespace QuestPDF.Elements decoration.Footer().Element(Footer); }); + (container as Element).ApplyDefaultTextStyle(DefaultTextStyle); + bool IsClose(float x, float y) { return Math.Abs(x - y) < Size.Epsilon; diff --git a/QuestPDF/Fluent/PageExtensions.cs b/QuestPDF/Fluent/PageExtensions.cs index f2c530c..82d7dc5 100644 --- a/QuestPDF/Fluent/PageExtensions.cs +++ b/QuestPDF/Fluent/PageExtensions.cs @@ -70,6 +70,16 @@ namespace QuestPDF.Fluent MarginHorizontal(value); } + public void DefaultTextStyle(TextStyle textStyle) + { + Page.DefaultTextStyle = textStyle; + } + + public void Background(string color) + { + Page.BackgroundColor = color; + } + public IContainer Header() { var container = new Container(); @@ -94,12 +104,6 @@ namespace QuestPDF.Fluent public static class PageExtensions { - public static IDocumentContainer DefaultTextStyle(this IDocumentContainer document, TextStyle textStyle) - { - (document as DocumentContainer).DefaultTextStyle = textStyle; - return document; - } - public static IDocumentContainer Page(this IDocumentContainer document, Action handler) { var descriptor = new PageDescriptor(); diff --git a/QuestPDF/Helpers/Placeholders.cs b/QuestPDF/Helpers/Placeholders.cs index d79abd9..bd11a6a 100644 --- a/QuestPDF/Helpers/Placeholders.cs +++ b/QuestPDF/Helpers/Placeholders.cs @@ -7,7 +7,7 @@ namespace QuestPDF.Helpers { public static class Placeholders { - private static Random Random = new Random(); + public static readonly Random Random = new Random(); #region Word Cache From dadb8a5087cb57e6e71d3e71a7f5fbb97abd99ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Zi=C4=85bek?= Date: Sat, 23 Oct 2021 02:59:47 +0200 Subject: [PATCH 15/21] Improved test engine. Added more examples --- QuestPDF.Examples/BarCode.cs | 1 - QuestPDF.Examples/BarcodeExamples.cs | 1 - QuestPDF.Examples/DefaultTextStyleExample.cs | 48 +++++++++++++++++ QuestPDF.Examples/ElementExamples.cs | 25 --------- QuestPDF.Examples/Engine/RenderingTest.cs | 27 +++++++--- QuestPDF.Examples/Engine/SimpleDocument.cs | 17 +++--- QuestPDF.Examples/EnsureSpaceExample.cs | 53 +++++++++++++++++++ QuestPDF.Examples/FrameExample.cs | 1 - QuestPDF.Examples/InlinedExamples.cs | 39 +++++++++++++- QuestPDF.Examples/LoremPicsumExample.cs | 1 - QuestPDF.Examples/Padding.cs | 4 -- QuestPDF.Examples/ShowOnceExample.cs | 54 ++++++++++++++++++++ QuestPDF.Examples/SkipOnceExample.cs | 47 +++++++++++++++++ QuestPDF.Examples/TextBenchmark.cs | 4 +- QuestPDF.Examples/TextExamples.cs | 8 +-- 15 files changed, 269 insertions(+), 61 deletions(-) create mode 100644 QuestPDF.Examples/DefaultTextStyleExample.cs create mode 100644 QuestPDF.Examples/EnsureSpaceExample.cs create mode 100644 QuestPDF.Examples/ShowOnceExample.cs create mode 100644 QuestPDF.Examples/SkipOnceExample.cs diff --git a/QuestPDF.Examples/BarCode.cs b/QuestPDF.Examples/BarCode.cs index f90aca1..fd4853b 100644 --- a/QuestPDF.Examples/BarCode.cs +++ b/QuestPDF.Examples/BarCode.cs @@ -18,7 +18,6 @@ namespace QuestPDF.Examples RenderingTest .Create() .PageSize(400, 100) - .FileName() .ShowResults() .Render(container => { diff --git a/QuestPDF.Examples/BarcodeExamples.cs b/QuestPDF.Examples/BarcodeExamples.cs index 5711c37..b9b3f9f 100644 --- a/QuestPDF.Examples/BarcodeExamples.cs +++ b/QuestPDF.Examples/BarcodeExamples.cs @@ -15,7 +15,6 @@ namespace QuestPDF.Examples RenderingTest .Create() .PageSize(300, 300) - .FileName() .Render(container => { container diff --git a/QuestPDF.Examples/DefaultTextStyleExample.cs b/QuestPDF.Examples/DefaultTextStyleExample.cs new file mode 100644 index 0000000..c88f59c --- /dev/null +++ b/QuestPDF.Examples/DefaultTextStyleExample.cs @@ -0,0 +1,48 @@ +using NUnit.Framework; +using QuestPDF.Examples.Engine; +using QuestPDF.Fluent; +using QuestPDF.Helpers; +using QuestPDF.Infrastructure; + +namespace QuestPDF.Examples +{ + public class DefaultTextStyleExample + { + [Test] + public void DefaultTextStyle() + { + RenderingTest + .Create() + .ProduceImages() + .ShowResults() + .RenderDocument(container => + { + container.Page(page => + { + // all text in this set of pages has size 20 + page.DefaultTextStyle(TextStyle.Default.Size(20)); + + page.Margin(20); + page.Size(PageSizes.A4); + page.Background(Colors.White); + + page.Content().Stack(stack => + { + stack.Item().Text(Placeholders.Sentence()); + + stack.Item().Text(text => + { + // text in this block is additionally semibold + text.DefaultTextStyle(TextStyle.Default.SemiBold()); + + text.Line(Placeholders.Sentence()); + + // this text has size 20 but also semibold and red + text.Span(Placeholders.Sentence(), TextStyle.Default.Color(Colors.Red.Medium)); + }); + }); + }); + }); + } + } +} \ No newline at end of file diff --git a/QuestPDF.Examples/ElementExamples.cs b/QuestPDF.Examples/ElementExamples.cs index da81826..d1fb3a2 100644 --- a/QuestPDF.Examples/ElementExamples.cs +++ b/QuestPDF.Examples/ElementExamples.cs @@ -17,7 +17,6 @@ namespace QuestPDF.Examples RenderingTest .Create() .PageSize(200, 150) - .FileName() .Render(container => { container @@ -33,7 +32,6 @@ namespace QuestPDF.Examples RenderingTest .Create() .PageSize(300, 300) - .FileName() .Render(container => { container @@ -63,7 +61,6 @@ namespace QuestPDF.Examples RenderingTest .Create() .PageSize(740, 200) - .FileName() .Render(container => { container @@ -104,7 +101,6 @@ namespace QuestPDF.Examples RenderingTest .Create() .PageSize(740, 200) - .FileName() .Render(container => { container @@ -126,7 +122,6 @@ namespace QuestPDF.Examples RenderingTest .Create() .PageSize(500, 360) - .FileName() .Render(container => { container @@ -149,7 +144,6 @@ namespace QuestPDF.Examples RenderingTest .Create() .PageSize(210, 210) - .FileName() .Render(container => { container @@ -172,7 +166,6 @@ namespace QuestPDF.Examples RenderingTest .Create() .PageSize(300, 200) - .FileName() .Render(container => { var text = ""; @@ -195,7 +188,6 @@ namespace QuestPDF.Examples RenderingTest .Create() .PageSize(400, 230) - .FileName() .Render(container => { var textStyle = TextStyle.Default.Size(14); @@ -230,7 +222,6 @@ namespace QuestPDF.Examples RenderingTest .Create() .PageSize(300, 200) - .FileName() .Render(container => { container @@ -260,7 +251,6 @@ namespace QuestPDF.Examples RenderingTest .Create() .PageSize(400, 250) - .FileName() .Render(container => { container @@ -336,7 +326,6 @@ namespace QuestPDF.Examples RenderingTest .Create() .PageSize(300, 300) - .FileName() .Render(container => { container @@ -381,7 +370,6 @@ namespace QuestPDF.Examples RenderingTest .Create() .PageSize(450, 150) - .FileName() .Render(container => { container @@ -414,7 +402,6 @@ namespace QuestPDF.Examples RenderingTest .Create() .PageSize(500, 175) - .FileName() .Render(container => { container @@ -441,7 +428,6 @@ namespace QuestPDF.Examples RenderingTest .Create() .PageSize(300, 300) - .FileName() .Render(container => { container @@ -481,7 +467,6 @@ namespace QuestPDF.Examples RenderingTest .Create() .PageSize(300, 150) - .FileName() .Render(container => { container @@ -502,7 +487,6 @@ namespace QuestPDF.Examples RenderingTest .Create() .PageSize(300, 175) - .FileName() .Render(container => { container @@ -554,7 +538,6 @@ namespace QuestPDF.Examples RenderingTest .Create() .PageSize(300, 200) - .FileName() .Render(container => { container @@ -582,7 +565,6 @@ namespace QuestPDF.Examples RenderingTest .Create() .PageSize(650, 450) - .FileName() .Render(container => { container @@ -621,7 +603,6 @@ namespace QuestPDF.Examples RenderingTest .Create() .PageSize(300, 300) - .FileName() .Render(container => { container @@ -648,7 +629,6 @@ namespace QuestPDF.Examples RenderingTest .Create() .PageSize(300, 300) - .FileName() .Render(container => { container @@ -680,7 +660,6 @@ namespace QuestPDF.Examples RenderingTest .Create() .PageSize(350, 350) - .FileName() .Render(container => { container @@ -722,7 +701,6 @@ namespace QuestPDF.Examples RenderingTest .Create() .PageSize(200, 200) - .FileName() .Render(container => { container @@ -748,7 +726,6 @@ namespace QuestPDF.Examples RenderingTest .Create() .PageSize(400, 350) - .FileName() .Render(container => { container @@ -785,7 +762,6 @@ namespace QuestPDF.Examples RenderingTest .Create() .PageSize(500, 225) - .FileName() .Render(container => { container @@ -839,7 +815,6 @@ namespace QuestPDF.Examples RenderingTest .Create() .PageSize(600, 310) - .FileName() .Render(container => { container diff --git a/QuestPDF.Examples/Engine/RenderingTest.cs b/QuestPDF.Examples/Engine/RenderingTest.cs index e8aacf2..6279a99 100644 --- a/QuestPDF.Examples/Engine/RenderingTest.cs +++ b/QuestPDF.Examples/Engine/RenderingTest.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Runtime.CompilerServices; using QuestPDF.Elements; using QuestPDF.Fluent; +using QuestPDF.Helpers; using QuestPDF.Infrastructure; namespace QuestPDF.Examples.Engine @@ -25,12 +26,12 @@ namespace QuestPDF.Examples.Engine } - public static RenderingTest Create() + public static RenderingTest Create([CallerMemberName] string fileName = "test") { - return new RenderingTest(); + return new RenderingTest().FileName(fileName); } - public RenderingTest FileName([CallerMemberName] string fileName = "test") + public RenderingTest FileName(string fileName) { FileNamePrefix = fileName; return this; @@ -67,12 +68,26 @@ namespace QuestPDF.Examples.Engine public void Render(Action content) { - var container = new Container(); - content(container); + RenderDocument(container => + { + container.Page(page => + { + page.Size(new PageSize(Size.Width, Size.Height)); + page.Content().Container().Background(Colors.White).Element(content); + }); + }); + } + public void RenderDocument(Action content) + { var maxPages = ResultType == RenderingTestResult.Pdf ? 1000 : 10; - var document = new SimpleDocument(container, Size, maxPages); + var document = new SimpleDocument(content, maxPages); + Render(document); + } + + private void Render(IDocument document) + { if (ResultType == RenderingTestResult.Images) { Func fileNameSchema = i => $"{FileNamePrefix}-${i}.png"; diff --git a/QuestPDF.Examples/Engine/SimpleDocument.cs b/QuestPDF.Examples/Engine/SimpleDocument.cs index 9e2feb7..25d3887 100644 --- a/QuestPDF.Examples/Engine/SimpleDocument.cs +++ b/QuestPDF.Examples/Engine/SimpleDocument.cs @@ -1,4 +1,5 @@ -using QuestPDF.Drawing; +using System; +using QuestPDF.Drawing; using QuestPDF.Elements; using QuestPDF.Fluent; using QuestPDF.Helpers; @@ -10,14 +11,12 @@ namespace QuestPDF.Examples.Engine { public const int ImageScalingFactor = 2; - private IContainer Container { get; } - private Size Size { get; } + private Action Content { get; } private int MaxPages { get; } - public SimpleDocument(IContainer container, Size size, int maxPages) + public SimpleDocument(Action content, int maxPages) { - Container = container; - Size = size; + Content = content; MaxPages = maxPages; } @@ -32,11 +31,7 @@ namespace QuestPDF.Examples.Engine public void Compose(IDocumentContainer container) { - container.Page(page => - { - page.Size(new PageSize(Size.Width, Size.Height)); - page.Content().Container().Background(Colors.White).Element(Container as Container); - }); + Content(container); } } } \ No newline at end of file diff --git a/QuestPDF.Examples/EnsureSpaceExample.cs b/QuestPDF.Examples/EnsureSpaceExample.cs new file mode 100644 index 0000000..0808774 --- /dev/null +++ b/QuestPDF.Examples/EnsureSpaceExample.cs @@ -0,0 +1,53 @@ +using NUnit.Framework; +using QuestPDF.Examples.Engine; +using QuestPDF.Fluent; +using QuestPDF.Helpers; +using QuestPDF.Infrastructure; + +namespace QuestPDF.Examples +{ + public class EnsureSpaceExample + { + [Test] + public void EnsureSpaceWith() + { + RenderingTest + .Create() + .ProduceImages() + .ShowResults() + .RenderDocument(container => + { + container.Page(page => + { + page.Margin(20); + page.Size(PageSizes.A7.Landscape()); + page.Background(Colors.White); + + page.Header().Text("With ensure space", TextStyle.Default.SemiBold()); + + page.Content().Stack(stack => + { + stack + .Item() + .ExtendHorizontal() + .Height(75) + .Background(Colors.Grey.Lighten2); + + stack + .Item() + .EnsureSpace(100) + .Text(Placeholders.LoremIpsum()); + }); + + page.Footer().Text(text => + { + text.Span("Page "); + text.CurrentPageNumber(); + text.Span(" out of "); + text.TotalPages(); + }); + }); + }); + } + } +} \ No newline at end of file diff --git a/QuestPDF.Examples/FrameExample.cs b/QuestPDF.Examples/FrameExample.cs index f8d8ae0..614aa54 100644 --- a/QuestPDF.Examples/FrameExample.cs +++ b/QuestPDF.Examples/FrameExample.cs @@ -28,7 +28,6 @@ namespace QuestPDF.Examples RenderingTest .Create() .PageSize(550, 400) - .FileName() .ShowResults() .Render(container => { diff --git a/QuestPDF.Examples/InlinedExamples.cs b/QuestPDF.Examples/InlinedExamples.cs index 5b8f4a2..adf4e68 100644 --- a/QuestPDF.Examples/InlinedExamples.cs +++ b/QuestPDF.Examples/InlinedExamples.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using System.Reflection.Metadata.Ecma335; using NUnit.Framework; using QuestPDF.Examples.Engine; using QuestPDF.Fluent; @@ -16,7 +17,6 @@ namespace QuestPDF.Examples RenderingTest .Create() .PageSize(800, 650) - .FileName() .ProduceImages() .ShowResults() .Render(container => @@ -81,5 +81,42 @@ namespace QuestPDF.Examples }); }); } + + [Test] + public void Inline_AlignLeft_BaselineBottom() + { + RenderingTest + .Create() + .PageSize(400, 250) + .ProduceImages() + .ShowResults() + .Render(container => + { + container + .Padding(20) + .Border(1) + .Background(Colors.Grey.Lighten3) + .Inlined(inlined => + { + inlined.VerticalSpacing(50); + inlined.HorizontalSpacing(20); + inlined.AlignSpaceAround(); + inlined.BaselineTop(); + + foreach (var _ in Enumerable.Range(0, 20)) + inlined.Item().Element(RandomBlock); + }); + }); + + void RandomBlock(IContainer container) + { + container + .Width(Placeholders.Random.Next(1, 5) * 20) + .Height(Placeholders.Random.Next(1, 5) * 20) + .Border(1) + .BorderColor(Colors.Grey.Darken2) + .Background(Placeholders.BackgroundColor()); + } + } } } \ No newline at end of file diff --git a/QuestPDF.Examples/LoremPicsumExample.cs b/QuestPDF.Examples/LoremPicsumExample.cs index 3919bec..c20094a 100644 --- a/QuestPDF.Examples/LoremPicsumExample.cs +++ b/QuestPDF.Examples/LoremPicsumExample.cs @@ -36,7 +36,6 @@ namespace QuestPDF.Examples RenderingTest .Create() .PageSize(350, 280) - .FileName() .Render(container => { container diff --git a/QuestPDF.Examples/Padding.cs b/QuestPDF.Examples/Padding.cs index 6d7bfca..e20b71b 100644 --- a/QuestPDF.Examples/Padding.cs +++ b/QuestPDF.Examples/Padding.cs @@ -13,7 +13,6 @@ namespace QuestPDF.Examples RenderingTest .Create() .PageSize(300, 300) - .FileName() .ShowResults() .Render(container => { @@ -37,7 +36,6 @@ namespace QuestPDF.Examples RenderingTest .Create() .PageSize(200, 150) - .FileName() .Render(container => { container @@ -62,7 +60,6 @@ namespace QuestPDF.Examples RenderingTest .Create() .PageSize(200, 150) - .FileName() .Render(container => { container @@ -113,7 +110,6 @@ namespace QuestPDF.Examples RenderingTest .Create() .PageSize(200, 150) - .FileName() .Render(container => { container diff --git a/QuestPDF.Examples/ShowOnceExample.cs b/QuestPDF.Examples/ShowOnceExample.cs new file mode 100644 index 0000000..72e4727 --- /dev/null +++ b/QuestPDF.Examples/ShowOnceExample.cs @@ -0,0 +1,54 @@ +using NUnit.Framework; +using QuestPDF.Examples.Engine; +using QuestPDF.Fluent; +using QuestPDF.Helpers; +using QuestPDF.Infrastructure; + +namespace QuestPDF.Examples +{ + public class ShowOnceExample + { + [Test] + public void ShowOnce() + { + RenderingTest + .Create() + .ProduceImages() + .ShowResults() + .RenderDocument(container => + { + container.Page(page => + { + page.Margin(20); + page.Size(PageSizes.A7.Landscape()); + page.Background(Colors.White); + + page.Header().Text("With show once", TextStyle.Default.SemiBold()); + + page.Content().PaddingVertical(5).Row(row => + { + row.RelativeColumn() + .Background(Colors.Grey.Lighten2) + .Border(1) + .Padding(5) + .ShowOnce() + .Text(Placeholders.Label()); + + row.RelativeColumn(2) + .Border(1) + .Padding(5) + .Text(Placeholders.Paragraph()); + }); + + page.Footer().Text(text => + { + text.Span("Page "); + text.CurrentPageNumber(); + text.Span(" out of "); + text.TotalPages(); + }); + }); + }); + } + } +} \ No newline at end of file diff --git a/QuestPDF.Examples/SkipOnceExample.cs b/QuestPDF.Examples/SkipOnceExample.cs new file mode 100644 index 0000000..8e73e9d --- /dev/null +++ b/QuestPDF.Examples/SkipOnceExample.cs @@ -0,0 +1,47 @@ +using NUnit.Framework; +using QuestPDF.Examples.Engine; +using QuestPDF.Fluent; +using QuestPDF.Helpers; +using QuestPDF.Infrastructure; + +namespace QuestPDF.Examples +{ + public class SkipOnceExample + { + [Test] + public void SkipOnce() + { + RenderingTest + .Create() + .ProduceImages() + .ShowResults() + .RenderDocument(container => + { + container.Page(page => + { + page.Margin(20); + page.Size(PageSizes.A7.Landscape()); + page.Background(Colors.White); + + page.Header().Stack(stack => + { + stack.Item().ShowOnce().Text("This header is visible on the first page."); + stack.Item().SkipOnce().Text("This header is visible on the second page and all following."); + }); + + page.Content() + .PaddingVertical(10) + .Text(Placeholders.Paragraphs(), TextStyle.Default.Color(Colors.Grey.Medium)); + + page.Footer().Text(text => + { + text.Span("Page "); + text.CurrentPageNumber(); + text.Span(" out of "); + text.TotalPages(); + }); + }); + }); + } + } +} \ No newline at end of file diff --git a/QuestPDF.Examples/TextBenchmark.cs b/QuestPDF.Examples/TextBenchmark.cs index 990a370..301df11 100644 --- a/QuestPDF.Examples/TextBenchmark.cs +++ b/QuestPDF.Examples/TextBenchmark.cs @@ -21,7 +21,6 @@ namespace QuestPDF.Examples RenderingTest .Create() .PageSize(PageSizes.A4) - .FileName() .ProducePdf() .ShowResults() .Render(x => ComposeBook(x, chapters)); @@ -43,8 +42,7 @@ namespace QuestPDF.Examples RenderingTest .Create() .PageSize(PageSizes.A4) - .FileName() - .ProducePdf() + .ProducePdf() .Render(x => ComposeBook(x, chapters)); } diff --git a/QuestPDF.Examples/TextExamples.cs b/QuestPDF.Examples/TextExamples.cs index b01400f..9d1b82e 100644 --- a/QuestPDF.Examples/TextExamples.cs +++ b/QuestPDF.Examples/TextExamples.cs @@ -17,7 +17,7 @@ namespace QuestPDF.Examples RenderingTest .Create() .PageSize(500, 300) - .FileName() + .ProduceImages() .ShowResults() .Render(container => @@ -42,7 +42,6 @@ namespace QuestPDF.Examples RenderingTest .Create() .PageSize(500, 300) - .FileName() .ProduceImages() .ShowResults() .Render(container => @@ -71,7 +70,6 @@ namespace QuestPDF.Examples RenderingTest .Create() .PageSize(500, 200) - .FileName() .ProduceImages() .ShowResults() .Render(container => @@ -103,7 +101,6 @@ namespace QuestPDF.Examples RenderingTest .Create() .PageSize(PageSizes.A4) - .FileName() .ProducePdf() .ShowResults() .Render(container => @@ -134,7 +131,6 @@ namespace QuestPDF.Examples RenderingTest .Create() .PageSize(PageSizes.A4) - .FileName() .ProducePdf() .ShowResults() .Render(container => @@ -164,7 +160,6 @@ namespace QuestPDF.Examples RenderingTest .Create() .PageSize(PageSizes.A4) - .FileName() .ProducePdf() .ShowResults() .Render(container => @@ -228,7 +223,6 @@ namespace QuestPDF.Examples RenderingTest .Create() .PageSize(PageSizes.A4) - .FileName() .ProducePdf() .ShowResults() .Render(container => From b04420819590873a5fe1f37a3467c538a3032818 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Zi=C4=85bek?= Date: Sat, 23 Oct 2021 03:02:18 +0200 Subject: [PATCH 16/21] Updated nuget details --- QuestPDF/QuestPDF.csproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/QuestPDF/QuestPDF.csproj b/QuestPDF/QuestPDF.csproj index fcf853c..bd435f7 100644 --- a/QuestPDF/QuestPDF.csproj +++ b/QuestPDF/QuestPDF.csproj @@ -4,9 +4,9 @@ MarcinZiabek CodeFlint QuestPDF - 2021.10.1 + 2021.11.0-beta 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. - Enhanced text rendering capabilities. Improved rendering performance. + Implemented new elements: SkipOnce and Inlined. Added possibility to define global, page-wide test style. Improved exception handling experience. 8 true Logo.png @@ -15,7 +15,7 @@ https://github.com/QuestPDF/library.git git Marcin Ziąbek, QuestPDF contributors - pdf file export generate generation tool create creation render portable document format quest html library converter open source free standard core + pdf report file export generate generation tool create creation render portable document format quest html library converter open source free standard core MIT enable net462;netstandard2.0;netcoreapp2.0;netcoreapp3.0 From 011668fb4cef2f95d603fb6638c20eb9829fba12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Zi=C4=85bek?= Date: Fri, 29 Oct 2021 01:23:46 +0200 Subject: [PATCH 17/21] Image: added support for common overloads --- QuestPDF.Examples/ComplexLayoutBenchmark.cs | 57 ++++++++++++++++++++ QuestPDF.Examples/Engine/RenderingTest.cs | 10 +++- QuestPDF.Examples/ImageExamples.cs | 41 ++++++++++++++ QuestPDF.Examples/QuestPDF.Examples.csproj | 3 ++ QuestPDF.Examples/logo.png | Bin 0 -> 27032 bytes QuestPDF/Fluent/ImageExtensions.cs | 27 ++++++++-- QuestPDF/QuestPDF.csproj | 2 +- 7 files changed, 133 insertions(+), 7 deletions(-) create mode 100644 QuestPDF.Examples/ComplexLayoutBenchmark.cs create mode 100644 QuestPDF.Examples/ImageExamples.cs create mode 100644 QuestPDF.Examples/logo.png diff --git a/QuestPDF.Examples/ComplexLayoutBenchmark.cs b/QuestPDF.Examples/ComplexLayoutBenchmark.cs new file mode 100644 index 0000000..8f7df56 --- /dev/null +++ b/QuestPDF.Examples/ComplexLayoutBenchmark.cs @@ -0,0 +1,57 @@ +using NUnit.Framework; +using QuestPDF.Examples.Engine; +using QuestPDF.Fluent; +using QuestPDF.Helpers; +using QuestPDF.Infrastructure; + +namespace QuestPDF.Examples +{ + public class ComplexLayoutBenchmark + { + [Test] + public void ComplexLayout() + { + RenderingTest + .Create() + .PageSize(PageSizes.A4) + .ProducePdf() + .ShowResults() + .Render(x => x.Image(new byte[] { 1, 2, 3 })); + //.Render(x => GenerateStructure(x, 16)); + } + + private void GenerateStructure(IContainer container, int level) + { + if (level <= 0) + { + container.Background(Placeholders.BackgroundColor()).Height(10); + return; + } + + level--; + + if (level % 3 == 0) + { + container + .Border(level / 10f) + .BorderColor(Colors.Black) + .Row(row => + { + row.RelativeColumn().Element(x => GenerateStructure(x, level)); + row.RelativeColumn().Element(x => GenerateStructure(x, level)); + }); + } + else + { + container + .Border(level / 10f) + .BorderColor(Colors.Black) + .Stack(stack => + { + stack.Item().Element(x => GenerateStructure(x, level)); + stack.Item().Element(x => GenerateStructure(x, level)); + }); + } + } + } +} \ No newline at end of file diff --git a/QuestPDF.Examples/Engine/RenderingTest.cs b/QuestPDF.Examples/Engine/RenderingTest.cs index 6279a99..3309c4a 100644 --- a/QuestPDF.Examples/Engine/RenderingTest.cs +++ b/QuestPDF.Examples/Engine/RenderingTest.cs @@ -18,6 +18,7 @@ namespace QuestPDF.Examples.Engine { private string FileNamePrefix = "test"; private Size Size { get; set; } + private int? MaxPagesThreshold { get; set; } private bool ShowResult { get; set; } private RenderingTestResult ResultType { get; set; } = RenderingTestResult.Images; @@ -77,11 +78,16 @@ namespace QuestPDF.Examples.Engine }); }); } + + public void MaxPages(int value) + { + MaxPagesThreshold = value; + } public void RenderDocument(Action content) { - var maxPages = ResultType == RenderingTestResult.Pdf ? 1000 : 10; - var document = new SimpleDocument(content, maxPages); + MaxPagesThreshold ??= ResultType == RenderingTestResult.Pdf ? 1000 : 10; + var document = new SimpleDocument(content, MaxPagesThreshold.Value); Render(document); } diff --git a/QuestPDF.Examples/ImageExamples.cs b/QuestPDF.Examples/ImageExamples.cs new file mode 100644 index 0000000..89801bf --- /dev/null +++ b/QuestPDF.Examples/ImageExamples.cs @@ -0,0 +1,41 @@ +using System.IO; +using NUnit.Framework; +using QuestPDF.Examples.Engine; +using QuestPDF.Fluent; +using QuestPDF.Helpers; + +namespace QuestPDF.Examples +{ + public class ImageExamples + { + [Test] + public void LoadingImage() + { + RenderingTest + .Create() + .PageSize(PageSizes.A5) + .ProducePdf() + .ShowResults() + .Render(page => + { + page.Padding(25).Stack(stack => + { + stack.Spacing(25); + stack.Item().Image(File.ReadAllBytes("logo.png")); + stack.Item().Image("logo.png"); + }); + }); + } + + [Test] + public void Exception() + { + RenderingTest + .Create() + .PageSize(PageSizes.A5) + .ProducePdf() + .ShowResults() + .Render(page => page.Image("non_existent.png")); + } + } +} \ No newline at end of file diff --git a/QuestPDF.Examples/QuestPDF.Examples.csproj b/QuestPDF.Examples/QuestPDF.Examples.csproj index 5dc14b7..a7a0014 100644 --- a/QuestPDF.Examples/QuestPDF.Examples.csproj +++ b/QuestPDF.Examples/QuestPDF.Examples.csproj @@ -23,6 +23,9 @@ PreserveNewest + + PreserveNewest + diff --git a/QuestPDF.Examples/logo.png b/QuestPDF.Examples/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..a0d780f366898ad01679f708814b0c3bcae511b5 GIT binary patch literal 27032 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 T>-ll{hf2>>wN*-$%>(}r1j0kU literal 0 HcmV?d00001 diff --git a/QuestPDF/Fluent/ImageExtensions.cs b/QuestPDF/Fluent/ImageExtensions.cs index 7a3fe4e..bc29883 100644 --- a/QuestPDF/Fluent/ImageExtensions.cs +++ b/QuestPDF/Fluent/ImageExtensions.cs @@ -1,4 +1,6 @@ using System; +using System.IO; +using QuestPDF.Drawing.Exceptions; using QuestPDF.Elements; using QuestPDF.Infrastructure; using SkiaSharp; @@ -7,12 +9,29 @@ namespace QuestPDF.Fluent { public static class ImageExtensions { - public static void Image(this IContainer parent, byte[] data, ImageScaling scaling = ImageScaling.FitWidth) + public static void Image(this IContainer parent, byte[] imageData, ImageScaling scaling = ImageScaling.FitWidth) { - if (data == null) - return; + var image = SKImage.FromEncodedData(imageData); + parent.Image(image, scaling); + } + + public static void Image(this IContainer parent, string filePath, ImageScaling scaling = ImageScaling.FitWidth) + { + var image = SKImage.FromEncodedData(filePath); + parent.Image(image, scaling); + } + + public static void Image(this IContainer parent, Stream fileStream, ImageScaling scaling = ImageScaling.FitWidth) + { + var image = SKImage.FromEncodedData(fileStream); + parent.Image(image, scaling); + } + + private static void Image(this IContainer parent, SKImage image, ImageScaling scaling = ImageScaling.FitWidth) + { + if (image == null) + throw new DocumentComposeException("Cannot load or decode provided image."); - var image = SKImage.FromEncodedData(data); var aspectRatio = image.Width / (float)image.Height; var imageElement = new Image diff --git a/QuestPDF/QuestPDF.csproj b/QuestPDF/QuestPDF.csproj index bd435f7..e74e2fb 100644 --- a/QuestPDF/QuestPDF.csproj +++ b/QuestPDF/QuestPDF.csproj @@ -4,7 +4,7 @@ MarcinZiabek CodeFlint QuestPDF - 2021.11.0-beta + 2021.11.0-beta2 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. Implemented new elements: SkipOnce and Inlined. Added possibility to define global, page-wide test style. Improved exception handling experience. 8 From d260fa75871ccf30881516408d761949639ec220 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Zi=C4=85bek?= Date: Fri, 29 Oct 2021 01:30:28 +0200 Subject: [PATCH 18/21] Added generating symbols --- QuestPDF/QuestPDF.csproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/QuestPDF/QuestPDF.csproj b/QuestPDF/QuestPDF.csproj index e74e2fb..0d388ce 100644 --- a/QuestPDF/QuestPDF.csproj +++ b/QuestPDF/QuestPDF.csproj @@ -19,6 +19,8 @@ MIT enable net462;netstandard2.0;netcoreapp2.0;netcoreapp3.0 + true + snupkg From 22cdad69b762d1aa72352fa533e5b08771fd9d0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Zi=C4=85bek?= Date: Wed, 3 Nov 2021 21:21:23 +0100 Subject: [PATCH 19/21] Text element improvement: null string behaves like an empty text, not throws an exception --- QuestPDF/Fluent/TextExtensions.cs | 41 ++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/QuestPDF/Fluent/TextExtensions.cs b/QuestPDF/Fluent/TextExtensions.cs index 71036ab..8282e39 100644 --- a/QuestPDF/Fluent/TextExtensions.cs +++ b/QuestPDF/Fluent/TextExtensions.cs @@ -50,8 +50,11 @@ namespace QuestPDF.Fluent TextBlocks.Last().Children.Add(item); } - public void Span(string text, TextStyle? style = null) + public void Span(string? text, TextStyle? style = null) { + if (IsNullOrEmpty(text)) + return; + style ??= TextStyle.Default; var items = text @@ -76,16 +79,12 @@ namespace QuestPDF.Fluent .ForEach(TextBlocks.Add); } - public void Line(string text, TextStyle? style = null) + public void Line(string? text, TextStyle? style = null) { + text ??= string.Empty; Span(text + Environment.NewLine, style); } - - public void Line(string text) - { - Span(text + Environment.NewLine); - } - + public void EmptyLine() { Span(Environment.NewLine); @@ -93,6 +92,9 @@ namespace QuestPDF.Fluent private void PageNumber(string slotName, TextStyle? style = null) { + if (IsNullOrEmpty(slotName)) + throw new ArgumentException(nameof(slotName)); + style ??= TextStyle.Default; AddItemToLastTextBlock(new TextBlockPageNumber() @@ -114,11 +116,20 @@ namespace QuestPDF.Fluent public void PageNumberOfLocation(string locationName, TextStyle? style = null) { + if (IsNullOrEmpty(locationName)) + throw new ArgumentException(nameof(locationName)); + PageNumber(locationName, style); } - public void InternalLocation(string text, string locationName, TextStyle? style = null) + public void InternalLocation(string? text, string locationName, TextStyle? style = null) { + if (IsNullOrEmpty(text)) + return; + + if (IsNullOrEmpty(locationName)) + throw new ArgumentException(nameof(locationName)); + style ??= TextStyle.Default; AddItemToLastTextBlock(new TextBlockInternalLink @@ -129,8 +140,14 @@ namespace QuestPDF.Fluent }); } - public void ExternalLocation(string text, string url, TextStyle? style = null) + public void ExternalLocation(string? text, string url, TextStyle? style = null) { + if (IsNullOrEmpty(text)) + return; + + if (IsNullOrEmpty(url)) + throw new ArgumentException(nameof(url)); + style ??= TextStyle.Default; AddItemToLastTextBlock(new TextBlockExternalLink @@ -183,9 +200,9 @@ namespace QuestPDF.Fluent descriptor.Compose(element); } - public static void Text(this IContainer element, object text, TextStyle? style = null) + public static void Text(this IContainer element, object? text, TextStyle? style = null) { - element.Text(x => x.Span(text.ToString(), style)); + element.Text(x => x.Span(text?.ToString(), style)); } } } \ No newline at end of file From 520bfc840cb3d31569517428cfd761d65e3bb387 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Zi=C4=85bek?= Date: Wed, 3 Nov 2021 21:22:08 +0100 Subject: [PATCH 20/21] Improved code benchmarking (removed an unnecessary cost of image placeholder generation) --- QuestPDF.Examples/ImageExamples.cs | 8 ++++- QuestPDF.ReportSample/DataSource.cs | 4 +-- .../Layouts/ImagePlaceholder.cs | 20 ++++++++++++ .../Layouts/ImageTemplate.cs | 32 ------------------- .../Layouts/PhotoTemplate.cs | 6 ++-- .../Layouts/SectionTemplate.cs | 4 +-- QuestPDF.ReportSample/Tests.cs | 14 ++++++-- 7 files changed, 45 insertions(+), 43 deletions(-) create mode 100644 QuestPDF.ReportSample/Layouts/ImagePlaceholder.cs delete mode 100644 QuestPDF.ReportSample/Layouts/ImageTemplate.cs diff --git a/QuestPDF.Examples/ImageExamples.cs b/QuestPDF.Examples/ImageExamples.cs index 89801bf..ad24b57 100644 --- a/QuestPDF.Examples/ImageExamples.cs +++ b/QuestPDF.Examples/ImageExamples.cs @@ -21,8 +21,14 @@ namespace QuestPDF.Examples page.Padding(25).Stack(stack => { stack.Spacing(25); - stack.Item().Image(File.ReadAllBytes("logo.png")); + stack.Item().Image("logo.png"); + + var binaryData = File.ReadAllBytes("logo.png"); + stack.Item().Image(binaryData); + + using var stream = new FileStream("logo.png", FileMode.Open); + stack.Item().Image(stream); }); }); } diff --git a/QuestPDF.ReportSample/DataSource.cs b/QuestPDF.ReportSample/DataSource.cs index 062cea3..6f92802 100644 --- a/QuestPDF.ReportSample/DataSource.cs +++ b/QuestPDF.ReportSample/DataSource.cs @@ -113,8 +113,8 @@ namespace QuestPDF.ReportSample Date = DateTime.Now - TimeSpan.FromDays(Helpers.Random.NextDouble() * 100), Location = Helpers.RandomLocation(), - MapContextSource = x => Placeholders.Image(400, 300), - MapDetailsSource = x => Placeholders.Image(400, 300) + MapContextSource = Placeholders.Image, + MapDetailsSource = Placeholders.Image }; } } diff --git a/QuestPDF.ReportSample/Layouts/ImagePlaceholder.cs b/QuestPDF.ReportSample/Layouts/ImagePlaceholder.cs new file mode 100644 index 0000000..0a2a63a --- /dev/null +++ b/QuestPDF.ReportSample/Layouts/ImagePlaceholder.cs @@ -0,0 +1,20 @@ +using QuestPDF.Fluent; +using QuestPDF.Helpers; +using QuestPDF.Infrastructure; + +namespace QuestPDF.ReportSample.Layouts +{ + public class ImagePlaceholder : IComponent + { + public static bool Solid { get; set; } = false; + + public void Compose(IContainer container) + { + if (Solid) + container.Background(Placeholders.Color()); + + else + container.Image(Placeholders.Image); + } + } +} \ No newline at end of file diff --git a/QuestPDF.ReportSample/Layouts/ImageTemplate.cs b/QuestPDF.ReportSample/Layouts/ImageTemplate.cs deleted file mode 100644 index 0e3eca8..0000000 --- a/QuestPDF.ReportSample/Layouts/ImageTemplate.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using QuestPDF.Fluent; -using QuestPDF.Helpers; -using QuestPDF.Infrastructure; - -namespace QuestPDF.ReportSample.Layouts -{ - public class ImageTemplate : IComponent - { - private Func Source { get; } - private float AspectRatio { get; } - - public ImageTemplate(byte[] source, float aspectRatio = 1.333333f) : this(_ => source, aspectRatio) - { - - } - - public ImageTemplate(Func source, float aspectRatio = 1.333333f) - { - Source = source; - AspectRatio = aspectRatio; - } - - public void Compose(IContainer container) - { - container - .AspectRatio(AspectRatio) - .Background(Colors.Grey.Lighten3) - .Image(Source(Size.Zero)); - } - } -} \ No newline at end of file diff --git a/QuestPDF.ReportSample/Layouts/PhotoTemplate.cs b/QuestPDF.ReportSample/Layouts/PhotoTemplate.cs index f2a4c7d..d8fa483 100644 --- a/QuestPDF.ReportSample/Layouts/PhotoTemplate.cs +++ b/QuestPDF.ReportSample/Layouts/PhotoTemplate.cs @@ -30,14 +30,14 @@ namespace QuestPDF.ReportSample.Layouts container .Row(row => { - row.RelativeColumn(2).AspectRatio(4 / 3f).Image(Placeholders.Image); + row.RelativeColumn(2).AspectRatio(4 / 3f).Component(); row.RelativeColumn().PaddingLeft(5).Stack(stack => { stack.Spacing(7f); - stack.Item().AspectRatio(4 / 3f).Image(Placeholders.Image); - stack.Item().AspectRatio(4 / 3f).Image(Placeholders.Image); + stack.Item().AspectRatio(4 / 3f).Component(); + stack.Item().AspectRatio(4 / 3f).Component(); }); }); } diff --git a/QuestPDF.ReportSample/Layouts/SectionTemplate.cs b/QuestPDF.ReportSample/Layouts/SectionTemplate.cs index 8142da9..98514d0 100644 --- a/QuestPDF.ReportSample/Layouts/SectionTemplate.cs +++ b/QuestPDF.ReportSample/Layouts/SectionTemplate.cs @@ -59,7 +59,7 @@ namespace QuestPDF.ReportSample.Layouts { stack.Spacing(5); - stack.Item().MaxWidth(250).AspectRatio(4 / 3f).Image(Placeholders.Image); + stack.Item().MaxWidth(250).AspectRatio(4 / 3f).Component(); stack.Item().Text(model.Location.Format()); }); } @@ -77,7 +77,7 @@ namespace QuestPDF.ReportSample.Layouts grid.Spacing(5); grid.Columns(3); - model.Photos.ForEach(x => grid.Item().AspectRatio(4 / 3f).Image(Placeholders.Image)); + model.Photos.ForEach(x => grid.Item().AspectRatio(4 / 3f).Component()); }); } } diff --git a/QuestPDF.ReportSample/Tests.cs b/QuestPDF.ReportSample/Tests.cs index 089ad2e..fc5edd3 100644 --- a/QuestPDF.ReportSample/Tests.cs +++ b/QuestPDF.ReportSample/Tests.cs @@ -15,7 +15,7 @@ namespace QuestPDF.ReportSample { var reportModel = DataSource.GetReport(); var report = new StandardReport(reportModel); - + var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"test_result.pdf"); report.GeneratePdf(path); @@ -23,13 +23,16 @@ namespace QuestPDF.ReportSample } [Test] - public void PerformanceBenchmark() + public void PerformanceBenchmark() { // target document length should be around 100 pages // test size const int testSize = 10; const decimal performanceTarget = 1; // documents per second + + // generating placeholder images is slow, for benchmarking reasons, replace images with simple colorful boxes + ImagePlaceholder.Solid = true; // create report models var reports = Enumerable @@ -45,9 +48,14 @@ namespace QuestPDF.ReportSample var sw = new Stopwatch(); sw.Start(); - var totalSize = reports.Select(x => x.GeneratePdf()).Sum(x => (long)x.Length); + var totalSize = BenchmarkAndGenerate(); sw.Stop(); + long BenchmarkAndGenerate() + { + return reports.Select(x => x.GeneratePdf()).Sum(x => (long)x.Length);; + } + // show summary Console.WriteLine($"Total size: {totalSize:N0} bytes"); From 86cfd22316261e70a51a7e265917f5a9d3a97166 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Zi=C4=85bek?= Date: Wed, 3 Nov 2021 21:39:20 +0100 Subject: [PATCH 21/21] 2021.11.0-beta3 --- QuestPDF/QuestPDF.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/QuestPDF/QuestPDF.csproj b/QuestPDF/QuestPDF.csproj index 0d388ce..9019a0c 100644 --- a/QuestPDF/QuestPDF.csproj +++ b/QuestPDF/QuestPDF.csproj @@ -4,7 +4,7 @@ MarcinZiabek CodeFlint QuestPDF - 2021.11.0-beta2 + 2021.11.0-beta3 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. Implemented new elements: SkipOnce and Inlined. Added possibility to define global, page-wide test style. Improved exception handling experience. 8