├── .editorconfig ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ └── codeql-analysis.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── FUNDING.yml ├── LICENSE ├── QuestPDF.Examples ├── BarcodeExamples.cs ├── CanvasExamples.cs ├── ChartExamples.cs ├── ColumnExamples.cs ├── ComplexLayoutBenchmark.cs ├── ContinousPage.cs ├── DebuggingTesting.cs ├── DefaultTextStyleExample.cs ├── DefaultTextStyleExamples.cs ├── DifferentHeaderOnFirstPageExample.cs ├── ElementExamples.cs ├── Engine │ ├── RenderingTest.cs │ └── SimpleDocument.cs ├── EnsureSpaceExample.cs ├── ExecutionOrderExamples.cs ├── FrameExample.cs ├── ImageExamples.cs ├── InlinedExamples.cs ├── LibreBarcode39-Regular.ttf ├── LineExamples.cs ├── LoremPicsumExample.cs ├── MinimalApiExamples.cs ├── Padding.cs ├── PageBackgroundForegroundExample.cs ├── QuestPDF.Examples.csproj ├── RowExamples.cs ├── ScaleToFitExamples.cs ├── ShowOnceExample.cs ├── SkipOnceExample.cs ├── StopPaging.cs ├── SvgImageExample.cs ├── TableExamples.cs ├── TextBenchmark.cs ├── TextExamples.cs ├── logo.png ├── pdf-icon.svg └── quo-vadis.txt ├── QuestPDF.Previewer.Examples ├── Program.cs └── QuestPDF.Previewer.Examples.csproj ├── QuestPDF.Previewer ├── CommunicationService.cs ├── Helpers.cs ├── InteractiveCanvas.cs ├── PreviewerApp.axaml ├── PreviewerApp.axaml.cs ├── PreviewerCanvas.cs ├── PreviewerControl.cs ├── PreviewerRefreshCommand.cs ├── PreviewerWindow.axaml ├── PreviewerWindow.axaml.cs ├── PreviewerWindowViewModel.cs ├── Program.cs ├── QuestPDF.Previewer.csproj ├── Resources │ └── Logo.png └── readme.md ├── QuestPDF.ReportSample ├── DataSource.cs ├── Helpers.cs ├── Layouts │ ├── DifferentHeadersTemplate.cs │ ├── Helpers.cs │ ├── ImagePlaceholder.cs │ ├── PhotoTemplate.cs │ ├── SectionTemplate.cs │ ├── StandardReport.cs │ └── TableOfContentsTemplate.cs ├── Models.cs ├── PerformanceTests.cs ├── QuestPDF.ReportSample.csproj ├── Resources │ ├── Logo.svg │ └── logo.png ├── Tests.cs └── Typography.cs ├── QuestPDF.UnitTests ├── AlignmentTests.cs ├── AspectRatioTests.cs ├── BackgroundTests.cs ├── BorderTests.cs ├── BoxTests.cs ├── ColumnTests.cs ├── ConstrainedTests.cs ├── DecorationTests.cs ├── DynamicImageTests.cs ├── EnsureSpaceTests.cs ├── ExtendTests.cs ├── ExternalLinkTests.cs ├── FontStyleSetTests.cs ├── GridTests.cs ├── ImageTests.cs ├── InternalLinkTests.cs ├── InternalLocationTests.cs ├── LayersTests.cs ├── PaddingTests.cs ├── PageBreakTests.cs ├── QuestPDF.UnitTests.csproj ├── RotateTests.cs ├── ScaleTests.cs ├── ShowEntireTests.cs ├── ShowOnceTest.cs ├── SimpleRotateTests.cs ├── TestEngine │ ├── ElementMock.cs │ ├── MockCanvas.cs │ ├── OperationBase.cs │ ├── OperationRecordingCanvas.cs │ ├── Operations │ │ ├── CanvasDrawImageOperation.cs │ │ ├── CanvasDrawRectangleOperation.cs │ │ ├── CanvasDrawTextOperation.cs │ │ ├── CanvasRotateOperation.cs │ │ ├── CanvasScaleOperation.cs │ │ ├── CanvasTranslateOperation.cs │ │ ├── ChildDrawOperation.cs │ │ ├── ChildMeasureOperation.cs │ │ └── ElementMeasureOperation.cs │ ├── SimpleContainerTests.cs │ └── TestPlan.cs ├── TestsBase.cs ├── TranslateTests.cs └── UnconstrainedTests.cs ├── QuestPDF.sln ├── QuestPDF ├── Drawing │ ├── DocumentContainer.cs │ ├── DocumentGenerator.cs │ ├── DocumentMetadata.cs │ ├── Exceptions │ │ ├── DocumentComposeException.cs │ │ ├── DocumentDrawingException.cs │ │ └── DocumentLayoutException.cs │ ├── FontManager.cs │ ├── FontStyleSet.cs │ ├── FreeCanvas.cs │ ├── ImageCanvas.cs │ ├── PdfCanvas.cs │ ├── Proxy │ │ ├── CacheProxy.cs │ │ ├── DebugStackItem.cs │ │ ├── DebuggingProxy.cs │ │ ├── DebuggingState.cs │ │ └── ElementProxy.cs │ ├── SkiaCanvasBase.cs │ ├── SkiaDocumentCanvasBase.cs │ ├── SkiaPictureCanvas.cs │ ├── SpacePlan.cs │ ├── SpacePlanType.cs │ ├── TextMeasurement.cs │ └── XpsCanvas.cs ├── Elements │ ├── Alignment.cs │ ├── AspectRatio.cs │ ├── Background.cs │ ├── Border.cs │ ├── Canvas.cs │ ├── Column.cs │ ├── Constrained.cs │ ├── Container.cs │ ├── DebugArea.cs │ ├── DebugPointer.cs │ ├── Decoration.cs │ ├── DefaultTextStyle.cs │ ├── DynamicImage.cs │ ├── Empty.cs │ ├── EnsureSpace.cs │ ├── Extend.cs │ ├── Grid.cs │ ├── Hyperlink.cs │ ├── Image.cs │ ├── Inlined.cs │ ├── Layers.cs │ ├── Line.cs │ ├── MinimalBox.cs │ ├── Padding.cs │ ├── Page.cs │ ├── PageBreak.cs │ ├── Placeholder.cs │ ├── Rotate.cs │ ├── Row.cs │ ├── Scale.cs │ ├── ScaleToFit.cs │ ├── Section.cs │ ├── SectionLink.cs │ ├── ShowEntire.cs │ ├── ShowOnce.cs │ ├── SimpleRotate.cs │ ├── SkipOnce.cs │ ├── StopPaging.cs │ ├── Table │ │ ├── DynamicDictionary.cs │ │ ├── ITableCellContainer.cs │ │ ├── Table.cs │ │ ├── TableCell.cs │ │ ├── TableCellRenderingCommand.cs │ │ ├── TableColumnDefinition.cs │ │ ├── TableLayoutPlanner.cs │ │ └── TableLayoutValidator.cs │ ├── Text │ │ ├── Calculation │ │ │ ├── TextDrawingRequest.cs │ │ │ ├── TextLine.cs │ │ │ ├── TextLineElement.cs │ │ │ ├── TextMeasurementRequest.cs │ │ │ └── TextMeasurementResult.cs │ │ ├── Items │ │ │ ├── ITextBlockItem.cs │ │ │ ├── TextBlockElement.cs │ │ │ ├── TextBlockHyperlink.cs │ │ │ ├── TextBlockPageNumber.cs │ │ │ ├── TextBlockSectionlLink.cs │ │ │ └── TextBlockSpan.cs │ │ └── TextBlock.cs │ ├── Translate.cs │ └── Unconstrained.cs ├── Fluent │ ├── AlignmentExtensions.cs │ ├── BorderExtensions.cs │ ├── ColumnExtensions.cs │ ├── ComponentExtentions.cs │ ├── ConstrainedExtensions.cs │ ├── DebugExtensions.cs │ ├── DecorationExtensions.cs │ ├── ElementExtensions.cs │ ├── ExtendExtensions.cs │ ├── GenerateExtensions.cs │ ├── GridExtensions.cs │ ├── ImageExtensions.cs │ ├── InlinedExtensions.cs │ ├── LayerExtensions.cs │ ├── LineExtensions.cs │ ├── MinimalApi.cs │ ├── PaddingExtensions.cs │ ├── PageExtensions.cs │ ├── RotateExtensions.cs │ ├── RowExtensions.cs │ ├── ScaleExtensions.cs │ ├── TableExtensions.cs │ ├── TextExtensions.cs │ ├── TextSpanDescriptorExtensions.cs │ ├── TextStyleExtensions.cs │ └── TranslateExtensions.cs ├── Helpers │ ├── Colors.cs │ ├── Fonts.cs │ ├── Helpers.cs │ ├── PageSizes.cs │ └── Placeholders.cs ├── Infrastructure │ ├── AspectRatioOption.cs │ ├── ContainerElement.cs │ ├── Element.cs │ ├── FontWeight.cs │ ├── HorizontalAlignment.cs │ ├── ICacheable.cs │ ├── ICanvas.cs │ ├── IComponent.cs │ ├── IContainer.cs │ ├── IDocument.cs │ ├── IDocumentContainer.cs │ ├── IElement.cs │ ├── IPageContext.cs │ ├── IRenderingCanvas.cs │ ├── IStateResettable.cs │ ├── ImageScaling.cs │ ├── PageContext.cs │ ├── Position.cs │ ├── Size.cs │ ├── TextStyle.cs │ ├── Unit.cs │ └── VerticalAlignment.cs ├── Previewer │ ├── ExceptionDocument.cs │ ├── HotReloadManager.cs │ ├── PreviewerExtensions.cs │ ├── PreviewerRefreshCommand.cs │ └── PreviewerService.cs ├── QuestPDF.csproj └── Resources │ ├── Description.md │ ├── ImagePlaceholder.png │ ├── Logo.png │ └── ReleaseNotes.txt ├── SECURITY.md └── readme.md /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Please provide an example, minimalistic code that shows the problem. 15 | 16 | **Expected behavior** 17 | A clear and concise description of what you expected to happen. 18 | 19 | **Screenshots** 20 | If applicable, add screenshots to help explain your problem. 21 | 22 | **Additional context** 23 | Add any other context about the problem here. 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: QuestPDF 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 QuestPDF 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /QuestPDF.Examples/BarcodeExamples.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using NUnit.Framework; 3 | using QuestPDF.Drawing; 4 | using QuestPDF.Examples.Engine; 5 | using QuestPDF.Fluent; 6 | using QuestPDF.Helpers; 7 | 8 | namespace QuestPDF.Examples 9 | { 10 | public class BarcodeExamples 11 | { 12 | [Test] 13 | public void Example() 14 | { 15 | FontManager.RegisterFont(File.OpenRead("LibreBarcode39-Regular.ttf")); 16 | 17 | RenderingTest 18 | .Create() 19 | .PageSize(400, 200) 20 | .ShowResults() 21 | .Render(container => 22 | { 23 | container 24 | .Background(Colors.White) 25 | .AlignCenter() 26 | .AlignMiddle() 27 | .Text("*QuestPDF*") 28 | .FontFamily("Libre Barcode 39") 29 | .FontSize(64); 30 | }); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /QuestPDF.Examples/CanvasExamples.cs: -------------------------------------------------------------------------------- 1 | using Microcharts; 2 | using NUnit.Framework; 3 | using QuestPDF.Examples.Engine; 4 | using QuestPDF.Fluent; 5 | using QuestPDF.Helpers; 6 | using QuestPDF.Infrastructure; 7 | using SkiaSharp; 8 | 9 | namespace QuestPDF.Examples 10 | { 11 | public class CanvasExamples 12 | { 13 | [Test] 14 | public void BorderRadius() 15 | { 16 | RenderingTest 17 | .Create() 18 | .PageSize(175, 100) 19 | .ProduceImages() 20 | .ShowResults() 21 | .Render(container => 22 | { 23 | container 24 | .Background(Colors.Grey.Lighten2) 25 | .Padding(25) 26 | .MinimalBox() 27 | .Layers(layers => 28 | { 29 | layers.Layer().Canvas((canvas, size) => 30 | { 31 | DrawRoundedRectangle(Colors.White, false); 32 | DrawRoundedRectangle(Colors.Blue.Darken2, true); 33 | 34 | void DrawRoundedRectangle(string color, bool isStroke) 35 | { 36 | using var paint = new SKPaint 37 | { 38 | Color = SKColor.Parse(color), 39 | IsStroke = isStroke, 40 | StrokeWidth = 2, 41 | IsAntialias = true 42 | }; 43 | 44 | canvas.DrawRoundRect(0, 0, size.Width, size.Height, 20, 20, paint); 45 | } 46 | }); 47 | 48 | layers 49 | .PrimaryLayer() 50 | .PaddingVertical(10) 51 | .PaddingHorizontal(20) 52 | .Text("Sample text") 53 | .FontSize(16) 54 | .FontColor(Colors.Blue.Darken2) 55 | .SemiBold(); 56 | }); 57 | }); 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /QuestPDF.Examples/ColumnExamples.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using NUnit.Framework; 4 | using QuestPDF.Examples.Engine; 5 | using QuestPDF.Fluent; 6 | using QuestPDF.Helpers; 7 | using QuestPDF.Infrastructure; 8 | 9 | namespace QuestPDF.Examples 10 | { 11 | public class ColumnExamples 12 | { 13 | [Test] 14 | public void Column() 15 | { 16 | RenderingTest 17 | .Create() 18 | .PageSize(PageSizes.A4) 19 | .ShowResults() 20 | .ProducePdf() 21 | .Render(container => 22 | { 23 | container.Column(column => 24 | { 25 | foreach (var i in Enumerable.Range(0, 10)) 26 | column.Item().Element(Block); 27 | 28 | static void Block(IContainer container) 29 | { 30 | container 31 | .Width(72) 32 | .Height(3.5f, Unit.Inch) 33 | .Height(1.5f, Unit.Inch) 34 | .Background(Placeholders.BackgroundColor()); 35 | } 36 | }); 37 | }); 38 | } 39 | 40 | [Test] 41 | public void Stability_NoItems() 42 | { 43 | RenderingTest 44 | .Create() 45 | .ProducePdf() 46 | .MaxPages(100) 47 | .PageSize(250, 150) 48 | .Render(container => 49 | { 50 | container 51 | .Padding(25) 52 | .Column(column => { }); 53 | }); 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /QuestPDF.Examples/ComplexLayoutBenchmark.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using QuestPDF.Examples.Engine; 3 | using QuestPDF.Fluent; 4 | using QuestPDF.Helpers; 5 | using QuestPDF.Infrastructure; 6 | 7 | namespace QuestPDF.Examples 8 | { 9 | public class ComplexLayoutBenchmark 10 | { 11 | [Test] 12 | public void ComplexLayout() 13 | { 14 | RenderingTest 15 | .Create() 16 | .PageSize(PageSizes.A4) 17 | .ProducePdf() 18 | .ShowResults() 19 | .Render(x => GenerateStructure(x, 12)); 20 | } 21 | 22 | private void GenerateStructure(IContainer container, int level) 23 | { 24 | if (level <= 0) 25 | { 26 | container.Background(Placeholders.BackgroundColor()).Height(10); 27 | return; 28 | } 29 | 30 | level--; 31 | 32 | if (level % 3 == 0) 33 | { 34 | container 35 | .Border(level / 4f) 36 | .BorderColor(Colors.Black) 37 | .Row(row => 38 | { 39 | row.RelativeItem().Element(x => GenerateStructure(x, level)); 40 | row.RelativeItem().Element(x => GenerateStructure(x, level)); 41 | }); 42 | } 43 | else 44 | { 45 | container 46 | .Border(level / 4f) 47 | .BorderColor(Colors.Black) 48 | .Column(column => 49 | { 50 | column.Item().Element(x => GenerateStructure(x, level)); 51 | column.Item().Element(x => GenerateStructure(x, level)); 52 | }); 53 | } 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /QuestPDF.Examples/ContinousPage.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Linq; 3 | using NUnit.Framework; 4 | using QuestPDF.Drawing; 5 | using QuestPDF.Examples.Engine; 6 | using QuestPDF.Fluent; 7 | using QuestPDF.Helpers; 8 | using QuestPDF.Infrastructure; 9 | 10 | namespace QuestPDF.Examples 11 | { 12 | public class ContinuousPageDocument : IDocument 13 | { 14 | public DocumentMetadata GetMetadata() => DocumentMetadata.Default; 15 | 16 | public void Compose(IDocumentContainer container) 17 | { 18 | container.Page(page => 19 | { 20 | page.Margin(20); 21 | page.ContinuousSize(150); 22 | 23 | page.Header().Text("Header"); 24 | 25 | page.Content().PaddingVertical(10).Border(1).Padding(10).Column(column => 26 | { 27 | foreach (var index in Enumerable.Range(1, 100)) 28 | column.Item().Text($"Line {index}").FontColor(Placeholders.Color()); 29 | }); 30 | 31 | page.Footer().Text("Footer"); 32 | }); 33 | } 34 | } 35 | 36 | public class ContinuousPageExamples 37 | { 38 | [Test] 39 | public void ContinuousPage() 40 | { 41 | var path = "example.pdf"; 42 | new ContinuousPageDocument().GeneratePdf(path); 43 | Process.Start("explorer", path); 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /QuestPDF.Examples/DefaultTextStyleExample.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using QuestPDF.Examples.Engine; 3 | using QuestPDF.Fluent; 4 | using QuestPDF.Helpers; 5 | using QuestPDF.Infrastructure; 6 | 7 | namespace QuestPDF.Examples 8 | { 9 | public class DefaultTextStyleExample 10 | { 11 | [Test] 12 | public void DefaultTextStyle() 13 | { 14 | RenderingTest 15 | .Create() 16 | .ProducePdf() 17 | .ShowResults() 18 | .RenderDocument(container => 19 | { 20 | container.Page(page => 21 | { 22 | // all text in this set of pages has size 20 23 | page.DefaultTextStyle(TextStyle.Default.Size(20)); 24 | 25 | page.Margin(20); 26 | page.Size(PageSizes.A4); 27 | page.PageColor(Colors.White); 28 | 29 | page.Content().Column(column => 30 | { 31 | column.Item().Text(Placeholders.Sentence()); 32 | 33 | column.Item().Text(text => 34 | { 35 | // text in this block is additionally semibold 36 | text.DefaultTextStyle(TextStyle.Default.SemiBold()); 37 | 38 | text.Line(Placeholders.Sentence()); 39 | 40 | // this text has size 20 but also semibold and red 41 | text.Span(Placeholders.Sentence()).FontColor(Colors.Red.Medium); 42 | }); 43 | }); 44 | }); 45 | }); 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /QuestPDF.Examples/DefaultTextStyleExamples.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | using System.Linq; 3 | using NUnit.Framework; 4 | using QuestPDF.Examples.Engine; 5 | using QuestPDF.Fluent; 6 | using QuestPDF.Helpers; 7 | using QuestPDF.Infrastructure; 8 | 9 | namespace QuestPDF.Examples 10 | { 11 | public class DefaultTextStyleExamples 12 | { 13 | [Test] 14 | public void Placeholder() 15 | { 16 | RenderingTest 17 | .Create() 18 | .PageSize(220, 270) 19 | .ProduceImages() 20 | .ShowResults() 21 | .EnableDebugging() 22 | .Render(container => 23 | { 24 | container 25 | .Padding(10) 26 | .DefaultTextStyle(TextStyle.Default.Bold().Underline()) 27 | .Column(column => 28 | { 29 | column.Item().Text("Default style applies to all children"); 30 | column.Item().Text("You can override certain styles").Underline(false).FontColor(Colors.Green.Darken2); 31 | 32 | column.Item().PaddingTop(10).Border(1).Grid(grid => 33 | { 34 | grid.Columns(4); 35 | 36 | foreach (var i in Enumerable.Range(1, 16)) 37 | { 38 | grid.Item() 39 | .Border(1) 40 | .BorderColor(Colors.Grey.Lighten1) 41 | .Background(Colors.Grey.Lighten3) 42 | .Width(50) 43 | .Height(50) 44 | .AlignCenter() 45 | .AlignMiddle() 46 | .Text(i) 47 | .FontSize(16 + i / 4); 48 | } 49 | }); 50 | }); 51 | }); 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /QuestPDF.Examples/DifferentHeaderOnFirstPageExample.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using NUnit.Framework; 3 | using QuestPDF.Examples.Engine; 4 | using QuestPDF.Fluent; 5 | using QuestPDF.Helpers; 6 | using QuestPDF.Infrastructure; 7 | 8 | namespace QuestPDF.Examples 9 | { 10 | public class DifferentHeaderOnFirstPageExample 11 | { 12 | [Test] 13 | public void Placeholder() 14 | { 15 | RenderingTest 16 | .Create() 17 | .PageSize(PageSizes.A6) 18 | .ProduceImages() 19 | .ShowResults() 20 | .EnableDebugging() 21 | .RenderDocument(container => 22 | { 23 | container.Page(page => 24 | { 25 | page.Size(PageSizes.A6); 26 | page.Margin(5); 27 | page.PageColor(Colors.White); 28 | 29 | page.Header().Column(column => 30 | { 31 | column.Item().ShowOnce().Background(Colors.Blue.Lighten2).Height(60); 32 | column.Item().SkipOnce().Background(Colors.Green.Lighten2).Height(40); 33 | }); 34 | 35 | page.Content().PaddingVertical(10).Column(column => 36 | { 37 | column.Spacing(10); 38 | 39 | foreach (var _ in Enumerable.Range(0, 13)) 40 | column.Item().Background(Colors.Grey.Lighten2).Height(40); 41 | }); 42 | 43 | page.Footer().AlignCenter().Text(text => 44 | { 45 | text.DefaultTextStyle(TextStyle.Default.Size(16)); 46 | 47 | text.CurrentPageNumber(); 48 | text.Span(" / "); 49 | text.TotalPages(); 50 | }); 51 | }); 52 | }); 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /QuestPDF.Examples/Engine/SimpleDocument.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using QuestPDF.Drawing; 3 | using QuestPDF.Elements; 4 | using QuestPDF.Fluent; 5 | using QuestPDF.Helpers; 6 | using QuestPDF.Infrastructure; 7 | 8 | namespace QuestPDF.Examples.Engine 9 | { 10 | public class SimpleDocument : IDocument 11 | { 12 | public const int ImageScalingFactor = 2; 13 | 14 | private Action Content { get; } 15 | private int MaxPages { get; } 16 | private bool ApplyCaching { get; } 17 | private bool ApplyDebugging { get; } 18 | 19 | public SimpleDocument(Action content, int maxPages, bool applyCaching, bool applyDebugging) 20 | { 21 | Content = content; 22 | MaxPages = maxPages; 23 | ApplyCaching = applyCaching; 24 | ApplyDebugging = applyDebugging; 25 | } 26 | 27 | public DocumentMetadata GetMetadata() 28 | { 29 | return new DocumentMetadata() 30 | { 31 | RasterDpi = PageSizes.PointsPerInch * ImageScalingFactor, 32 | DocumentLayoutExceptionThreshold = MaxPages, 33 | ApplyCaching = ApplyCaching, 34 | ApplyDebugging = ApplyDebugging 35 | }; 36 | } 37 | 38 | public void Compose(IDocumentContainer container) 39 | { 40 | Content(container); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /QuestPDF.Examples/EnsureSpaceExample.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using QuestPDF.Examples.Engine; 3 | using QuestPDF.Fluent; 4 | using QuestPDF.Helpers; 5 | using QuestPDF.Infrastructure; 6 | 7 | namespace QuestPDF.Examples 8 | { 9 | public class EnsureSpaceExample 10 | { 11 | [Test] 12 | public void EnsureSpaceWith() 13 | { 14 | RenderingTest 15 | .Create() 16 | .ProduceImages() 17 | .ShowResults() 18 | .RenderDocument(container => 19 | { 20 | container.Page(page => 21 | { 22 | page.Margin(20); 23 | page.Size(PageSizes.A7.Landscape()); 24 | page.PageColor(Colors.White); 25 | 26 | page.Header().Text("With ensure space").SemiBold(); 27 | 28 | page.Content().Column(column => 29 | { 30 | column 31 | .Item() 32 | .ExtendHorizontal() 33 | .Height(75) 34 | .Background(Colors.Grey.Lighten2); 35 | 36 | column 37 | .Item() 38 | .EnsureSpace(100) 39 | .Text(Placeholders.LoremIpsum()); 40 | }); 41 | 42 | page.Footer().Text(text => 43 | { 44 | text.Span("Page "); 45 | text.CurrentPageNumber(); 46 | text.Span(" out of "); 47 | text.TotalPages(); 48 | }); 49 | }); 50 | }); 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /QuestPDF.Examples/FrameExample.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using QuestPDF.Examples.Engine; 3 | using QuestPDF.Fluent; 4 | using QuestPDF.Helpers; 5 | using QuestPDF.Infrastructure; 6 | 7 | namespace QuestPDF.Examples 8 | { 9 | static class SimpleExtension 10 | { 11 | private static IContainer Cell(this IContainer container, bool dark) 12 | { 13 | return container 14 | .Border(1) 15 | .BorderColor(Colors.Grey.Medium) 16 | .Background(dark ? Colors.Grey.Lighten3 : Colors.White) 17 | .Padding(5); 18 | } 19 | 20 | public static void LabelCell(this IContainer container, string text) => container.Cell(true).Text(text).SemiBold(); 21 | public static IContainer ValueCell(this IContainer container) => container.Cell(false); 22 | public static void ValueCell(this IContainer container, string text) => container.ValueCell().Text(text); 23 | } 24 | 25 | public class FrameExample 26 | { 27 | [Test] 28 | public void Frame() 29 | { 30 | RenderingTest 31 | .Create() 32 | .PageSize(550, 400) 33 | .ShowResults() 34 | .Render(container => 35 | { 36 | container 37 | .Background("#FFF") 38 | .Padding(25) 39 | .Column(column => 40 | { 41 | for(var i = 1; i <= 4; i++) 42 | { 43 | column.Item().Row(row => 44 | { 45 | row.RelativeItem(2).LabelCell(Placeholders.Label()); 46 | row.RelativeItem(3).ValueCell().Text(Placeholders.Paragraph()); 47 | }); 48 | } 49 | }); 50 | }); 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /QuestPDF.Examples/ImageExamples.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using NUnit.Framework; 3 | using QuestPDF.Drawing.Exceptions; 4 | using QuestPDF.Examples.Engine; 5 | using QuestPDF.Fluent; 6 | using QuestPDF.Helpers; 7 | 8 | namespace QuestPDF.Examples 9 | { 10 | public class ImageExamples 11 | { 12 | [Test] 13 | public void LoadingImage() 14 | { 15 | RenderingTest 16 | .Create() 17 | .PageSize(PageSizes.A5) 18 | .ProducePdf() 19 | .ShowResults() 20 | .Render(page => 21 | { 22 | page.Padding(25).Column(column => 23 | { 24 | column.Spacing(25); 25 | 26 | column.Item().Image("logo.png"); 27 | 28 | var binaryData = File.ReadAllBytes("logo.png"); 29 | column.Item().Image(binaryData); 30 | 31 | using var stream = new FileStream("logo.png", FileMode.Open); 32 | column.Item().Image(stream); 33 | }); 34 | }); 35 | } 36 | 37 | [Test] 38 | public void Exception() 39 | { 40 | Assert.Throws(() => 41 | { 42 | RenderingTest 43 | .Create() 44 | .PageSize(PageSizes.A2) 45 | .ProducePdf() 46 | .ShowResults() 47 | .Render(page => page.Image("non_existent.png")); 48 | }); 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /QuestPDF.Examples/LibreBarcode39-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nirzaf/QuestPDFTutorial_Samples_Batch_05/1b8920a7a210c0d31e83915871c49d435eb69602/QuestPDF.Examples/LibreBarcode39-Regular.ttf -------------------------------------------------------------------------------- /QuestPDF.Examples/LineExamples.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using NUnit.Framework; 3 | using QuestPDF.Examples.Engine; 4 | using QuestPDF.Fluent; 5 | using QuestPDF.Helpers; 6 | using QuestPDF.Infrastructure; 7 | 8 | namespace QuestPDF.Examples 9 | { 10 | public class LineExamples 11 | { 12 | [Test] 13 | public void LineHorizontal() 14 | { 15 | RenderingTest 16 | .Create() 17 | .PageSize(PageSizes.A5) 18 | .ProduceImages() 19 | .ShowResults() 20 | .Render(container => 21 | { 22 | container 23 | .Padding(15) 24 | .MinimalBox() 25 | .DefaultTextStyle(TextStyle.Default.Size(16)) 26 | .Column(column => 27 | { 28 | column.Item().Text("Above text"); 29 | column.Item().PaddingVertical(5).LineHorizontal(1).LineColor(Colors.Grey.Medium); 30 | column.Item().Text("Below text"); 31 | }); 32 | }); 33 | } 34 | 35 | [Test] 36 | public void LineVertical() 37 | { 38 | RenderingTest 39 | .Create() 40 | .PageSize(PageSizes.A5) 41 | .ProduceImages() 42 | .ShowResults() 43 | .Render(container => 44 | { 45 | container 46 | .Padding(15) 47 | .DefaultTextStyle(TextStyle.Default.Size(16)) 48 | .Row(row => 49 | { 50 | row.AutoItem().Text("Left text"); 51 | row.AutoItem().PaddingHorizontal(10).LineVertical(1).LineColor(Colors.Grey.Medium); 52 | row.AutoItem().Text("Right text"); 53 | }); 54 | }); 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /QuestPDF.Examples/LoremPicsumExample.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using NUnit.Framework; 3 | using QuestPDF.Examples.Engine; 4 | using QuestPDF.Fluent; 5 | using QuestPDF.Infrastructure; 6 | 7 | namespace QuestPDF.Examples 8 | { 9 | public class LoremPicsum : IComponent 10 | { 11 | public bool Greyscale { get; } 12 | 13 | public LoremPicsum(bool greyscale) 14 | { 15 | Greyscale = greyscale; 16 | } 17 | 18 | public void Compose(IContainer container) 19 | { 20 | var url = "https://picsum.photos/300/200"; 21 | 22 | if(Greyscale) 23 | url += "?grayscale"; 24 | 25 | using var client = new WebClient(); 26 | var response = client.DownloadData(url); 27 | container.Image(response); 28 | } 29 | } 30 | 31 | public class LoremPicsumExample 32 | { 33 | [Test] 34 | public void LoremPicsum() 35 | { 36 | RenderingTest 37 | .Create() 38 | .PageSize(350, 280) 39 | .ProducePdf() 40 | .ShowResults() 41 | .Render(container => 42 | { 43 | container 44 | .Background("#FFF") 45 | .Padding(25) 46 | .Column(column => 47 | { 48 | column.Spacing(10); 49 | 50 | column 51 | .Item() 52 | .Component(new LoremPicsum(true)); 53 | 54 | column 55 | .Item() 56 | .AlignRight() 57 | .Text("From Lorem Picsum"); 58 | }); 59 | }); 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /QuestPDF.Examples/MinimalApiExamples.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using NUnit.Framework; 3 | using QuestPDF.Examples.Engine; 4 | using QuestPDF.Fluent; 5 | using QuestPDF.Helpers; 6 | using QuestPDF.Infrastructure; 7 | 8 | namespace QuestPDF.Examples 9 | { 10 | public class MinimalApiExamples 11 | { 12 | [Test] 13 | public void MinimalApi() 14 | { 15 | Document 16 | .Create(container => 17 | { 18 | container.Page(page => 19 | { 20 | page.Size(PageSizes.A4); 21 | page.Margin(2, Unit.Centimetre); 22 | page.PageColor(Colors.White); 23 | page.DefaultTextStyle(x => x.FontSize(20)); 24 | 25 | page.Header() 26 | .Text("Hello PDF!") 27 | .SemiBold().FontSize(36).FontColor(Colors.Blue.Medium); 28 | 29 | page.Content() 30 | .PaddingVertical(1, Unit.Centimetre) 31 | .Column(x => 32 | { 33 | x.Spacing(20); 34 | 35 | x.Item().Text(Placeholders.LoremIpsum()); 36 | x.Item().Image(Placeholders.Image(200, 100)); 37 | }); 38 | 39 | page.Footer() 40 | .AlignCenter() 41 | .Text(x => 42 | { 43 | x.Span("Page "); 44 | x.CurrentPageNumber(); 45 | }); 46 | }); 47 | }) 48 | .GeneratePdf("hello.pdf"); 49 | 50 | Process.Start("explorer.exe", "hello.pdf"); 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /QuestPDF.Examples/PageBackgroundForegroundExample.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using NUnit.Framework; 4 | using QuestPDF.Examples.Engine; 5 | using QuestPDF.Fluent; 6 | using QuestPDF.Helpers; 7 | using QuestPDF.Infrastructure; 8 | 9 | namespace QuestPDF.Examples 10 | { 11 | public class PageBackgroundForegroundExample 12 | { 13 | [Test] 14 | public void PageBackgroundForeground() 15 | { 16 | RenderingTest 17 | .Create() 18 | .ProduceImages() 19 | .MaxPages(100) 20 | .ShowResults() 21 | .RenderDocument(document => 22 | { 23 | document.Page(page => 24 | { 25 | page.Size(PageSizes.A4); 26 | page.Margin(1, Unit.Inch); 27 | page.DefaultTextStyle(TextStyle.Default.FontSize(16)); 28 | page.PageColor(Colors.White); 29 | 30 | const string transparentBlue = "#662196f3"; 31 | 32 | page.Background() 33 | .AlignTop() 34 | .ExtendHorizontal() 35 | .Height(200) 36 | .Background(transparentBlue); 37 | 38 | page.Foreground() 39 | .AlignBottom() 40 | .ExtendHorizontal() 41 | .Height(250) 42 | .Background(transparentBlue); 43 | 44 | page.Header().Text("Background and foreground").Bold().FontColor(Colors.Blue.Darken2).FontSize(36); 45 | 46 | page.Content().PaddingVertical(25).Column(column => 47 | { 48 | column.Spacing(25); 49 | 50 | foreach (var i in Enumerable.Range(0, 100)) 51 | column.Item().Background(Colors.Grey.Lighten2).Height(75); 52 | }); 53 | }); 54 | }); 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /QuestPDF.Examples/QuestPDF.Examples.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | false 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | Always 25 | 26 | 27 | PreserveNewest 28 | 29 | 30 | PreserveNewest 31 | 32 | 33 | PreserveNewest 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /QuestPDF.Examples/ScaleToFitExamples.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using NUnit.Framework; 3 | using QuestPDF.Examples.Engine; 4 | using QuestPDF.Fluent; 5 | using QuestPDF.Helpers; 6 | using QuestPDF.Infrastructure; 7 | 8 | namespace QuestPDF.Examples 9 | { 10 | public class ScaleToFitExamples 11 | { 12 | [Test] 13 | public void ScaleToFit() 14 | { 15 | RenderingTest 16 | .Create() 17 | .PageSize(PageSizes.A4) 18 | .ProduceImages() 19 | .ShowResults() 20 | .Render(container => 21 | { 22 | container.Padding(25).Column(column => 23 | { 24 | var text = Placeholders.Paragraph(); 25 | 26 | foreach (var i in Enumerable.Range(2, 5)) 27 | { 28 | column 29 | .Item() 30 | .MinimalBox() 31 | .Border(1) 32 | .Padding(5) 33 | .Width(i * 40) 34 | .Height(i * 20) 35 | .ScaleToFit() 36 | .Text(text); 37 | } 38 | }); 39 | }); 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /QuestPDF.Examples/ShowOnceExample.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using QuestPDF.Examples.Engine; 3 | using QuestPDF.Fluent; 4 | using QuestPDF.Helpers; 5 | using QuestPDF.Infrastructure; 6 | 7 | namespace QuestPDF.Examples 8 | { 9 | public class ShowOnceExample 10 | { 11 | [Test] 12 | public void ShowOnce() 13 | { 14 | RenderingTest 15 | .Create() 16 | .ProduceImages() 17 | .ShowResults() 18 | .RenderDocument(container => 19 | { 20 | container.Page(page => 21 | { 22 | page.Margin(20); 23 | page.Size(PageSizes.A7.Landscape()); 24 | page.PageColor(Colors.White); 25 | 26 | page.Header().Text("With show once").SemiBold(); 27 | 28 | page.Content().PaddingVertical(5).Row(row => 29 | { 30 | row.RelativeItem() 31 | .Background(Colors.Grey.Lighten2) 32 | .Border(1) 33 | .Padding(5) 34 | .ShowOnce() 35 | .Text(Placeholders.Label()); 36 | 37 | row.RelativeItem(2) 38 | .Border(1) 39 | .Padding(5) 40 | .Text(Placeholders.Paragraph()); 41 | }); 42 | 43 | page.Footer().Text(text => 44 | { 45 | text.Span("Page "); 46 | text.CurrentPageNumber(); 47 | text.Span(" out of "); 48 | text.TotalPages(); 49 | }); 50 | }); 51 | }); 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /QuestPDF.Examples/SkipOnceExample.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using QuestPDF.Examples.Engine; 3 | using QuestPDF.Fluent; 4 | using QuestPDF.Helpers; 5 | using QuestPDF.Infrastructure; 6 | 7 | namespace QuestPDF.Examples 8 | { 9 | public class SkipOnceExample 10 | { 11 | [Test] 12 | public void SkipOnce() 13 | { 14 | RenderingTest 15 | .Create() 16 | .ProduceImages() 17 | .ShowResults() 18 | .RenderDocument(container => 19 | { 20 | container.Page(page => 21 | { 22 | page.Margin(20); 23 | page.Size(PageSizes.A7.Landscape()); 24 | page.PageColor(Colors.White); 25 | 26 | page.Header().Column(column => 27 | { 28 | column.Item().ShowOnce().Text("This header is visible on the first page."); 29 | column.Item().SkipOnce().Text("This header is visible on the second page and all following."); 30 | }); 31 | 32 | page.Content() 33 | .PaddingVertical(10) 34 | .Text(Placeholders.Paragraphs()) 35 | .FontColor(Colors.Grey.Medium); 36 | 37 | page.Footer().Text(text => 38 | { 39 | text.Span("Page "); 40 | text.CurrentPageNumber(); 41 | text.Span(" out of "); 42 | text.TotalPages(); 43 | }); 44 | }); 45 | }); 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /QuestPDF.Examples/StopPaging.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using QuestPDF.Examples.Engine; 3 | using QuestPDF.Fluent; 4 | using QuestPDF.Helpers; 5 | using QuestPDF.Infrastructure; 6 | 7 | namespace QuestPDF.Examples 8 | { 9 | public class StopPaging 10 | { 11 | [Test] 12 | public void Example() 13 | { 14 | RenderingTest 15 | .Create() 16 | .PageSize(300, 250) 17 | .ProduceImages() 18 | .ShowResults() 19 | .Render(container => 20 | { 21 | container 22 | .Padding(25) 23 | .DefaultTextStyle(TextStyle.Default.Size(14)) 24 | .Decoration(decoration => 25 | { 26 | decoration 27 | .Before() 28 | .Text(text => 29 | { 30 | text.DefaultTextStyle(TextStyle.Default.SemiBold().Color(Colors.Blue.Medium)); 31 | 32 | text.Span("Page "); 33 | text.CurrentPageNumber(); 34 | }); 35 | 36 | decoration 37 | .Content() 38 | .Column(column => 39 | { 40 | column.Spacing(25); 41 | column.Item().StopPaging().Text(Placeholders.LoremIpsum()); 42 | column.Item().ExtendHorizontal().Height(75).Background(Colors.Grey.Lighten2); 43 | }); 44 | }); 45 | }); 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /QuestPDF.Examples/SvgImageExample.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using QuestPDF.Examples.Engine; 3 | using QuestPDF.Fluent; 4 | using QuestPDF.Helpers; 5 | using QuestPDF.Infrastructure; 6 | using SkiaSharp; 7 | using Svg.Skia; 8 | 9 | namespace QuestPDF.Examples 10 | { 11 | public class SvgImageExample 12 | { 13 | [Test] 14 | public void BorderRadius() 15 | { 16 | RenderingTest 17 | .Create() 18 | .PageSize(175, 100) 19 | .ProduceImages() 20 | .ShowResults() 21 | .Render(container => 22 | { 23 | container 24 | .Background(Colors.Grey.Lighten2) 25 | .Padding(25) 26 | .Canvas((canvas, space) => 27 | { 28 | using var svg = new SKSvg(); 29 | svg.Load("pdf-icon.svg"); 30 | 31 | canvas.DrawPicture(svg.Picture); 32 | }); 33 | }); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /QuestPDF.Examples/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nirzaf/QuestPDFTutorial_Samples_Batch_05/1b8920a7a210c0d31e83915871c49d435eb69602/QuestPDF.Examples/logo.png -------------------------------------------------------------------------------- /QuestPDF.Previewer.Examples/Program.cs: -------------------------------------------------------------------------------- 1 | using QuestPDF.Fluent; 2 | using QuestPDF.Helpers; 3 | using QuestPDF.Infrastructure; 4 | using QuestPDF.Previewer; 5 | 6 | //ImagePlaceholder.Solid = true; 7 | // var model = DataSource.GetReport(); 8 | // var report = new StandardReport(model); 9 | // report.ShowInPreviewer().Wait(); 10 | // return; 11 | 12 | Document 13 | .Create(container => 14 | { 15 | container.Page(page => 16 | { 17 | page.Size(PageSizes.A4); 18 | page.Margin(2, Unit.Centimetre); 19 | page.PageColor(Colors.White); 20 | page.DefaultTextStyle(x => x.FontSize(20)); 21 | 22 | page.Header() 23 | .Text("Hot Reload!") 24 | .SemiBold().FontSize(36).FontColor(Colors.Blue.Darken2); 25 | 26 | page.Content() 27 | .PaddingVertical(1, Unit.Centimetre) 28 | .Column(x => 29 | { 30 | x.Spacing(20); 31 | 32 | x.Item().Table(t => 33 | { 34 | t.ColumnsDefinition(c => 35 | { 36 | c.RelativeColumn(); 37 | c.RelativeColumn(3); 38 | }); 39 | 40 | t.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(5).Text("Visual Studio"); 41 | t.Cell().Border(1).Padding(5).Text("Start in debug mode with 'Hot Reload on Save' enabled."); 42 | t.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(5).Text("Command line"); 43 | t.Cell().Border(1).Padding(5).Text("Run 'dotnet watch'."); 44 | }); 45 | 46 | x.Item().Text("Modify this line and the preview should show your changes instantly."); 47 | }); 48 | 49 | page.Footer() 50 | .AlignCenter() 51 | .Text(x => 52 | { 53 | x.Span("Page "); 54 | x.CurrentPageNumber(); 55 | }); 56 | }); 57 | }) 58 | .ShowInPreviewer(); -------------------------------------------------------------------------------- /QuestPDF.Previewer.Examples/QuestPDF.Previewer.Examples.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | WinExe 5 | net6.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /QuestPDF.Previewer/Helpers.cs: -------------------------------------------------------------------------------- 1 | using SkiaSharp; 2 | 3 | namespace QuestPDF.Previewer; 4 | 5 | class Helpers 6 | { 7 | public static void GeneratePdfFromDocumentSnapshots(string filePath, ICollection pages) 8 | { 9 | using var stream = File.Create(filePath); 10 | 11 | var document = SKDocument.CreatePdf(stream); 12 | 13 | foreach (var page in pages) 14 | { 15 | using var canvas = document.BeginPage(page.Width, page.Height); 16 | canvas.DrawPicture(page.Picture); 17 | document.EndPage(); 18 | canvas.Dispose(); 19 | } 20 | 21 | document.Close(); 22 | } 23 | } -------------------------------------------------------------------------------- /QuestPDF.Previewer/PreviewerApp.axaml: -------------------------------------------------------------------------------- 1 |  4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /QuestPDF.Previewer/PreviewerApp.axaml.cs: -------------------------------------------------------------------------------- 1 | using Avalonia; 2 | using Avalonia.Controls.ApplicationLifetimes; 3 | using Avalonia.Markup.Xaml; 4 | 5 | namespace QuestPDF.Previewer 6 | { 7 | internal class PreviewerApp : Application 8 | { 9 | public override void Initialize() 10 | { 11 | AvaloniaXamlLoader.Load(this); 12 | } 13 | 14 | public override void OnFrameworkInitializationCompleted() 15 | { 16 | if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) 17 | { 18 | desktop.MainWindow = new PreviewerWindow() 19 | { 20 | DataContext = new PreviewerWindowViewModel() 21 | }; 22 | } 23 | 24 | base.OnFrameworkInitializationCompleted(); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /QuestPDF.Previewer/PreviewerCanvas.cs: -------------------------------------------------------------------------------- 1 | using SkiaSharp; 2 | 3 | namespace QuestPDF.Previewer 4 | { 5 | record PreviewPage(SKPicture Picture, float Width, float Height); 6 | } 7 | -------------------------------------------------------------------------------- /QuestPDF.Previewer/PreviewerRefreshCommand.cs: -------------------------------------------------------------------------------- 1 | using SkiaSharp; 2 | 3 | namespace QuestPDF.Previewer; 4 | 5 | internal class DocumentSnapshot 6 | { 7 | public ICollection Pages { get; set; } 8 | 9 | public class PageSnapshot 10 | { 11 | public string Id { get; set; } 12 | 13 | public float Width { get; set; } 14 | public float Height { get; set; } 15 | 16 | public SKPicture Picture { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /QuestPDF.Previewer/PreviewerWindow.axaml.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Controls; 2 | using Avalonia.Markup.Xaml; 3 | 4 | namespace QuestPDF.Previewer 5 | { 6 | class PreviewerWindow : Window 7 | { 8 | public PreviewerWindow() 9 | { 10 | InitializeComponent(); 11 | } 12 | 13 | protected override void OnClosed(EventArgs e) 14 | { 15 | 16 | } 17 | 18 | private void InitializeComponent() 19 | { 20 | AvaloniaXamlLoader.Load(this); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /QuestPDF.Previewer/Program.cs: -------------------------------------------------------------------------------- 1 | using Avalonia; 2 | using Avalonia.Controls.ApplicationLifetimes; 3 | using Avalonia.ReactiveUI; 4 | using QuestPDF.Previewer; 5 | 6 | var applicationPort = GetCommunicationPort(); 7 | CommunicationService.Instance.Start(applicationPort); 8 | 9 | if(Application.Current?.ApplicationLifetime is ClassicDesktopStyleApplicationLifetime desktop) 10 | { 11 | desktop.MainWindow = new PreviewerWindow() 12 | { 13 | DataContext = new PreviewerWindowViewModel() 14 | }; 15 | 16 | desktop.MainWindow.Show(); 17 | desktop.Start(Array.Empty()); 18 | 19 | return; 20 | } 21 | 22 | AppBuilder 23 | .Configure(() => new PreviewerApp()) 24 | .UsePlatformDetect() 25 | .UseReactiveUI() 26 | .StartWithClassicDesktopLifetime(Array.Empty()); 27 | 28 | static int GetCommunicationPort() 29 | { 30 | const int defaultApplicationPort = 12500; 31 | 32 | var arguments = Environment.GetCommandLineArgs(); 33 | 34 | if (arguments.Length < 2) 35 | return defaultApplicationPort; 36 | 37 | return int.TryParse(arguments[1], out var port) ? port : defaultApplicationPort; 38 | } -------------------------------------------------------------------------------- /QuestPDF.Previewer/Resources/Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nirzaf/QuestPDFTutorial_Samples_Batch_05/1b8920a7a210c0d31e83915871c49d435eb69602/QuestPDF.Previewer/Resources/Logo.png -------------------------------------------------------------------------------- /QuestPDF.Previewer/readme.md: -------------------------------------------------------------------------------- 1 | Install nuget locally (in directory where nupkg file is located) 2 | 3 | ``` 4 | dotnet tool install --global --add-source . QuestPDF.Previewer --global 5 | ``` 6 | 7 | Run on default port 8 | 9 | ``` 10 | questpdf-previewer 11 | ``` 12 | 13 | Run on custom port 14 | 15 | ``` 16 | questpdf-previewer 12500 17 | ``` 18 | 19 | Remove nuget locally 20 | 21 | ``` 22 | dotnet tool uninstall QuestPDF.Previewer --global 23 | ``` -------------------------------------------------------------------------------- /QuestPDF.ReportSample/Helpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.IO; 4 | using SkiaSharp; 5 | 6 | namespace QuestPDF.ReportSample 7 | { 8 | public static class Helpers 9 | { 10 | public static Random Random { get; } = new Random(1); 11 | 12 | public static string GetTestItem(string path) => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Resources", path); 13 | 14 | public static byte[] GetImage(string name) 15 | { 16 | var photoPath = GetTestItem(name); 17 | return SKImage.FromEncodedData(photoPath).EncodedData.ToArray(); 18 | } 19 | 20 | public static Location RandomLocation() 21 | { 22 | return new Location 23 | { 24 | Longitude = Helpers.Random.NextDouble() * 360f - 180f, 25 | Latitude = Helpers.Random.NextDouble() * 180f - 90f 26 | }; 27 | } 28 | 29 | private static readonly ConcurrentDictionary RomanNumeralCache = new ConcurrentDictionary(); 30 | 31 | public static string FormatAsRomanNumeral(this int number) 32 | { 33 | if (number < 0 || number > 3999) 34 | throw new ArgumentOutOfRangeException("Number should be in range from 1 to 3999"); 35 | 36 | return RomanNumeralCache.GetOrAdd(number, x => 37 | { 38 | if (x >= 1000) return "M" + FormatAsRomanNumeral(x - 1000); 39 | if (x >= 900) return "CM" + FormatAsRomanNumeral(x - 900); 40 | if (x >= 500) return "D" + FormatAsRomanNumeral(x - 500); 41 | if (x >= 400) return "CD" + FormatAsRomanNumeral(x - 400); 42 | if (x >= 100) return "C" + FormatAsRomanNumeral(x - 100); 43 | if (x >= 90) return "XC" + FormatAsRomanNumeral(x - 90); 44 | if (x >= 50) return "L" + FormatAsRomanNumeral(x - 50); 45 | if (x >= 40) return "XL" + FormatAsRomanNumeral(x - 40); 46 | if (x >= 10) return "X" + FormatAsRomanNumeral(x - 10); 47 | if (x >= 9) return "IX" + FormatAsRomanNumeral(x - 9); 48 | if (x >= 5) return "V" + FormatAsRomanNumeral(x - 5); 49 | if (x >= 4) return "IV" + FormatAsRomanNumeral(x - 4); 50 | if (x >= 1) return "I" + FormatAsRomanNumeral(x - 1); 51 | 52 | return string.Empty; 53 | }); 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /QuestPDF.ReportSample/Layouts/Helpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using QuestPDF.Fluent; 3 | using QuestPDF.Helpers; 4 | using QuestPDF.Infrastructure; 5 | 6 | namespace QuestPDF.ReportSample.Layouts 7 | { 8 | public static class Helpers 9 | { 10 | static IContainer Cell(this IContainer container, bool background) 11 | { 12 | return container 13 | .Border(0.5f) 14 | .BorderColor(Colors.Grey.Lighten1) 15 | .Background(background ? Colors.Grey.Lighten4 : Colors.White) 16 | .Padding(5); 17 | } 18 | 19 | public static IContainer ValueCell(this IContainer container) 20 | { 21 | return container.Cell(false); 22 | } 23 | 24 | public static IContainer LabelCell(this IContainer container) 25 | { 26 | return container.Cell(true); 27 | } 28 | 29 | public static string Format(this Location location) 30 | { 31 | if (location == null) 32 | return string.Empty; 33 | 34 | var lon = location.Longitude; 35 | var lat = location.Latitude; 36 | 37 | var typeLon = lon > 0 ? "E" : "W"; 38 | lon = Math.Abs(lon); 39 | 40 | var typeLat = lat > 0 ? "N" : "S"; 41 | lat = Math.Abs(lat); 42 | 43 | return $"{lat:F5}° {typeLat} {lon:F5}° {typeLon}"; 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /QuestPDF.ReportSample/Layouts/ImagePlaceholder.cs: -------------------------------------------------------------------------------- 1 | using QuestPDF.Fluent; 2 | using QuestPDF.Helpers; 3 | using QuestPDF.Infrastructure; 4 | 5 | namespace QuestPDF.ReportSample.Layouts 6 | { 7 | public class ImagePlaceholder : IComponent 8 | { 9 | public static bool Solid { get; set; } = false; 10 | 11 | public void Compose(IContainer container) 12 | { 13 | if (Solid) 14 | container.Background(Placeholders.Color()); 15 | 16 | else 17 | container.Image(Placeholders.Image); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /QuestPDF.ReportSample/Layouts/PhotoTemplate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using QuestPDF.Fluent; 3 | using QuestPDF.Helpers; 4 | using QuestPDF.Infrastructure; 5 | 6 | namespace QuestPDF.ReportSample.Layouts 7 | { 8 | public class PhotoTemplate : IComponent 9 | { 10 | public ReportPhoto Model { get; set; } 11 | 12 | public PhotoTemplate(ReportPhoto model) 13 | { 14 | Model = model; 15 | } 16 | 17 | public void Compose(IContainer container) 18 | { 19 | container 20 | .ShowEntire() 21 | .Column(column => 22 | { 23 | column.Spacing(5); 24 | column.Item().Element(PhotoWithMaps); 25 | column.Item().Element(PhotoDetails); 26 | }); 27 | } 28 | 29 | void PhotoWithMaps(IContainer container) 30 | { 31 | container 32 | .Row(row => 33 | { 34 | row.RelativeItem(2).AspectRatio(4 / 3f).Component(); 35 | 36 | row.RelativeItem().PaddingLeft(5).Column(column => 37 | { 38 | column.Spacing(7f); 39 | 40 | column.Item().AspectRatio(4 / 3f).Component(); 41 | column.Item().AspectRatio(4 / 3f).Component(); 42 | }); 43 | }); 44 | } 45 | 46 | void PhotoDetails(IContainer container) 47 | { 48 | container.Border(0.75f).BorderColor(Colors.Grey.Medium).Grid(grid => 49 | { 50 | grid.Columns(6); 51 | 52 | grid.Item().LabelCell().Text("Date"); 53 | grid.Item(2).ValueCell().Text(Model.Date?.ToString("g") ?? string.Empty); 54 | grid.Item().LabelCell().Text("Location"); 55 | grid.Item(2).ValueCell().Text(Model.Location.Format()); 56 | 57 | grid.Item().LabelCell().Text("Comments"); 58 | grid.Item(5).ValueCell().Text(Model.Comments); 59 | }); 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /QuestPDF.ReportSample/Models.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using QuestPDF.Infrastructure; 4 | 5 | namespace QuestPDF.ReportSample 6 | { 7 | public class ReportModel 8 | { 9 | public string Title { get; set; } 10 | public byte[] LogoData { get; set; } 11 | public List HeaderFields { get; set; } 12 | 13 | public List Sections { get; set; } 14 | public List Photos { get; set; } 15 | } 16 | 17 | public class ReportHeaderField 18 | { 19 | public string Label { get; set; } 20 | public string Value { get; set; } 21 | } 22 | 23 | public class Location 24 | { 25 | public double Longitude { get; set; } 26 | public double Latitude { get; set; } 27 | } 28 | 29 | public class ReportSection 30 | { 31 | public string Title { get; set; } 32 | public List Parts { get; set; } 33 | } 34 | 35 | public abstract class ReportSectionElement 36 | { 37 | public string Label { get; set; } 38 | } 39 | 40 | public class ReportSectionText : ReportSectionElement 41 | { 42 | public string Text { get; set; } 43 | } 44 | 45 | public class ReportSectionMap : ReportSectionElement 46 | { 47 | public Location Location { get; set; } 48 | } 49 | 50 | public class ReportSectionPhotos : ReportSectionElement 51 | { 52 | public int PhotoCount { get; set; } 53 | } 54 | 55 | public class ReportPhoto 56 | { 57 | public Location Location { get; set; } 58 | 59 | public DateTime? Date { get; set; } 60 | public string Comments { get; set; } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /QuestPDF.ReportSample/QuestPDF.ReportSample.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | false 6 | 8 7 | QuestPDF.ReportSample 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | PreserveNewest 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /QuestPDF.ReportSample/Resources/Logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | comp 8 | any 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /QuestPDF.ReportSample/Resources/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nirzaf/QuestPDFTutorial_Samples_Batch_05/1b8920a7a210c0d31e83915871c49d435eb69602/QuestPDF.ReportSample/Resources/logo.png -------------------------------------------------------------------------------- /QuestPDF.ReportSample/Tests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.Linq; 5 | using NUnit.Framework; 6 | using QuestPDF.Drawing; 7 | using QuestPDF.Fluent; 8 | using QuestPDF.Infrastructure; 9 | using QuestPDF.ReportSample.Layouts; 10 | 11 | namespace QuestPDF.ReportSample 12 | { 13 | public class ReportGeneration 14 | { 15 | private StandardReport Report { get; set; } 16 | 17 | [SetUp] 18 | public void SetUp() 19 | { 20 | var model = DataSource.GetReport(); 21 | Report = new StandardReport(model); 22 | } 23 | 24 | [Test] 25 | public void GenerateAndShowPdf() 26 | { 27 | //ImagePlaceholder.Solid = true; 28 | 29 | var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"test_result.pdf"); 30 | Report.GeneratePdf(path); 31 | Process.Start("explorer.exe", path); 32 | } 33 | 34 | [Test] 35 | public void GenerateAndShowXps() 36 | { 37 | var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"test_result.xps"); 38 | Report.GenerateXps(path); 39 | Process.Start("explorer.exe", path); 40 | } 41 | 42 | [Test] 43 | public void Profile() 44 | { 45 | ImagePlaceholder.Solid = true; 46 | 47 | var container = new DocumentContainer(); 48 | Report.Compose(container); 49 | var content = container.Compose(); 50 | 51 | var metadata = Report.GetMetadata(); 52 | var pageContext = new PageContext(); 53 | 54 | DocumentGenerator.RenderPass(pageContext, new FreeCanvas(), content, metadata, null); 55 | DocumentGenerator.RenderPass(pageContext, new FreeCanvas(), content, metadata, null); 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /QuestPDF.ReportSample/Typography.cs: -------------------------------------------------------------------------------- 1 | using QuestPDF.Fluent; 2 | using QuestPDF.Helpers; 3 | using QuestPDF.Infrastructure; 4 | 5 | namespace QuestPDF.ReportSample 6 | { 7 | public static class Typography 8 | { 9 | public static TextStyle Title => TextStyle.Default.FontType(Fonts.Calibri).Color(Colors.Blue.Darken3).Size(26).Black(); 10 | public static TextStyle Headline => TextStyle.Default.FontType(Fonts.Calibri).Color(Colors.Blue.Medium).Size(16).SemiBold(); 11 | public static TextStyle Normal => TextStyle.Default.FontType(Fonts.Verdana).Color(Colors.Black).Size(10).LineHeight(1.2f); 12 | } 13 | } -------------------------------------------------------------------------------- /QuestPDF.UnitTests/BackgroundTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using QuestPDF.Elements; 3 | using QuestPDF.Helpers; 4 | using QuestPDF.Infrastructure; 5 | using QuestPDF.UnitTests.TestEngine; 6 | 7 | namespace QuestPDF.UnitTests 8 | { 9 | [TestFixture] 10 | public class BackgroundTests 11 | { 12 | [Test] 13 | public void Measure() => SimpleContainerTests.Measure(); 14 | 15 | [Test] 16 | public void Draw_ShouldHandleNullChild() 17 | { 18 | TestPlan 19 | .For(x => new Background 20 | { 21 | Color = Colors.Red.Medium 22 | }) 23 | .DrawElement(new Size(400, 300)) 24 | .ExpectCanvasDrawRectangle(new Position(0, 0), new Size(400, 300), Colors.Red.Medium) 25 | .CheckDrawResult(); 26 | } 27 | 28 | [Test] 29 | public void Draw_ShouldHandleChild() 30 | { 31 | TestPlan 32 | .For(x => new Background 33 | { 34 | Color = Colors.Red.Medium, 35 | Child = x.CreateChild() 36 | }) 37 | .DrawElement(new Size(400, 300)) 38 | .ExpectCanvasDrawRectangle(new Position(0, 0), new Size(400, 300), Colors.Red.Medium) 39 | .ExpectChildDraw(new Size(400, 300)) 40 | .CheckDrawResult(); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /QuestPDF.UnitTests/BorderTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using QuestPDF.Drawing; 3 | using QuestPDF.Elements; 4 | using QuestPDF.Helpers; 5 | using QuestPDF.Infrastructure; 6 | using QuestPDF.UnitTests.TestEngine; 7 | 8 | namespace QuestPDF.UnitTests 9 | { 10 | [TestFixture] 11 | public class BorderTests 12 | { 13 | [Test] 14 | public void Measure() => SimpleContainerTests.Measure(); 15 | 16 | [Test] 17 | public void ComponentShouldNotAffectLayout() 18 | { 19 | TestPlan 20 | .For(x => new Border 21 | { 22 | Top = 10, 23 | Right = 20, 24 | Bottom = 30, 25 | Left = 40, 26 | 27 | Child = x.CreateChild() 28 | }) 29 | .MeasureElement(new Size(400, 300)) 30 | .ExpectChildMeasure(expectedInput: new Size(400, 300), returns: SpacePlan.FullRender(new Size(100, 50))) 31 | .CheckMeasureResult( SpacePlan.FullRender(new Size(100, 50))); 32 | } 33 | 34 | [Test] 35 | public void Draw_HorizontalRight_VerticalTop() 36 | { 37 | TestPlan 38 | .For(x => new Border 39 | { 40 | Top = 10, 41 | Right = 20, 42 | Bottom = 30, 43 | Left = 40, 44 | 45 | Color = Colors.Red.Medium, 46 | 47 | Child = x.CreateChild() 48 | }) 49 | .DrawElement(new Size(400, 300)) 50 | .ExpectChildDraw(new Size(400, 300)) 51 | .ExpectCanvasDrawRectangle(new Position(-20, -5), new Size(430, 10), Colors.Red.Medium) // top 52 | .ExpectCanvasDrawRectangle(new Position(-20, -5), new Size(40, 320), Colors.Red.Medium) // left 53 | .ExpectCanvasDrawRectangle(new Position(-20, 285), new Size(430, 30), Colors.Red.Medium) // bottom 54 | .ExpectCanvasDrawRectangle(new Position(390, -5), new Size(20, 320), Colors.Red.Medium) // right 55 | .ExpectChildDraw(new Size(400, 300)) 56 | .CheckDrawResult(); 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /QuestPDF.UnitTests/BoxTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using QuestPDF.Drawing; 3 | using QuestPDF.Elements; 4 | using QuestPDF.Infrastructure; 5 | using QuestPDF.UnitTests.TestEngine; 6 | 7 | namespace QuestPDF.UnitTests 8 | { 9 | [TestFixture] 10 | public class BoxTests 11 | { 12 | [Test] 13 | public void Measure() => SimpleContainerTests.Measure(); 14 | 15 | [Test] 16 | public void Draw_Wrap() 17 | { 18 | TestPlan 19 | .For(x => new MinimalBox 20 | { 21 | Child = x.CreateChild() 22 | }) 23 | .DrawElement(new Size(400, 300)) 24 | .ExpectChildMeasure(expectedInput: new Size(400, 300), returns: SpacePlan.Wrap()) 25 | .CheckDrawResult(); 26 | } 27 | 28 | [Test] 29 | public void Measure_PartialRender() 30 | { 31 | TestPlan 32 | .For(x => new MinimalBox 33 | { 34 | Child = x.CreateChild() 35 | }) 36 | .MeasureElement(new Size(400, 300)) 37 | .ExpectChildMeasure(expectedInput: new Size(400, 300), returns: SpacePlan.PartialRender(200, 100)) 38 | .ExpectChildDraw(new Size(200, 100)) 39 | .CheckDrawResult(); 40 | } 41 | 42 | [Test] 43 | public void Measure_FullRender() 44 | { 45 | TestPlan 46 | .For(x => new MinimalBox 47 | { 48 | Child = x.CreateChild() 49 | }) 50 | .MeasureElement(new Size(500, 400)) 51 | .ExpectChildMeasure(expectedInput: new Size(500, 400), returns: SpacePlan.FullRender(300, 200)) 52 | .ExpectChildDraw(new Size(300, 200)) 53 | .CheckDrawResult(); 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /QuestPDF.UnitTests/ExternalLinkTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using QuestPDF.Elements; 3 | using QuestPDF.UnitTests.TestEngine; 4 | 5 | namespace QuestPDF.UnitTests 6 | { 7 | [TestFixture] 8 | public class ExternalLinkTests 9 | { 10 | [Test] 11 | public void Measure() => SimpleContainerTests.Measure(); 12 | 13 | // TODO: consider tests for the Draw method 14 | } 15 | } -------------------------------------------------------------------------------- /QuestPDF.UnitTests/ImageTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using QuestPDF.Drawing; 3 | using QuestPDF.Elements; 4 | using QuestPDF.Fluent; 5 | using QuestPDF.Infrastructure; 6 | using QuestPDF.UnitTests.TestEngine; 7 | using SkiaSharp; 8 | 9 | namespace QuestPDF.UnitTests 10 | { 11 | [TestFixture] 12 | public class ImageTests 13 | { 14 | [Test] 15 | public void Measure_TakesAvailableSpaceRegardlessOfSize() 16 | { 17 | TestPlan 18 | .For(x => new Image 19 | { 20 | InternalImage = GenerateImage(400, 300) 21 | }) 22 | .MeasureElement(new Size(300, 200)) 23 | .CheckMeasureResult(SpacePlan.FullRender(300, 200)); 24 | } 25 | 26 | [Test] 27 | public void Draw_TakesAvailableSpaceRegardlessOfSize() 28 | { 29 | TestPlan 30 | .For(x => new Image 31 | { 32 | InternalImage = GenerateImage(400, 300) 33 | }) 34 | .DrawElement(new Size(300, 200)) 35 | .ExpectCanvasDrawImage(new Position(0, 0), new Size(300, 200)) 36 | .CheckDrawResult(); 37 | } 38 | 39 | [Test] 40 | public void Fluent_RecognizesImageProportions() 41 | { 42 | var image = GenerateImage(600, 200).Encode(SKEncodedImageFormat.Png, 100).ToArray(); 43 | 44 | TestPlan 45 | .For(x => 46 | { 47 | var container = new Container(); 48 | container.Image(image); 49 | return container; 50 | }) 51 | .MeasureElement(new Size(300, 200)) 52 | .CheckMeasureResult(SpacePlan.FullRender(300, 100));; 53 | } 54 | 55 | SKImage GenerateImage(int width, int height) 56 | { 57 | var imageInfo = new SKImageInfo(width, height); 58 | using var surface = SKSurface.Create(imageInfo); 59 | return surface.Snapshot(); 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /QuestPDF.UnitTests/InternalLinkTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using QuestPDF.Elements; 3 | using QuestPDF.UnitTests.TestEngine; 4 | 5 | namespace QuestPDF.UnitTests 6 | { 7 | [TestFixture] 8 | public class InternalLinkTests 9 | { 10 | [Test] 11 | public void Measure() => SimpleContainerTests.Measure(); 12 | 13 | // TODO: consider tests for the Draw method 14 | } 15 | } -------------------------------------------------------------------------------- /QuestPDF.UnitTests/InternalLocationTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using QuestPDF.Elements; 3 | using QuestPDF.UnitTests.TestEngine; 4 | 5 | namespace QuestPDF.UnitTests 6 | { 7 | [TestFixture] 8 | public class InternalLocationTests 9 | { 10 | [Test] 11 | public void Measure() => SimpleContainerTests.Measure(); 12 | 13 | // TODO: consider tests for the Draw method 14 | } 15 | } -------------------------------------------------------------------------------- /QuestPDF.UnitTests/PageBreakTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using QuestPDF.Drawing; 3 | using QuestPDF.Elements; 4 | using QuestPDF.Infrastructure; 5 | using QuestPDF.UnitTests.TestEngine; 6 | 7 | namespace QuestPDF.UnitTests 8 | { 9 | [TestFixture] 10 | public class PageBreakTests 11 | { 12 | [Test] 13 | public void Measure() 14 | { 15 | TestPlan 16 | .For(x => new PageBreak()) 17 | 18 | .MeasureElement(new Size(400, 300)) 19 | .CheckMeasureResult(SpacePlan.PartialRender(Size.Zero)) 20 | 21 | .DrawElement(new Size(400, 300)) 22 | .CheckDrawResult() 23 | 24 | .MeasureElement(new Size(500, 400)) 25 | .CheckMeasureResult(SpacePlan.FullRender(0, 0)); 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /QuestPDF.UnitTests/QuestPDF.UnitTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | false 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /QuestPDF.UnitTests/RotateTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using QuestPDF.Elements; 3 | using QuestPDF.Infrastructure; 4 | using QuestPDF.UnitTests.TestEngine; 5 | 6 | namespace QuestPDF.UnitTests 7 | { 8 | [TestFixture] 9 | public class RotateTests 10 | { 11 | [Test] 12 | public void Measure() => SimpleContainerTests.Measure(); 13 | 14 | [Test] 15 | public void Draw() 16 | { 17 | TestPlan 18 | .For(x => new Rotate 19 | { 20 | Child = x.CreateChild(), 21 | Angle = 123 22 | }) 23 | .DrawElement(new Size(400, 300)) 24 | .ExpectCanvasRotate(123) 25 | .ExpectChildDraw(new Size(400, 300)) 26 | .ExpectCanvasRotate(-123) 27 | .CheckDrawResult(); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /QuestPDF.UnitTests/ShowEntireTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using QuestPDF.Drawing; 3 | using QuestPDF.Elements; 4 | using QuestPDF.Infrastructure; 5 | using QuestPDF.UnitTests.TestEngine; 6 | 7 | namespace QuestPDF.UnitTests 8 | { 9 | [TestFixture] 10 | public class ShowEntireTests 11 | { 12 | [Test] 13 | public void Measure_ReturnsWrap_WhenElementReturnsWrap() 14 | { 15 | TestPlan 16 | .For(x => new ShowEntire 17 | { 18 | Child = x.CreateChild() 19 | }) 20 | .MeasureElement(new Size(400, 300)) 21 | .ExpectChildMeasure(new Size(400, 300), SpacePlan.Wrap()) 22 | .CheckMeasureResult(SpacePlan.Wrap()); 23 | } 24 | 25 | [Test] 26 | public void Measure_ReturnsWrap_WhenElementReturnsPartialRender() 27 | { 28 | TestPlan 29 | .For(x => new ShowEntire 30 | { 31 | Child = x.CreateChild() 32 | }) 33 | .MeasureElement(new Size(400, 300)) 34 | .ExpectChildMeasure(new Size(400, 300), SpacePlan.PartialRender(300, 200)) 35 | .CheckMeasureResult(SpacePlan.Wrap()); 36 | } 37 | 38 | [Test] 39 | public void Measure_ReturnsFullRender_WhenElementReturnsFullRender() 40 | { 41 | TestPlan 42 | .For(x => new ShowEntire 43 | { 44 | Child = x.CreateChild() 45 | }) 46 | .MeasureElement(new Size(400, 300)) 47 | .ExpectChildMeasure(new Size(400, 300), SpacePlan.FullRender(300, 200)) 48 | .CheckMeasureResult(SpacePlan.FullRender(300, 200)); 49 | } 50 | 51 | [Test] 52 | public void Draw() => SimpleContainerTests.Draw(); 53 | } 54 | } -------------------------------------------------------------------------------- /QuestPDF.UnitTests/ShowOnceTest.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using QuestPDF.Drawing; 3 | using QuestPDF.Elements; 4 | using QuestPDF.Infrastructure; 5 | using QuestPDF.UnitTests.TestEngine; 6 | 7 | namespace QuestPDF.UnitTests 8 | { 9 | [TestFixture] 10 | public class ShowOnceTest 11 | { 12 | [Test] 13 | public void Draw() 14 | { 15 | TestPlan 16 | .For(x => new ShowOnce() 17 | { 18 | Child = x.CreateChild() 19 | }) 20 | 21 | // Measure the element and return result 22 | .MeasureElement(new Size(300, 200)) 23 | .ExpectChildMeasure("child", new Size(300, 200), SpacePlan.PartialRender(new Size(200, 200))) 24 | .CheckMeasureResult(SpacePlan.PartialRender(new Size(200, 200))) 25 | 26 | // Draw element partially 27 | .DrawElement(new Size(200, 200)) 28 | .ExpectChildMeasure(new Size(200, 200), SpacePlan.PartialRender(new Size(200, 200))) 29 | .ExpectChildDraw(new Size(200, 200)) 30 | .CheckDrawResult() 31 | 32 | // Element was not fully drawn 33 | // It should be measured again for rendering on next page 34 | .MeasureElement(new Size(800, 200)) 35 | .ExpectChildMeasure(new Size(800, 200), SpacePlan.FullRender(new Size(400, 200))) 36 | .CheckMeasureResult(SpacePlan.FullRender(new Size(400, 200))) 37 | 38 | // Draw element on next page 39 | // Element was fully drawn at this point 40 | .DrawElement(new Size(400, 200)) 41 | .ExpectChildMeasure(new Size(400, 200), SpacePlan.FullRender(new Size(400, 200))) 42 | .ExpectChildDraw(new Size(400, 200)) 43 | .CheckDrawResult() 44 | 45 | // In the next attempt of measuring element, it should behave like empty parent. 46 | .MeasureElement(new Size(600, 200)) 47 | .CheckMeasureResult(SpacePlan.FullRender(0, 0)) 48 | 49 | // In the next attempt of measuring element, it should not draw its child 50 | .DrawElement(new Size(600, 200)) 51 | .CheckDrawResult(); 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /QuestPDF.UnitTests/TestEngine/ElementMock.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using QuestPDF.Drawing; 3 | using QuestPDF.Infrastructure; 4 | 5 | namespace QuestPDF.UnitTests.TestEngine 6 | { 7 | internal class ElementMock : Element 8 | { 9 | public string Id { get; set; } 10 | public Func MeasureFunc { get; set; } 11 | public Action DrawFunc { get; set; } 12 | 13 | internal override SpacePlan Measure(Size availableSpace) => MeasureFunc(availableSpace); 14 | internal override void Draw(Size availableSpace) => DrawFunc(availableSpace); 15 | } 16 | } -------------------------------------------------------------------------------- /QuestPDF.UnitTests/TestEngine/MockCanvas.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using QuestPDF.Infrastructure; 3 | using SkiaSharp; 4 | 5 | namespace QuestPDF.UnitTests.TestEngine 6 | { 7 | internal class MockCanvas : ICanvas 8 | { 9 | public Action TranslateFunc { get; set; } 10 | public Action RotateFunc { get; set; } 11 | public Action ScaleFunc { get; set; } 12 | public Action DrawImageFunc { get; set; } 13 | public Action DrawTextFunc { get; set; } 14 | public Action DrawRectFunc { get; set; } 15 | 16 | public void Translate(Position vector) => TranslateFunc(vector); 17 | public void Rotate(float angle) => RotateFunc(angle); 18 | public void Scale(float scaleX, float scaleY) => ScaleFunc(scaleX, scaleY); 19 | 20 | public void DrawRectangle(Position vector, Size size, string color) => DrawRectFunc(vector, size, color); 21 | public void DrawText(string text, Position position, TextStyle style) => DrawTextFunc(text, position, style); 22 | public void DrawImage(SKImage image, Position position, Size size) => DrawImageFunc(image, position, size); 23 | 24 | public void DrawHyperlink(string url, Size size) => throw new NotImplementedException(); 25 | public void DrawSectionLink(string sectionName, Size size) => throw new NotImplementedException(); 26 | public void DrawSection(string sectionName) => throw new NotImplementedException(); 27 | } 28 | } -------------------------------------------------------------------------------- /QuestPDF.UnitTests/TestEngine/OperationBase.cs: -------------------------------------------------------------------------------- 1 | namespace QuestPDF.UnitTests.TestEngine 2 | { 3 | public abstract class OperationBase 4 | { 5 | 6 | } 7 | } -------------------------------------------------------------------------------- /QuestPDF.UnitTests/TestEngine/OperationRecordingCanvas.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using QuestPDF.Infrastructure; 4 | using QuestPDF.UnitTests.TestEngine.Operations; 5 | using SkiaSharp; 6 | 7 | namespace QuestPDF.UnitTests.TestEngine 8 | { 9 | internal class OperationRecordingCanvas : ICanvas 10 | { 11 | public ICollection Operations { get; } = new List(); 12 | 13 | public void Translate(Position vector) => Operations.Add(new CanvasTranslateOperation(vector)); 14 | public void Rotate(float angle) => Operations.Add(new CanvasRotateOperation(angle)); 15 | public void Scale(float scaleX, float scaleY) => Operations.Add(new CanvasScaleOperation(scaleX, scaleY)); 16 | 17 | public void DrawRectangle(Position vector, Size size, string color) => Operations.Add(new CanvasDrawRectangleOperation(vector, size, color)); 18 | public void DrawText(string text, Position position, TextStyle style) => Operations.Add(new CanvasDrawTextOperation(text, position, style)); 19 | public void DrawImage(SKImage image, Position position, Size size) => Operations.Add(new CanvasDrawImageOperation(position, size)); 20 | 21 | public void DrawHyperlink(string url, Size size) => throw new NotImplementedException(); 22 | public void DrawSectionLink(string sectionName, Size size) => throw new NotImplementedException(); 23 | public void DrawSection(string sectionName) => throw new NotImplementedException(); 24 | } 25 | } -------------------------------------------------------------------------------- /QuestPDF.UnitTests/TestEngine/Operations/CanvasDrawImageOperation.cs: -------------------------------------------------------------------------------- 1 | using QuestPDF.Infrastructure; 2 | 3 | namespace QuestPDF.UnitTests.TestEngine.Operations 4 | { 5 | internal class CanvasDrawImageOperation : OperationBase 6 | { 7 | public Position Position { get; } 8 | public Size Size { get; } 9 | 10 | public CanvasDrawImageOperation(Position position, Size size) 11 | { 12 | Position = position; 13 | Size = size; 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /QuestPDF.UnitTests/TestEngine/Operations/CanvasDrawRectangleOperation.cs: -------------------------------------------------------------------------------- 1 | using QuestPDF.Infrastructure; 2 | 3 | namespace QuestPDF.UnitTests.TestEngine.Operations 4 | { 5 | internal class CanvasDrawRectangleOperation : OperationBase 6 | { 7 | public Position Position { get; } 8 | public Size Size { get; } 9 | public string Color { get; } 10 | 11 | public CanvasDrawRectangleOperation(Position position, Size size, string color) 12 | { 13 | Position = position; 14 | Size = size; 15 | Color = color; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /QuestPDF.UnitTests/TestEngine/Operations/CanvasDrawTextOperation.cs: -------------------------------------------------------------------------------- 1 | using QuestPDF.Infrastructure; 2 | 3 | namespace QuestPDF.UnitTests.TestEngine.Operations 4 | { 5 | internal class CanvasDrawTextOperation : OperationBase 6 | { 7 | public string Text { get; } 8 | public Position Position { get; } 9 | public TextStyle Style { get; } 10 | 11 | public CanvasDrawTextOperation(string text, Position position, TextStyle style) 12 | { 13 | Text = text; 14 | Position = position; 15 | Style = style; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /QuestPDF.UnitTests/TestEngine/Operations/CanvasRotateOperation.cs: -------------------------------------------------------------------------------- 1 | namespace QuestPDF.UnitTests.TestEngine.Operations 2 | { 3 | public class CanvasRotateOperation : OperationBase 4 | { 5 | public float Angle { get; } 6 | 7 | public CanvasRotateOperation(float angle) 8 | { 9 | Angle = angle; 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /QuestPDF.UnitTests/TestEngine/Operations/CanvasScaleOperation.cs: -------------------------------------------------------------------------------- 1 | namespace QuestPDF.UnitTests.TestEngine.Operations 2 | { 3 | public class CanvasScaleOperation : OperationBase 4 | { 5 | public float ScaleX { get; } 6 | public float ScaleY { get; } 7 | 8 | public CanvasScaleOperation(float scaleX, float scaleY) 9 | { 10 | ScaleX = scaleX; 11 | ScaleY = scaleY; 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /QuestPDF.UnitTests/TestEngine/Operations/CanvasTranslateOperation.cs: -------------------------------------------------------------------------------- 1 | using QuestPDF.Infrastructure; 2 | 3 | namespace QuestPDF.UnitTests.TestEngine.Operations 4 | { 5 | internal class CanvasTranslateOperation : OperationBase 6 | { 7 | public Position Position { get; } 8 | 9 | public CanvasTranslateOperation(Position position) 10 | { 11 | Position = position; 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /QuestPDF.UnitTests/TestEngine/Operations/ChildDrawOperation.cs: -------------------------------------------------------------------------------- 1 | using QuestPDF.Infrastructure; 2 | 3 | namespace QuestPDF.UnitTests.TestEngine.Operations 4 | { 5 | public class ChildDrawOperation : OperationBase 6 | { 7 | public string ChildId { get; } 8 | public Size Input { get; } 9 | 10 | public ChildDrawOperation(string childId, Size input) 11 | { 12 | ChildId = childId; 13 | Input = input; 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /QuestPDF.UnitTests/TestEngine/Operations/ChildMeasureOperation.cs: -------------------------------------------------------------------------------- 1 | using QuestPDF.Drawing; 2 | using QuestPDF.Infrastructure; 3 | 4 | namespace QuestPDF.UnitTests.TestEngine.Operations 5 | { 6 | internal class ChildMeasureOperation : OperationBase 7 | { 8 | public string ChildId { get; } 9 | public Size Input { get; } 10 | public SpacePlan Output { get; } 11 | 12 | public ChildMeasureOperation(string childId, Size input, SpacePlan output) 13 | { 14 | ChildId = childId; 15 | Input = input; 16 | Output = output; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /QuestPDF.UnitTests/TestEngine/Operations/ElementMeasureOperation.cs: -------------------------------------------------------------------------------- 1 | using QuestPDF.Infrastructure; 2 | 3 | namespace QuestPDF.UnitTests.TestEngine.Operations 4 | { 5 | public class ElementMeasureOperation : OperationBase 6 | { 7 | public ElementMeasureOperation(Size input) 8 | { 9 | 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /QuestPDF.UnitTests/TestsBase.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using NUnit.Framework; 3 | 4 | namespace QuestPDF.UnitTests 5 | { 6 | [SetUpFixture] 7 | public class TestsBase 8 | { 9 | [OneTimeSetUp] 10 | public void RunBeforeAnyTests() 11 | { 12 | AssertionOptions.AssertEquivalencyUsing(options => options 13 | .IncludingNestedObjects() 14 | .IncludingInternalProperties() 15 | .IncludingInternalFields() 16 | .AllowingInfiniteRecursion() 17 | .RespectingRuntimeTypes() 18 | .WithTracing() 19 | .WithStrictOrdering()); 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /QuestPDF.UnitTests/TranslateTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using QuestPDF.Elements; 3 | using QuestPDF.Infrastructure; 4 | using QuestPDF.UnitTests.TestEngine; 5 | 6 | namespace QuestPDF.UnitTests 7 | { 8 | [TestFixture] 9 | public class TranslateTests 10 | { 11 | [Test] 12 | public void Measure() => SimpleContainerTests.Measure(); 13 | 14 | [Test] 15 | public void Draw() 16 | { 17 | TestPlan 18 | .For(x => new Translate 19 | { 20 | Child = x.CreateChild(), 21 | TranslateX = 50, 22 | TranslateY = 75 23 | }) 24 | .DrawElement(new Size(400, 300)) 25 | .ExpectCanvasTranslate(50, 75) 26 | .ExpectChildDraw(new Size(400, 300)) 27 | .ExpectCanvasTranslate(-50, -75) 28 | .CheckDrawResult(); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /QuestPDF/Drawing/DocumentContainer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using QuestPDF.Elements; 5 | using QuestPDF.Fluent; 6 | using QuestPDF.Infrastructure; 7 | 8 | namespace QuestPDF.Drawing 9 | { 10 | internal class DocumentContainer : IDocumentContainer 11 | { 12 | internal List Pages { get; set; } = new List(); 13 | 14 | internal Container Compose() 15 | { 16 | var container = new Container(); 17 | 18 | container 19 | .Column(column => 20 | { 21 | Pages 22 | .SelectMany(x => new List() 23 | { 24 | () => column.Item().PageBreak(), 25 | () => column.Item().Component(x) 26 | }) 27 | .Skip(1) 28 | .ToList() 29 | .ForEach(x => x()); 30 | }); 31 | 32 | return container; 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /QuestPDF/Drawing/DocumentMetadata.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace QuestPDF.Drawing 4 | { 5 | public class DocumentMetadata 6 | { 7 | public int ImageQuality { get; set; } = 101; 8 | public int RasterDpi { get; set; } = 72; 9 | public bool PdfA { get; set; } 10 | 11 | public string? Title { get; set; } 12 | public string? Author { get; set; } 13 | public string? Subject { get; set; } 14 | public string? Keywords { get; set; } 15 | public string? Creator { get; set; } 16 | public string? Producer { get; set; } 17 | 18 | public DateTime CreationDate { get; set; } = DateTime.Now; 19 | public DateTime ModifiedDate { get; set; } = DateTime.Now; 20 | 21 | /// 22 | /// If the number of generated pages exceeds this threshold 23 | /// (likely due to infinite layout), the exception is thrown. 24 | /// 25 | public int DocumentLayoutExceptionThreshold { get; set; } = 250; 26 | 27 | public bool ApplyCaching { get; set; } = !System.Diagnostics.Debugger.IsAttached; 28 | public bool ApplyDebugging { get; set; } = System.Diagnostics.Debugger.IsAttached; 29 | 30 | public static DocumentMetadata Default => new DocumentMetadata(); 31 | } 32 | } -------------------------------------------------------------------------------- /QuestPDF/Drawing/Exceptions/DocumentComposeException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace QuestPDF.Drawing.Exceptions 4 | { 5 | public class DocumentComposeException : Exception 6 | { 7 | public DocumentComposeException() 8 | { 9 | 10 | } 11 | 12 | public DocumentComposeException(string message) : base(message) 13 | { 14 | 15 | } 16 | 17 | public DocumentComposeException(string message, Exception inner) : base(message, inner) 18 | { 19 | 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /QuestPDF/Drawing/Exceptions/DocumentDrawingException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace QuestPDF.Drawing.Exceptions 4 | { 5 | public class DocumentDrawingException : Exception 6 | { 7 | public DocumentDrawingException() 8 | { 9 | 10 | } 11 | 12 | public DocumentDrawingException(string message) : base(message) 13 | { 14 | 15 | } 16 | 17 | public DocumentDrawingException(string message, Exception inner) : base(message, inner) 18 | { 19 | 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /QuestPDF/Drawing/Exceptions/DocumentLayoutException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace QuestPDF.Drawing.Exceptions 4 | { 5 | public class DocumentLayoutException : Exception 6 | { 7 | public string ElementTrace { get; set; } 8 | 9 | public DocumentLayoutException() 10 | { 11 | 12 | } 13 | 14 | public DocumentLayoutException(string message) : base(message) 15 | { 16 | 17 | } 18 | 19 | public DocumentLayoutException(string message, Exception inner) : base(message, inner) 20 | { 21 | 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /QuestPDF/Drawing/FreeCanvas.cs: -------------------------------------------------------------------------------- 1 | using QuestPDF.Infrastructure; 2 | using SkiaSharp; 3 | 4 | namespace QuestPDF.Drawing 5 | { 6 | internal class FreeCanvas : ICanvas, IRenderingCanvas 7 | { 8 | #region IRenderingCanvas 9 | 10 | public void BeginDocument() 11 | { 12 | 13 | } 14 | 15 | public void EndDocument() 16 | { 17 | 18 | } 19 | 20 | public void BeginPage(Size size) 21 | { 22 | 23 | } 24 | 25 | public void EndPage() 26 | { 27 | 28 | } 29 | 30 | #endregion 31 | 32 | #region ICanvas 33 | 34 | public void Translate(Position vector) 35 | { 36 | 37 | } 38 | 39 | public void DrawRectangle(Position vector, Size size, string color) 40 | { 41 | 42 | } 43 | 44 | public void DrawText(string text, Position position, TextStyle style) 45 | { 46 | 47 | } 48 | 49 | public void DrawImage(SKImage image, Position position, Size size) 50 | { 51 | 52 | } 53 | 54 | public void DrawHyperlink(string url, Size size) 55 | { 56 | 57 | } 58 | 59 | public void DrawSectionLink(string sectionName, Size size) 60 | { 61 | 62 | } 63 | 64 | public void DrawSection(string sectionName) 65 | { 66 | 67 | } 68 | 69 | public void Rotate(float angle) 70 | { 71 | 72 | } 73 | 74 | public void Scale(float scaleX, float scaleY) 75 | { 76 | 77 | } 78 | 79 | #endregion 80 | } 81 | } -------------------------------------------------------------------------------- /QuestPDF/Drawing/ImageCanvas.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using QuestPDF.Helpers; 3 | using QuestPDF.Infrastructure; 4 | using SkiaSharp; 5 | 6 | namespace QuestPDF.Drawing 7 | { 8 | internal class ImageCanvas : SkiaCanvasBase 9 | { 10 | private DocumentMetadata Metadata { get; } 11 | private SKSurface Surface { get; set; } 12 | 13 | internal ICollection Images { get; } = new List(); 14 | 15 | public ImageCanvas(DocumentMetadata metadata) 16 | { 17 | Metadata = metadata; 18 | } 19 | 20 | public override void BeginDocument() 21 | { 22 | 23 | } 24 | 25 | public override void EndDocument() 26 | { 27 | Canvas?.Dispose(); 28 | Surface?.Dispose(); 29 | } 30 | 31 | public override void BeginPage(Size size) 32 | { 33 | var scalingFactor = Metadata.RasterDpi / (float) PageSizes.PointsPerInch; 34 | var imageInfo = new SKImageInfo((int) (size.Width * scalingFactor), (int) (size.Height * scalingFactor)); 35 | 36 | Surface = SKSurface.Create(imageInfo); 37 | Canvas = Surface.Canvas; 38 | 39 | Canvas.Scale(scalingFactor); 40 | } 41 | 42 | public override void EndPage() 43 | { 44 | Canvas.Save(); 45 | var image = Surface.Snapshot().Encode(SKEncodedImageFormat.Png, 100).ToArray(); 46 | Images.Add(image); 47 | 48 | Canvas.Dispose(); 49 | Surface.Dispose(); 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /QuestPDF/Drawing/PdfCanvas.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using SkiaSharp; 3 | 4 | namespace QuestPDF.Drawing 5 | { 6 | internal class PdfCanvas : SkiaDocumentCanvasBase 7 | { 8 | public PdfCanvas(Stream stream, DocumentMetadata documentMetadata) 9 | : base(SKDocument.CreatePdf(stream, MapMetadata(documentMetadata))) 10 | { 11 | 12 | } 13 | 14 | private static SKDocumentPdfMetadata MapMetadata(DocumentMetadata metadata) 15 | { 16 | return new SKDocumentPdfMetadata 17 | { 18 | Title = metadata.Title, 19 | Author = metadata.Author, 20 | Subject = metadata.Subject, 21 | Keywords = metadata.Keywords, 22 | Creator = metadata.Creator, 23 | Producer = metadata.Producer, 24 | 25 | Creation = metadata.CreationDate, 26 | Modified = metadata.ModifiedDate, 27 | 28 | RasterDpi = metadata.RasterDpi, 29 | EncodingQuality = metadata.ImageQuality, 30 | PdfA = metadata.PdfA 31 | }; 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /QuestPDF/Drawing/Proxy/CacheProxy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using QuestPDF.Infrastructure; 3 | 4 | namespace QuestPDF.Drawing.Proxy 5 | { 6 | internal class CacheProxy : ElementProxy 7 | { 8 | public Size? AvailableSpace { get; set; } 9 | public SpacePlan? MeasurementResult { get; set; } 10 | 11 | public CacheProxy(Element child) 12 | { 13 | Child = child; 14 | } 15 | 16 | internal override SpacePlan Measure(Size availableSpace) 17 | { 18 | if (MeasurementResult != null && 19 | AvailableSpace != null && 20 | IsClose(AvailableSpace.Value.Width, availableSpace.Width) && 21 | IsClose(AvailableSpace.Value.Height, availableSpace.Height)) 22 | { 23 | return MeasurementResult.Value; 24 | } 25 | 26 | AvailableSpace = availableSpace; 27 | MeasurementResult = base.Measure(availableSpace); 28 | 29 | return MeasurementResult.Value; 30 | } 31 | 32 | internal override void Draw(Size availableSpace) 33 | { 34 | AvailableSpace = null; 35 | MeasurementResult = null; 36 | 37 | base.Draw(availableSpace); 38 | } 39 | 40 | private bool IsClose(float x, float y) 41 | { 42 | return Math.Abs(x - y) < Size.Epsilon; 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /QuestPDF/Drawing/Proxy/DebugStackItem.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using QuestPDF.Infrastructure; 3 | 4 | namespace QuestPDF.Drawing.Proxy 5 | { 6 | public class DebugStackItem 7 | { 8 | public IElement Element { get; internal set; } 9 | public Size AvailableSpace { get; internal set; } 10 | public SpacePlan SpacePlan { get; internal set; } 11 | 12 | public ICollection Stack { get; internal set; } = new List(); 13 | } 14 | } -------------------------------------------------------------------------------- /QuestPDF/Drawing/Proxy/DebuggingProxy.cs: -------------------------------------------------------------------------------- 1 | using QuestPDF.Infrastructure; 2 | 3 | namespace QuestPDF.Drawing.Proxy 4 | { 5 | internal class DebuggingProxy : ElementProxy 6 | { 7 | private DebuggingState DebuggingState { get; } 8 | 9 | public DebuggingProxy(DebuggingState debuggingState, Element child) 10 | { 11 | DebuggingState = debuggingState; 12 | Child = child; 13 | } 14 | 15 | internal override SpacePlan Measure(Size availableSpace) 16 | { 17 | DebuggingState.RegisterMeasure(Child, availableSpace); 18 | var spacePlan = base.Measure(availableSpace); 19 | DebuggingState.RegisterMeasureResult(Child, spacePlan); 20 | 21 | return spacePlan; 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /QuestPDF/Drawing/Proxy/ElementProxy.cs: -------------------------------------------------------------------------------- 1 | using QuestPDF.Infrastructure; 2 | 3 | namespace QuestPDF.Drawing.Proxy 4 | { 5 | internal class ElementProxy : ContainerElement 6 | { 7 | 8 | } 9 | } -------------------------------------------------------------------------------- /QuestPDF/Drawing/SkiaCanvasBase.cs: -------------------------------------------------------------------------------- 1 | using QuestPDF.Infrastructure; 2 | using SkiaSharp; 3 | 4 | namespace QuestPDF.Drawing 5 | { 6 | internal abstract class SkiaCanvasBase : ICanvas, IRenderingCanvas 7 | { 8 | internal SKCanvas Canvas { get; set; } 9 | 10 | public abstract void BeginDocument(); 11 | public abstract void EndDocument(); 12 | 13 | public abstract void BeginPage(Size size); 14 | public abstract void EndPage(); 15 | 16 | public void Translate(Position vector) 17 | { 18 | Canvas.Translate(vector.X, vector.Y); 19 | } 20 | 21 | public void DrawRectangle(Position vector, Size size, string color) 22 | { 23 | if (size.Width < Size.Epsilon || size.Height < Size.Epsilon) 24 | return; 25 | 26 | var paint = color.ColorToPaint(); 27 | Canvas.DrawRect(vector.X, vector.Y, size.Width, size.Height, paint); 28 | } 29 | 30 | public void DrawText(string text, Position vector, TextStyle style) 31 | { 32 | Canvas.DrawText(text, vector.X, vector.Y, style.ToPaint()); 33 | } 34 | 35 | public void DrawImage(SKImage image, Position vector, Size size) 36 | { 37 | Canvas.DrawImage(image, new SKRect(vector.X, vector.Y, size.Width, size.Height)); 38 | } 39 | 40 | public void DrawHyperlink(string url, Size size) 41 | { 42 | Canvas.DrawUrlAnnotation(new SKRect(0, 0, size.Width, size.Height), url); 43 | } 44 | 45 | public void DrawSectionLink(string sectionName, Size size) 46 | { 47 | Canvas.DrawLinkDestinationAnnotation(new SKRect(0, 0, size.Width, size.Height), sectionName); 48 | } 49 | 50 | public void DrawSection(string sectionName) 51 | { 52 | Canvas.DrawNamedDestinationAnnotation(new SKPoint(0, 0), sectionName); 53 | } 54 | 55 | public void Rotate(float angle) 56 | { 57 | Canvas.RotateDegrees(angle); 58 | } 59 | 60 | public void Scale(float scaleX, float scaleY) 61 | { 62 | Canvas.Scale(scaleX, scaleY); 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /QuestPDF/Drawing/SkiaDocumentCanvasBase.cs: -------------------------------------------------------------------------------- 1 | using QuestPDF.Infrastructure; 2 | using SkiaSharp; 3 | 4 | namespace QuestPDF.Drawing 5 | { 6 | internal class SkiaDocumentCanvasBase : SkiaCanvasBase 7 | { 8 | private SKDocument? Document { get; } 9 | 10 | protected SkiaDocumentCanvasBase(SKDocument document) 11 | { 12 | Document = document; 13 | } 14 | 15 | ~SkiaDocumentCanvasBase() 16 | { 17 | Document?.Dispose(); 18 | } 19 | 20 | public override void BeginDocument() 21 | { 22 | 23 | } 24 | 25 | public override void EndDocument() 26 | { 27 | Canvas?.Dispose(); 28 | 29 | Document.Close(); 30 | Document.Dispose(); 31 | } 32 | 33 | public override void BeginPage(Size size) 34 | { 35 | Canvas = Document.BeginPage(size.Width, size.Height); 36 | } 37 | 38 | public override void EndPage() 39 | { 40 | Document.EndPage(); 41 | Canvas.Dispose(); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /QuestPDF/Drawing/SkiaPictureCanvas.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using QuestPDF.Infrastructure; 3 | using SkiaSharp; 4 | 5 | namespace QuestPDF.Drawing 6 | { 7 | public class PreviewerPicture 8 | { 9 | public SKPicture Picture { get; set; } 10 | public Size Size { get; set; } 11 | 12 | public PreviewerPicture(SKPicture picture, Size size) 13 | { 14 | Picture = picture; 15 | Size = size; 16 | } 17 | } 18 | 19 | internal class SkiaPictureCanvas : SkiaCanvasBase 20 | { 21 | private SKPictureRecorder? PictureRecorder { get; set; } 22 | private Size? CurrentPageSize { get; set; } 23 | 24 | public ICollection Pictures { get; } = new List(); 25 | 26 | public override void BeginDocument() 27 | { 28 | Pictures.Clear(); 29 | } 30 | 31 | public override void BeginPage(Size size) 32 | { 33 | CurrentPageSize = size; 34 | PictureRecorder = new SKPictureRecorder(); 35 | 36 | Canvas = PictureRecorder.BeginRecording(new SKRect(0, 0, size.Width, size.Height)); 37 | } 38 | 39 | public override void EndPage() 40 | { 41 | var picture = PictureRecorder?.EndRecording(); 42 | 43 | if (picture != null && CurrentPageSize.HasValue) 44 | Pictures.Add(new PreviewerPicture(picture, CurrentPageSize.Value)); 45 | 46 | PictureRecorder?.Dispose(); 47 | PictureRecorder = null; 48 | } 49 | 50 | public override void EndDocument() { } 51 | } 52 | } -------------------------------------------------------------------------------- /QuestPDF/Drawing/SpacePlan.cs: -------------------------------------------------------------------------------- 1 | using QuestPDF.Infrastructure; 2 | 3 | namespace QuestPDF.Drawing 4 | { 5 | public readonly struct SpacePlan 6 | { 7 | public readonly SpacePlanType Type; 8 | public readonly float Width; 9 | public readonly float Height; 10 | 11 | internal SpacePlan(SpacePlanType type, float width, float height) 12 | { 13 | Type = type; 14 | Width = width; 15 | Height = height; 16 | } 17 | 18 | internal static SpacePlan Wrap() => new SpacePlan(SpacePlanType.Wrap, 0, 0); 19 | 20 | internal static SpacePlan PartialRender(float width, float height) => new SpacePlan(SpacePlanType.PartialRender, width, height); 21 | 22 | internal static SpacePlan PartialRender(Size size) => PartialRender(size.Width, size.Height); 23 | 24 | internal static SpacePlan FullRender(float width, float height) => new SpacePlan(SpacePlanType.FullRender, width, height); 25 | 26 | internal static SpacePlan FullRender(Size size) => FullRender(size.Width, size.Height); 27 | 28 | public override string ToString() 29 | { 30 | if (Type == SpacePlanType.Wrap) 31 | return Type.ToString(); 32 | 33 | return $"{Type} (Width: {Width:N3}, Height: {Height:N3})"; 34 | } 35 | 36 | public static implicit operator Size(SpacePlan spacePlan) 37 | { 38 | return new Size(spacePlan.Width, spacePlan.Height); 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /QuestPDF/Drawing/SpacePlanType.cs: -------------------------------------------------------------------------------- 1 | namespace QuestPDF.Drawing 2 | { 3 | public enum SpacePlanType 4 | { 5 | Wrap, 6 | PartialRender, 7 | FullRender 8 | } 9 | } -------------------------------------------------------------------------------- /QuestPDF/Drawing/TextMeasurement.cs: -------------------------------------------------------------------------------- 1 | namespace QuestPDF.Drawing 2 | { 3 | internal struct TextMeasurement 4 | { 5 | public int LineIndex { get; set; } 6 | public float FragmentWidth { get; set; } 7 | } 8 | } -------------------------------------------------------------------------------- /QuestPDF/Drawing/XpsCanvas.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using SkiaSharp; 3 | 4 | namespace QuestPDF.Drawing 5 | { 6 | internal class XpsCanvas : SkiaDocumentCanvasBase 7 | { 8 | public XpsCanvas(Stream stream, DocumentMetadata documentMetadata) 9 | : base(SKDocument.CreateXps(stream, documentMetadata.RasterDpi)) 10 | { 11 | 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /QuestPDF/Elements/Alignment.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using QuestPDF.Drawing; 3 | using QuestPDF.Infrastructure; 4 | 5 | namespace QuestPDF.Elements 6 | { 7 | internal class Alignment : ContainerElement 8 | { 9 | public VerticalAlignment Vertical { get; set; } = VerticalAlignment.Top; 10 | public HorizontalAlignment Horizontal { get; set; } = HorizontalAlignment.Left; 11 | 12 | internal override void Draw(Size availableSpace) 13 | { 14 | if (Child == null) 15 | return; 16 | 17 | var childSize = base.Measure(availableSpace); 18 | 19 | if (childSize.Type == SpacePlanType.Wrap) 20 | return; 21 | 22 | var top = GetTopOffset(availableSpace, childSize); 23 | var left = GetLeftOffset(availableSpace, childSize); 24 | 25 | Canvas.Translate(new Position(left, top)); 26 | base.Draw(childSize); 27 | Canvas.Translate(new Position(-left, -top)); 28 | } 29 | 30 | private float GetTopOffset(Size availableSpace, Size childSize) 31 | { 32 | var difference = availableSpace.Height - childSize.Height; 33 | 34 | return Vertical switch 35 | { 36 | VerticalAlignment.Top => 0, 37 | VerticalAlignment.Middle => difference / 2, 38 | VerticalAlignment.Bottom => difference, 39 | _ => throw new NotSupportedException() 40 | }; 41 | } 42 | 43 | private float GetLeftOffset(Size availableSpace, Size childSize) 44 | { 45 | var difference = availableSpace.Width - childSize.Width; 46 | 47 | return Horizontal switch 48 | { 49 | HorizontalAlignment.Left => 0, 50 | HorizontalAlignment.Center => difference / 2, 51 | HorizontalAlignment.Right => difference, 52 | _ => throw new NotSupportedException() 53 | }; 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /QuestPDF/Elements/AspectRatio.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using QuestPDF.Drawing; 3 | using QuestPDF.Infrastructure; 4 | 5 | namespace QuestPDF.Elements 6 | { 7 | internal class AspectRatio : ContainerElement, ICacheable 8 | { 9 | public float Ratio { get; set; } = 1; 10 | public AspectRatioOption Option { get; set; } = AspectRatioOption.FitWidth; 11 | 12 | internal override SpacePlan Measure(Size availableSpace) 13 | { 14 | if(Child == null) 15 | return SpacePlan.FullRender(0, 0); 16 | 17 | var targetSize = GetTargetSize(availableSpace); 18 | 19 | if (targetSize.Height > availableSpace.Height + Size.Epsilon) 20 | return SpacePlan.Wrap(); 21 | 22 | if (targetSize.Width > availableSpace.Width + Size.Epsilon) 23 | return SpacePlan.Wrap(); 24 | 25 | var childSize = base.Measure(targetSize); 26 | 27 | if (childSize.Type == SpacePlanType.Wrap) 28 | return SpacePlan.Wrap(); 29 | 30 | if (childSize.Type == SpacePlanType.PartialRender) 31 | return SpacePlan.PartialRender(targetSize); 32 | 33 | if (childSize.Type == SpacePlanType.FullRender) 34 | return SpacePlan.FullRender(targetSize); 35 | 36 | throw new NotSupportedException(); 37 | } 38 | 39 | internal override void Draw(Size availableSpace) 40 | { 41 | if (Child == null) 42 | return; 43 | 44 | var size = GetTargetSize(availableSpace); 45 | base.Draw(size); 46 | } 47 | 48 | private Size GetTargetSize(Size availableSpace) 49 | { 50 | var spaceRatio = availableSpace.Width / availableSpace.Height; 51 | 52 | var fitHeight = new Size(availableSpace.Height * Ratio, availableSpace.Height) ; 53 | var fitWidth = new Size(availableSpace.Width, availableSpace.Width / Ratio); 54 | 55 | return Option switch 56 | { 57 | AspectRatioOption.FitWidth => fitWidth, 58 | AspectRatioOption.FitHeight => fitHeight, 59 | AspectRatioOption.FitArea => Ratio < spaceRatio ? fitHeight : fitWidth, 60 | _ => throw new ArgumentOutOfRangeException() 61 | }; 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /QuestPDF/Elements/Background.cs: -------------------------------------------------------------------------------- 1 | using QuestPDF.Helpers; 2 | using QuestPDF.Infrastructure; 3 | 4 | namespace QuestPDF.Elements 5 | { 6 | internal class Background : ContainerElement 7 | { 8 | public string Color { get; set; } = Colors.Black; 9 | 10 | internal override void Draw(Size availableSpace) 11 | { 12 | Canvas.DrawRectangle(Position.Zero, availableSpace, Color); 13 | base.Draw(availableSpace); 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /QuestPDF/Elements/Border.cs: -------------------------------------------------------------------------------- 1 | using QuestPDF.Helpers; 2 | using QuestPDF.Infrastructure; 3 | 4 | namespace QuestPDF.Elements 5 | { 6 | internal class Border : ContainerElement 7 | { 8 | public string Color { get; set; } = Colors.Black; 9 | 10 | public float Top { get; set; } 11 | public float Right { get; set; } 12 | public float Bottom { get; set; } 13 | public float Left { get; set; } 14 | 15 | internal override void Draw(Size availableSpace) 16 | { 17 | base.Draw(availableSpace); 18 | 19 | Canvas.DrawRectangle( 20 | new Position(-Left/2, -Top/2), 21 | new Size(availableSpace.Width + Left/2 + Right/2, Top), 22 | Color); 23 | 24 | Canvas.DrawRectangle( 25 | new Position(-Left/2, -Top/2), 26 | new Size(Left, availableSpace.Height + Top/2 + Bottom/2), 27 | Color); 28 | 29 | Canvas.DrawRectangle( 30 | new Position(-Left/2, availableSpace.Height-Bottom/2), 31 | new Size(availableSpace.Width + Left/2 + Right/2, Bottom), 32 | Color); 33 | 34 | Canvas.DrawRectangle( 35 | new Position(availableSpace.Width-Right/2, -Top/2), 36 | new Size(Right, availableSpace.Height + Top/2 + Bottom/2), 37 | Color); 38 | } 39 | 40 | public override string ToString() 41 | { 42 | return $"Border: Top({Top}) Right({Right}) Bottom({Bottom}) Left({Left}) Color({Color})"; 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /QuestPDF/Elements/Canvas.cs: -------------------------------------------------------------------------------- 1 | using QuestPDF.Drawing; 2 | using QuestPDF.Infrastructure; 3 | using SkiaSharp; 4 | 5 | namespace QuestPDF.Elements 6 | { 7 | public delegate void DrawOnCanvas(SKCanvas canvas, Size availableSpace); 8 | 9 | internal class Canvas : Element, ICacheable 10 | { 11 | public DrawOnCanvas Handler { get; set; } 12 | 13 | internal override SpacePlan Measure(Size availableSpace) 14 | { 15 | return SpacePlan.FullRender(availableSpace); 16 | } 17 | 18 | internal override void Draw(Size availableSpace) 19 | { 20 | var skiaCanvas = (Canvas as Drawing.SkiaCanvasBase)?.Canvas; 21 | 22 | if (Handler == null || skiaCanvas == null) 23 | return; 24 | 25 | var originalMatrix = skiaCanvas.TotalMatrix; 26 | Handler.Invoke(skiaCanvas, availableSpace); 27 | skiaCanvas.SetMatrix(originalMatrix); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /QuestPDF/Elements/Constrained.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using QuestPDF.Drawing; 3 | using QuestPDF.Infrastructure; 4 | 5 | namespace QuestPDF.Elements 6 | { 7 | internal class Constrained : ContainerElement, ICacheable 8 | { 9 | public float? MinWidth { get; set; } 10 | public float? MaxWidth { get; set; } 11 | 12 | public float? MinHeight { get; set; } 13 | public float? MaxHeight { get; set; } 14 | 15 | internal override SpacePlan Measure(Size availableSpace) 16 | { 17 | if (MinWidth > availableSpace.Width + Size.Epsilon) 18 | return SpacePlan.Wrap(); 19 | 20 | if (MinHeight > availableSpace.Height + Size.Epsilon) 21 | return SpacePlan.Wrap(); 22 | 23 | var available = new Size( 24 | Min(MaxWidth, availableSpace.Width), 25 | Min(MaxHeight, availableSpace.Height)); 26 | 27 | var measurement = base.Measure(available); 28 | 29 | if (measurement.Type == SpacePlanType.Wrap) 30 | return SpacePlan.Wrap(); 31 | 32 | var actualSize = new Size( 33 | Max(MinWidth, measurement.Width), 34 | Max(MinHeight, measurement.Height)); 35 | 36 | if (measurement.Type == SpacePlanType.FullRender) 37 | return SpacePlan.FullRender(actualSize); 38 | 39 | if (measurement.Type == SpacePlanType.PartialRender) 40 | return SpacePlan.PartialRender(actualSize); 41 | 42 | throw new NotSupportedException(); 43 | } 44 | 45 | internal override void Draw(Size availableSpace) 46 | { 47 | var available = new Size( 48 | Min(MaxWidth, availableSpace.Width), 49 | Min(MaxHeight, availableSpace.Height)); 50 | 51 | Child?.Draw(available); 52 | } 53 | 54 | private static float Min(float? x, float y) 55 | { 56 | return x.HasValue ? Math.Min(x.Value, y) : y; 57 | } 58 | 59 | private static float Max(float? x, float y) 60 | { 61 | return x.HasValue ? Math.Max(x.Value, y) : y; 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /QuestPDF/Elements/Container.cs: -------------------------------------------------------------------------------- 1 | using QuestPDF.Infrastructure; 2 | 3 | namespace QuestPDF.Elements 4 | { 5 | internal class Container : ContainerElement 6 | { 7 | internal Container() 8 | { 9 | 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /QuestPDF/Elements/DebugArea.cs: -------------------------------------------------------------------------------- 1 | using QuestPDF.Fluent; 2 | using QuestPDF.Helpers; 3 | using QuestPDF.Infrastructure; 4 | using SkiaSharp; 5 | 6 | namespace QuestPDF.Elements 7 | { 8 | internal class DebugArea : IComponent 9 | { 10 | public IElement? Child { get; set; } 11 | 12 | public string Text { get; set; } 13 | public string Color { get; set; } = Colors.Red.Medium; 14 | public void Compose(IContainer container) 15 | { 16 | var backgroundColor = SKColor.Parse(Color).WithAlpha(50).ToString(); 17 | 18 | container 19 | .Border(1) 20 | .BorderColor(Color) 21 | .Layers(layers => 22 | { 23 | layers.PrimaryLayer().Element(Child); 24 | layers.Layer().Background(backgroundColor); 25 | 26 | layers 27 | .Layer() 28 | .ShowIf(!string.IsNullOrWhiteSpace(Text)) 29 | .AlignCenter() 30 | .MinimalBox() 31 | .Background(Colors.White) 32 | .Padding(2) 33 | .Text(Text) 34 | .FontColor(Color) 35 | .FontFamily(Fonts.Consolas) 36 | .FontSize(8); 37 | }); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /QuestPDF/Elements/DebugPointer.cs: -------------------------------------------------------------------------------- 1 | namespace QuestPDF.Elements 2 | { 3 | internal class DebugPointer : Container 4 | { 5 | public string Target { get; set; } 6 | public bool Highlight { get; set; } 7 | } 8 | } -------------------------------------------------------------------------------- /QuestPDF/Elements/DefaultTextStyle.cs: -------------------------------------------------------------------------------- 1 | using QuestPDF.Infrastructure; 2 | 3 | namespace QuestPDF.Elements 4 | { 5 | internal class DefaultTextStyle : ContainerElement 6 | { 7 | public TextStyle TextStyle { get; set; } = TextStyle.Default; 8 | } 9 | } -------------------------------------------------------------------------------- /QuestPDF/Elements/DynamicImage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using QuestPDF.Drawing; 3 | using QuestPDF.Infrastructure; 4 | using SkiaSharp; 5 | 6 | namespace QuestPDF.Elements 7 | { 8 | internal class DynamicImage : Element 9 | { 10 | public Func? Source { get; set; } 11 | 12 | internal override SpacePlan Measure(Size availableSpace) 13 | { 14 | return SpacePlan.FullRender(availableSpace.Width, availableSpace.Height); 15 | } 16 | 17 | internal override void Draw(Size availableSpace) 18 | { 19 | var imageData = Source?.Invoke(availableSpace); 20 | 21 | if (imageData == null) 22 | return; 23 | 24 | var imageElement = new Image 25 | { 26 | InternalImage = SKImage.FromEncodedData(imageData) 27 | }; 28 | 29 | imageElement.Initialize(PageContext, Canvas); 30 | imageElement.Draw(availableSpace); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /QuestPDF/Elements/Empty.cs: -------------------------------------------------------------------------------- 1 | using QuestPDF.Drawing; 2 | using QuestPDF.Infrastructure; 3 | 4 | namespace QuestPDF.Elements 5 | { 6 | internal class Empty : Element 7 | { 8 | internal static Empty Instance { get; } = new Empty(); 9 | 10 | internal override SpacePlan Measure(Size availableSpace) 11 | { 12 | return SpacePlan.FullRender(0, 0); 13 | } 14 | 15 | internal override void Draw(Size availableSpace) 16 | { 17 | 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /QuestPDF/Elements/EnsureSpace.cs: -------------------------------------------------------------------------------- 1 | using QuestPDF.Drawing; 2 | using QuestPDF.Infrastructure; 3 | 4 | namespace QuestPDF.Elements 5 | { 6 | internal class EnsureSpace : ContainerElement, ICacheable 7 | { 8 | public const float DefaultMinHeight = 150; 9 | public float MinHeight { get; set; } = DefaultMinHeight; 10 | 11 | internal override SpacePlan Measure(Size availableSpace) 12 | { 13 | var measurement = base.Measure(availableSpace); 14 | 15 | if (measurement.Type == SpacePlanType.PartialRender && availableSpace.Height < MinHeight) 16 | return SpacePlan.Wrap(); 17 | 18 | return measurement; 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /QuestPDF/Elements/Extend.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using QuestPDF.Drawing; 3 | using QuestPDF.Infrastructure; 4 | 5 | namespace QuestPDF.Elements 6 | { 7 | internal class Extend : ContainerElement, ICacheable 8 | { 9 | public bool ExtendVertical { get; set; } 10 | public bool ExtendHorizontal { get; set; } 11 | 12 | internal override SpacePlan Measure(Size availableSpace) 13 | { 14 | var childSize = base.Measure(availableSpace); 15 | 16 | if (childSize.Type == SpacePlanType.Wrap) 17 | return childSize; 18 | 19 | var targetSize = GetTargetSize(availableSpace, childSize); 20 | 21 | if (childSize.Type == SpacePlanType.PartialRender) 22 | return SpacePlan.PartialRender(targetSize); 23 | 24 | if (childSize.Type == SpacePlanType.FullRender) 25 | return SpacePlan.FullRender(targetSize); 26 | 27 | throw new NotSupportedException(); 28 | } 29 | 30 | private Size GetTargetSize(Size availableSpace, Size childSize) 31 | { 32 | return new Size( 33 | ExtendHorizontal ? availableSpace.Width : childSize.Width, 34 | ExtendVertical ? availableSpace.Height : childSize.Height); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /QuestPDF/Elements/Hyperlink.cs: -------------------------------------------------------------------------------- 1 | using QuestPDF.Drawing; 2 | using QuestPDF.Infrastructure; 3 | 4 | namespace QuestPDF.Elements 5 | { 6 | internal class Hyperlink : ContainerElement 7 | { 8 | public string Url { get; set; } = "https://www.questpdf.com"; 9 | 10 | internal override void Draw(Size availableSpace) 11 | { 12 | var targetSize = base.Measure(availableSpace); 13 | 14 | if (targetSize.Type == SpacePlanType.Wrap) 15 | return; 16 | 17 | Canvas.DrawHyperlink(Url, targetSize); 18 | base.Draw(availableSpace); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /QuestPDF/Elements/Image.cs: -------------------------------------------------------------------------------- 1 | using QuestPDF.Drawing; 2 | using QuestPDF.Infrastructure; 3 | using SkiaSharp; 4 | 5 | namespace QuestPDF.Elements 6 | { 7 | internal class Image : Element, ICacheable 8 | { 9 | public SKImage? InternalImage { get; set; } 10 | 11 | ~Image() 12 | { 13 | InternalImage?.Dispose(); 14 | } 15 | 16 | internal override SpacePlan Measure(Size availableSpace) 17 | { 18 | return SpacePlan.FullRender(availableSpace); 19 | } 20 | 21 | internal override void Draw(Size availableSpace) 22 | { 23 | if (InternalImage == null) 24 | return; 25 | 26 | Canvas.DrawImage(InternalImage, Position.Zero, availableSpace); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /QuestPDF/Elements/Layers.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using QuestPDF.Drawing; 4 | using QuestPDF.Infrastructure; 5 | 6 | namespace QuestPDF.Elements 7 | { 8 | internal class Layer : ContainerElement 9 | { 10 | public bool IsPrimary { get; set; } 11 | } 12 | 13 | internal class Layers : Element, ICacheable 14 | { 15 | public List Children { get; set; } = new List(); 16 | 17 | internal override IEnumerable GetChildren() 18 | { 19 | return Children; 20 | } 21 | 22 | internal override SpacePlan Measure(Size availableSpace) 23 | { 24 | return Children 25 | .Single(x => x.IsPrimary) 26 | .Measure(availableSpace); 27 | } 28 | 29 | internal override void Draw(Size availableSpace) 30 | { 31 | Children 32 | .Where(x => x.Measure(availableSpace).Type != SpacePlanType.Wrap) 33 | .ToList() 34 | .ForEach(x => x.Draw(availableSpace)); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /QuestPDF/Elements/Line.cs: -------------------------------------------------------------------------------- 1 | using QuestPDF.Drawing; 2 | using QuestPDF.Helpers; 3 | using QuestPDF.Infrastructure; 4 | 5 | namespace QuestPDF.Elements 6 | { 7 | public interface ILine 8 | { 9 | 10 | } 11 | 12 | internal enum LineType 13 | { 14 | Vertical, 15 | Horizontal 16 | } 17 | 18 | internal class Line : Element, ILine, ICacheable 19 | { 20 | public LineType Type { get; set; } = LineType.Vertical; 21 | public string Color { get; set; } = Colors.Black; 22 | public float Size { get; set; } = 1; 23 | 24 | internal override SpacePlan Measure(Size availableSpace) 25 | { 26 | return Type switch 27 | { 28 | LineType.Vertical when availableSpace.Width + Infrastructure.Size.Epsilon >= Size => SpacePlan.FullRender(Size, 0), 29 | LineType.Horizontal when availableSpace.Height + Infrastructure.Size.Epsilon >= Size => SpacePlan.FullRender(0, Size), 30 | _ => SpacePlan.Wrap() 31 | }; 32 | } 33 | 34 | internal override void Draw(Size availableSpace) 35 | { 36 | if (Type == LineType.Vertical) 37 | { 38 | Canvas.DrawRectangle(new Position(-Size/2, 0), new Size(Size, availableSpace.Height), Color); 39 | } 40 | else if (Type == LineType.Horizontal) 41 | { 42 | Canvas.DrawRectangle(new Position(0, -Size/2), new Size(availableSpace.Width, Size), Color); 43 | } 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /QuestPDF/Elements/MinimalBox.cs: -------------------------------------------------------------------------------- 1 | using QuestPDF.Drawing; 2 | using QuestPDF.Infrastructure; 3 | 4 | namespace QuestPDF.Elements 5 | { 6 | internal class MinimalBox : ContainerElement 7 | { 8 | internal override void Draw(Size availableSpace) 9 | { 10 | var targetSize = base.Measure(availableSpace); 11 | 12 | if (targetSize.Type == SpacePlanType.Wrap) 13 | return; 14 | 15 | base.Draw(targetSize); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /QuestPDF/Elements/Padding.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using QuestPDF.Drawing; 3 | using QuestPDF.Infrastructure; 4 | 5 | namespace QuestPDF.Elements 6 | { 7 | internal class Padding : ContainerElement, ICacheable 8 | { 9 | public float Top { get; set; } 10 | public float Right { get; set; } 11 | public float Bottom { get; set; } 12 | public float Left { get; set; } 13 | 14 | internal override SpacePlan Measure(Size availableSpace) 15 | { 16 | if (Child == null) 17 | return SpacePlan.FullRender(0, 0); 18 | 19 | var internalSpace = InternalSpace(availableSpace); 20 | 21 | if (internalSpace.Width < 0 || internalSpace.Height < 0) 22 | return SpacePlan.Wrap(); 23 | 24 | var measure = base.Measure(internalSpace); 25 | 26 | if (measure.Type == SpacePlanType.Wrap) 27 | return SpacePlan.Wrap(); 28 | 29 | var newSize = new Size( 30 | measure.Width + Left + Right, 31 | measure.Height + Top + Bottom); 32 | 33 | if (measure.Type == SpacePlanType.PartialRender) 34 | return SpacePlan.PartialRender(newSize); 35 | 36 | if (measure.Type == SpacePlanType.FullRender) 37 | return SpacePlan.FullRender(newSize); 38 | 39 | throw new NotSupportedException(); 40 | } 41 | 42 | internal override void Draw(Size availableSpace) 43 | { 44 | if (Child == null) 45 | return; 46 | 47 | var internalSpace = InternalSpace(availableSpace); 48 | 49 | Canvas.Translate(new Position(Left, Top)); 50 | base.Draw(internalSpace); 51 | Canvas.Translate(new Position(-Left, -Top)); 52 | } 53 | 54 | private Size InternalSpace(Size availableSpace) 55 | { 56 | return new Size( 57 | availableSpace.Width - Left - Right, 58 | availableSpace.Height - Top - Bottom); 59 | } 60 | 61 | public override string ToString() 62 | { 63 | return $"Padding: Top({Top}) Right({Right}) Bottom({Bottom}) Left({Left})"; 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /QuestPDF/Elements/PageBreak.cs: -------------------------------------------------------------------------------- 1 | using QuestPDF.Drawing; 2 | using QuestPDF.Infrastructure; 3 | 4 | namespace QuestPDF.Elements 5 | { 6 | internal class PageBreak : Element, IStateResettable 7 | { 8 | private bool IsRendered { get; set; } 9 | 10 | public void ResetState() 11 | { 12 | IsRendered = false; 13 | } 14 | 15 | internal override SpacePlan Measure(Size availableSpace) 16 | { 17 | if (IsRendered) 18 | return SpacePlan.FullRender(0, 0); 19 | 20 | return SpacePlan.PartialRender(Size.Zero); 21 | } 22 | 23 | internal override void Draw(Size availableSpace) 24 | { 25 | IsRendered = true; 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /QuestPDF/Elements/Placeholder.cs: -------------------------------------------------------------------------------- 1 | using QuestPDF.Fluent; 2 | using QuestPDF.Helpers; 3 | using QuestPDF.Infrastructure; 4 | 5 | namespace QuestPDF.Elements 6 | { 7 | internal class Placeholder : IComponent 8 | { 9 | public string Text { get; set; } 10 | private static readonly byte[] ImageData; 11 | 12 | static Placeholder() 13 | { 14 | ImageData = Helpers.Helpers.LoadEmbeddedResource("QuestPDF.Resources.ImagePlaceholder.png"); 15 | } 16 | 17 | public void Compose(IContainer container) 18 | { 19 | container 20 | .Background(Colors.Grey.Lighten2) 21 | .Padding(5) 22 | .AlignMiddle() 23 | .AlignCenter() 24 | .Element(x => 25 | { 26 | if (string.IsNullOrWhiteSpace(Text)) 27 | x.MaxHeight(32).Image(ImageData, ImageScaling.FitArea); 28 | else 29 | x.Text(Text).FontSize(14); 30 | }); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /QuestPDF/Elements/Rotate.cs: -------------------------------------------------------------------------------- 1 | using QuestPDF.Infrastructure; 2 | 3 | namespace QuestPDF.Elements 4 | { 5 | internal class Rotate : ContainerElement 6 | { 7 | public float Angle { get; set; } = 0; 8 | 9 | internal override void Draw(Size availableSpace) 10 | { 11 | Canvas.Rotate(Angle); 12 | Child?.Draw(availableSpace); 13 | Canvas.Rotate(-Angle); 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /QuestPDF/Elements/Scale.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using QuestPDF.Drawing; 3 | using QuestPDF.Infrastructure; 4 | 5 | namespace QuestPDF.Elements 6 | { 7 | internal class Scale : ContainerElement, ICacheable 8 | { 9 | public float ScaleX { get; set; } = 1; 10 | public float ScaleY { get; set; } = 1; 11 | 12 | internal override SpacePlan Measure(Size availableSpace) 13 | { 14 | var targetSpace = new Size( 15 | Math.Abs(availableSpace.Width / ScaleX), 16 | Math.Abs(availableSpace.Height / ScaleY)); 17 | 18 | var measure = base.Measure(targetSpace); 19 | 20 | if (measure.Type == SpacePlanType.Wrap) 21 | return SpacePlan.Wrap(); 22 | 23 | var targetSize = new Size( 24 | Math.Abs(measure.Width * ScaleX), 25 | Math.Abs(measure.Height * ScaleY)); 26 | 27 | if (measure.Type == SpacePlanType.PartialRender) 28 | return SpacePlan.PartialRender(targetSize); 29 | 30 | if (measure.Type == SpacePlanType.FullRender) 31 | return SpacePlan.FullRender(targetSize); 32 | 33 | throw new ArgumentException(); 34 | } 35 | 36 | internal override void Draw(Size availableSpace) 37 | { 38 | var targetSpace = new Size( 39 | Math.Abs(availableSpace.Width / ScaleX), 40 | Math.Abs(availableSpace.Height / ScaleY)); 41 | 42 | var translate = new Position( 43 | ScaleX < 0 ? availableSpace.Width : 0, 44 | ScaleY < 0 ? availableSpace.Height : 0); 45 | 46 | Canvas.Translate(translate); 47 | Canvas.Scale(ScaleX, ScaleY); 48 | 49 | Child?.Draw(targetSpace); 50 | 51 | Canvas.Scale(1/ScaleX, 1/ScaleY); 52 | Canvas.Translate(translate.Reverse()); 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /QuestPDF/Elements/Section.cs: -------------------------------------------------------------------------------- 1 | using QuestPDF.Infrastructure; 2 | 3 | namespace QuestPDF.Elements 4 | { 5 | internal class Section : ContainerElement, IStateResettable 6 | { 7 | public string LocationName { get; set; } 8 | private bool IsRendered { get; set; } 9 | 10 | public void ResetState() 11 | { 12 | IsRendered = false; 13 | } 14 | 15 | internal override void Draw(Size availableSpace) 16 | { 17 | if (!IsRendered) 18 | { 19 | Canvas.DrawSection(LocationName); 20 | IsRendered = true; 21 | } 22 | 23 | PageContext.SetSectionPage(LocationName); 24 | base.Draw(availableSpace); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /QuestPDF/Elements/SectionLink.cs: -------------------------------------------------------------------------------- 1 | using QuestPDF.Drawing; 2 | using QuestPDF.Infrastructure; 3 | 4 | namespace QuestPDF.Elements 5 | { 6 | internal class SectionLink : ContainerElement 7 | { 8 | public string SectionName { get; set; } 9 | 10 | internal override void Draw(Size availableSpace) 11 | { 12 | var targetSize = base.Measure(availableSpace); 13 | 14 | if (targetSize.Type == SpacePlanType.Wrap) 15 | return; 16 | 17 | Canvas.DrawSectionLink(SectionName, targetSize); 18 | base.Draw(availableSpace); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /QuestPDF/Elements/ShowEntire.cs: -------------------------------------------------------------------------------- 1 | using QuestPDF.Drawing; 2 | using QuestPDF.Infrastructure; 3 | 4 | namespace QuestPDF.Elements 5 | { 6 | internal class ShowEntire : ContainerElement, ICacheable 7 | { 8 | internal override SpacePlan Measure(Size availableSpace) 9 | { 10 | var childMeasurement = base.Measure(availableSpace); 11 | 12 | if (childMeasurement.Type == SpacePlanType.FullRender) 13 | return childMeasurement; 14 | 15 | return SpacePlan.Wrap(); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /QuestPDF/Elements/ShowOnce.cs: -------------------------------------------------------------------------------- 1 | using QuestPDF.Drawing; 2 | using QuestPDF.Infrastructure; 3 | 4 | namespace QuestPDF.Elements 5 | { 6 | internal class ShowOnce : ContainerElement, IStateResettable, ICacheable 7 | { 8 | private bool IsRendered { get; set; } 9 | 10 | public void ResetState() 11 | { 12 | IsRendered = false; 13 | } 14 | 15 | internal override SpacePlan Measure(Size availableSpace) 16 | { 17 | if (Child == null || IsRendered) 18 | return SpacePlan.FullRender(0, 0); 19 | 20 | return base.Measure(availableSpace); 21 | } 22 | 23 | internal override void Draw(Size availableSpace) 24 | { 25 | if (Child == null || IsRendered) 26 | return; 27 | 28 | if (base.Measure(availableSpace).Type == SpacePlanType.FullRender) 29 | IsRendered = true; 30 | 31 | base.Draw(availableSpace); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /QuestPDF/Elements/SimpleRotate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using QuestPDF.Drawing; 3 | using QuestPDF.Infrastructure; 4 | 5 | namespace QuestPDF.Elements 6 | { 7 | internal class SimpleRotate : ContainerElement, ICacheable 8 | { 9 | public int TurnCount { get; set; } 10 | public int NormalizedTurnCount => (TurnCount % 4 + 4) % 4; 11 | 12 | internal override SpacePlan Measure(Size availableSpace) 13 | { 14 | if (NormalizedTurnCount == 0 || NormalizedTurnCount == 2) 15 | return base.Measure(availableSpace); 16 | 17 | availableSpace = new Size(availableSpace.Height, availableSpace.Width); 18 | var childSpace = base.Measure(availableSpace); 19 | 20 | if (childSpace.Type == SpacePlanType.Wrap) 21 | return SpacePlan.Wrap(); 22 | 23 | var targetSpace = new Size(childSpace.Height, childSpace.Width); 24 | 25 | if (childSpace.Type == SpacePlanType.FullRender) 26 | return SpacePlan.FullRender(targetSpace); 27 | 28 | if (childSpace.Type == SpacePlanType.PartialRender) 29 | return SpacePlan.PartialRender(targetSpace); 30 | 31 | throw new ArgumentException(); 32 | } 33 | 34 | internal override void Draw(Size availableSpace) 35 | { 36 | var translate = new Position( 37 | (NormalizedTurnCount == 1 || NormalizedTurnCount == 2) ? availableSpace.Width : 0, 38 | (NormalizedTurnCount == 2 || NormalizedTurnCount == 3) ? availableSpace.Height : 0); 39 | 40 | var rotate = NormalizedTurnCount * 90; 41 | 42 | Canvas.Translate(translate); 43 | Canvas.Rotate(rotate); 44 | 45 | if (NormalizedTurnCount == 1 || NormalizedTurnCount == 3) 46 | availableSpace = new Size(availableSpace.Height, availableSpace.Width); 47 | 48 | Child?.Draw(availableSpace); 49 | 50 | Canvas.Rotate(-rotate); 51 | Canvas.Translate(translate.Reverse()); 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /QuestPDF/Elements/SkipOnce.cs: -------------------------------------------------------------------------------- 1 | using QuestPDF.Drawing; 2 | using QuestPDF.Infrastructure; 3 | 4 | namespace QuestPDF.Elements 5 | { 6 | internal class SkipOnce : ContainerElement, IStateResettable 7 | { 8 | private bool FirstPageWasSkipped { get; set; } 9 | 10 | public void ResetState() 11 | { 12 | FirstPageWasSkipped = false; 13 | } 14 | 15 | internal override SpacePlan Measure(Size availableSpace) 16 | { 17 | if (Child == null || !FirstPageWasSkipped) 18 | return SpacePlan.FullRender(Size.Zero); 19 | 20 | return Child.Measure(availableSpace); 21 | } 22 | 23 | internal override void Draw(Size availableSpace) 24 | { 25 | if (Child == null) 26 | return; 27 | 28 | if (FirstPageWasSkipped) 29 | Child.Draw(availableSpace); 30 | 31 | FirstPageWasSkipped = true; 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /QuestPDF/Elements/StopPaging.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using QuestPDF.Drawing; 3 | using QuestPDF.Infrastructure; 4 | 5 | namespace QuestPDF.Elements 6 | { 7 | internal class StopPaging : ContainerElement 8 | { 9 | internal override SpacePlan Measure(Size availableSpace) 10 | { 11 | if (Child == null) 12 | return SpacePlan.FullRender(Size.Zero); 13 | 14 | var measurement = Child.Measure(availableSpace); 15 | 16 | return measurement.Type switch 17 | { 18 | SpacePlanType.Wrap => SpacePlan.FullRender(Size.Zero), 19 | SpacePlanType.PartialRender => SpacePlan.FullRender(measurement), 20 | SpacePlanType.FullRender => measurement, 21 | _ => throw new ArgumentOutOfRangeException() 22 | }; 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /QuestPDF/Elements/Table/DynamicDictionary.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace QuestPDF.Elements.Table 5 | { 6 | /// 7 | /// This dictionary allows to access key that does not exist. 8 | /// Instead of throwing an exception, it returns a default value. 9 | /// 10 | internal class DynamicDictionary 11 | { 12 | private TValue Default { get; } 13 | private IDictionary Dictionary { get; } = new Dictionary(); 14 | 15 | public DynamicDictionary() 16 | { 17 | 18 | } 19 | 20 | public DynamicDictionary(TValue defaultValue) 21 | { 22 | Default = defaultValue; 23 | } 24 | 25 | public TValue this[TKey key] 26 | { 27 | get => Dictionary.TryGetValue(key, out var value) ? value : Default; 28 | set => Dictionary[key] = value; 29 | } 30 | 31 | public List> Items => Dictionary.ToList(); 32 | } 33 | } -------------------------------------------------------------------------------- /QuestPDF/Elements/Table/ITableCellContainer.cs: -------------------------------------------------------------------------------- 1 | using QuestPDF.Infrastructure; 2 | 3 | namespace QuestPDF.Elements.Table 4 | { 5 | public interface ITableCellContainer : IContainer 6 | { 7 | 8 | } 9 | } -------------------------------------------------------------------------------- /QuestPDF/Elements/Table/TableCell.cs: -------------------------------------------------------------------------------- 1 | namespace QuestPDF.Elements.Table 2 | { 3 | internal class TableCell : Container, ITableCellContainer 4 | { 5 | public int Row { get; set; } = 0; 6 | public int RowSpan { get; set; } = 1; 7 | 8 | public int Column { get; set; } = 0; 9 | public int ColumnSpan { get; set; } = 1; 10 | 11 | public bool IsRendered { get; set; } 12 | } 13 | } -------------------------------------------------------------------------------- /QuestPDF/Elements/Table/TableCellRenderingCommand.cs: -------------------------------------------------------------------------------- 1 | using QuestPDF.Drawing; 2 | using QuestPDF.Infrastructure; 3 | 4 | namespace QuestPDF.Elements.Table 5 | { 6 | internal class TableCellRenderingCommand 7 | { 8 | public TableCell Cell { get; set; } 9 | public SpacePlan Measurement { get; set; } 10 | public Size Size { get; set; } 11 | public Position Offset { get; set; } 12 | } 13 | } -------------------------------------------------------------------------------- /QuestPDF/Elements/Table/TableColumnDefinition.cs: -------------------------------------------------------------------------------- 1 | namespace QuestPDF.Elements.Table 2 | { 3 | internal class TableColumnDefinition 4 | { 5 | public float ConstantSize { get; } 6 | public float RelativeSize { get; } 7 | 8 | internal float Width { get; set; } 9 | 10 | public TableColumnDefinition(float constantSize, float relativeSize) 11 | { 12 | ConstantSize = constantSize; 13 | RelativeSize = relativeSize; 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /QuestPDF/Elements/Table/TableLayoutValidator.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using QuestPDF.Drawing.Exceptions; 3 | 4 | namespace QuestPDF.Elements.Table 5 | { 6 | static class TableLayoutPlanner 7 | { 8 | public static void ValidateCellPositions(this Table table) 9 | { 10 | ValidateCellPositions(table.Columns.Count, table.Cells); 11 | } 12 | 13 | private static void ValidateCellPositions(int columnsCount, ICollection cells) 14 | { 15 | const string prefix = "Detected issue in table cells configuration."; 16 | 17 | foreach (var cell in cells) 18 | { 19 | if (cell.Column < 1) 20 | throw new DocumentComposeException($"{prefix} A cell column position should be greater or equal to 1. Got {cell.Column}."); 21 | 22 | if (cell.Row < 1) 23 | throw new DocumentComposeException($"{prefix} A cell row position should be greater or equal to 1. Got {cell.Row}."); 24 | 25 | if (cell.Column > columnsCount) 26 | throw new DocumentComposeException($"{prefix} Cell starts at column that does not exist. Cell details: {GetCellDetails(cell)}."); 27 | 28 | if (cell.Column + cell.ColumnSpan - 1 > columnsCount) 29 | throw new DocumentComposeException($"{prefix} Table cell location is incorrect. Cell spans over columns that do not exist. Cell details: {GetCellDetails(cell)}."); 30 | } 31 | 32 | string GetCellDetails(TableCell cell) 33 | { 34 | return $"Row {cell.Row}, Column {cell.Column}, RowSpan {cell.RowSpan}, ColumnSpan {cell.ColumnSpan}"; 35 | } 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /QuestPDF/Elements/Text/Calculation/TextDrawingRequest.cs: -------------------------------------------------------------------------------- 1 | using QuestPDF.Infrastructure; 2 | 3 | namespace QuestPDF.Elements.Text.Calculation 4 | { 5 | internal class TextDrawingRequest 6 | { 7 | public ICanvas Canvas { get; set; } 8 | public IPageContext PageContext { get; set; } 9 | 10 | public int StartIndex { get; set; } 11 | public int EndIndex { get; set; } 12 | 13 | public float TotalAscent { get; set; } 14 | public Size TextSize { get; set; } 15 | } 16 | } -------------------------------------------------------------------------------- /QuestPDF/Elements/Text/Calculation/TextLine.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace QuestPDF.Elements.Text.Calculation 5 | { 6 | internal class TextLine 7 | { 8 | public ICollection Elements { get; private set; } 9 | 10 | public float TextHeight { get; private set; } 11 | public float LineHeight { get; private set; } 12 | 13 | public float Ascent { get; private set; } 14 | public float Descent { get; private set; } 15 | 16 | public float Width { get; private set; } 17 | 18 | public static TextLine From(ICollection elements) 19 | { 20 | if (elements.Count == 0) 21 | { 22 | return new TextLine 23 | { 24 | Elements = elements 25 | }; 26 | } 27 | 28 | var textHeight = elements.Max(x => x.Measurement.Height); 29 | var lineHeight = elements.Max(x => x.Measurement.LineHeight * x.Measurement.Height); 30 | 31 | return new TextLine 32 | { 33 | Elements = elements, 34 | 35 | TextHeight = textHeight, 36 | LineHeight = lineHeight, 37 | 38 | Ascent = elements.Min(x => x.Measurement.Ascent) - (lineHeight - textHeight) / 2, 39 | Descent = elements.Max(x => x.Measurement.Descent) + (lineHeight - textHeight) / 2, 40 | 41 | Width = elements.Sum(x => x.Measurement.Width) 42 | }; 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /QuestPDF/Elements/Text/Calculation/TextLineElement.cs: -------------------------------------------------------------------------------- 1 | using QuestPDF.Elements.Text.Items; 2 | 3 | namespace QuestPDF.Elements.Text.Calculation 4 | { 5 | internal class TextLineElement 6 | { 7 | public ITextBlockItem Item { get; set; } 8 | public TextMeasurementResult Measurement { get; set; } 9 | } 10 | } -------------------------------------------------------------------------------- /QuestPDF/Elements/Text/Calculation/TextMeasurementRequest.cs: -------------------------------------------------------------------------------- 1 | using QuestPDF.Infrastructure; 2 | 3 | namespace QuestPDF.Elements.Text.Calculation 4 | { 5 | internal class TextMeasurementRequest 6 | { 7 | public ICanvas Canvas { get; set; } 8 | public IPageContext PageContext { get; set; } 9 | 10 | public int StartIndex { get; set; } 11 | public float AvailableWidth { get; set; } 12 | 13 | public bool IsFirstElementInBlock { get; set; } 14 | public bool IsFirstElementInLine { get; set; } 15 | } 16 | } -------------------------------------------------------------------------------- /QuestPDF/Elements/Text/Calculation/TextMeasurementResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace QuestPDF.Elements.Text.Calculation 4 | { 5 | internal class TextMeasurementResult 6 | { 7 | public float Width { get; set; } 8 | public float Height => Math.Abs(Descent) + Math.Abs(Ascent); 9 | 10 | public float Ascent { get; set; } 11 | public float Descent { get; set; } 12 | 13 | public float LineHeight { get; set; } 14 | 15 | public int StartIndex { get; set; } 16 | public int EndIndex { get; set; } 17 | public int NextIndex { get; set; } 18 | public int TotalIndex { get; set; } 19 | 20 | public bool IsLast => EndIndex == TotalIndex; 21 | } 22 | } -------------------------------------------------------------------------------- /QuestPDF/Elements/Text/Items/ITextBlockItem.cs: -------------------------------------------------------------------------------- 1 | using QuestPDF.Elements.Text.Calculation; 2 | 3 | namespace QuestPDF.Elements.Text.Items 4 | { 5 | internal interface ITextBlockItem 6 | { 7 | TextMeasurementResult? Measure(TextMeasurementRequest request); 8 | void Draw(TextDrawingRequest request); 9 | } 10 | } -------------------------------------------------------------------------------- /QuestPDF/Elements/Text/Items/TextBlockElement.cs: -------------------------------------------------------------------------------- 1 | using QuestPDF.Drawing; 2 | using QuestPDF.Elements.Text.Calculation; 3 | using QuestPDF.Helpers; 4 | using QuestPDF.Infrastructure; 5 | 6 | namespace QuestPDF.Elements.Text.Items 7 | { 8 | internal class TextBlockElement : ITextBlockItem 9 | { 10 | public Element Element { get; set; } = Empty.Instance; 11 | 12 | public TextMeasurementResult? Measure(TextMeasurementRequest request) 13 | { 14 | Element.VisitChildren(x => (x as IStateResettable)?.ResetState()); 15 | Element.VisitChildren(x => x.Initialize(request.PageContext, request.Canvas)); 16 | 17 | var measurement = Element.Measure(new Size(request.AvailableWidth, Size.Max.Height)); 18 | 19 | if (measurement.Type != SpacePlanType.FullRender) 20 | return null; 21 | 22 | return new TextMeasurementResult 23 | { 24 | Width = measurement.Width, 25 | 26 | Ascent = -measurement.Height, 27 | Descent = 0, 28 | 29 | LineHeight = 1, 30 | 31 | StartIndex = 0, 32 | EndIndex = 0, 33 | TotalIndex = 0 34 | }; 35 | } 36 | 37 | public void Draw(TextDrawingRequest request) 38 | { 39 | Element.VisitChildren(x => (x as IStateResettable)?.ResetState()); 40 | Element.VisitChildren(x => x.Initialize(request.PageContext, request.Canvas)); 41 | 42 | request.Canvas.Translate(new Position(0, request.TotalAscent)); 43 | Element.Draw(new Size(request.TextSize.Width, -request.TotalAscent)); 44 | request.Canvas.Translate(new Position(0, -request.TotalAscent)); 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /QuestPDF/Elements/Text/Items/TextBlockHyperlink.cs: -------------------------------------------------------------------------------- 1 | using QuestPDF.Elements.Text.Calculation; 2 | using QuestPDF.Infrastructure; 3 | 4 | namespace QuestPDF.Elements.Text.Items 5 | { 6 | internal class TextBlockHyperlink : TextBlockSpan 7 | { 8 | public string Url { get; set; } 9 | 10 | public override TextMeasurementResult? Measure(TextMeasurementRequest request) 11 | { 12 | return MeasureWithoutCache(request); 13 | } 14 | 15 | public override void Draw(TextDrawingRequest request) 16 | { 17 | request.Canvas.Translate(new Position(0, request.TotalAscent)); 18 | request.Canvas.DrawHyperlink(Url, new Size(request.TextSize.Width, request.TextSize.Height)); 19 | request.Canvas.Translate(new Position(0, -request.TotalAscent)); 20 | 21 | base.Draw(request); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /QuestPDF/Elements/Text/Items/TextBlockPageNumber.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using QuestPDF.Elements.Text.Calculation; 3 | using QuestPDF.Infrastructure; 4 | 5 | namespace QuestPDF.Elements.Text.Items 6 | { 7 | internal class TextBlockPageNumber : TextBlockSpan 8 | { 9 | public const string PageNumberPlaceholder = "123"; 10 | public Func Source { get; set; } = _ => PageNumberPlaceholder; 11 | 12 | public override TextMeasurementResult? Measure(TextMeasurementRequest request) 13 | { 14 | UpdatePageNumberText(request.PageContext); 15 | return MeasureWithoutCache(request); 16 | } 17 | 18 | public override void Draw(TextDrawingRequest request) 19 | { 20 | UpdatePageNumberText(request.PageContext); 21 | base.Draw(request); 22 | } 23 | 24 | private void UpdatePageNumberText(IPageContext context) 25 | { 26 | Text = Source(context) ?? string.Empty; 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /QuestPDF/Elements/Text/Items/TextBlockSectionlLink.cs: -------------------------------------------------------------------------------- 1 | using QuestPDF.Elements.Text.Calculation; 2 | using QuestPDF.Infrastructure; 3 | 4 | namespace QuestPDF.Elements.Text.Items 5 | { 6 | internal class TextBlockSectionlLink : TextBlockSpan 7 | { 8 | public string SectionName { get; set; } 9 | 10 | public override TextMeasurementResult? Measure(TextMeasurementRequest request) 11 | { 12 | return MeasureWithoutCache(request); 13 | } 14 | 15 | public override void Draw(TextDrawingRequest request) 16 | { 17 | request.Canvas.Translate(new Position(0, request.TotalAscent)); 18 | request.Canvas.DrawSectionLink(SectionName, new Size(request.TextSize.Width, request.TextSize.Height)); 19 | request.Canvas.Translate(new Position(0, -request.TotalAscent)); 20 | 21 | base.Draw(request); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /QuestPDF/Elements/Translate.cs: -------------------------------------------------------------------------------- 1 |  2 | using QuestPDF.Infrastructure; 3 | 4 | namespace QuestPDF.Elements 5 | { 6 | internal class Translate : ContainerElement 7 | { 8 | public float TranslateX { get; set; } = 0; 9 | public float TranslateY { get; set; } = 0; 10 | 11 | internal override void Draw(Size availableSpace) 12 | { 13 | var translate = new Position(TranslateX, TranslateY); 14 | 15 | Canvas.Translate(translate); 16 | base.Draw(availableSpace); 17 | Canvas.Translate(translate.Reverse()); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /QuestPDF/Elements/Unconstrained.cs: -------------------------------------------------------------------------------- 1 | using QuestPDF.Drawing; 2 | using QuestPDF.Infrastructure; 3 | 4 | namespace QuestPDF.Elements 5 | { 6 | internal class Unconstrained : ContainerElement, ICacheable 7 | { 8 | internal override SpacePlan Measure(Size availableSpace) 9 | { 10 | var childSize = base.Measure(Size.Max); 11 | 12 | if (childSize.Type == SpacePlanType.PartialRender) 13 | return SpacePlan.PartialRender(0, 0); 14 | 15 | if (childSize.Type == SpacePlanType.FullRender) 16 | return SpacePlan.FullRender(0, 0); 17 | 18 | return childSize; 19 | } 20 | 21 | internal override void Draw(Size availableSpace) 22 | { 23 | var measurement = base.Measure(Size.Max); 24 | 25 | if (measurement.Type == SpacePlanType.Wrap) 26 | return; 27 | 28 | base.Draw(measurement); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /QuestPDF/Fluent/AlignmentExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using QuestPDF.Elements; 3 | using QuestPDF.Infrastructure; 4 | 5 | namespace QuestPDF.Fluent 6 | { 7 | public static class AlignmentExtensions 8 | { 9 | private static IContainer Alignment(this IContainer element, Action handler) 10 | { 11 | var alignment = element as Alignment ?? new Alignment(); 12 | handler(alignment); 13 | 14 | return element.Element(alignment); 15 | } 16 | 17 | public static IContainer AlignLeft(this IContainer element) 18 | { 19 | return element.Alignment(x => x.Horizontal = HorizontalAlignment.Left); 20 | } 21 | 22 | public static IContainer AlignCenter(this IContainer element) 23 | { 24 | return element.Alignment(x => x.Horizontal = HorizontalAlignment.Center); 25 | } 26 | 27 | public static IContainer AlignRight(this IContainer element) 28 | { 29 | return element.Alignment(x => x.Horizontal = HorizontalAlignment.Right); 30 | } 31 | 32 | public static IContainer AlignTop(this IContainer element) 33 | { 34 | return element.Alignment(x => x.Vertical = VerticalAlignment.Top); 35 | } 36 | 37 | public static IContainer AlignMiddle(this IContainer element) 38 | { 39 | return element.Alignment(x => x.Vertical = VerticalAlignment.Middle); 40 | } 41 | 42 | public static IContainer AlignBottom(this IContainer element) 43 | { 44 | return element.Alignment(x => x.Vertical = VerticalAlignment.Bottom); 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /QuestPDF/Fluent/BorderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using QuestPDF.Elements; 3 | using QuestPDF.Infrastructure; 4 | 5 | namespace QuestPDF.Fluent 6 | { 7 | public static class BorderExtensions 8 | { 9 | private static IContainer Border(this IContainer element, Action handler) 10 | { 11 | var border = element as Border ?? new Border(); 12 | handler(border); 13 | 14 | return element.Element(border); 15 | } 16 | 17 | public static IContainer Border(this IContainer element, float value, Unit unit = Unit.Point) 18 | { 19 | return element 20 | .BorderHorizontal(value, unit) 21 | .BorderVertical(value, unit); 22 | } 23 | 24 | public static IContainer BorderVertical(this IContainer element, float value, Unit unit = Unit.Point) 25 | { 26 | return element 27 | .BorderLeft(value, unit) 28 | .BorderRight(value, unit); 29 | } 30 | 31 | public static IContainer BorderHorizontal(this IContainer element, float value, Unit unit = Unit.Point) 32 | { 33 | return element 34 | .BorderTop(value, unit) 35 | .BorderBottom(value, unit); 36 | } 37 | 38 | public static IContainer BorderLeft(this IContainer element, float value, Unit unit = Unit.Point) 39 | { 40 | return element.Border(x => x.Left = value.ToPoints(unit)); 41 | } 42 | 43 | public static IContainer BorderRight(this IContainer element, float value, Unit unit = Unit.Point) 44 | { 45 | return element.Border(x => x.Right = value.ToPoints(unit)); 46 | } 47 | 48 | public static IContainer BorderTop(this IContainer element, float value, Unit unit = Unit.Point) 49 | { 50 | return element.Border(x => x.Top = value.ToPoints(unit)); 51 | } 52 | 53 | public static IContainer BorderBottom(this IContainer element, float value, Unit unit = Unit.Point) 54 | { 55 | return element.Border(x => x.Bottom = value.ToPoints(unit)); 56 | } 57 | 58 | public static IContainer BorderColor(this IContainer element, string color) 59 | { 60 | return element.Border(x => x.Color = color); 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /QuestPDF/Fluent/ColumnExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using QuestPDF.Elements; 3 | using QuestPDF.Infrastructure; 4 | 5 | namespace QuestPDF.Fluent 6 | { 7 | public class ColumnDescriptor 8 | { 9 | internal Column Column { get; } = new(); 10 | 11 | public void Spacing(float value, Unit unit = Unit.Point) 12 | { 13 | Column.Spacing = value.ToPoints(unit); 14 | } 15 | 16 | public IContainer Item() 17 | { 18 | var container = new Container(); 19 | 20 | Column.Items.Add(new ColumnItem 21 | { 22 | Child = container 23 | }); 24 | 25 | return container; 26 | } 27 | } 28 | 29 | public static class ColumnExtensions 30 | { 31 | [Obsolete("This element has been renamed since version 2022.2. Please use the 'Column' method.")] 32 | public static void Stack(this IContainer element, Action handler) 33 | { 34 | element.Column(handler); 35 | } 36 | 37 | public static void Column(this IContainer element, Action handler) 38 | { 39 | var descriptor = new ColumnDescriptor(); 40 | handler(descriptor); 41 | element.Element(descriptor.Column); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /QuestPDF/Fluent/ConstrainedExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using QuestPDF.Elements; 3 | using QuestPDF.Infrastructure; 4 | 5 | namespace QuestPDF.Fluent 6 | { 7 | public static class ConstrainedExtensions 8 | { 9 | private static IContainer Constrained(this IContainer element, Action handler) 10 | { 11 | var constrained = element as Constrained ?? new Constrained(); 12 | handler(constrained); 13 | 14 | return element.Element(constrained); 15 | } 16 | 17 | public static IContainer Width(this IContainer element, float value, Unit unit = Unit.Point) 18 | { 19 | return element 20 | .MinWidth(value, unit) 21 | .MaxWidth(value, unit); 22 | } 23 | 24 | public static IContainer MinWidth(this IContainer element, float value, Unit unit = Unit.Point) 25 | { 26 | return element.Constrained(x => x.MinWidth = value.ToPoints(unit)); 27 | } 28 | 29 | public static IContainer MaxWidth(this IContainer element, float value, Unit unit = Unit.Point) 30 | { 31 | return element.Constrained(x => x.MaxWidth = value.ToPoints(unit)); 32 | } 33 | 34 | public static IContainer Height(this IContainer element, float value, Unit unit = Unit.Point) 35 | { 36 | return element 37 | .MinHeight(value, unit) 38 | .MaxHeight(value, unit); 39 | } 40 | 41 | public static IContainer MinHeight(this IContainer element, float value, Unit unit = Unit.Point) 42 | { 43 | return element.Constrained(x => x.MinHeight = value.ToPoints(unit)); 44 | } 45 | 46 | public static IContainer MaxHeight(this IContainer element, float value, Unit unit = Unit.Point) 47 | { 48 | return element.Constrained(x => x.MaxHeight = value.ToPoints(unit)); 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /QuestPDF/Fluent/DebugExtensions.cs: -------------------------------------------------------------------------------- 1 | using QuestPDF.Elements; 2 | using QuestPDF.Helpers; 3 | using QuestPDF.Infrastructure; 4 | 5 | namespace QuestPDF.Fluent 6 | { 7 | public static class DebugExtensions 8 | { 9 | public static IContainer DebugArea(this IContainer parent, string text, string color) 10 | { 11 | var container = new Container(); 12 | 13 | parent.Component(new DebugArea 14 | { 15 | Child = container, 16 | Text = text, 17 | Color = color 18 | }); 19 | 20 | return container; 21 | } 22 | 23 | public static IContainer DebugArea(this IContainer parent, string text) 24 | { 25 | return parent.DebugArea(text, Colors.Red.Medium); 26 | } 27 | 28 | public static IContainer DebugArea(this IContainer parent) 29 | { 30 | return parent.DebugArea(string.Empty, Colors.Red.Medium); 31 | } 32 | 33 | /// 34 | /// Creates a virtual element that is visible on the elements trace when the layout overflow exception is thrown. 35 | /// This can be used to easily identify elements inside the elements trace tree and faster find issue root cause. 36 | /// 37 | public static IContainer DebugPointer(this IContainer parent, string elementTraceText) 38 | { 39 | return parent.DebugPointer(elementTraceText, true); 40 | } 41 | 42 | internal static IContainer DebugPointer(this IContainer parent, string elementTraceText, bool highlight) 43 | { 44 | return parent.Element(new DebugPointer 45 | { 46 | Target = elementTraceText, 47 | Highlight = highlight 48 | }); 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /QuestPDF/Fluent/ExtendExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using QuestPDF.Elements; 3 | using QuestPDF.Infrastructure; 4 | 5 | namespace QuestPDF.Fluent 6 | { 7 | public static class ExtendExtensions 8 | { 9 | private static IContainer Extend(this IContainer element, Action handler) 10 | { 11 | var extend = element as Extend ?? new Extend(); 12 | handler(extend); 13 | 14 | return element.Element(extend); 15 | } 16 | 17 | public static IContainer Extend(this IContainer element) 18 | { 19 | return element.ExtendVertical().ExtendHorizontal(); 20 | } 21 | 22 | public static IContainer ExtendVertical(this IContainer element) 23 | { 24 | return element.Extend(x => x.ExtendVertical = true); 25 | } 26 | 27 | public static IContainer ExtendHorizontal(this IContainer element) 28 | { 29 | return element.Extend(x => x.ExtendHorizontal = true); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /QuestPDF/Fluent/GridExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using QuestPDF.Elements; 3 | using QuestPDF.Infrastructure; 4 | 5 | namespace QuestPDF.Fluent 6 | { 7 | public class GridDescriptor 8 | { 9 | internal Grid Grid { get; } = new Grid(); 10 | 11 | public void Spacing(float value, Unit unit = Unit.Point) 12 | { 13 | VerticalSpacing(value, unit); 14 | HorizontalSpacing(value, unit); 15 | } 16 | 17 | public void VerticalSpacing(float value, Unit unit = Unit.Point) 18 | { 19 | Grid.VerticalSpacing = value.ToPoints(unit); 20 | } 21 | 22 | public void HorizontalSpacing(float value, Unit unit = Unit.Point) 23 | { 24 | Grid.HorizontalSpacing = value.ToPoints(unit); 25 | } 26 | 27 | public void Columns(int value = Grid.DefaultColumnsCount) 28 | { 29 | Grid.ColumnsCount = value; 30 | } 31 | 32 | public void Alignment(HorizontalAlignment alignment) 33 | { 34 | Grid.Alignment = alignment; 35 | } 36 | 37 | public void AlignLeft() => Alignment(HorizontalAlignment.Left); 38 | public void AlignCenter() => Alignment(HorizontalAlignment.Center); 39 | public void AlignRight() => Alignment(HorizontalAlignment.Right); 40 | 41 | public IContainer Item(int columns = 1) 42 | { 43 | var container = new Container(); 44 | 45 | var element = new GridElement 46 | { 47 | Columns = columns, 48 | Child = container 49 | }; 50 | 51 | Grid.Children.Add(element); 52 | return container; 53 | } 54 | } 55 | 56 | public static class GridExtensions 57 | { 58 | public static void Grid(this IContainer element, Action handler) 59 | { 60 | var descriptor = new GridDescriptor(); 61 | 62 | if (element is Alignment alignment) 63 | descriptor.Alignment(alignment.Horizontal); 64 | 65 | handler(descriptor); 66 | element.Component(descriptor.Grid); 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /QuestPDF/Fluent/InlinedExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using QuestPDF.Elements; 3 | using QuestPDF.Infrastructure; 4 | 5 | namespace QuestPDF.Fluent 6 | { 7 | public class InlinedDescriptor 8 | { 9 | internal Inlined Inlined { get; } = new Inlined(); 10 | 11 | public void Spacing(float value, Unit unit = Unit.Point) 12 | { 13 | VerticalSpacing(value, unit); 14 | HorizontalSpacing(value, unit); 15 | } 16 | 17 | public void VerticalSpacing(float value, Unit unit = Unit.Point) 18 | { 19 | Inlined.VerticalSpacing = value.ToPoints(unit); 20 | } 21 | 22 | public void HorizontalSpacing(float value, Unit unit = Unit.Point) 23 | { 24 | Inlined.HorizontalSpacing = value.ToPoints(unit); 25 | } 26 | 27 | public void BaselineTop() => Inlined.BaselineAlignment = VerticalAlignment.Top; 28 | public void BaselineMiddle() => Inlined.BaselineAlignment = VerticalAlignment.Middle; 29 | public void BaselineBottom() => Inlined.BaselineAlignment = VerticalAlignment.Bottom; 30 | 31 | public void AlignLeft() => Inlined.ElementsAlignment = InlinedAlignment.Left; 32 | public void AlignCenter() => Inlined.ElementsAlignment = InlinedAlignment.Center; 33 | public void AlignRight() => Inlined.ElementsAlignment = InlinedAlignment.Right; 34 | public void AlignJustify() => Inlined.ElementsAlignment = InlinedAlignment.Justify; 35 | public void AlignSpaceAround() => Inlined.ElementsAlignment = InlinedAlignment.SpaceAround; 36 | 37 | public IContainer Item() 38 | { 39 | var container = new InlinedElement(); 40 | Inlined.Elements.Add(container); 41 | return container; 42 | } 43 | } 44 | 45 | public static class InlinedExtensions 46 | { 47 | public static void Inlined(this IContainer element, Action handler) 48 | { 49 | var descriptor = new InlinedDescriptor(); 50 | handler(descriptor); 51 | 52 | element.Element(descriptor.Inlined); 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /QuestPDF/Fluent/LayerExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using QuestPDF.Drawing.Exceptions; 4 | using QuestPDF.Elements; 5 | using QuestPDF.Infrastructure; 6 | 7 | namespace QuestPDF.Fluent 8 | { 9 | public class LayersDescriptor 10 | { 11 | internal Layers Layers { get; } = new Layers(); 12 | 13 | private IContainer Layer(bool isPrimary) 14 | { 15 | var container = new Container(); 16 | 17 | var element = new Layer 18 | { 19 | IsPrimary = isPrimary, 20 | Child = container 21 | }; 22 | 23 | Layers.Children.Add(element); 24 | return container; 25 | } 26 | 27 | public IContainer Layer() => Layer(false); 28 | public IContainer PrimaryLayer() => Layer(true); 29 | 30 | internal void Validate() 31 | { 32 | var primaryLayers = Layers.Children.Count(x => x.IsPrimary); 33 | 34 | if (primaryLayers == 0) 35 | throw new DocumentComposeException("The Layers component needs to have exactly one primary layer. It has none."); 36 | 37 | if (primaryLayers != 1) 38 | throw new DocumentComposeException($"The Layers component needs to have exactly one primary layer. It has {primaryLayers}."); 39 | } 40 | } 41 | 42 | public static class LayerExtensions 43 | { 44 | public static void Layers(this IContainer element, Action handler) 45 | { 46 | var descriptor = new LayersDescriptor(); 47 | 48 | handler(descriptor); 49 | descriptor.Validate(); 50 | 51 | element.Element(descriptor.Layers); 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /QuestPDF/Fluent/LineExtensions.cs: -------------------------------------------------------------------------------- 1 | using QuestPDF.Elements; 2 | using QuestPDF.Infrastructure; 3 | 4 | namespace QuestPDF.Fluent 5 | { 6 | public static class LineExtensions 7 | { 8 | private static ILine Line(this IContainer element, LineType type, float size) 9 | { 10 | var line = new Line 11 | { 12 | Size = size, 13 | Type = type 14 | }; 15 | 16 | element.Element(line); 17 | return line; 18 | } 19 | 20 | public static ILine LineVertical(this IContainer element, float size, Unit unit = Unit.Point) 21 | { 22 | return element.Line(LineType.Vertical, size.ToPoints(unit)); 23 | } 24 | 25 | public static ILine LineHorizontal(this IContainer element, float size, Unit unit = Unit.Point) 26 | { 27 | return element.Line(LineType.Horizontal, size.ToPoints(unit)); 28 | } 29 | 30 | public static void LineColor(this ILine descriptor, string value) 31 | { 32 | (descriptor as Line).Color = value; 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /QuestPDF/Fluent/MinimalApi.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using QuestPDF.Drawing; 3 | using QuestPDF.Infrastructure; 4 | 5 | namespace QuestPDF.Fluent 6 | { 7 | public class Document : IDocument 8 | { 9 | private Action ContentSource { get; } 10 | private DocumentMetadata Metadata { get; set; } = DocumentMetadata.Default; 11 | 12 | private Document(Action contentSource) 13 | { 14 | ContentSource = contentSource; 15 | } 16 | 17 | public static Document Create(Action handler) 18 | { 19 | return new Document(handler); 20 | } 21 | 22 | public Document WithMetadata(DocumentMetadata metadata) 23 | { 24 | Metadata = metadata ?? Metadata; 25 | return this; 26 | } 27 | 28 | #region IDocument 29 | 30 | public DocumentMetadata GetMetadata() => Metadata; 31 | public void Compose(IDocumentContainer container) => ContentSource(container); 32 | 33 | #endregion 34 | } 35 | } -------------------------------------------------------------------------------- /QuestPDF/Fluent/PaddingExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using QuestPDF.Elements; 3 | using QuestPDF.Infrastructure; 4 | 5 | namespace QuestPDF.Fluent 6 | { 7 | public static class PaddingExtensions 8 | { 9 | private static IContainer Padding(this IContainer element, Action handler) 10 | { 11 | var padding = element as Padding ?? new Padding(); 12 | handler(padding); 13 | 14 | return element.Element(padding); 15 | } 16 | 17 | public static IContainer Padding(this IContainer element, float value, Unit unit = Unit.Point) 18 | { 19 | return element 20 | .PaddingVertical(value, unit) 21 | .PaddingHorizontal(value, unit); 22 | } 23 | 24 | public static IContainer PaddingHorizontal(this IContainer element, float value, Unit unit = Unit.Point) 25 | { 26 | return element 27 | .PaddingLeft(value, unit) 28 | .PaddingRight(value, unit); 29 | } 30 | 31 | public static IContainer PaddingVertical(this IContainer element, float value, Unit unit = Unit.Point) 32 | { 33 | return element 34 | .PaddingTop(value, unit) 35 | .PaddingBottom(value, unit); 36 | } 37 | 38 | public static IContainer PaddingTop(this IContainer element, float value, Unit unit = Unit.Point) 39 | { 40 | return element.Padding(x => x.Top += value.ToPoints(unit)); 41 | } 42 | 43 | public static IContainer PaddingBottom(this IContainer element, float value, Unit unit = Unit.Point) 44 | { 45 | return element.Padding(x => x.Bottom += value.ToPoints(unit)); 46 | } 47 | 48 | public static IContainer PaddingLeft(this IContainer element, float value, Unit unit = Unit.Point) 49 | { 50 | return element.Padding(x => x.Left += value.ToPoints(unit)); 51 | } 52 | 53 | public static IContainer PaddingRight(this IContainer element, float value, Unit unit = Unit.Point) 54 | { 55 | return element.Padding(x => x.Right += value.ToPoints(unit)); 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /QuestPDF/Fluent/RotateExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using QuestPDF.Elements; 3 | using QuestPDF.Infrastructure; 4 | 5 | namespace QuestPDF.Fluent 6 | { 7 | public static class RotateExtensions 8 | { 9 | private static IContainer SimpleRotate(this IContainer element, Action handler) 10 | { 11 | var scale = element as SimpleRotate ?? new SimpleRotate(); 12 | handler(scale); 13 | 14 | return element.Element(scale); 15 | } 16 | 17 | public static IContainer RotateLeft(this IContainer element) 18 | { 19 | return element.SimpleRotate(x => x.TurnCount--); 20 | } 21 | 22 | public static IContainer RotateRight(this IContainer element) 23 | { 24 | return element.SimpleRotate(x => x.TurnCount++); 25 | } 26 | 27 | /// In degrees 28 | public static IContainer Rotate(this IContainer element, float angle) 29 | { 30 | return element.Element(new Rotate 31 | { 32 | Angle = angle 33 | }); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /QuestPDF/Fluent/RowExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using QuestPDF.Elements; 3 | using QuestPDF.Infrastructure; 4 | 5 | namespace QuestPDF.Fluent 6 | { 7 | public class RowDescriptor 8 | { 9 | internal Row Row { get; } = new(); 10 | 11 | public void Spacing(float value) 12 | { 13 | Row.Spacing = value; 14 | } 15 | 16 | private IContainer Item(RowItemType type, float size = 0) 17 | { 18 | var element = new RowItem 19 | { 20 | Type = type, 21 | Size = size 22 | }; 23 | 24 | Row.Items.Add(element); 25 | return element; 26 | } 27 | 28 | [Obsolete("This element has been renamed since version 2022.2. Please use the RelativeItem method.")] 29 | public IContainer RelativeColumn(float size = 1) 30 | { 31 | return Item(RowItemType.Relative, size); 32 | } 33 | 34 | [Obsolete("This element has been renamed since version 2022.2. Please use the ConstantItem method.")] 35 | public IContainer ConstantColumn(float size) 36 | { 37 | return Item(RowItemType.Constant, size); 38 | } 39 | 40 | public IContainer RelativeItem(float size = 1) 41 | { 42 | return Item(RowItemType.Relative, size); 43 | } 44 | 45 | public IContainer ConstantItem(float size, Unit unit = Unit.Point) 46 | { 47 | return Item(RowItemType.Constant, size.ToPoints(unit)); 48 | } 49 | 50 | public IContainer AutoItem() 51 | { 52 | return Item(RowItemType.Auto); 53 | } 54 | } 55 | 56 | public static class RowExtensions 57 | { 58 | public static void Row(this IContainer element, Action handler) 59 | { 60 | var descriptor = new RowDescriptor(); 61 | handler(descriptor); 62 | element.Element(descriptor.Row); 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /QuestPDF/Fluent/ScaleExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using QuestPDF.Elements; 3 | using QuestPDF.Infrastructure; 4 | 5 | namespace QuestPDF.Fluent 6 | { 7 | public static class ScaleExtensions 8 | { 9 | private static IContainer Scale(this IContainer element, Action handler) 10 | { 11 | var scale = element as Scale ?? new Scale(); 12 | handler(scale); 13 | 14 | return element.Element(scale); 15 | } 16 | 17 | public static IContainer Scale(this IContainer element, float value) 18 | { 19 | return element.ScaleHorizontal(value).ScaleVertical(value); 20 | } 21 | 22 | public static IContainer ScaleHorizontal(this IContainer element, float value) 23 | { 24 | return element.Scale(x => x.ScaleX *= value); 25 | } 26 | 27 | public static IContainer FlipHorizontal(this IContainer element) 28 | { 29 | return element.ScaleHorizontal(-1); 30 | } 31 | 32 | public static IContainer ScaleVertical(this IContainer element, float value) 33 | { 34 | return element.Scale(x => x.ScaleY *= value); 35 | } 36 | 37 | public static IContainer FlipVertical(this IContainer element) 38 | { 39 | return element.ScaleVertical(-1); 40 | } 41 | 42 | public static IContainer FlipOver(this IContainer element) 43 | { 44 | return element.FlipHorizontal().FlipVertical(); 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /QuestPDF/Fluent/TranslateExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using QuestPDF.Elements; 3 | using QuestPDF.Infrastructure; 4 | 5 | namespace QuestPDF.Fluent 6 | { 7 | public static class TranslateExtensions 8 | { 9 | private static IContainer Translate(this IContainer element, Action handler) 10 | { 11 | var translate = element as Translate ?? new Translate(); 12 | handler(translate); 13 | 14 | return element.Element(translate); 15 | } 16 | 17 | public static IContainer TranslateX(this IContainer element, float value, Unit unit = Unit.Point) 18 | { 19 | return element.Translate(x => x.TranslateX += value.ToPoints(unit)); 20 | } 21 | 22 | public static IContainer TranslateY(this IContainer element, float value, Unit unit = Unit.Point) 23 | { 24 | return element.Translate(x => x.TranslateY += value.ToPoints(unit)); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /QuestPDF/Helpers/Fonts.cs: -------------------------------------------------------------------------------- 1 | namespace QuestPDF.Helpers 2 | { 3 | public class Fonts 4 | { 5 | public const string Arial = "Arial"; 6 | public const string Calibri = "Calibri"; 7 | public const string Cambria = "Cambria"; 8 | public const string Candara = "Candara"; 9 | public const string ComicSans = "Comic Sans MS"; 10 | public const string Consolas = "Consolas"; 11 | public const string Corbel = "Corbel"; 12 | public const string Courier = "Courier"; 13 | public const string CourierNew = "Courier New"; 14 | public const string Georgia = "Georgia"; 15 | public const string Impact = "Impact"; 16 | public const string LucidaConsole = "Lucida Console"; 17 | public const string SegoeSD = "Segoe SD"; 18 | public const string SegoeUI = "Segoe UI"; 19 | public const string Tahoma = "Tahoma"; 20 | public const string TimesNewRoman = "Times New Roman"; 21 | public const string TimesRoman = "Times Roman"; 22 | public const string Trebuchet = "Trebuchet MS"; 23 | public const string Verdana = "Verdana"; 24 | } 25 | } -------------------------------------------------------------------------------- /QuestPDF/Helpers/Helpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | using System.Text.RegularExpressions; 7 | using QuestPDF.Infrastructure; 8 | 9 | namespace QuestPDF.Helpers 10 | { 11 | internal static class Helpers 12 | { 13 | internal static byte[] LoadEmbeddedResource(string resourceName) 14 | { 15 | using var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName); 16 | using var reader = new BinaryReader(stream); 17 | 18 | return reader.ReadBytes((int) stream.Length); 19 | } 20 | 21 | private static PropertyInfo? ToPropertyInfo(this Expression> selector) 22 | { 23 | return (selector.Body as MemberExpression)?.Member as PropertyInfo; 24 | } 25 | 26 | internal static string? GetPropertyName(this Expression> selector) where TValue : class 27 | { 28 | return selector.ToPropertyInfo()?.Name; 29 | } 30 | 31 | internal static TValue? GetPropertyValue(this T target, Expression> selector) where TValue : class 32 | { 33 | return selector.ToPropertyInfo()?.GetValue(target) as TValue; 34 | } 35 | 36 | internal static void SetPropertyValue(this T target, Expression> selector, TValue value) 37 | { 38 | var property = selector.ToPropertyInfo() ?? throw new Exception("Expected property with getter and setter."); 39 | property?.SetValue(target, value); 40 | } 41 | 42 | internal static string PrettifyName(this string text) 43 | { 44 | return Regex.Replace(text, @"([a-z])([A-Z])", "$1 $2"); 45 | } 46 | 47 | internal static void VisitChildren(this Element? element, Action handler) 48 | { 49 | foreach (var child in element.GetChildren().Where(x => x != null)) 50 | VisitChildren(child, handler); 51 | 52 | handler(element); 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /QuestPDF/Infrastructure/AspectRatioOption.cs: -------------------------------------------------------------------------------- 1 | namespace QuestPDF.Infrastructure 2 | { 3 | public enum AspectRatioOption 4 | { 5 | FitWidth, 6 | FitHeight, 7 | FitArea 8 | } 9 | } -------------------------------------------------------------------------------- /QuestPDF/Infrastructure/ContainerElement.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using QuestPDF.Drawing; 4 | using QuestPDF.Elements; 5 | 6 | namespace QuestPDF.Infrastructure 7 | { 8 | internal abstract class ContainerElement : Element, IContainer 9 | { 10 | internal Element? Child { get; set; } = Empty.Instance; 11 | 12 | IElement? IContainer.Child 13 | { 14 | get => Child; 15 | set => Child = value as Element; 16 | } 17 | 18 | internal override IEnumerable GetChildren() 19 | { 20 | yield return Child; 21 | } 22 | 23 | internal override void CreateProxy(Func create) 24 | { 25 | Child = create(Child); 26 | } 27 | 28 | internal override SpacePlan Measure(Size availableSpace) 29 | { 30 | return Child?.Measure(availableSpace) ?? SpacePlan.FullRender(0, 0); 31 | } 32 | 33 | internal override void Draw(Size availableSpace) 34 | { 35 | Child?.Draw(availableSpace); 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /QuestPDF/Infrastructure/Element.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using QuestPDF.Drawing; 4 | 5 | namespace QuestPDF.Infrastructure 6 | { 7 | internal abstract class Element : IElement 8 | { 9 | internal IPageContext PageContext { get; set; } 10 | internal ICanvas Canvas { get; set; } 11 | 12 | internal virtual IEnumerable GetChildren() 13 | { 14 | yield break; 15 | } 16 | 17 | internal virtual void Initialize(IPageContext pageContext, ICanvas canvas) 18 | { 19 | PageContext = pageContext; 20 | Canvas = canvas; 21 | } 22 | 23 | internal virtual void CreateProxy(Func create) 24 | { 25 | 26 | } 27 | 28 | internal abstract SpacePlan Measure(Size availableSpace); 29 | internal abstract void Draw(Size availableSpace); 30 | } 31 | } -------------------------------------------------------------------------------- /QuestPDF/Infrastructure/FontWeight.cs: -------------------------------------------------------------------------------- 1 | namespace QuestPDF.Infrastructure 2 | { 3 | public enum FontWeight 4 | { 5 | Thin = 100, 6 | ExtraLight = 200, 7 | Light = 300, 8 | Normal = 400, 9 | Medium = 500, 10 | SemiBold = 600, 11 | Bold = 700, 12 | ExtraBold = 800, 13 | Black = 900, 14 | ExtraBlack = 1000 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /QuestPDF/Infrastructure/HorizontalAlignment.cs: -------------------------------------------------------------------------------- 1 | namespace QuestPDF.Infrastructure 2 | { 3 | public enum HorizontalAlignment 4 | { 5 | Left, 6 | Center, 7 | Right 8 | } 9 | } -------------------------------------------------------------------------------- /QuestPDF/Infrastructure/ICacheable.cs: -------------------------------------------------------------------------------- 1 | namespace QuestPDF.Infrastructure 2 | { 3 | public interface ICacheable 4 | { 5 | 6 | } 7 | } -------------------------------------------------------------------------------- /QuestPDF/Infrastructure/ICanvas.cs: -------------------------------------------------------------------------------- 1 | using SkiaSharp; 2 | 3 | namespace QuestPDF.Infrastructure 4 | { 5 | internal interface ICanvas 6 | { 7 | void Translate(Position vector); 8 | 9 | void DrawRectangle(Position vector, Size size, string color); 10 | void DrawText(string text, Position position, TextStyle style); 11 | void DrawImage(SKImage image, Position position, Size size); 12 | 13 | void DrawHyperlink(string url, Size size); 14 | void DrawSectionLink(string sectionName, Size size); 15 | void DrawSection(string sectionName); 16 | 17 | void Rotate(float angle); 18 | void Scale(float scaleX, float scaleY); 19 | } 20 | } -------------------------------------------------------------------------------- /QuestPDF/Infrastructure/IComponent.cs: -------------------------------------------------------------------------------- 1 | using QuestPDF.Elements; 2 | 3 | namespace QuestPDF.Infrastructure 4 | { 5 | interface ISlot 6 | { 7 | 8 | } 9 | 10 | class Slot : Container, ISlot 11 | { 12 | 13 | } 14 | 15 | public interface IComponent 16 | { 17 | void Compose(IContainer container); 18 | } 19 | } -------------------------------------------------------------------------------- /QuestPDF/Infrastructure/IContainer.cs: -------------------------------------------------------------------------------- 1 | namespace QuestPDF.Infrastructure 2 | { 3 | public interface IContainer 4 | { 5 | IElement? Child { get; set; } 6 | } 7 | } -------------------------------------------------------------------------------- /QuestPDF/Infrastructure/IDocument.cs: -------------------------------------------------------------------------------- 1 | using QuestPDF.Drawing; 2 | 3 | namespace QuestPDF.Infrastructure 4 | { 5 | public interface IDocument 6 | { 7 | DocumentMetadata GetMetadata(); 8 | void Compose(IDocumentContainer container); 9 | } 10 | } -------------------------------------------------------------------------------- /QuestPDF/Infrastructure/IDocumentContainer.cs: -------------------------------------------------------------------------------- 1 | namespace QuestPDF.Infrastructure 2 | { 3 | public interface IDocumentContainer 4 | { 5 | 6 | } 7 | } -------------------------------------------------------------------------------- /QuestPDF/Infrastructure/IElement.cs: -------------------------------------------------------------------------------- 1 | namespace QuestPDF.Infrastructure 2 | { 3 | public interface IElement 4 | { 5 | 6 | } 7 | } -------------------------------------------------------------------------------- /QuestPDF/Infrastructure/IPageContext.cs: -------------------------------------------------------------------------------- 1 | namespace QuestPDF.Infrastructure 2 | { 3 | internal class DocumentLocation 4 | { 5 | public string Name { get; set; } 6 | public int PageStart { get; set; } 7 | public int PageEnd { get; set; } 8 | public int Length => PageEnd - PageStart + 1; 9 | } 10 | 11 | internal interface IPageContext 12 | { 13 | int CurrentPage { get; } 14 | void SetSectionPage(string name); 15 | DocumentLocation? GetLocation(string name); 16 | } 17 | } -------------------------------------------------------------------------------- /QuestPDF/Infrastructure/IRenderingCanvas.cs: -------------------------------------------------------------------------------- 1 | namespace QuestPDF.Infrastructure 2 | { 3 | public interface IRenderingCanvas 4 | { 5 | void BeginDocument(); 6 | void EndDocument(); 7 | 8 | void BeginPage(Size size); 9 | void EndPage(); 10 | } 11 | } -------------------------------------------------------------------------------- /QuestPDF/Infrastructure/IStateResettable.cs: -------------------------------------------------------------------------------- 1 | namespace QuestPDF.Infrastructure 2 | { 3 | internal interface IStateResettable 4 | { 5 | void ResetState(); 6 | } 7 | } -------------------------------------------------------------------------------- /QuestPDF/Infrastructure/ImageScaling.cs: -------------------------------------------------------------------------------- 1 | namespace QuestPDF.Infrastructure 2 | { 3 | public enum ImageScaling 4 | { 5 | FitWidth, 6 | FitHeight, 7 | FitArea, 8 | Resize 9 | } 10 | } -------------------------------------------------------------------------------- /QuestPDF/Infrastructure/PageContext.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace QuestPDF.Infrastructure 5 | { 6 | internal class PageContext : IPageContext 7 | { 8 | public const string DocumentLocation = "document"; 9 | 10 | private List Locations { get; } = new(); 11 | public int CurrentPage { get; private set; } 12 | 13 | internal void SetPageNumber(int number) 14 | { 15 | CurrentPage = number; 16 | SetSectionPage(DocumentLocation); 17 | } 18 | 19 | public void SetSectionPage(string name) 20 | { 21 | var location = GetLocation(name); 22 | 23 | if (location == null) 24 | { 25 | location = new DocumentLocation 26 | { 27 | Name = name, 28 | PageStart = CurrentPage, 29 | PageEnd = CurrentPage 30 | }; 31 | 32 | Locations.Add(location); 33 | } 34 | 35 | if (location.PageEnd < CurrentPage) 36 | location.PageEnd = CurrentPage; 37 | } 38 | 39 | public DocumentLocation? GetLocation(string name) 40 | { 41 | return Locations.FirstOrDefault(x => x.Name == name); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /QuestPDF/Infrastructure/Position.cs: -------------------------------------------------------------------------------- 1 | namespace QuestPDF.Infrastructure 2 | { 3 | internal readonly struct Position 4 | { 5 | public readonly float X; 6 | public readonly float Y; 7 | 8 | public static Position Zero => new Position(0, 0); 9 | 10 | public Position(float x, float y) 11 | { 12 | X = x; 13 | Y = y; 14 | } 15 | 16 | public Position Reverse() 17 | { 18 | return new Position(-X, -Y); 19 | } 20 | 21 | public override string ToString() => $"(Left: {X:N3}, Top: {Y:N3})"; 22 | } 23 | } -------------------------------------------------------------------------------- /QuestPDF/Infrastructure/Size.cs: -------------------------------------------------------------------------------- 1 | namespace QuestPDF.Infrastructure 2 | { 3 | public readonly struct Size 4 | { 5 | public const float Epsilon = 0.001f; 6 | public const float Infinity = 14_400; 7 | 8 | public readonly float Width; 9 | public readonly float Height; 10 | 11 | public static Size Zero { get; } = new Size(0, 0); 12 | public static Size Max { get; } = new Size(Infinity, Infinity); 13 | 14 | public Size(float width, float height) 15 | { 16 | Width = width; 17 | Height = height; 18 | } 19 | 20 | public override string ToString() => $"(Width: {Width:N3}, Height: {Height:N3})"; 21 | } 22 | } -------------------------------------------------------------------------------- /QuestPDF/Infrastructure/Unit.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using static QuestPDF.Infrastructure.Unit; 3 | 4 | namespace QuestPDF.Infrastructure 5 | { 6 | public enum Unit 7 | { 8 | Point, 9 | 10 | Meter, 11 | Centimetre, 12 | Millimetre, 13 | 14 | Feet, 15 | Inch, 16 | 17 | /// 18 | /// 1/1000th of inch 19 | /// 20 | Mill 21 | } 22 | 23 | internal static class UnitExtensions 24 | { 25 | private const float InchToCentimetre = 2.54f; 26 | private const float InchToPoints = 72; 27 | 28 | public static float ToPoints(this float value, Unit unit) 29 | { 30 | return value * GetConversionFactor(); 31 | 32 | float GetConversionFactor() 33 | { 34 | return unit switch 35 | { 36 | Point => 1, 37 | Meter => 100 / InchToCentimetre * InchToPoints, 38 | Centimetre => 1 / InchToCentimetre * InchToPoints, 39 | Millimetre => 0.1f / InchToCentimetre * InchToPoints, 40 | Feet => 12 * InchToPoints, 41 | Inch => InchToPoints, 42 | Mill => InchToPoints / 1000f, 43 | _ => throw new ArgumentOutOfRangeException(nameof(unit), unit, null) 44 | }; 45 | } 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /QuestPDF/Infrastructure/VerticalAlignment.cs: -------------------------------------------------------------------------------- 1 | namespace QuestPDF.Infrastructure 2 | { 3 | public enum VerticalAlignment 4 | { 5 | Top, 6 | Middle, 7 | Bottom 8 | } 9 | } -------------------------------------------------------------------------------- /QuestPDF/Previewer/HotReloadManager.cs: -------------------------------------------------------------------------------- 1 | #if NET6_0_OR_GREATER 2 | 3 | using System; 4 | 5 | [assembly: System.Reflection.Metadata.MetadataUpdateHandler(typeof(QuestPDF.Previewer.HotReloadManager))] 6 | 7 | namespace QuestPDF.Previewer 8 | { 9 | /// 10 | /// Helper for subscribing to hot reload notifications. 11 | /// 12 | internal static class HotReloadManager 13 | { 14 | public static event EventHandler? UpdateApplicationRequested; 15 | 16 | public static void UpdateApplication(Type[]? _) 17 | { 18 | UpdateApplicationRequested?.Invoke(null, EventArgs.Empty); 19 | } 20 | } 21 | } 22 | 23 | #endif -------------------------------------------------------------------------------- /QuestPDF/Previewer/PreviewerExtensions.cs: -------------------------------------------------------------------------------- 1 | #if NET6_0_OR_GREATER 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using QuestPDF.Drawing; 8 | using QuestPDF.Infrastructure; 9 | 10 | namespace QuestPDF.Previewer 11 | { 12 | public static class Extensions 13 | { 14 | public static void ShowInPreviewer(this IDocument document, int port = 12500) 15 | { 16 | document.ShowInPreviewerAsync(port).ConfigureAwait(true).GetAwaiter().GetResult(); 17 | } 18 | 19 | public static async Task ShowInPreviewerAsync(this IDocument document, int port = 12500) 20 | { 21 | var previewerService = new PreviewerService(port); 22 | 23 | var cancellationTokenSource = new CancellationTokenSource(); 24 | previewerService.OnPreviewerStopped += () => cancellationTokenSource.Cancel(); 25 | 26 | await previewerService.Connect(); 27 | await RefreshPreview(); 28 | 29 | HotReloadManager.UpdateApplicationRequested += (_, _) => RefreshPreview(); 30 | 31 | await WaitForPreviewerExit(cancellationTokenSource.Token); 32 | 33 | Task RefreshPreview() 34 | { 35 | var pictures = GetPictures(); 36 | return previewerService.RefreshPreview(pictures); 37 | 38 | ICollection GetPictures() 39 | { 40 | try 41 | { 42 | return DocumentGenerator.GeneratePreviewerPictures(document); 43 | } 44 | catch (Exception exception) 45 | { 46 | var exceptionDocument = new ExceptionDocument(exception); 47 | return DocumentGenerator.GeneratePreviewerPictures(exceptionDocument); 48 | } 49 | } 50 | } 51 | 52 | async Task WaitForPreviewerExit(CancellationToken cancellationToken) 53 | { 54 | while (true) 55 | { 56 | if (cancellationToken.IsCancellationRequested) 57 | return; 58 | 59 | await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken); 60 | } 61 | } 62 | } 63 | } 64 | } 65 | 66 | #endif 67 | -------------------------------------------------------------------------------- /QuestPDF/Previewer/PreviewerRefreshCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | #if NET6_0_OR_GREATER 5 | 6 | namespace QuestPDF.Previewer 7 | { 8 | internal class PreviewerRefreshCommand 9 | { 10 | public ICollection Pages { get; set; } 11 | 12 | public class Page 13 | { 14 | public string Id { get; } = Guid.NewGuid().ToString("N"); 15 | 16 | public float Width { get; init; } 17 | public float Height { get; init; } 18 | } 19 | } 20 | } 21 | 22 | #endif -------------------------------------------------------------------------------- /QuestPDF/Resources/Description.md: -------------------------------------------------------------------------------- 1 | QuestPDF is an open-source .NET library for PDF documents generation. 2 | 3 | It offers a layout engine designed with a full paging support in mind. The document consists of many simple elements (e.g. border, background, image, text, padding, table, grid etc.) that are composed together to create more complex structures. This way, as a developer, you can understand the behavior of every element and use them with full confidence. Additionally, the document and all its elements support paging functionality. For example, an element can be moved to the next page (if there is not enough space) or even be split between pages like table's rows. All of it done with predictable and discoverable Fluent API. 4 | 5 | ## Documentation 6 | 7 | [![Getting started tutorial]( https://img.shields.io/badge/%F0%9F%9A%80%20read-getting%20started-blue)](https://www.questpdf.com/documentation/getting-started.html) 8 | A short and easy to follow tutorial showing how to design an invoice document under 200 lines of code. 9 | 10 | 11 | [![API reference](https://img.shields.io/badge/%F0%9F%93%96%20read-API%20reference-blue)](https://www.questpdf.com/documentation/api-reference.html) 12 | A detailed description of behavior of all available components and how to use them with C# Fluent API. 13 | 14 | 15 | [![Patterns and Practices](https://img.shields.io/badge/%F0%9F%94%8D%20read-patterns%20and%20practices-blue)](https://www.questpdf.com/documentation/patterns-and-practices.html#document-metadata) 16 | Everything that may help you designing great reports and create reusable code that is easy to maintain. 17 | 18 | ## Simplicity is the key 19 | 20 | How easy it is to start and prototype with QuestPDF? Really easy thanks to its minimal API! Please analyse the code below: 21 | 22 | 23 | -------------------------------------------------------------------------------- /QuestPDF/Resources/ImagePlaceholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nirzaf/QuestPDFTutorial_Samples_Batch_05/1b8920a7a210c0d31e83915871c49d435eb69602/QuestPDF/Resources/ImagePlaceholder.png -------------------------------------------------------------------------------- /QuestPDF/Resources/Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nirzaf/QuestPDFTutorial_Samples_Batch_05/1b8920a7a210c0d31e83915871c49d435eb69602/QuestPDF/Resources/Logo.png -------------------------------------------------------------------------------- /QuestPDF/Resources/ReleaseNotes.txt: -------------------------------------------------------------------------------- 1 | Release theme: 2 | Introduced the QuestPDF Previewer tool - a hot-reload powered, cross-platform program that visualizes your PDF document and updates its preview every time you make a code change. You don't need to recompile your code after every small adjustment. Save time and enjoy the design process! (available only for dotnet 6 and beyond) 3 | 4 | Other changes: 5 | - Improved default word-wrapping algorithm to better handle words which do not fit on the available width, 6 | - Introduced new word-wrapping option 'WrapAnywhere' that wraps word at the last possible character instead of moving it into new line. 7 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | | Version | Supported | 6 | | ------- | ------------------ | 7 | | 2021.5.2 | :white_check_mark: | 8 | | Older | :x: | 9 | 10 | ## Reporting a Vulnerability 11 | 12 | Please report any code vulnerabilities using GitHub Issues. We are trying to respond within 1 day and analyze reported situation as soon as possible. 13 | Please collaborate with us to find best solutation available. 14 | Once the vulnerability is recognized and fixed, the issue is closed with appropraite message. 15 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | QuestPDF is an open-source .NET library for PDF documents generation. 2 | 3 | It offers a layouting engine designed with full paging support in mind. The document consists of many simple elements (e.g. border, background, image, text, padding, table, grid, etc.) composed together to create more complex structures. This way, as a developer, you can understand the behaviour of every element and use them with complete confidence. Additionally, the document and all its features support paging functionality. For example, a part can be moved to the next page (if there is insufficient space) or split between pages like table rows. 4 | 5 | Unlike other libraries, it does not rely on the HTML-to-PDF conversion, which is not reliable in many cases. Instead, it implements its own optimised layout engine to cover all paging-related requirements. 6 | 7 | ## Please show the value 8 | 9 | Choosing a project dependency could be difficult. We need to ensure the stability and maintainability of our projects. Surveys show that GitHub stars count plays an important factor when assessing library quality. 10 | 11 | ⭐ Please give this repository a star. It takes seconds and helps thousands of developers! ⭐ 12 | 13 | ## Please share with the community 14 | 15 | As an open-source project without funding, I cannot afford to advertise QuestPDF in a typical way. Instead, the library relies on community interactions. Please consider sharing a post about QuestPDF and the value it provides. It does help! 16 | --------------------------------------------------------------------------------