diff --git a/QuestPDF.Examples/ImageExamples.cs b/QuestPDF.Examples/ImageExamples.cs index b3bed24..91bf3af 100644 --- a/QuestPDF.Examples/ImageExamples.cs +++ b/QuestPDF.Examples/ImageExamples.cs @@ -1,4 +1,5 @@ -using System.IO; +using System; +using System.IO; using NUnit.Framework; using QuestPDF.Drawing.Exceptions; using QuestPDF.Examples.Engine; @@ -34,6 +35,21 @@ namespace QuestPDF.Examples }); } + [Test] + public void DynamicImage() + { + RenderingTest + .Create() + .PageSize(450, 350) + .ProducePdf() + .ShowResults() + .Render(page => + { + page.Padding(25) + .Image(Placeholders.Image); + }); + } + [Test] public void Exception() { @@ -47,5 +63,43 @@ namespace QuestPDF.Examples .Render(page => page.Image("non_existent.png")); }); } + + [Test] + public void ReusingTheSameImageFileShouldBePossible() + { + var fileName = Path.GetTempFileName() + ".jpg"; + + try + { + var image = Placeholders.Image(300, 100); + + using var file = File.Create(fileName); + file.Write(image); + file.Dispose(); + + RenderingTest + .Create() + .ProducePdf() + .PageSize(PageSizes.A4) + .ShowResults() + .Render(container => + { + container + .Padding(20) + .Column(column => + { + column.Spacing(20); + + column.Item().Image(fileName); + column.Item().Image(fileName); + column.Item().Image(fileName); + }); + }); + } + finally + { + File.Delete(fileName); + } + } } } \ No newline at end of file diff --git a/QuestPDF.Examples/RowExamples.cs b/QuestPDF.Examples/RowExamples.cs index cb01f78..57bdbe2 100644 --- a/QuestPDF.Examples/RowExamples.cs +++ b/QuestPDF.Examples/RowExamples.cs @@ -105,5 +105,41 @@ namespace QuestPDF.Examples .Row(row => { }); }); } + + [Test] + public void RowElementForRelativeHeightDivision() + { + RenderingTest + .Create() + .ProduceImages() + .ShowResults() + .MaxPages(100) + .PageSize(250, 400) + .Render(container => + { + container + .Padding(25) + .AlignLeft() + .RotateRight() + .Row(row => + { + row.Spacing(20); + + row.RelativeItem(1).Element(Content); + row.RelativeItem(2).Element(Content); + row.RelativeItem(3).Element(Content); + + void Content(IContainer container) + { + container + .RotateLeft() + .Border(1) + .Background(Placeholders.BackgroundColor()) + .Padding(5) + .Text(Placeholders.Label()); + } + }); + }); + } } } \ No newline at end of file diff --git a/QuestPDF.Examples/TextExamples.cs b/QuestPDF.Examples/TextExamples.cs index 1566c3e..6423f8f 100644 --- a/QuestPDF.Examples/TextExamples.cs +++ b/QuestPDF.Examples/TextExamples.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using System.Text; using NUnit.Framework; @@ -658,6 +658,38 @@ namespace QuestPDF.Examples }); } + [Test] + public void WordWrappingStability() + { + // instruction: check if any characters repeat when performing the word-wrapping algorithm + + RenderingTest + .Create() + .PageSize(PageSizes.A4) + .ProducePdf() + .ShowResults() + .Render(container => + { + var text = "Lorem ipsum dolor sit amet consectetuer"; + + container + .Padding(20) + .Column(column => + { + column.Spacing(10); + + foreach (var width in Enumerable.Range(25, 200)) + { + column + .Item() + .MaxWidth(width) + .Background(Colors.Grey.Lighten3) + .Text(text); + } + }); + }); + } + [Test] public void TextDirectionality() { diff --git a/QuestPDF/Elements/DynamicImage.cs b/QuestPDF/Elements/DynamicImage.cs index 1837db5..e3ac262 100644 --- a/QuestPDF/Elements/DynamicImage.cs +++ b/QuestPDF/Elements/DynamicImage.cs @@ -24,13 +24,8 @@ namespace QuestPDF.Elements if (imageData == null) return; - var imageElement = new Image - { - InternalImage = SKImage.FromEncodedData(imageData) - }; - - imageElement.InjectDependencies(PageContext, Canvas); - imageElement.Draw(availableSpace); + using var image = SKImage.FromEncodedData(imageData); + Canvas.DrawImage(image, Position.Zero, availableSpace); } } } \ No newline at end of file diff --git a/QuestPDF/Elements/Inlined.cs b/QuestPDF/Elements/Inlined.cs index 9547504..dabab48 100644 --- a/QuestPDF/Elements/Inlined.cs +++ b/QuestPDF/Elements/Inlined.cs @@ -228,7 +228,7 @@ namespace QuestPDF.Elements break; var element = queue.Peek(); - var size = element.Measure(Size.Max); + var size = element.Measure(new Size(availableSize.Width, Size.Max.Height)); if (size.Type == SpacePlanType.Wrap) break; diff --git a/QuestPDF/Elements/Table/Table.cs b/QuestPDF/Elements/Table/Table.cs index 9a4be8d..8decf6c 100644 --- a/QuestPDF/Elements/Table/Table.cs +++ b/QuestPDF/Elements/Table/Table.cs @@ -27,6 +27,7 @@ namespace QuestPDF.Elements.Table // inner table: list of all cells that ends at the corresponding row private TableCell[][] CellsCache { get; set; } private int MaxRow { get; set; } + private int MaxRowSpan { get; set; } internal override IEnumerable GetChildren() { @@ -64,6 +65,7 @@ namespace QuestPDF.Elements.Table if (Cells.Count == 0) { MaxRow = 0; + MaxRowSpan = 1; CellsCache = Array.Empty(); return; @@ -74,6 +76,7 @@ namespace QuestPDF.Elements.Table .ToDictionary(x => x.Key, x => x.OrderBy(x => x.Column).ToArray()); MaxRow = groups.Max(x => x.Key); + MaxRowSpan = Cells.Max(x => x.RowSpan); CellsCache = Enumerable .Range(0, MaxRow + 1) @@ -211,9 +214,9 @@ namespace QuestPDF.Elements.Table currentRow = cell.Row; } - + // cell visibility optimizations - if (cell.Row > maxRenderingRow) + if (cell.Row > maxRenderingRow + MaxRowSpan) break; // calculate cell position / size @@ -230,14 +233,14 @@ namespace QuestPDF.Elements.Table { maxRenderingRow = Math.Min(maxRenderingRow, cell.Row + cell.RowSpan - 1); } - + // corner case: if cell within the row want to wrap to the next page, do not attempt to render this row if (cellSize.Type == SpacePlanType.Wrap) { maxRenderingRow = Math.Min(maxRenderingRow, cell.Row - 1); continue; } - + // update position of the last row that cell occupies var bottomRow = cell.Row + cell.RowSpan - 1; rowBottomOffsets[bottomRow] = Math.Max(rowBottomOffsets[bottomRow], topOffset + cellSize.Height); diff --git a/QuestPDF/Elements/Text/FontFallback.cs b/QuestPDF/Elements/Text/FontFallback.cs index 5eabe9c..cb215d4 100644 --- a/QuestPDF/Elements/Text/FontFallback.cs +++ b/QuestPDF/Elements/Text/FontFallback.cs @@ -121,7 +121,11 @@ namespace QuestPDF.Elements.Text { foreach (var textBlockItem in textBlockItems) { - if (textBlockItem is TextBlockSpan textBlockSpan and not TextBlockPageNumber) + if (textBlockItem is TextBlockPageNumber or TextBlockElement) + { + yield return textBlockItem; + } + else if (textBlockItem is TextBlockSpan textBlockSpan) { if (!Settings.CheckIfAllTextGlyphsAreAvailable && textBlockSpan.Style.Fallback == null) { @@ -130,19 +134,25 @@ namespace QuestPDF.Elements.Text } var textRuns = textBlockSpan.Text.SplitWithFontFallback(textBlockSpan.Style); - + foreach (var textRun in textRuns) { - yield return new TextBlockSpan + var newElement = textBlockSpan switch { - Text = textRun.Content, - Style = textRun.Style + TextBlockHyperlink hyperlink => new TextBlockHyperlink { Url = hyperlink.Url }, + TextBlockSectionLink sectionLink => new TextBlockSectionLink { SectionName = sectionLink.SectionName }, + TextBlockSpan => new TextBlockSpan() }; + + newElement.Text = textRun.Content; + newElement.Style = textRun.Style; + + yield return newElement; } } else { - yield return textBlockItem; + throw new NotSupportedException(); } } } diff --git a/QuestPDF/Elements/Text/Items/TextBlockSpan.cs b/QuestPDF/Elements/Text/Items/TextBlockSpan.cs index 677fcc0..c2319d7 100644 --- a/QuestPDF/Elements/Text/Items/TextBlockSpan.cs +++ b/QuestPDF/Elements/Text/Items/TextBlockSpan.cs @@ -15,7 +15,7 @@ namespace QuestPDF.Elements.Text.Items { public string Text { get; set; } public TextStyle Style { get; set; } = TextStyle.Default; - public TextShapingResult? TextShapingResult { get; set; } + private TextShapingResult? TextShapingResult { get; set; } private Dictionary<(int startIndex, float availableWidth), TextMeasurementResult?> MeasureCache = new (); protected virtual bool EnableTextCache => true; diff --git a/readme.md b/readme.md index 94bd094..7c3cd3f 100644 --- a/readme.md +++ b/readme.md @@ -17,7 +17,7 @@ It offers a layouting engine designed with a full paging support in mind. The do Unlike other libraries, it does not rely on the HTML-to-PDF conversion which in many cases is not reliable. Instead, it implements its own layouting engine that is optimized to cover all paging-related requirements. -## Please show the value +## Please help by giving a star Choosing a project dependency could be difficult. We need to ensure stability and maintainability of our projects. Surveys show that GitHub stars count play an important factor when assessing library quality. @@ -47,6 +47,7 @@ Special thanks to all companies that decided to sponsor QuestPDF development. Th | Company | Description | |--------------------------------------------------------------------------------------|:--------------------------------------------------------------------------------------------------------------------------------| | | [JetBrains](https://www.jetbrains.com/) supports this project as part of the OSS Power-Ups program. Thank you!
100$ / month | +| | [Mark Gould](https://github.com/markgould) supports this project. Thank you!
100$ / month | [![Sponsor project](https://img.shields.io/badge/%E2%9D%A4%EF%B8%8F%20sponsor-QuestPDF-red)](https://github.com/sponsors/QuestPDF) @@ -62,14 +63,14 @@ Install-Package QuestPDF dotnet add package QuestPDF // Package reference in .csproj file - + ``` [![Nuget version](https://img.shields.io/badge/package%20details-QuestPDF-blue?logo=nuget)](https://www.nuget.org/packages/QuestPDF/) ## Documentation -[![Getting started tutorial]( https://img.shields.io/badge/%F0%9F%9A%80%20read-getting%20started-blue)](https://www.questpdf.com/getting-started.html) +[![Getting started tutorial]( https://img.shields.io/badge/%F0%9F%9A%80%20read-getting%20started-blue)](https://www.questpdf.com/getting-started) A short and easy to follow tutorial showing how to design an invoice document under 200 lines of code. @@ -77,14 +78,14 @@ A short and easy to follow tutorial showing how to design an invoice document un A detailed description of behavior of all available components and how to use them with C# Fluent API. -[![Patterns and Practices](https://img.shields.io/badge/%E2%9C%A8%20read-patterns%20and%20practices-blue)](https://www.questpdf.com/design-patterns.html) +[![Patterns and Practices](https://img.shields.io/badge/%E2%9C%A8%20read-patterns%20and%20practices-blue)](https://www.questpdf.com/design-patterns) Everything that may help you designing great reports and create reusable code that is easy to maintain. ## QuestPDF Previewer The QuestPDF Previewer is a tool designed to simplify and speed up your development lifecycle. First, it shows a preview of your document. But the real magic starts with the hot-reload capability! It observes your code and updates the preview every time you change the implementation. Get real-time results without the need of code recompilation. Save time and enjoy the task! -[![Learn more](https://img.shields.io/badge/%F0%9F%93%96%20Previewer-learn%20more-blue)](https://www.questpdf.com/document-previewer.html) +[![Learn more](https://img.shields.io/badge/%F0%9F%93%96%20Previewer-learn%20more-blue)](https://www.questpdf.com/document-previewer)