Compare commits

..

73 Commits

Author SHA1 Message Date
MarcinZiabek 1b9b164583 DefaultTextStyle API improvements 2022-03-12 16:11:03 +01:00
MarcinZiabek 2c14f91ebe Added support for background and foreground in page API 2022-03-11 18:04:30 +01:00
MarcinZiabek b533a32b61 Text API naming adjustments 2022-03-11 13:23:36 +01:00
MarcinZiabek 262bd3c182 Improved backwards compatibility 2022-03-09 22:13:51 +01:00
MarcinZiabek fcff989ab2 Improved text API 2022-03-08 17:27:30 +01:00
MarcinZiabek 054e7a5f20 Added minimal API example 2022-02-28 15:41:10 +01:00
MarcinZiabek a92cc25fb1 Improved font matching and exception message. Refactored tests. 2022-02-27 15:19:50 +01:00
MarcinZiabek 8db283d559 Code refactorization + added support for multi typeface font types 2022-02-25 23:02:42 +01:00
Marcin Ziąbek 8a64abaac2 Merge pull request #106 from SvizelPritula/main
FontManager: Add support for multiple variants of font families registered at runtime
2022-02-21 22:26:51 +01:00
MarcinZiabek 68df390d0e Element renaming + custom text format 2022-02-21 00:57:53 +01:00
MarcinZiabek d73c694452 Added minimal API 2022-02-20 16:17:43 +01:00
MarcinZiabek 4479a49ad0 Improved support for page sections 2022-02-16 15:34:54 +01:00
MarcinZiabek 63288dfb60 Updated examples 2022-02-07 22:29:59 +01:00
MarcinZiabek fc325d8b76 2022.2.3 2022-02-07 22:25:48 +01:00
Marcin Ziąbek 5ffeaa3075 Merge pull request #119 from B-wareBS/features/unit-conversion-fix
Fixed an issue with unit conversion.
2022-02-07 22:04:55 +01:00
Chris de Jong-Pobedinskiy fe431fbc12 Fixed an issue with unit conversion.
Fixed an issue where unit conversions from mm to pt would be off by a
factor of 100.

This fixes #118
2022-02-07 13:56:20 +01:00
Marcin Ziąbek e0de408821 2022.2.2 Fixed regression: exception when Column/Row have no items 2022-02-01 23:39:43 +01:00
Marcin Ziąbek 8ce6afef5a Simplified readme 2022-02-01 11:14:38 +01:00
Marcin Ziąbek 1c70b20028 Added examples for execution order 2022-01-31 00:15:10 +01:00
Marcin Ziąbek 3aef4ec4c9 2022.2.1 2022-01-30 23:46:11 +01:00
Marcin Ziąbek 3649607bd9 Merge remote-tracking branch 'origin/2022.02'
# Conflicts:
#	QuestPDF.Examples/RowExamples.cs
#	QuestPDF.UnitTests/RowTests.cs
#	QuestPDF/Elements/Row.cs
#	QuestPDF/Fluent/RowExtensions.cs
#	QuestPDF/QuestPDF.csproj
#	QuestPDF/Resources/ReleaseNotes.txt
2022-01-30 23:44:20 +01:00
Marcin Ziąbek 9a6b11ab85 Updated examples and readme 2022-01-30 22:52:56 +01:00
Marcin Ziąbek ac29ed4794 2022.2.0-beta2 2022-01-26 22:26:47 +01:00
Marcin Ziąbek 5f7da8b5b3 Line: added default configuration 2022-01-26 22:26:34 +01:00
Benjamin Swart 78eef45ce2 FontManager: Add support for multiple variants of font families registered at runtime 2022-01-24 15:13:41 +01:00
Marcin Ziąbek 5424d9f0a0 2022.2.0-beta1 2022-01-24 14:53:01 +01:00
Marcin Ziąbek 946fe91a98 WriteOnlyStream refactorization 2022-01-24 00:34:27 +01:00
Marcin Ziąbek b5839d3989 Merge pull request #65 from schulz3000/main
fix rendering direct to aspnet core response stream
2022-01-24 00:17:14 +01:00
MarcinZiabek 2ca6d1cb47 Decoration: improved implementation, fixed unit tests 2022-01-23 23:08:18 +01:00
Marcin Ziąbek 49a3371af7 Added StopPaging element 2022-01-23 21:42:03 +01:00
Marcin Ziąbek 3f3ada6d8a Minor code adjustments 2022-01-23 21:41:45 +01:00
Marcin Ziąbek ba409a8712 Create codeql-analysis.yml 2022-01-21 03:11:22 +01:00
Marcin Ziąbek b96c67a8af Fixed Column tests 2022-01-21 00:09:39 +01:00
Marcin Ziąbek b9fbb9267f Obsolete methods refactorization 2022-01-18 23:24:10 +01:00
Marcin Ziąbek 272b0fc11a Added support for unit types 2022-01-18 23:14:57 +01:00
Marcin Ziąbek b2cc7bbd36 Renamed Decoration element 2022-01-16 17:08:57 +01:00
Marcin Ziąbek e3ce194fd1 Simple SVG example 2022-01-16 17:03:01 +01:00
Marcin Ziąbek 9b892c86ed 2022.1.0 2022-01-10 02:43:58 +01:00
Marcin Ziąbek c9833d410d 2022.1.0-beta6 2022-01-09 03:08:18 +01:00
Marcin Ziąbek f8057a840c Added: Line, Reimplemented: Column, Row and Decoration 2022-01-09 00:11:34 +01:00
Marcin Ziąbek a2ce31022c Improved default behavior of fluent configuration 2022-01-08 01:11:25 +01:00
Marcin Ziąbek 763fdfac0f Row: fixed paging behavior, Table: improved paging behavior for already rendered elements 2022-01-07 13:15:27 +01:00
Marcin Ziąbek ba8b35b1c0 Implemented ScaleToFit element 2022-01-07 00:09:25 +01:00
Marcin Ziąbek 648a359d15 Implemented the PreventPaging element 2022-01-06 22:19:33 +01:00
Marcin Ziąbek 630d3389d3 Table: added support for headers and footers 2022-01-06 21:36:27 +01:00
Marcin Ziąbek a7c3c88e81 Updated release notes 2022-01-06 19:57:22 +01:00
Marcin Ziąbek 9524cc6eb1 2022.1.0-beta3 Renaming Box() to MinimalBox() 2022-01-06 19:57:22 +01:00
Marcin Ziąbek 4152b67679 Table: row height fixes for overlapping cells. Added code examples 2022-01-06 19:57:22 +01:00
Marcin Ziąbek 8020cb30cb Table: improved repeating+paging support, added example 2022-01-06 19:57:22 +01:00
Marcin Ziąbek 9358fb8937 Fixed: traversing elements graph 2022-01-06 19:57:21 +01:00
Marcin Ziąbek eb297177ea VisitChildren -> GetChildren traversal refactorization 2022-01-06 19:57:21 +01:00
Marcin Ziąbek 7f651932b9 Table: added default style, fixed bugs, added examples 2022-01-06 19:57:21 +01:00
Marcin Ziąbek caaae5fa1c Table: fixed cross-cell size adjusting 2022-01-06 19:57:21 +01:00
Marcin Ziąbek f76caa28b1 Update Table.cs 2022-01-06 19:57:21 +01:00
MarcinZiabek ea92655ef5 Typo fixes 2022-01-06 19:57:21 +01:00
MarcinZiabek 3f8463e9b7 2022.1.0-beta0 2022-01-06 19:57:21 +01:00
MarcinZiabek 02e25b193d 2022.1.0-alpha0 2022-01-06 19:57:21 +01:00
MarcinZiabek 7cc272ff9e Border radius: added example 2022-01-06 19:57:21 +01:00
MarcinZiabek b8ea30be23 Improved readibility of the table layout algorithm 2022-01-06 19:57:21 +01:00
MarcinZiabek 06418238dc Table layout: refactorization 2022-01-06 19:57:21 +01:00
MarcinZiabek ed73c5b82d Table: layouting improvements for paging 2022-01-06 19:57:21 +01:00
MarcinZiabek 96732b5856 Fixed: corner cases of paging support 2022-01-06 19:57:21 +01:00
MarcinZiabek c7008ad940 Improved wrapping support 2022-01-06 19:57:21 +01:00
MarcinZiabek 940616cd3e Table: optimized rendering performance 2022-01-06 19:57:21 +01:00
MarcinZiabek 01a80032b0 Table: minor optimizations + performance test 2022-01-06 19:57:21 +01:00
MarcinZiabek 3e5e3843d2 Rendering algorithm partially working 2022-01-06 19:57:21 +01:00
MarcinZiabek d3911d5687 Table: refactorization, cell placement algorithm 2022-01-06 19:57:21 +01:00
Marcin Ziąbek 82e4dd36f0 Improved table paging support 2022-01-06 19:57:21 +01:00
Marcin Ziąbek 561d6a004c Row: added support for mixed columns (constant + relational), good when performing visual column merging 2022-01-06 19:57:20 +01:00
Marcin Ziąbek 59b77e55d4 Improved Row element stability 2022-01-06 19:57:20 +01:00
Marcin Ziąbek ea398c562f Merge pull request #64 from markhewett/us-page-sizes
Tweak US Letter and Legal page sizes
2022-01-04 19:31:57 +01:00
Xaver Schulz cb493562fc fix rendering direct to aspnet core response stream
adding a WrapperStream to support streams that not implement the Position property needed by SkiaSharp.
e.g. useful for the aspnet core response stream

fix #52
2021-12-10 21:07:42 +01:00
Hewett, Mark A 5b85ae2627 Tweak US Letter and Legal page sizes
Adobe Acrobat Reader DC now shows the correct sizes for letter and legal size documents.
2021-12-08 19:17:42 -06:00
111 changed files with 3125 additions and 1833 deletions
+70
View File
@@ -0,0 +1,70 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ main ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ main ]
schedule:
- cron: '22 16 * * 5'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'csharp' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://git.io/codeql-language-support
steps:
- name: Checkout repository
uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
+5 -3
View File
@@ -13,11 +13,11 @@ namespace QuestPDF.Examples
[Test]
public void Example()
{
FontManager.RegisterFontType("LibreBarcode39", File.OpenRead("LibreBarcode39-Regular.ttf"));
FontManager.RegisterFont(File.OpenRead("LibreBarcode39-Regular.ttf"));
RenderingTest
.Create()
.PageSize(400, 100)
.PageSize(400, 200)
.ShowResults()
.Render(container =>
{
@@ -25,7 +25,9 @@ namespace QuestPDF.Examples
.Background(Colors.White)
.AlignCenter()
.AlignMiddle()
.Text("*QuestPDF*", TextStyle.Default.FontType("LibreBarcode39").Size(64));
.Text("*QuestPDF*")
.FontFamily("Libre Barcode 39")
.FontSize(64);
});
}
}
+4 -1
View File
@@ -49,7 +49,10 @@ namespace QuestPDF.Examples
.PrimaryLayer()
.PaddingVertical(10)
.PaddingHorizontal(20)
.Text("Sample text", TextStyle.Default.Size(16).Color(Colors.Blue.Darken2).SemiBold());
.Text("Sample text")
.FontSize(16)
.FontColor(Colors.Blue.Darken2)
.SemiBold();
});
});
}
+7 -4
View File
@@ -52,14 +52,17 @@ namespace QuestPDF.Examples
container
.Background(Colors.White)
.Padding(25)
.Stack(stack =>
.Column(column =>
{
stack
column
.Item()
.PaddingBottom(10)
.Text("Chart example", TextStyle.Default.Size(20).SemiBold().Color(Colors.Blue.Medium));
.Text("Chart example")
.FontSize(20)
.SemiBold()
.FontColor(Colors.Blue.Medium);
stack
column
.Item()
.Border(1)
.ExtendHorizontal()
+56
View File
@@ -0,0 +1,56 @@
using System;
using System.Linq;
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class ColumnExamples
{
[Test]
public void Column()
{
RenderingTest
.Create()
.PageSize(PageSizes.A4)
.ShowResults()
.ProducePdf()
.Render(container =>
{
container.Column(column =>
{
foreach (var i in Enumerable.Range(0, 10))
column.Item().Element(Block);
static void Block(IContainer container)
{
container
.Width(72)
.Height(3.5f, Unit.Inch)
.Height(1.5f, Unit.Inch)
.Background(Placeholders.BackgroundColor());
}
});
});
}
[Test]
public void Stability_NoItems()
{
RenderingTest
.Create()
.ProducePdf()
.MaxPages(100)
.PageSize(250, 150)
.Render(container =>
{
container
.Padding(25)
.Column(column => { });
});
}
}
}
+5 -5
View File
@@ -36,8 +36,8 @@ namespace QuestPDF.Examples
.BorderColor(Colors.Black)
.Row(row =>
{
row.RelativeColumn().Element(x => GenerateStructure(x, level));
row.RelativeColumn().Element(x => GenerateStructure(x, level));
row.RelativeItem().Element(x => GenerateStructure(x, level));
row.RelativeItem().Element(x => GenerateStructure(x, level));
});
}
else
@@ -45,10 +45,10 @@ namespace QuestPDF.Examples
container
.Border(level / 4f)
.BorderColor(Colors.Black)
.Stack(stack =>
.Column(column =>
{
stack.Item().Element(x => GenerateStructure(x, level));
stack.Item().Element(x => GenerateStructure(x, level));
column.Item().Element(x => GenerateStructure(x, level));
column.Item().Element(x => GenerateStructure(x, level));
});
}
}
+2 -2
View File
@@ -22,10 +22,10 @@ namespace QuestPDF.Examples
page.Header().Text("Header");
page.Content().PaddingVertical(10).Border(1).Padding(10).Stack(stack =>
page.Content().PaddingVertical(10).Border(1).Padding(10).Column(column =>
{
foreach (var index in Enumerable.Range(1, 100))
stack.Item().Text($"Line {index}", TextStyle.Default.Color(Placeholders.Color()));
column.Item().Text($"Line {index}").FontColor(Placeholders.Color());
});
page.Footer().Text("Footer");
+2 -2
View File
@@ -9,7 +9,7 @@ namespace QuestPDF.Examples
public class DebuggingTesting
{
[Test]
public void Stack()
public void Column()
{
Assert.Throws<DocumentLayoutException>(() =>
{
@@ -23,7 +23,7 @@ namespace QuestPDF.Examples
.Width(100)
.Background(Colors.Grey.Lighten3)
.DebugPointer("Example debug pointer")
.Stack(x =>
.Column(x =>
{
x.Item().Text("Test");
x.Item().Width(150);
+5 -5
View File
@@ -13,7 +13,7 @@ namespace QuestPDF.Examples
{
RenderingTest
.Create()
.ProduceImages()
.ProducePdf()
.ShowResults()
.RenderDocument(container =>
{
@@ -26,11 +26,11 @@ namespace QuestPDF.Examples
page.Size(PageSizes.A4);
page.Background(Colors.White);
page.Content().Stack(stack =>
page.Content().Column(column =>
{
stack.Item().Text(Placeholders.Sentence());
column.Item().Text(Placeholders.Sentence());
stack.Item().Text(text =>
column.Item().Text(text =>
{
// text in this block is additionally semibold
text.DefaultTextStyle(TextStyle.Default.SemiBold());
@@ -38,7 +38,7 @@ namespace QuestPDF.Examples
text.Line(Placeholders.Sentence());
// this text has size 20 but also semibold and red
text.Span(Placeholders.Sentence(), TextStyle.Default.Color(Colors.Red.Medium));
text.Span(Placeholders.Sentence()).FontColor(Colors.Red.Medium);
});
});
});
@@ -24,12 +24,12 @@ namespace QuestPDF.Examples
container
.Padding(10)
.DefaultTextStyle(TextStyle.Default.Bold().Underline())
.Stack(stack =>
.Column(column =>
{
stack.Item().Text("Default style applies to all children", TextStyle.Default);
stack.Item().Text("You can override certain styles", TextStyle.Default.Underline(false).Color(Colors.Green.Darken2));
column.Item().Text("Default style applies to all children");
column.Item().Text("You can override certain styles").Underline(false).FontColor(Colors.Green.Darken2);
stack.Item().PaddingTop(10).Border(1).Grid(grid =>
column.Item().PaddingTop(10).Border(1).Grid(grid =>
{
grid.Columns(4);
@@ -43,7 +43,8 @@ namespace QuestPDF.Examples
.Height(50)
.AlignCenter()
.AlignMiddle()
.Text(i, TextStyle.Default.Size(16 + i / 4));
.Text(i)
.FontSize(16 + i / 4);
}
});
});
@@ -23,21 +23,21 @@ namespace QuestPDF.Examples
container.Page(page =>
{
page.Size(PageSizes.A6);
page.Margin(30);
page.Margin(5);
page.Background(Colors.White);
page.Header().Stack(stack =>
page.Header().Column(column =>
{
stack.Item().ShowOnce().Background(Colors.Blue.Lighten2).Height(60);
stack.Item().SkipOnce().Background(Colors.Green.Lighten2).Height(40);
column.Item().ShowOnce().Background(Colors.Blue.Lighten2).Height(60);
column.Item().SkipOnce().Background(Colors.Green.Lighten2).Height(40);
});
page.Content().PaddingVertical(10).Stack(stack =>
page.Content().PaddingVertical(10).Column(column =>
{
stack.Spacing(10);
column.Spacing(10);
foreach (var _ in Enumerable.Range(0, 13))
stack.Item().Background(Colors.Grey.Lighten2).Height(40);
column.Item().Background(Colors.Grey.Lighten2).Height(40);
});
page.Footer().AlignCenter().Text(text =>
+69 -57
View File
@@ -40,10 +40,12 @@ namespace QuestPDF.Examples
.Decoration(decoration =>
{
decoration
.Header()
.Before()
.Background(Colors.Grey.Medium)
.Padding(10)
.Text("Notes", TextStyle.Default.Size(16).Color("#FFF"));
.Text("Notes")
.FontSize(16)
.FontColor("#FFF");
decoration
.Content()
@@ -66,27 +68,27 @@ namespace QuestPDF.Examples
container
.Background("#FFF")
.Padding(20)
.Stack(stack =>
.Column(column =>
{
stack.Item()
column.Item()
.PaddingBottom(10)
.AlignCenter()
.Text("This Row element is 700pt wide");
stack.Item().Row(row =>
column.Item().Row(row =>
{
row.ConstantColumn(100)
row.ConstantItem(100)
.Background(Colors.Grey.Lighten1)
.Padding(10)
.ExtendVertical()
.Text("This column is 100 pt wide");
row.RelativeColumn()
row.RelativeItem()
.Background(Colors.Grey.Lighten2)
.Padding(10)
.Text("This column takes 1/3 of the available space (200pt)");
row.RelativeColumn(2)
row.RelativeItem(2)
.Background(Colors.Grey.Lighten3)
.Padding(10)
.Text("This column takes 2/3 of the available space (400pt)");
@@ -109,15 +111,15 @@ namespace QuestPDF.Examples
.Row(row =>
{
row.Spacing(20);
row.RelativeColumn(2).Border(1).Background(Colors.Grey.Lighten1);
row.RelativeColumn(3).Border(1).Background(Colors.Grey.Lighten2);
row.RelativeColumn(4).Border(1).Background(Colors.Grey.Lighten3);
row.RelativeItem(2).Border(1).Background(Colors.Grey.Lighten1);
row.RelativeItem(3).Border(1).Background(Colors.Grey.Lighten2);
row.RelativeItem(4).Border(1).Background(Colors.Grey.Lighten3);
});
});
}
[Test]
public void Stack()
public void column()
{
RenderingTest
.Create()
@@ -127,13 +129,13 @@ namespace QuestPDF.Examples
container
.Background("#FFF")
.Padding(15)
.Stack(stack =>
.Column(column =>
{
stack.Spacing(15);
column.Spacing(15);
stack.Item().Background(Colors.Grey.Medium).Height(50);
stack.Item().Background(Colors.Grey.Lighten1).Height(100);
stack.Item().Background(Colors.Grey.Lighten2).Height(150);
column.Item().Background(Colors.Grey.Medium).Height(50);
column.Item().Background(Colors.Grey.Lighten1).Height(100);
column.Item().Background(Colors.Grey.Lighten2).Height(150);
});
});
}
@@ -267,12 +269,12 @@ namespace QuestPDF.Examples
layers
.PrimaryLayer()
.Padding(25)
.Stack(stack =>
.Column(column =>
{
stack.Spacing(5);
column.Spacing(5);
foreach (var _ in Enumerable.Range(0, 7))
stack.Item().Text(Placeholders.Sentence());
column.Item().Text(Placeholders.Sentence());
});
// layer above the main content
@@ -280,12 +282,15 @@ namespace QuestPDF.Examples
.Layer()
.AlignCenter()
.AlignMiddle()
.Text("Watermark", TextStyle.Default.Size(48).Bold().Color(Colors.Green.Lighten3));
.Text("Watermark")
.FontSize(48)
.Bold()
.FontColor(Colors.Green.Lighten3);
layers
.Layer()
.AlignBottom()
.Text(text => text.CurrentPageNumber(TextStyle.Default.Size(16).Color(Colors.Green.Medium)));
.Text(text => text.CurrentPageNumber().FontSize(16).FontColor(Colors.Green.Medium));
});
});
}
@@ -304,16 +309,16 @@ namespace QuestPDF.Examples
// {
// page.Header().PageNumber("Page {pdf:currentPage}");
//
// page.Content().Height(300).Stack(content =>
// page.Content().Height(300).column(content =>
// {
// content.Item().Height(200).Background(Colors.Grey.Lighten2);
//
// content.Item().EnsureSpace(100).Stack(stack =>
// content.Item().EnsureSpace(100).column(column =>
// {
// stack.Spacing(10);
// column.Spacing(10);
//
// foreach (var _ in Enumerable.Range(0, 4))
// stack.Item().Height(50).Background(Colors.Green.Lighten1);
// column.Item().Height(50).Background(Colors.Green.Lighten1);
// });
// });
// });
@@ -378,7 +383,7 @@ namespace QuestPDF.Examples
.Row(row =>
{
foreach (var color in colors)
row.RelativeColumn().Background(color);
row.RelativeItem().Background(color);
});
});
}
@@ -416,7 +421,9 @@ namespace QuestPDF.Examples
.Border(1)
.BorderColor(Colors.Grey.Medium)
.Padding(10)
.Text(font, TextStyle.Default.FontType(font).Size(16));
.Text(font)
.FontFamily(font)
.FontSize(16);
}
});
});
@@ -437,10 +444,10 @@ namespace QuestPDF.Examples
{
layers.Layer().Text("Something else");
layers.PrimaryLayer().Stack(stack =>
layers.PrimaryLayer().Column(column =>
{
stack.Item().PaddingTop(20).Text("Text 1");
stack.Item().PaddingTop(40).Text("Text 2");
column.Item().PaddingTop(20).Text("Text 1");
column.Item().PaddingTop(40).Text("Text 2");
});
layers.Layer().Canvas((canvas, size) =>
@@ -456,7 +463,7 @@ namespace QuestPDF.Examples
});
layers.Layer().Background("#8F00").Extend();
layers.Layer().PaddingTop(40).Text("It works!", TextStyle.Default.Size(24));
layers.Layer().PaddingTop(40).Text("It works!").FontSize(24);
});
});
}
@@ -477,7 +484,7 @@ namespace QuestPDF.Examples
//.MinimalBox()
.Background(Colors.Grey.Lighten2)
.Padding(15)
.Text("Test of the \n box element", TextStyle.Default.Size(20));
.Text("Test of the \n box element").FontSize(20);
});
}
@@ -501,13 +508,14 @@ namespace QuestPDF.Examples
.SemiBold();
decoration
.Header()
.Before()
.PaddingBottom(10)
.Text("Example: scale component", headerFontStyle);
.Text("Example: scale component")
.Style(headerFontStyle);
decoration
.Content()
.Stack(stack =>
.Column(column =>
{
var scales = new[] { 0.8f, 0.9f, 1.1f, 1.2f };
@@ -519,13 +527,14 @@ namespace QuestPDF.Examples
var fontStyle = TextStyle.Default.Size(16);
stack
column
.Item()
.Border(1)
.Background(fontColor)
.Scale(scale)
.Padding(5)
.Text($"Content with {scale} scale.", fontStyle);
.Text($"Content with {scale} scale.")
.Style(fontStyle);
}
});
});
@@ -555,7 +564,8 @@ namespace QuestPDF.Examples
.BorderColor(Colors.Green.Darken1)
.Padding(50)
.Text("Moved text", TextStyle.Default.Size(25));
.Text("Moved text")
.FontSize(25);
});
}
@@ -591,7 +601,8 @@ namespace QuestPDF.Examples
.MinimalBox()
.Background(Colors.White)
.Padding(10)
.Text($"Rotated {turns * 90}°", TextStyle.Default.Size(16));
.Text($"Rotated {turns * 90}°")
.FontSize(16);
}
});
});
@@ -689,7 +700,8 @@ namespace QuestPDF.Examples
.MinimalBox()
.Background(Colors.White)
.Padding(10)
.Text($"Flipped {turns}", TextStyle.Default.Size(16));
.Text($"Flipped {turns}")
.FontSize(16);
}
});
});
@@ -708,14 +720,14 @@ namespace QuestPDF.Examples
.Border(2)
.Row(row =>
{
row.ConstantColumn(25)
row.ConstantItem(25)
.Border(1)
.RotateLeft()
.AlignCenter()
.AlignMiddle()
.Text("Sample text");
row.RelativeColumn().Border(1).Padding(5).Text(Placeholders.Paragraph());
row.RelativeItem().Border(1).Padding(5).Text(Placeholders.Paragraph());
});
});
}
@@ -731,11 +743,11 @@ namespace QuestPDF.Examples
container
.Padding(25)
.PaddingLeft(75)
.Stack(stack =>
.Column(column =>
{
stack.Item().Width(300).Height(150).Background(Colors.Blue.Lighten4);
column.Item().Width(300).Height(150).Background(Colors.Blue.Lighten4);
stack
column
.Item()
// creates an infinite space for its child
@@ -751,7 +763,7 @@ namespace QuestPDF.Examples
.Background(Colors.Blue.Darken1);
stack.Item().Width(300).Height(150).Background(Colors.Blue.Lighten3);
column.Item().Width(300).Height(150).Background(Colors.Blue.Lighten3);
});
});
}
@@ -766,13 +778,13 @@ namespace QuestPDF.Examples
{
container
.Padding(25)
.Stack(stack =>
.Column(column =>
{
stack.Item().Row(row =>
column.Item().Row(row =>
{
row.RelativeColumn().LabelCell("Label 1");
row.RelativeItem().LabelCell("Label 1");
row.RelativeColumn(3).Grid(grid =>
row.RelativeItem(3).Grid(grid =>
{
grid.Columns(3);
@@ -784,11 +796,11 @@ namespace QuestPDF.Examples
});
});
stack.Item().Row(row =>
column.Item().Row(row =>
{
row.RelativeColumn().ValueCell().Text("Value 1");
row.RelativeItem().ValueCell().Text("Value 1");
row.RelativeColumn(3).Grid(grid =>
row.RelativeItem(3).Grid(grid =>
{
grid.Columns(3);
@@ -800,10 +812,10 @@ namespace QuestPDF.Examples
});
});
stack.Item().Row(row =>
column.Item().Row(row =>
{
row.RelativeColumn().LabelCell("Label 6");
row.RelativeColumn().ValueCell().Text("Value 6");
row.RelativeItem().LabelCell("Label 6");
row.RelativeItem().ValueCell().Text("Value 6");
});
});
});
+4 -4
View File
@@ -23,17 +23,17 @@ namespace QuestPDF.Examples
page.Size(PageSizes.A7.Landscape());
page.Background(Colors.White);
page.Header().Text("With ensure space", TextStyle.Default.SemiBold());
page.Header().Text("With ensure space").SemiBold();
page.Content().Stack(stack =>
page.Content().Column(column =>
{
stack
column
.Item()
.ExtendHorizontal()
.Height(75)
.Background(Colors.Grey.Lighten2);
stack
column
.Item()
.EnsureSpace(100)
.Text(Placeholders.LoremIpsum());
@@ -0,0 +1,76 @@
using NUnit.Framework;
using QuestPDF.Drawing.Exceptions;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class ExecutionOrderExamples
{
[Test]
public void Example()
{
RenderingTest
.Create()
.PageSize(400, 300)
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.DefaultTextStyle(TextStyle.Default.Size(18))
.Padding(25)
.Row(row =>
{
row.Spacing(25);
row.RelativeItem()
.Border(1)
.Padding(15)
.Background(Colors.Grey.Lighten2)
.Text("Lorem ipsum");
row.RelativeItem()
.Border(1)
.Background(Colors.Grey.Lighten2)
.Padding(15)
.Text("dolor sit amet");
});
});
}
[Test]
public void Example2()
{
RenderingTest
.Create()
.PageSize(200, 200)
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.Padding(25)
.Border(2)
.Width(150)
.Height(150)
.Background(Colors.Blue.Lighten2)
.PaddingTop(50)
.Background(Colors.Green.Lighten2)
.PaddingRight(50)
.Background(Colors.Red.Lighten2)
.PaddingBottom(50)
.Background(Colors.Amber.Lighten2)
.PaddingLeft(50)
.Background(Colors.Grey.Lighten2);
});
}
}
}
+6 -6
View File
@@ -17,9 +17,9 @@ namespace QuestPDF.Examples
.Padding(5);
}
public static void LabelCell(this IContainer container, string text) => container.Cell(true).Text(text, TextStyle.Default.SemiBold());
public static void LabelCell(this IContainer container, string text) => container.Cell(true).Text(text).SemiBold();
public static IContainer ValueCell(this IContainer container) => container.Cell(false);
public static void ValueCell(this IContainer container, string text) => container.ValueCell().Text(text, TextStyle.Default);
public static void ValueCell(this IContainer container, string text) => container.ValueCell().Text(text);
}
public class FrameExample
@@ -36,14 +36,14 @@ namespace QuestPDF.Examples
container
.Background("#FFF")
.Padding(25)
.Stack(stack =>
.Column(column =>
{
for(var i = 1; i <= 4; i++)
{
stack.Item().Row(row =>
column.Item().Row(row =>
{
row.RelativeColumn(2).LabelCell(Placeholders.Label());
row.RelativeColumn(3).ValueCell().Text(Placeholders.Paragraph());
row.RelativeItem(2).LabelCell(Placeholders.Label());
row.RelativeItem(3).ValueCell().Text(Placeholders.Paragraph());
});
}
});
+5 -5
View File
@@ -19,17 +19,17 @@ namespace QuestPDF.Examples
.ShowResults()
.Render(page =>
{
page.Padding(25).Stack(stack =>
page.Padding(25).Column(column =>
{
stack.Spacing(25);
column.Spacing(25);
stack.Item().Image("logo.png");
column.Item().Image("logo.png");
var binaryData = File.ReadAllBytes("logo.png");
stack.Item().Image(binaryData);
column.Item().Image(binaryData);
using var stream = new FileStream("logo.png", FileMode.Open);
stack.Item().Image(stream);
column.Item().Image(stream);
});
});
}
+3 -2
View File
@@ -25,7 +25,7 @@ namespace QuestPDF.Examples
.Padding(25)
.Decoration(decoration =>
{
decoration.Header().Text(text =>
decoration.Before().Text(text =>
{
text.DefaultTextStyle(TextStyle.Default.Size(20));
@@ -74,7 +74,8 @@ namespace QuestPDF.Examples
.PrimaryLayer()
.AlignCenter()
.AlignMiddle()
.Text(sizeText, TextStyle.Default.Size(15));
.Text(sizeText)
.FontSize(15);
});
}
});
+57
View File
@@ -0,0 +1,57 @@
using System.IO;
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class LineExamples
{
[Test]
public void LineHorizontal()
{
RenderingTest
.Create()
.PageSize(PageSizes.A5)
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.Padding(15)
.MinimalBox()
.DefaultTextStyle(TextStyle.Default.Size(16))
.Column(column =>
{
column.Item().Text("Above text");
column.Item().PaddingVertical(5).LineHorizontal(1).LineColor(Colors.Grey.Medium);
column.Item().Text("Below text");
});
});
}
[Test]
public void LineVertical()
{
RenderingTest
.Create()
.PageSize(PageSizes.A5)
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.Padding(15)
.DefaultTextStyle(TextStyle.Default.Size(16))
.Row(row =>
{
row.AutoItem().Text("Left text");
row.AutoItem().PaddingHorizontal(10).LineVertical(1).LineColor(Colors.Grey.Medium);
row.AutoItem().Text("Right text");
});
});
}
}
}
+1 -1
View File
@@ -43,7 +43,7 @@ namespace QuestPDF.Examples
container
.Background("#FFF")
.Padding(25)
.Stack(column =>
.Column(column =>
{
column.Spacing(10);
+95
View File
@@ -0,0 +1,95 @@
using System.Diagnostics;
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class MinimalApiExamples
{
[Test]
public void MinimalApi()
{
Document
.Create(container =>
{
container.Page(page =>
{
page.Size(PageSizes.A4);
page.Margin(2, Unit.Centimetre);
page.Background(Colors.White);
page.DefaultTextStyle(TextStyle.Default.Size(20));
page.Header()
.Text("Hello PDF!").SemiBold().FontSize(36).FontColor(Colors.Blue.Medium);
page.Content()
.PaddingVertical(1, Unit.Centimetre)
.Column(x =>
{
x.Spacing(20);
x.Item().Text(Placeholders.LoremIpsum());
x.Item().Image(Placeholders.Image(200, 100));
});
page.Footer()
.AlignCenter()
.Text(x =>
{
x.Span("Page ");
x.CurrentPageNumber();
});
});
})
.GeneratePdf("hello.pdf");
Process.Start("explorer.exe", "hello.pdf");
}
[Test]
public void MinimalApi2()
{
RenderingTest
.Create()
.ProduceImages()
.ShowResults()
.RenderDocument(container =>
{
container.Page(page =>
{
page.Size(PageSizes.A4);
page.Margin(2, Unit.Centimetre);
page.Background(Colors.White);
page.DefaultTextStyle(TextStyle.Default.Size(20));
page.Header()
.Text("Hello PDF!")
.SemiBold()
.FontSize(36)
.FontColor(Colors.Blue.Medium);
page.Content()
.PaddingVertical(1, Unit.Centimetre)
.Column(x =>
{
x.Spacing(20);
x.Item().Text(Placeholders.LoremIpsum());
x.Item().Image(Placeholders.Image(200, 100));
});
page.Footer()
.AlignCenter()
.Text(x =>
{
x.Span("Page ");
x.CurrentPageNumber();
});
});
});
}
}
}
+8 -7
View File
@@ -50,7 +50,8 @@ namespace QuestPDF.Examples
.Background("FFF")
.Padding(5)
.AlignCenter()
.Text("Sample text", TextStyle.Default.FontType("Segoe UI emoji"));
.Text("Sample text")
.FontFamily("Segoe UI emoji");
});
}
@@ -63,7 +64,7 @@ namespace QuestPDF.Examples
.Render(container =>
{
container
.Stack(column =>
.Column(column =>
{
column
.Item()
@@ -113,14 +114,14 @@ namespace QuestPDF.Examples
.Render(container =>
{
container
.Stack(column =>
.Column(column =>
{
column
.Item()
.Height(150)
.Row(row =>
{
row.RelativeColumn()
row.RelativeItem()
.Extend()
.Background("FFF")
@@ -128,7 +129,7 @@ namespace QuestPDF.Examples
.Width(50)
.Background("444");
row.RelativeColumn()
row.RelativeItem()
.Extend()
.Background("BBB")
@@ -142,7 +143,7 @@ namespace QuestPDF.Examples
.Height(150)
.Row(row =>
{
row.RelativeColumn()
row.RelativeItem()
.Extend()
.Background("BBB")
@@ -150,7 +151,7 @@ namespace QuestPDF.Examples
.Width(50)
.Background("444");
row.RelativeColumn()
row.RelativeItem()
.Extend()
.Background("BBB")
@@ -0,0 +1,49 @@
using System;
using System.Linq;
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class PageBackgroundForeground
{
[Test]
public void Test()
{
RenderingTest
.Create()
.PageSize(550, 400)
.ProducePdf()
.ShowResults()
.RenderDocument(document =>
{
document.Page(page =>
{
page.Size(PageSizes.A4);
page.Margin(1, Unit.Inch);
page.DefaultTextStyle(TextStyle.Default.FontSize(16));
page.Foreground()
.AlignMiddle()
.AlignCenter()
.Text("Watermark")
.FontSize(64)
.FontColor(Colors.Blue.Lighten3);
page.Header().Text("Background and foreground").Bold().FontColor(Colors.Blue.Medium).FontSize(24);
page.Content().PaddingVertical(25).Column(column =>
{
column.Spacing(25);
foreach (var i in Enumerable.Range(0, 100))
column.Item().Background(Colors.Grey.Lighten2).Height(75);
});
});
});
}
}
}
@@ -12,6 +12,7 @@
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
<PackageReference Include="SkiaSharp" Version="2.80.3" />
<PackageReference Include="Svg.Skia" Version="0.5.10" />
</ItemGroup>
<ItemGroup>
@@ -28,6 +29,9 @@
<None Update="logo.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="pdf-icon.svg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
+36 -21
View File
@@ -9,7 +9,7 @@ namespace QuestPDF.Examples
public class RowExamples
{
[Test]
public void ColumnTypes()
public void ItemTypes()
{
RenderingTest
.Create()
@@ -22,25 +22,24 @@ namespace QuestPDF.Examples
.Padding(25)
.MinimalBox()
.Border(1)
.Stack(stack =>
.Column(column =>
{
stack.Item().LabelCell("Total width: 600px");
column.Item().LabelCell("Total width: 600px");
stack.Item().Row(row =>
column.Item().Row(row =>
{
row.ConstantColumn(150).ValueCell("150px");
row.ConstantColumn(100).ValueCell("100px");
row.RelativeColumn(4).ValueCell("200px");
row.RelativeColumn(3).ValueCell("150px");
row.ConstantItem(150).ValueCell("150px");
row.ConstantItem(100).ValueCell("100px");
row.RelativeItem(4).ValueCell("200px");
row.RelativeItem(3).ValueCell("150px");
});
stack.Item().Row(row =>
column.Item().Row(row =>
{
row.ConstantColumn(100).ValueCell("100px");
row.ConstantColumn(50).ValueCell("50px");
row.Column(constantWidth: 100, relativeWidth: 4).ValueCell("350px");
row.RelativeColumn(2).ValueCell("100px");
row.RelativeColumn(1).ValueCell("50px");
row.ConstantItem(100).ValueCell("100px");
row.ConstantItem(50).ValueCell("50px");
row.RelativeItem(2).ValueCell("100px");
row.RelativeItem(1).ValueCell("50px");
});
});
});
@@ -63,17 +62,17 @@ namespace QuestPDF.Examples
.Padding(25)
.Row(row =>
{
row.RelativeColumn().Stack(stack =>
row.RelativeItem().Column(column =>
{
stack.Item().ShowOnce().Element(CreateBox).Text("X");
stack.Item().Element(CreateBox).Text("1");
stack.Item().Element(CreateBox).Text("2");
column.Item().ShowOnce().Element(CreateBox).Text("X");
column.Item().Element(CreateBox).Text("1");
column.Item().Element(CreateBox).Text("2");
});
row.RelativeColumn().Stack(stack =>
row.RelativeItem().Column(column =>
{
stack.Item().Element(CreateBox).Text("1");
stack.Item().Element(CreateBox).Text("2");
column.Item().Element(CreateBox).Text("1");
column.Item().Element(CreateBox).Text("2");
});
});
});
@@ -90,5 +89,21 @@ namespace QuestPDF.Examples
.ShowOnce();
}
}
[Test]
public void Stability_NoItems()
{
RenderingTest
.Create()
.ProducePdf()
.MaxPages(100)
.PageSize(250, 150)
.Render(container =>
{
container
.Padding(25)
.Row(row => { });
});
}
}
}
+42
View File
@@ -0,0 +1,42 @@
using System.Linq;
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class ScaleToFitExamples
{
[Test]
public void ScaleToFit()
{
RenderingTest
.Create()
.PageSize(PageSizes.A4)
.ProduceImages()
.ShowResults()
.Render(container =>
{
container.Padding(25).Column(column =>
{
var text = Placeholders.Paragraph();
foreach (var i in Enumerable.Range(2, 5))
{
column
.Item()
.MinimalBox()
.Border(1)
.Padding(5)
.Width(i * 40)
.Height(i * 20)
.ScaleToFit()
.Text(text);
}
});
});
}
}
}
+3 -3
View File
@@ -23,18 +23,18 @@ namespace QuestPDF.Examples
page.Size(PageSizes.A7.Landscape());
page.Background(Colors.White);
page.Header().Text("With show once", TextStyle.Default.SemiBold());
page.Header().Text("With show once").SemiBold();
page.Content().PaddingVertical(5).Row(row =>
{
row.RelativeColumn()
row.RelativeItem()
.Background(Colors.Grey.Lighten2)
.Border(1)
.Padding(5)
.ShowOnce()
.Text(Placeholders.Label());
row.RelativeColumn(2)
row.RelativeItem(2)
.Border(1)
.Padding(5)
.Text(Placeholders.Paragraph());
+5 -4
View File
@@ -23,15 +23,16 @@ namespace QuestPDF.Examples
page.Size(PageSizes.A7.Landscape());
page.Background(Colors.White);
page.Header().Stack(stack =>
page.Header().Column(column =>
{
stack.Item().ShowOnce().Text("This header is visible on the first page.");
stack.Item().SkipOnce().Text("This header is visible on the second page and all following.");
column.Item().ShowOnce().Text("This header is visible on the first page.");
column.Item().SkipOnce().Text("This header is visible on the second page and all following.");
});
page.Content()
.PaddingVertical(10)
.Text(Placeholders.Paragraphs(), TextStyle.Default.Color(Colors.Grey.Medium));
.Text(Placeholders.Paragraphs())
.FontColor(Colors.Grey.Medium);
page.Footer().Text(text =>
{
+48
View File
@@ -0,0 +1,48 @@
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class StopPaging
{
[Test]
public void Example()
{
RenderingTest
.Create()
.PageSize(300, 250)
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.Padding(25)
.DefaultTextStyle(TextStyle.Default.Size(14))
.Decoration(decoration =>
{
decoration
.Before()
.Text(text =>
{
text.DefaultTextStyle(TextStyle.Default.SemiBold().Color(Colors.Blue.Medium));
text.Span("Page ");
text.CurrentPageNumber();
});
decoration
.Content()
.Column(column =>
{
column.Spacing(25);
column.Item().StopPaging().Text(Placeholders.LoremIpsum());
column.Item().ExtendHorizontal().Height(75).Background(Colors.Grey.Lighten2);
});
});
});
}
}
}
+36
View File
@@ -0,0 +1,36 @@
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using SkiaSharp;
using Svg.Skia;
namespace QuestPDF.Examples
{
public class SvgImageExample
{
[Test]
public void BorderRadius()
{
RenderingTest
.Create()
.PageSize(175, 100)
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.Background(Colors.Grey.Lighten2)
.Padding(25)
.Canvas((canvas, space) =>
{
using var svg = new SKSvg();
svg.Load("pdf-icon.svg");
canvas.DrawPicture(svg.Picture);
});
});
}
}
}
+37 -2
View File
@@ -49,6 +49,39 @@ namespace QuestPDF.Examples
});
}
[Test]
public void PagingSupport()
{
RenderingTest
.Create()
.ProducePdf()
.PageSize(420, 220)
.ShowResults()
.Render(container =>
{
container
.Padding(10)
.MinimalBox()
.Border(1)
.Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.RelativeColumn();
columns.RelativeColumn();
columns.RelativeColumn();
columns.RelativeColumn();
});
// by using custom 'Element' method, we can reuse visual configuration
table.Cell().Element(Block).Text(Placeholders.Label());
table.Cell().Element(Block).Text(Placeholders.Label());
table.Cell().Element(Block).Text(Placeholders.Paragraph());
table.Cell().Element(Block).Text(Placeholders.Label());
});
});
}
[Test]
public void DefaultCellStyle()
{
@@ -217,7 +250,7 @@ namespace QuestPDF.Examples
RenderingTest
.Create()
.ProduceImages()
.PageSize(220, 220)
.PageSize(220, 170)
.ShowResults()
.Render(container =>
{
@@ -380,7 +413,7 @@ namespace QuestPDF.Examples
.EnableCaching()
.EnableDebugging(false)
.ShowResults()
.Render(container => GeneratePerformanceStructure(container, 1000));
.Render(container => GeneratePerformanceStructure(container, 250));
}
public static void GeneratePerformanceStructure(IContainer container, int repeats)
@@ -456,8 +489,10 @@ namespace QuestPDF.Examples
return container
.Border(1)
.Background(Colors.Grey.Lighten3)
.ShowOnce()
.MinWidth(50)
.MinHeight(50)
.Padding(10)
.AlignCenter()
.AlignMiddle();
}
+30 -30
View File
@@ -117,19 +117,19 @@ namespace QuestPDF.Examples
{
decoration
.Content()
.Stack(stack =>
.Column(column =>
{
stack.Item().Element(Title);
stack.Item().PageBreak();
stack.Item().Element(TableOfContents);
stack.Item().PageBreak();
column.Item().Element(Title);
column.Item().PageBreak();
column.Item().Element(TableOfContents);
column.Item().PageBreak();
Chapters(stack);
Chapters(column);
stack.Item().Element(Acknowledgements);
column.Item().Element(Acknowledgements);
});
decoration.Footer().Element(Footer);
decoration.After().Element(Footer);
});
}
@@ -139,75 +139,75 @@ namespace QuestPDF.Examples
.Extend()
.PaddingBottom(200)
.AlignBottom()
.Stack(stack =>
.Column(column =>
{
stack.Item().Text("Quo Vadis", TextStyle.Default.Size(72).Bold().Color(Colors.Blue.Darken2));
stack.Item().Text("Henryk Sienkiewicz", TextStyle.Default.Size(24).Color(Colors.Grey.Darken2));
column.Item().Text("Quo Vadis").FontSize(72).Bold().FontColor(Colors.Blue.Darken2);
column.Item().Text("Henryk Sienkiewicz").FontSize(24).FontColor(Colors.Grey.Darken2);
});
}
void TableOfContents(IContainer container)
{
container.Stack(stack =>
container.Column(column =>
{
SectionTitle(stack, "Spis treści");
SectionTitle(column, "Spis treści");
foreach (var chapter in chapters)
{
stack.Item().InternalLink(chapter.Title).Row(row =>
column.Item().InternalLink(chapter.Title).Row(row =>
{
row.RelativeColumn().Text(chapter.Title, normalStyle);
row.ConstantColumn(100).AlignRight().Text(text => text.PageNumberOfLocation(chapter.Title, normalStyle));
row.RelativeItem().Text(chapter.Title).Style(normalStyle);
row.ConstantItem(100).AlignRight().Text(text => text.BeginPageNumberOfSection(chapter.Title).Style(normalStyle));
});
}
});
}
void Chapters(StackDescriptor stack)
void Chapters(ColumnDescriptor column)
{
foreach (var chapter in chapters)
{
stack.Item().Element(container => Chapter(container, chapter.Title, chapter.Content));
column.Item().Element(container => Chapter(container, chapter.Title, chapter.Content));
}
}
void Chapter(IContainer container, string title, string content)
{
container.Stack(stack =>
container.Column(column =>
{
SectionTitle(stack, title);
SectionTitle(column, title);
stack.Item().Text(text =>
column.Item().Text(text =>
{
text.ParagraphSpacing(5);
text.Span(content, normalStyle);
text.Span(content).Style(normalStyle);
});
stack.Item().PageBreak();
column.Item().PageBreak();
});
}
void Acknowledgements(IContainer container)
{
container.Stack(stack =>
container.Column(column =>
{
SectionTitle(stack, "Podziękowania");
SectionTitle(column, "Podziękowania");
stack.Item().Text(text =>
column.Item().Text(text =>
{
text.DefaultTextStyle(normalStyle);
text.Span("Ten dokument został wygenerowany na podstawie książki w formacie TXT opublikowanej w serwisie ");
text.ExternalLocation("wolnelektury.pl", "https://wolnelektury.pl/", normalStyle.Color(Colors.Blue.Medium).Underline());
text.Hyperlink("wolnelektury.pl", "https://wolnelektury.pl/").FontColor(Colors.Blue.Medium).Underline();
text.Span(". Dziękuję za wspieranie polskiego czytelnictwa!");
});
});
}
void SectionTitle(StackDescriptor stack, string text)
void SectionTitle(ColumnDescriptor column, string text)
{
stack.Item().Location(text).Text(text, subtitleStyle);
stack.Item().PaddingTop(10).PaddingBottom(50).BorderBottom(1).BorderColor(Colors.Grey.Lighten2).ExtendHorizontal();
column.Item().Location(text).Text(text).Style(subtitleStyle);
column.Item().PaddingTop(10).PaddingBottom(50).BorderBottom(1).BorderColor(Colors.Grey.Lighten2).ExtendHorizontal();
}
void Footer(IContainer container)
+13 -11
View File
@@ -31,7 +31,7 @@ namespace QuestPDF.Examples
{
text.DefaultTextStyle(TextStyle.Default.Size(20));
text.Span("This is a normal text, followed by an ");
text.Span("underlined red text.", TextStyle.Default.Size(20).Color(Colors.Red.Medium).Underline());
text.Span("underlined red text.").FontSize(20).FontColor(Colors.Red.Medium).Underline();
});
});
}
@@ -57,7 +57,7 @@ namespace QuestPDF.Examples
foreach (var i in Enumerable.Range(1, 3))
{
text.Span($"Paragraph {i}: ", TextStyle.Default.SemiBold());
text.Span($"Paragraph {i}: ").SemiBold();
text.Line(Placeholders.Paragraph());
}
});
@@ -120,13 +120,13 @@ namespace QuestPDF.Examples
text.Line(Placeholders.LoremIpsum());
text.Span($"This is target text that should show up. {DateTime.UtcNow:T} > This is a short sentence that will be wrapped into second line hopefully, right? <", TextStyle.Default.Underline());
text.Span($"This is target text that should show up. {DateTime.UtcNow:T} > This is a short sentence that will be wrapped into second line hopefully, right? <").Underline();
});
});
}
[Test]
public void TextStack()
public void Textcolumn()
{
RenderingTest
.Create()
@@ -173,6 +173,8 @@ namespace QuestPDF.Examples
.Padding(10)
.Text(text =>
{
text.DefaultTextStyle(x => x.Bold());
text.DefaultTextStyle(TextStyle.Default);
text.AlignLeft();
text.ParagraphSpacing(10);
@@ -182,9 +184,9 @@ namespace QuestPDF.Examples
text.EmptyLine();
text.Span("This text is a normal text, ");
text.Span("this is a bold text, ", TextStyle.Default.Bold());
text.Span("this is a red and underlined text, ", TextStyle.Default.Color(Colors.Red.Medium).Underline());
text.Span("and this is slightly bigger text.", TextStyle.Default.Size(16));
text.Span("this is a bold text, ").Bold();
text.Span("this is a red and underlined text, ").FontColor(Colors.Red.Medium).Underline();
text.Span("and this is slightly bigger text.").FontSize(16);
text.EmptyLine();
@@ -210,7 +212,7 @@ namespace QuestPDF.Examples
text.EmptyLine();
text.Span(Placeholders.Paragraphs(), TextStyle.Default.Italic());
text.Span(Placeholders.Paragraphs()).Italic();
text.Line("This is target text that does not show up. " + Placeholders.Paragraph());
});
@@ -241,9 +243,9 @@ namespace QuestPDF.Examples
text.ParagraphSpacing(10);
text.Span("This text is a normal text, ");
text.Span("this is a bold text, ", TextStyle.Default.Bold());
text.Span("this is a red and underlined text, ", TextStyle.Default.Color(Colors.Red.Medium).Underline());
text.Span("and this is slightly bigger text.", TextStyle.Default.Size(16));
text.Span("this is a bold text, ").Bold();
text.Span("this is a red and underlined text, ").FontColor(Colors.Red.Medium).Underline();
text.Span("and this is slightly bigger text.").FontSize(16);
text.Span("The new text element also supports injecting custom content between words: ");
text.Element().PaddingBottom(-10).Height(16).Width(32).Image(Placeholders.Image);
+149
View File
@@ -0,0 +1,149 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="75.320129mm"
height="92.604164mm"
viewBox="0 0 75.320129 92.604164"
version="1.1"
id="svg8"
inkscape:version="0.92.3 (2405546, 2018-03-11)"
sodipodi:docname="PDF file icon.svg">
<title
id="title2682">PDF file icon</title>
<defs
id="defs2">
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath839">
<rect
style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke"
id="rect841"
width="68.688263"
height="67.886459"
x="-74.033661"
y="141.44913" />
</clipPath>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.98994949"
inkscape:cx="122.12727"
inkscape:cy="167.02823"
inkscape:document-units="mm"
inkscape:current-layer="g899"
showgrid="false"
inkscape:object-nodes="false"
inkscape:window-width="1366"
inkscape:window-height="705"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:snap-page="false"
inkscape:snap-others="false"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title>PDF file icon</dc:title>
<dc:date>08/10/2018</dc:date>
<dc:creator>
<cc:Agent>
<dc:title>Adobe Systems</dc:title>
</cc:Agent>
</dc:creator>
<dc:publisher>
<cc:Agent>
<dc:title>CMetalCore</dc:title>
</cc:Agent>
</dc:publisher>
<dc:description>Fuente del texto &quot;PDF&quot;:
Franklin Gothic Medium Cond</dc:description>
<cc:license
rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/" />
</cc:Work>
<cc:License
rdf:about="http://creativecommons.org/licenses/by-sa/4.0/">
<cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Notice" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Attribution" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#ShareAlike" />
</cc:License>
</rdf:RDF>
</metadata>
<g
inkscape:label="Capa 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(36.076172,-93.731774)">
<g
id="g899"
transform="matrix(1.4843054,0,0,1.4843054,17.471885,-90.243502)">
<g
id="g876">
<path
inkscape:connector-curvature="0"
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ff2116;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.11666656;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m -29.632812,123.94727 c -3.551967,0 -6.44336,2.89347 -6.44336,6.44531 v 49.49804 c 0,3.55185 2.891393,6.44532 6.44336,6.44532 H 8.2167969 c 3.5519661,0 6.4433591,-2.89335 6.4433591,-6.44532 v -40.70117 c 0,0 0.101353,-1.19181 -0.416015,-2.35156 -0.484969,-1.08711 -1.275391,-1.84375 -1.275391,-1.84375 a 1.0584391,1.0584391 0 0 0 -0.0059,-0.008 L 3.5722246,125.7752 a 1.0584391,1.0584391 0 0 0 -0.015625,-0.0156 c 0,0 -0.8017392,-0.76344 -1.9902344,-1.27344 -1.39939552,-0.6005 -2.8417968,-0.53711 -2.8417968,-0.53711 l 0.021484,-0.002 z"
id="path890" />
<path
inkscape:connector-curvature="0"
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#f5f5f5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.11666656;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m -29.632812,126.06445 h 28.3789058 a 1.0584391,1.0584391 0 0 0 0.021484,0 c 0,0 1.13480448,0.011 1.96484378,0.36719 0.79889772,0.34282 1.36536982,0.86176 1.36914062,0.86524 1.25e-5,1e-5 0.00391,0.004 0.00391,0.004 l 9.3671868,9.18945 c 0,0 0.564354,0.59582 0.837891,1.20899 0.220779,0.49491 0.234375,1.40039 0.234375,1.40039 a 1.0584391,1.0584391 0 0 0 -0.002,0.0449 v 40.74609 c 0,2.41592 -1.910258,4.32813 -4.3261717,4.32813 H -29.632812 c -2.415914,0 -4.326172,-1.91209 -4.326172,-4.32813 v -49.49804 c 0,-2.41603 1.910258,-4.32813 4.326172,-4.32813 z"
id="rect2684" />
<path
sodipodi:nodetypes="scccscccscccccssscacccsccccccccccccccccccccaccccccccc"
inkscape:connector-curvature="0"
d="m -23.40766,161.09299 c -1.45669,-1.45669 0.11934,-3.45839 4.39648,-5.58397 l 2.69124,-1.33743 1.04845,-2.29399 c 0.57665,-1.26169 1.43729,-3.32036 1.91254,-4.5748 l 0.8641,-2.28082 -0.59546,-1.68793 c -0.73217,-2.07547 -0.99326,-5.19438 -0.52872,-6.31588 0.62923,-1.51909 2.69029,-1.36323 3.50626,0.26515 0.63727,1.27176 0.57212,3.57488 -0.18329,6.47946 l -0.6193,2.38125 0.5455,0.92604 c 0.30003,0.50932 1.1764,1.71867 1.9475,2.68743 l 1.44924,1.80272 1.8033728,-0.23533 c 5.72900399,-0.74758 7.6912472,0.523 7.6912472,2.34476 0,2.29921 -4.4984914,2.48899 -8.2760865,-0.16423 -0.8499666,-0.59698 -1.4336605,-1.19001 -1.4336605,-1.19001 0,0 -2.3665326,0.48178 -3.531704,0.79583 -1.202707,0.32417 -1.80274,0.52719 -3.564509,1.12186 0,0 -0.61814,0.89767 -1.02094,1.55026 -1.49858,2.4279 -3.24833,4.43998 -4.49793,5.1723 -1.3991,0.81993 -2.86584,0.87582 -3.60433,0.13733 z m 2.28605,-0.81668 c 0.81883,-0.50607 2.47616,-2.46625 3.62341,-4.28553 l 0.46449,-0.73658 -2.11497,1.06339 c -3.26655,1.64239 -4.76093,3.19033 -3.98386,4.12664 0.43653,0.52598 0.95874,0.48237 2.01093,-0.16792 z m 21.21809,-5.95578 c 0.80089,-0.56097 0.68463,-1.69142 -0.22082,-2.1472 -0.70466,-0.35471 -1.2726074,-0.42759 -3.1031574,-0.40057 -1.1249,0.0767 -2.9337647,0.3034 -3.2403347,0.37237 0,0 0.993716,0.68678 1.434896,0.93922 0.58731,0.33544 2.0145161,0.95811 3.0565161,1.27706 1.02785,0.31461 1.6224,0.28144 2.0729,-0.0409 z m -8.53152,-3.54594 c -0.4847,-0.50952 -1.30889,-1.57296 -1.83152,-2.3632 -0.68353,-0.89643 -1.02629,-1.52887 -1.02629,-1.52887 0,0 -0.4996,1.60694 -0.90948,2.57394 l -1.27876,3.16076 -0.37075,0.71695 c 0,0 1.971043,-0.64627 2.97389,-0.90822 1.0621668,-0.27744 3.21787,-0.70134 3.21787,-0.70134 z m -2.74938,-11.02573 c 0.12363,-1.0375 0.1761,-2.07346 -0.15724,-2.59587 -0.9246,-1.01077 -2.04057,-0.16787 -1.85154,2.23517 0.0636,0.8084 0.26443,2.19033 0.53292,3.04209 l 0.48817,1.54863 0.34358,-1.16638 c 0.18897,-0.64151 0.47882,-2.02015 0.64411,-3.06364 z"
style="fill:#ff2116;fill-opacity:1;stroke-width:0.26458335"
id="path2697" />
<g
id="g858">
<path
inkscape:connector-curvature="0"
id="path845"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:125%;font-family:'Franklin Gothic Medium Cond';-inkscape-font-specification:'Franklin Gothic Medium Cond';letter-spacing:0px;word-spacing:4.26000023px;fill:#2c2c2c;fill-opacity:1;stroke:none;stroke-width:0.35824656px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m -20.930423,167.83862 h 2.364986 q 1.133514,0 1.840213,0.2169 0.706698,0.20991 1.189489,0.9446 0.482795,0.72769 0.482795,1.75625 0,0.94459 -0.391832,1.6233 -0.391833,0.67871 -1.056548,0.97958 -0.65772,0.30087 -2.02913,0.30087 h -0.818651 v 3.72941 h -1.581322 z m 1.581322,1.22447 v 3.33058 h 0.783664 q 1.049552,0 1.44838,-0.39184 0.405826,-0.39183 0.405826,-1.27345 0,-0.65772 -0.265887,-1.06355 -0.265884,-0.41282 -0.587747,-0.50378 -0.314866,-0.098 -1.000572,-0.098 z" />
<path
inkscape:connector-curvature="0"
id="path847"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:125%;font-family:'Franklin Gothic Medium Cond';-inkscape-font-specification:'Franklin Gothic Medium Cond';letter-spacing:0px;word-spacing:4.26000023px;fill:#2c2c2c;fill-opacity:1;stroke:none;stroke-width:0.35824656px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m -13.842461,167.83862 h 2.148082 q 1.560333,0 2.4909318,0.55276 0.9375993,0.55276 1.4133973,1.6443 0.482791,1.09153 0.482791,2.42096 0,1.3994 -0.4338151,2.49793 -0.4268149,1.09153 -1.3154348,1.76324 -0.8816233,0.67172 -2.5189212,0.67172 h -2.267031 z m 1.581326,1.26645 v 7.018 h 0.657715 q 1.378411,0 2.001144,-0.9516 0.6227329,-0.95858 0.6227329,-2.5539 0,-3.5125 -2.6238769,-3.5125 z" />
<path
inkscape:connector-curvature="0"
id="path849"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:125%;font-family:'Franklin Gothic Medium Cond';-inkscape-font-specification:'Franklin Gothic Medium Cond';letter-spacing:0px;word-spacing:4.26000023px;fill:#2c2c2c;fill-opacity:1;stroke:none;stroke-width:0.35824656px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m -5.7889096,167.83862 h 5.30372941 v 1.26645 H -4.2075842 v 2.85478 h 2.9807225 v 1.26646 h -2.9807225 v 4.16322 h -1.5813254 z" />
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 12 KiB

+28
View File
@@ -1,4 +1,5 @@
using System;
using System.Collections.Concurrent;
using System.IO;
using SkiaSharp;
@@ -24,5 +25,32 @@ namespace QuestPDF.ReportSample
Latitude = Helpers.Random.NextDouble() * 180f - 90f
};
}
private static readonly ConcurrentDictionary<int, string> RomanNumeralCache = new ConcurrentDictionary<int, string>();
public static string FormatAsRomanNumeral(this int number)
{
if (number < 0 || number > 3999)
throw new ArgumentOutOfRangeException("Number should be in range from 1 to 3999");
return RomanNumeralCache.GetOrAdd(number, x =>
{
if (x >= 1000) return "M" + FormatAsRomanNumeral(x - 1000);
if (x >= 900) return "CM" + FormatAsRomanNumeral(x - 900);
if (x >= 500) return "D" + FormatAsRomanNumeral(x - 500);
if (x >= 400) return "CD" + FormatAsRomanNumeral(x - 400);
if (x >= 100) return "C" + FormatAsRomanNumeral(x - 100);
if (x >= 90) return "XC" + FormatAsRomanNumeral(x - 90);
if (x >= 50) return "L" + FormatAsRomanNumeral(x - 50);
if (x >= 40) return "XL" + FormatAsRomanNumeral(x - 40);
if (x >= 10) return "X" + FormatAsRomanNumeral(x - 10);
if (x >= 9) return "IX" + FormatAsRomanNumeral(x - 9);
if (x >= 5) return "V" + FormatAsRomanNumeral(x - 5);
if (x >= 4) return "IV" + FormatAsRomanNumeral(x - 4);
if (x >= 1) return "I" + FormatAsRomanNumeral(x - 1);
return string.Empty;
});
}
}
}
@@ -26,58 +26,58 @@ namespace QuestPDF.ReportSample.Layouts
private void ComposeHeader(IContainer container)
{
container.Background(Colors.Grey.Lighten3).Border(1).Stack(stack =>
container.Background(Colors.Grey.Lighten3).Border(1).Column(column =>
{
stack.Item().ShowOnce().Padding(5).AlignMiddle().Row(row =>
column.Item().ShowOnce().Padding(5).AlignMiddle().Row(row =>
{
row.RelativeColumn(2).AlignMiddle().Text("PRIMARY HEADER", TextStyle.Default.Color(Colors.Grey.Darken3).Size(30).Bold());
row.RelativeColumn(1).AlignRight().MinimalBox().AlignMiddle().Background(Colors.Blue.Darken2).Padding(30);
row.RelativeItem(2).AlignMiddle().Text("PRIMARY HEADER").FontColor(Colors.Grey.Darken3).FontSize(30).Bold();
row.RelativeItem(1).AlignRight().MinimalBox().AlignMiddle().Background(Colors.Blue.Darken2).Padding(30);
});
stack.Item().SkipOnce().Padding(5).Row(row =>
column.Item().SkipOnce().Padding(5).Row(row =>
{
row.RelativeColumn(2).Text("SECONDARY HEADER", TextStyle.Default.Color(Colors.Grey.Darken3).Size(30).Bold());
row.RelativeColumn(1).AlignRight().MinimalBox().Background(Colors.Blue.Lighten4).Padding(15);
row.RelativeItem(2).Text("SECONDARY HEADER").FontColor(Colors.Grey.Darken3).FontSize(30).Bold();
row.RelativeItem(1).AlignRight().MinimalBox().Background(Colors.Blue.Lighten4).Padding(15);
});
});
}
private void ComposeContent(IContainer container)
{
container.Stack(stack =>
container.Column(column =>
{
stack.Item().PaddingVertical(80).Text("First");
stack.Item().PageBreak();
stack.Item().PaddingVertical(80).Text("Second");
stack.Item().PageBreak();
stack.Item().PaddingVertical(80).Text("Third");
stack.Item().PageBreak();
column.Item().PaddingVertical(80).Text("First");
column.Item().PageBreak();
column.Item().PaddingVertical(80).Text("Second");
column.Item().PageBreak();
column.Item().PaddingVertical(80).Text("Third");
column.Item().PageBreak();
});
}
private void ComposeFooter(IContainer container)
{
container.Background(Colors.Grey.Lighten3).Stack(stack =>
container.Background(Colors.Grey.Lighten3).Column(column =>
{
stack.Item().ShowOnce().Background(Colors.Grey.Lighten3).Row(row =>
column.Item().ShowOnce().Background(Colors.Grey.Lighten3).Row(row =>
{
row.RelativeColumn().Text(x =>
row.RelativeItem().Text(x =>
{
x.CurrentPageNumber();
x.Span(" / ");
x.TotalPages();
});
row.RelativeColumn().AlignRight().Text("Footer for header");
row.RelativeItem().AlignRight().Text("Footer for header");
});
stack.Item().SkipOnce().Background(Colors.Grey.Lighten3).Row(row =>
column.Item().SkipOnce().Background(Colors.Grey.Lighten3).Row(row =>
{
row.RelativeColumn().Text(x =>
row.RelativeItem().Text(x =>
{
x.CurrentPageNumber();
x.Span(" / ");
x.TotalPages();
});
row.RelativeColumn().AlignRight().Text("Footer for every page except header");
row.RelativeItem().AlignRight().Text("Footer for every page except header");
});
});
}
@@ -17,11 +17,11 @@ namespace QuestPDF.ReportSample.Layouts
{
container
.ShowEntire()
.Stack(stack =>
.Column(column =>
{
stack.Spacing(5);
stack.Item().Element(PhotoWithMaps);
stack.Item().Element(PhotoDetails);
column.Spacing(5);
column.Item().Element(PhotoWithMaps);
column.Item().Element(PhotoDetails);
});
}
@@ -30,14 +30,14 @@ namespace QuestPDF.ReportSample.Layouts
container
.Row(row =>
{
row.RelativeColumn(2).AspectRatio(4 / 3f).Component<ImagePlaceholder>();
row.RelativeItem(2).AspectRatio(4 / 3f).Component<ImagePlaceholder>();
row.RelativeColumn().PaddingLeft(5).Stack(stack =>
row.RelativeItem().PaddingLeft(5).Column(column =>
{
stack.Spacing(7f);
column.Spacing(7f);
stack.Item().AspectRatio(4 / 3f).Component<ImagePlaceholder>();
stack.Item().AspectRatio(4 / 3f).Component<ImagePlaceholder>();
column.Item().AspectRatio(4 / 3f).Component<ImagePlaceholder>();
column.Item().AspectRatio(4 / 3f).Component<ImagePlaceholder>();
});
});
}
@@ -21,25 +21,26 @@ namespace QuestPDF.ReportSample.Layouts
.Decoration(decoration =>
{
decoration
.Header()
.Before()
.PaddingBottom(5)
.Text(Model.Title, Typography.Headline);
.Text(Model.Title)
.Style(Typography.Headline);
decoration.Content().Border(0.75f).BorderColor(Colors.Grey.Medium).Stack(stack =>
decoration.Content().Border(0.75f).BorderColor(Colors.Grey.Medium).Column(column =>
{
foreach (var part in Model.Parts)
{
stack.Item().EnsureSpace(25).Row(row =>
column.Item().EnsureSpace(25).Row(row =>
{
row.ConstantColumn(150).LabelCell().Text(part.Label);
var frame = row.RelativeColumn().ValueCell();
row.ConstantItem(150).LabelCell().Text(part.Label);
var frame = row.RelativeItem().ValueCell();
if (part is ReportSectionText text)
frame.ShowEntire().Text(text.Text);
if (part is ReportSectionMap map)
frame.Element(x => MapElement(x, map));
if (part is ReportSectionPhotos photos)
frame.Element(x => PhotosElement(x, photos));
});
@@ -56,12 +57,12 @@ namespace QuestPDF.ReportSample.Layouts
return;
}
container.ShowEntire().Stack(stack =>
container.ShowEntire().Column(column =>
{
stack.Spacing(5);
column.Spacing(5);
stack.Item().MaxWidth(250).AspectRatio(4 / 3f).Component<ImagePlaceholder>();
stack.Item().Text(model.Location.Format());
column.Item().MaxWidth(250).AspectRatio(4 / 3f).Component<ImagePlaceholder>();
column.Item().Text(model.Location.Format());
});
}
@@ -69,7 +70,7 @@ namespace QuestPDF.ReportSample.Layouts
{
if (model.PhotoCount == 0)
{
container.Text("No photos", Typography.Normal);
container.Text("No photos").Style(Typography.Normal);
return;
}
+19 -19
View File
@@ -39,28 +39,28 @@ namespace QuestPDF.ReportSample.Layouts
page.Footer().AlignCenter().Text(text =>
{
text.CurrentPageNumber();
text.CurrentPageNumber().Format(x => x?.FormatAsRomanNumeral() ?? "-----");
text.Span(" / ");
text.TotalPages();
text.TotalPages().Format(x => x?.FormatAsRomanNumeral() ?? "-----");
});
});
}
private void ComposeHeader(IContainer container)
{
container.Stack(stack =>
container.Column(column =>
{
stack.Item().Row(row =>
column.Item().Row(row =>
{
row.Spacing(50);
row.RelativeColumn().PaddingTop(-10).Text(Model.Title, Typography.Title);
row.ConstantColumn(90).ExternalLink("https://www.questpdf.com").MaxHeight(30).Component<ImagePlaceholder>();
row.RelativeItem().PaddingTop(-10).Text(Model.Title).Style(Typography.Title);
row.ConstantItem(90).ExternalLink("https://www.questpdf.com").MaxHeight(30).Component<ImagePlaceholder>();
});
stack.Item().ShowOnce().PaddingVertical(15).Border(1f).BorderColor(Colors.Grey.Lighten1).ExtendHorizontal();
column.Item().ShowOnce().PaddingVertical(15).Border(1f).BorderColor(Colors.Grey.Lighten1).ExtendHorizontal();
stack.Item().ShowOnce().Grid(grid =>
column.Item().ShowOnce().Grid(grid =>
{
grid.Columns(2);
grid.Spacing(5);
@@ -69,7 +69,7 @@ namespace QuestPDF.ReportSample.Layouts
{
grid.Item().Text(text =>
{
text.Span($"{field.Label}: ", TextStyle.Default.SemiBold());
text.Span($"{field.Label}: ").SemiBold();
text.Span(field.Value);
});
}
@@ -79,22 +79,22 @@ namespace QuestPDF.ReportSample.Layouts
void ComposeContent(IContainer container)
{
container.PaddingVertical(20).Stack(stack =>
container.PaddingVertical(20).Column(column =>
{
stack.Spacing(20);
stack.Item().Component(new TableOfContentsTemplate(Model.Sections));
column.Spacing(20);
stack.Item().PageBreak();
column.Item().Component(new TableOfContentsTemplate(Model.Sections));
column.Item().PageBreak();
foreach (var section in Model.Sections)
stack.Item().Location(section.Title).Component(new SectionTemplate(section));
stack.Item().PageBreak();
stack.Item().Location("Photos");
column.Item().Location(section.Title).Component(new SectionTemplate(section));
column.Item().PageBreak();
column.Item().Location("Photos");
foreach (var photo in Model.Photos)
stack.Item().Component(new PhotoTemplate(photo));
column.Item().Component(new PhotoTemplate(photo));
});
}
}
@@ -1,5 +1,7 @@
using System.Collections.Generic;
using System.Drawing;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.ReportSample.Layouts
@@ -19,18 +21,19 @@ namespace QuestPDF.ReportSample.Layouts
.Decoration(decoration =>
{
decoration
.Header()
.Before()
.PaddingBottom(5)
.Text("Table of contents", Typography.Headline);
.Text("Table of contents")
.Style(Typography.Headline);
decoration.Content().Stack(stack =>
decoration.Content().Column(column =>
{
stack.Spacing(5);
column.Spacing(5);
for (var i = 0; i < Sections.Count; i++)
stack.Item().Element(c => DrawLink(c, i+1, Sections[i].Title));
column.Item().Element(c => DrawLink(c, i+1, Sections[i].Title));
stack.Item().Element(c => DrawLink(c, Sections.Count+1, "Photos"));
column.Item().Element(c => DrawLink(c, Sections.Count+1, "Photos"));
});
});
}
@@ -41,9 +44,20 @@ namespace QuestPDF.ReportSample.Layouts
.InternalLink(locationName)
.Row(row =>
{
row.ConstantColumn(25).Text($"{number}.");
row.RelativeColumn().Text(locationName);
row.ConstantColumn(150).AlignRight().Text(text => text.PageNumberOfLocation(locationName));
row.ConstantItem(25).Text($"{number}.");
row.RelativeItem().Text(locationName);
row.ConstantItem(150).AlignRight().Text(text =>
{
text.BeginPageNumberOfSection(locationName);
text.Span(" - ");
text.EndPageNumberOfSection(locationName);
var lengthStyle = TextStyle.Default.Color(Colors.Grey.Medium);
text.Span(" (").Style(lengthStyle);
text.TotalPagesWithinSection(locationName).Style(lengthStyle).Format(x => x == 1 ? "1 page long" : $"{x} pages long");
text.Span(")").Style(lengthStyle);
});
});
}
}
+1 -1
View File
@@ -24,7 +24,7 @@ namespace QuestPDF.ReportSample
[Test]
public void GenerateAndShowPdf()
{
ImagePlaceholder.Solid = true;
//ImagePlaceholder.Solid = true;
var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"test_result.pdf");
Report.GeneratePdf(path);
@@ -1,4 +1,5 @@
using NUnit.Framework;
using System.Linq;
using NUnit.Framework;
using QuestPDF.Drawing;
using QuestPDF.Elements;
using QuestPDF.Fluent;
@@ -8,19 +9,40 @@ using QuestPDF.UnitTests.TestEngine;
namespace QuestPDF.UnitTests
{
[TestFixture]
public class StackTests
public class ColumnTests
{
private Column CreateColumnWithTwoItems(TestPlan testPlan)
{
return new Column
{
Items =
{
new ColumnItem
{
Child = testPlan.CreateChild("first")
},
new ColumnItem
{
Child = testPlan.CreateChild("second")
}
}
};
}
private Column CreateColumnWithTwoItemsWhereFirstIsFullyRendered(TestPlan testPlan)
{
var column = CreateColumnWithTwoItems(testPlan);
column.Items.First().IsRendered = true;
return column;
}
#region Measure
[Test]
public void Measure_ReturnsWrap_WhenFirstChildWraps()
{
TestPlan
.For(x => new BinaryStack
{
First = x.CreateChild("first"),
Second = x.CreateChild("second")
})
.For(CreateColumnWithTwoItems)
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("first", new Size(400, 300), SpacePlan.Wrap())
.CheckMeasureResult(SpacePlan.Wrap());
@@ -30,11 +52,7 @@ namespace QuestPDF.UnitTests
public void Measure_ReturnsPartialRender_WhenFirstChildReturnsPartialRender()
{
TestPlan
.For(x => new BinaryStack
{
First = x.CreateChild("first"),
Second = x.CreateChild("second")
})
.For(CreateColumnWithTwoItems)
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("first", new Size(400, 300), SpacePlan.PartialRender(300, 200))
.CheckMeasureResult(SpacePlan.PartialRender(300, 200));
@@ -44,11 +62,7 @@ namespace QuestPDF.UnitTests
public void Measure_ReturnsPartialRender_WhenSecondChildWraps()
{
TestPlan
.For(x => new BinaryStack
{
First = x.CreateChild("first"),
Second = x.CreateChild("second")
})
.For(CreateColumnWithTwoItems)
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("first", new Size(400, 300), SpacePlan.FullRender(200, 100))
.ExpectChildMeasure("second", new Size(400, 200), SpacePlan.Wrap())
@@ -59,11 +73,7 @@ namespace QuestPDF.UnitTests
public void Measure_ReturnsPartialRender_WhenSecondChildReturnsPartialRender()
{
TestPlan
.For(x => new BinaryStack
{
First = x.CreateChild("first"),
Second = x.CreateChild("second")
})
.For(CreateColumnWithTwoItems)
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("first", new Size(400, 300), SpacePlan.FullRender(200, 100))
.ExpectChildMeasure("second", new Size(400, 200), SpacePlan.PartialRender(300, 150))
@@ -74,33 +84,13 @@ namespace QuestPDF.UnitTests
public void Measure_ReturnsFullRender_WhenSecondChildReturnsFullRender()
{
TestPlan
.For(x => new BinaryStack
{
First = x.CreateChild("first"),
Second = x.CreateChild("second")
})
.For(CreateColumnWithTwoItems)
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("first", new Size(400, 300), SpacePlan.FullRender(200, 100))
.ExpectChildMeasure("second", new Size(400, 200), SpacePlan.FullRender(100, 50))
.CheckMeasureResult(SpacePlan.FullRender(200, 150));
}
[Test]
public void Measure_UsesEmpty_WhenFirstChildIsRendered()
{
TestPlan
.For(x => new BinaryStack
{
First = x.CreateChild("first"),
Second = x.CreateChild("second"),
IsFirstRendered = true
})
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("second", new Size(400, 300), SpacePlan.FullRender(200, 300))
.CheckMeasureResult(SpacePlan.FullRender(200, 300));
}
#endregion
#region Draw
@@ -109,11 +99,7 @@ namespace QuestPDF.UnitTests
public void Draw_WhenFirstChildWraps()
{
TestPlan
.For(x => new BinaryStack
{
First = x.CreateChild("first"),
Second = x.CreateChild("second")
})
.For(CreateColumnWithTwoItems)
.DrawElement(new Size(400, 300))
.ExpectChildMeasure("first", new Size(400, 300), SpacePlan.Wrap())
.CheckDrawResult();
@@ -123,14 +109,12 @@ namespace QuestPDF.UnitTests
public void Draw_WhenFirstChildPartiallyRenders()
{
TestPlan
.For(x => new BinaryStack
{
First = x.CreateChild("first"),
Second = x.CreateChild("second")
})
.For(CreateColumnWithTwoItems)
.DrawElement(new Size(400, 300))
.ExpectChildMeasure("first", new Size(400, 300), SpacePlan.PartialRender(200, 100))
.ExpectCanvasTranslate(0, 0)
.ExpectChildDraw("first", new Size(400, 100))
.ExpectCanvasTranslate(0, 0)
.CheckDrawResult();
}
@@ -138,15 +122,13 @@ namespace QuestPDF.UnitTests
public void Draw_WhenFirstChildFullyRenders_AndSecondChildWraps()
{
TestPlan
.For(x => new BinaryStack
{
First = x.CreateChild("first"),
Second = x.CreateChild("second")
})
.For(CreateColumnWithTwoItems)
.DrawElement(new Size(400, 300))
.ExpectChildMeasure("first", new Size(400, 300), SpacePlan.FullRender(200, 100))
.ExpectChildDraw("first", new Size(400, 100))
.ExpectChildMeasure("second", new Size(400, 200), SpacePlan.Wrap())
.ExpectCanvasTranslate(0, 0)
.ExpectChildDraw("first", new Size(400, 100))
.ExpectCanvasTranslate(0, 0)
.CheckDrawResult();
}
@@ -154,15 +136,13 @@ namespace QuestPDF.UnitTests
public void Draw_WhenFirstChildFullyRenders_AndSecondChildPartiallyRenders()
{
TestPlan
.For(x => new BinaryStack
{
First = x.CreateChild("first"),
Second = x.CreateChild("second")
})
.For(CreateColumnWithTwoItems)
.DrawElement(new Size(400, 300))
.ExpectChildMeasure("first", new Size(400, 300), SpacePlan.FullRender(200, 100))
.ExpectChildDraw("first", new Size(400, 100))
.ExpectChildMeasure("second", new Size(400, 200), SpacePlan.PartialRender(250, 150))
.ExpectCanvasTranslate(0, 0)
.ExpectChildDraw("first", new Size(400, 100))
.ExpectCanvasTranslate(0, 0)
.ExpectCanvasTranslate(0, 100)
.ExpectChildDraw("second", new Size(400, 150))
.ExpectCanvasTranslate(0, -100)
@@ -173,15 +153,13 @@ namespace QuestPDF.UnitTests
public void Draw_WhenFirstChildFullyRenders_AndSecondChildFullyRenders()
{
TestPlan
.For(x => new BinaryStack
{
First = x.CreateChild("first"),
Second = x.CreateChild("second")
})
.For(CreateColumnWithTwoItems)
.DrawElement(new Size(400, 300))
.ExpectChildMeasure("first", new Size(400, 300), SpacePlan.FullRender(200, 100))
.ExpectChildDraw("first", new Size(400, 100))
.ExpectChildMeasure("second", new Size(400, 200), SpacePlan.FullRender(250, 150))
.ExpectCanvasTranslate(0, 0)
.ExpectChildDraw("first", new Size(400, 100))
.ExpectCanvasTranslate(0, 0)
.ExpectCanvasTranslate(0, 100)
.ExpectChildDraw("second", new Size(400, 150))
.ExpectCanvasTranslate(0, -100)
@@ -192,19 +170,13 @@ namespace QuestPDF.UnitTests
public void Draw_UsesEmpty_WhenFirstChildIsRendered()
{
TestPlan
.For(x => new BinaryStack
{
First = x.CreateChild("first"),
Second = x.CreateChild("second"),
IsFirstRendered = true
})
.For(CreateColumnWithTwoItemsWhereFirstIsFullyRendered)
.DrawElement(new Size(400, 300))
.ExpectChildMeasure("second", new Size(400, 300), SpacePlan.PartialRender(200, 300))
.ExpectCanvasTranslate(0, 0)
.ExpectChildDraw("second", new Size(400, 300))
.ExpectCanvasTranslate(0, 0)
.CheckState<BinaryStack>(x => x.IsFirstRendered)
.CheckState<Column>(x => x.Items.First().IsRendered)
.CheckDrawResult();
}
@@ -212,97 +184,14 @@ namespace QuestPDF.UnitTests
public void Draw_TogglesFirstRenderedFlag_WhenSecondFullyRenders()
{
TestPlan
.For(x => new BinaryStack
{
First = x.CreateChild("first"),
Second = x.CreateChild("second"),
IsFirstRendered = true
})
.For(CreateColumnWithTwoItemsWhereFirstIsFullyRendered)
.DrawElement(new Size(400, 300))
.ExpectChildMeasure("second", new Size(400, 300), SpacePlan.FullRender(200, 300))
.ExpectCanvasTranslate(0, 0)
.ExpectChildDraw("second", new Size(400, 300))
.ExpectCanvasTranslate(0, 0)
.CheckDrawResult()
.CheckState<BinaryStack>(x => !x.IsFirstRendered);
}
#endregion
#region Structure
[Test]
public void Structure_Simple()
{
// arrange
var childA = TestPlan.CreateUniqueElement();
var childB = TestPlan.CreateUniqueElement();
var childC = TestPlan.CreateUniqueElement();
var childD = TestPlan.CreateUniqueElement();
var childE = TestPlan.CreateUniqueElement();
const int spacing = 20;
// act
var structure = new Container();
structure.Stack(stack =>
{
stack.Spacing(spacing);
stack.Item().Element(childA);
stack.Item().Element(childB);
stack.Item().Element(childC);
stack.Item().Element(childD);
stack.Item().Element(childE);
});
// assert
var expected = new Padding
{
Bottom = -spacing,
Child = new BinaryStack
{
First = new BinaryStack
{
First = new Padding
{
Bottom = spacing,
Child = childA
},
Second = new Padding
{
Bottom = spacing,
Child = childB
}
},
Second = new BinaryStack
{
First = new Padding
{
Bottom = spacing,
Child = childC
},
Second = new BinaryStack
{
First = new Padding
{
Bottom = spacing,
Child = childD
},
Second = new Padding
{
Bottom = spacing,
Child = childE
}
}
}
}
};
TestPlan.CompareOperations(structure, expected);
.CheckState<Column>(x => !x.Items.First().IsRendered);
}
#endregion
+83 -80
View File
@@ -9,35 +9,27 @@ namespace QuestPDF.UnitTests
[TestFixture]
public class DecorationTests
{
private Decoration CreateDecoration(TestPlan testPlan)
{
return new Decoration
{
Before = testPlan.CreateChild("before"),
Content = testPlan.CreateChild("content"),
After = testPlan.CreateChild("after"),
};
}
#region Measure
[Test]
public void Measure_ReturnsWrap_WhenDecorationReturnsWrap()
public void Measure_ReturnsWrap_WhenBeforeReturnsWrap()
{
TestPlan
.For(x => new BinaryDecoration
{
Type = DecorationType.Append,
DecorationElement = x.CreateChild("decoration"),
ContentElement = x.CreateChild("content")
})
.For(CreateDecoration)
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("decoration", new Size(400, 300), SpacePlan.Wrap())
.CheckMeasureResult(SpacePlan.Wrap());
}
[Test]
public void Measure_ReturnsWrap_WhenDecorationReturnsPartialRender()
{
TestPlan
.For(x => new BinaryDecoration
{
Type = DecorationType.Append,
DecorationElement = x.CreateChild("decoration"),
ContentElement = x.CreateChild("content")
})
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("decoration", new Size(400, 300), SpacePlan.PartialRender(300, 200))
.ExpectChildMeasure("before", new Size(400, 300), SpacePlan.Wrap())
.ExpectChildMeasure("after", new Size(400, 300), SpacePlan.FullRender(100, 50))
.ExpectChildMeasure("content", new Size(400, 250), SpacePlan.FullRender(100, 100))
.CheckMeasureResult(SpacePlan.Wrap());
}
@@ -45,89 +37,100 @@ namespace QuestPDF.UnitTests
public void Measure_ReturnsWrap_WhenContentReturnsWrap()
{
TestPlan
.For(x => new BinaryDecoration
{
Type = DecorationType.Append,
DecorationElement = x.CreateChild("decoration"),
ContentElement = x.CreateChild("content")
})
.For(CreateDecoration)
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("decoration", new Size(400, 300), SpacePlan.FullRender(300, 100))
.ExpectChildMeasure("before", new Size(400, 300), SpacePlan.FullRender(100, 50))
.ExpectChildMeasure("after", new Size(400, 300), SpacePlan.FullRender(100, 50))
.ExpectChildMeasure("content", new Size(400, 200), SpacePlan.Wrap())
.CheckMeasureResult(SpacePlan.Wrap());
}
[Test]
public void Measure_ReturnsPartialRender_WhenContentReturnsPartialRender()
public void Measure_ReturnsWrap_WhenAfterReturnsWrap()
{
TestPlan
.For(x => new BinaryDecoration
{
Type = DecorationType.Append,
DecorationElement = x.CreateChild("decoration"),
ContentElement = x.CreateChild("content")
})
.For(CreateDecoration)
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("decoration", new Size(400, 300), SpacePlan.FullRender(300, 100))
.ExpectChildMeasure("content", new Size(400, 200), SpacePlan.PartialRender(200, 150))
.CheckMeasureResult(SpacePlan.PartialRender(400, 250));
.ExpectChildMeasure("before", new Size(400, 300), SpacePlan.FullRender(100, 50))
.ExpectChildMeasure("after", new Size(400, 300), SpacePlan.Wrap())
.ExpectChildMeasure("content", new Size(400, 250), SpacePlan.FullRender(100, 100))
.CheckMeasureResult(SpacePlan.Wrap());
}
[Test]
public void Measure_ReturnsFullRender_WhenContentReturnsFullRender()
public void Measure_ReturnsWrap_WhenBeforeReturnsPartialRender()
{
TestPlan
.For(x => new BinaryDecoration
{
Type = DecorationType.Append,
DecorationElement = x.CreateChild("decoration"),
ContentElement = x.CreateChild("content")
})
.For(CreateDecoration)
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("decoration", new Size(400, 300), SpacePlan.FullRender(300, 100))
.ExpectChildMeasure("content", new Size(400, 200), SpacePlan.FullRender(200, 150))
.CheckMeasureResult(SpacePlan.FullRender(400, 250));
.ExpectChildMeasure("before", new Size(400, 300), SpacePlan.PartialRender(100, 50))
.ExpectChildMeasure("after", new Size(400, 300), SpacePlan.FullRender(100, 50))
.ExpectChildMeasure("content", new Size(400, 250), SpacePlan.FullRender(100, 100))
.CheckMeasureResult(SpacePlan.Wrap());
}
[Test]
public void Measure_ReturnsWrap_WhenAfterReturnsPartialRender()
{
TestPlan
.For(CreateDecoration)
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("before", new Size(400, 300), SpacePlan.FullRender(100, 50))
.ExpectChildMeasure("after", new Size(400, 300), SpacePlan.PartialRender(100, 50))
.ExpectChildMeasure("content", new Size(400, 250), SpacePlan.FullRender(100, 100))
.CheckMeasureResult(SpacePlan.Wrap());
}
[Test]
public void Measure_ReturnsWrap_WhenContentReturnsPartialRender()
{
TestPlan
.For(CreateDecoration)
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("before", new Size(400, 300), SpacePlan.FullRender(100, 50))
.ExpectChildMeasure("after", new Size(400, 300), SpacePlan.FullRender(100, 50))
.ExpectChildMeasure("content", new Size(400, 200), SpacePlan.PartialRender(150, 100))
.CheckMeasureResult(SpacePlan.PartialRender(150, 200));
}
[Test]
public void Measure_ReturnsWrap_WhenContentReturnsFullRender()
{
TestPlan
.For(CreateDecoration)
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("before", new Size(400, 300), SpacePlan.FullRender(100, 50))
.ExpectChildMeasure("after", new Size(400, 300), SpacePlan.FullRender(100, 50))
.ExpectChildMeasure("content", new Size(400, 200), SpacePlan.FullRender(150, 100))
.CheckMeasureResult(SpacePlan.FullRender(150, 200));
}
#endregion
#region Draw
[Test]
public void Draw_Prepend()
{
TestPlan
.For(x => new BinaryDecoration
{
Type = DecorationType.Prepend,
DecorationElement = x.CreateChild("decoration"),
ContentElement = x.CreateChild("content")
})
.DrawElement(new Size(400, 300))
.ExpectChildMeasure("decoration", new Size(400, 300), SpacePlan.FullRender(300, 100))
.ExpectChildDraw("decoration", new Size(400, 100))
.ExpectCanvasTranslate(0, 100)
.ExpectChildDraw("content", new Size(400, 200))
.ExpectCanvasTranslate(0, -100)
.CheckDrawResult();
}
[Test]
public void Draw_Append()
{
TestPlan
.For(x => new BinaryDecoration
{
Type = DecorationType.Append,
DecorationElement = x.CreateChild("decoration"),
ContentElement = x.CreateChild("content")
})
.For(CreateDecoration)
.DrawElement(new Size(400, 300))
.ExpectChildMeasure("decoration", new Size(400, 300), SpacePlan.FullRender(300, 100))
.ExpectChildDraw("content", new Size(400, 200))
.ExpectCanvasTranslate(0, 200)
.ExpectChildDraw("decoration", new Size(400, 100))
.ExpectCanvasTranslate(0, -200)
.ExpectChildMeasure("before", new Size(400, 300), SpacePlan.FullRender(200, 40))
.ExpectChildMeasure("after", new Size(400, 300), SpacePlan.FullRender(200, 60))
.ExpectChildMeasure("content", new Size(400, 200), SpacePlan.FullRender(300, 100))
.ExpectCanvasTranslate(0, 0)
.ExpectChildDraw("before", new Size(300, 40))
.ExpectCanvasTranslate(0, 0)
.ExpectCanvasTranslate(0, 40)
.ExpectChildDraw("content", new Size(300, 100))
.ExpectCanvasTranslate(0, -40)
.ExpectCanvasTranslate(0, 140)
.ExpectChildDraw("after", new Size(300, 60))
.ExpectCanvasTranslate(0, -140)
.CheckDrawResult();
}
+1 -1
View File
@@ -8,7 +8,7 @@ namespace QuestPDF.UnitTests
public class ExternalLinkTests
{
[Test]
public void Measure() => SimpleContainerTests.Measure<ExternalLink>();
public void Measure() => SimpleContainerTests.Measure<Hyperlink>();
// TODO: consider tests for the Draw method
}
+146
View File
@@ -0,0 +1,146 @@
using FluentAssertions;
using NUnit.Framework;
using QuestPDF.Drawing;
using SkiaSharp;
using static SkiaSharp.SKFontStyleSlant;
namespace QuestPDF.UnitTests
{
[TestFixture]
public class FontStyleSetTests
{
private void ExpectComparisonOrder(SKFontStyle target, SKFontStyle[] styles)
{
for (var i = 0; i < styles.Length - 1; i++)
{
var currentStyle = styles[i];
var nextStyle = styles[i + 1];
FontStyleSet.IsBetterMatch(target, currentStyle, nextStyle).Should().BeTrue();
FontStyleSet.IsBetterMatch(target, nextStyle, currentStyle).Should().BeFalse();
}
}
[Test]
public void FontStyleSet_IsBetterMatch_CondensedWidth()
{
var styles = new[]
{
new SKFontStyle(500, 5, Upright),
new SKFontStyle(500, 4, Upright),
new SKFontStyle(500, 3, Upright),
new SKFontStyle(500, 6, Upright)
};
ExpectComparisonOrder(new SKFontStyle(500, 5, Upright), styles);
}
[Test]
public void FontStyleSet_IsBetterMatch_ExpandedWidth()
{
var styles = new[]
{
new SKFontStyle(500, 6, Upright),
new SKFontStyle(500, 7, Upright),
new SKFontStyle(500, 8, Upright),
new SKFontStyle(500, 5, Upright)
};
ExpectComparisonOrder(new SKFontStyle(500, 6, Upright), styles);
}
[Test]
public void FontStyleSet_IsBetterMatch_ItalicSlant()
{
var styles = new[]
{
new SKFontStyle(500, 5, Italic),
new SKFontStyle(500, 5, Oblique),
new SKFontStyle(500, 5, Upright)
};
ExpectComparisonOrder(new SKFontStyle(500, 5, Italic), styles);
}
[Test]
public void FontStyleSet_IsBetterMatch_ObliqueSlant()
{
var styles = new[]
{
new SKFontStyle(500, 5, Oblique),
new SKFontStyle(500, 5, Italic),
new SKFontStyle(500, 5, Upright)
};
ExpectComparisonOrder(new SKFontStyle(500, 5, Oblique), styles);
}
[Test]
public void FontStyleSet_IsBetterMatch_UprightSlant()
{
var styles = new[]
{
new SKFontStyle(500, 5, Upright),
new SKFontStyle(500, 5, Oblique),
new SKFontStyle(500, 5, Italic)
};
ExpectComparisonOrder(new SKFontStyle(500, 5, Upright), styles);
}
[Test]
public void FontStyleSet_IsBetterMatch_ThinWeight()
{
var styles = new[]
{
new SKFontStyle(300, 5, Upright),
new SKFontStyle(200, 5, Upright),
new SKFontStyle(100, 5, Upright),
new SKFontStyle(400, 5, Upright)
};
ExpectComparisonOrder(new SKFontStyle(300, 5, Upright), styles);
}
[Test]
public void FontStyleSet_IsBetterMatch_RegularWeight()
{
var styles = new[]
{
new SKFontStyle(500, 5, Upright),
new SKFontStyle(300, 5, Upright),
new SKFontStyle(100, 5, Upright),
new SKFontStyle(600, 5, Upright)
};
ExpectComparisonOrder(new SKFontStyle(400, 5, Upright), styles);
}
[Test]
public void FontStyleSet_IsBetterMatch_BoldWeight()
{
var styles = new[]
{
new SKFontStyle(600, 5, Upright),
new SKFontStyle(700, 5, Upright),
new SKFontStyle(800, 5, Upright),
new SKFontStyle(500, 5, Upright)
};
ExpectComparisonOrder(new SKFontStyle(600, 5, Upright), styles);
}
[Test]
public void FontStyleSet_RespectsPriority()
{
var styles = new[]
{
new SKFontStyle(600, 5, Italic),
new SKFontStyle(600, 6, Upright),
new SKFontStyle(500, 6, Italic)
};
ExpectComparisonOrder(new SKFontStyle(500, 5, Upright), styles);
}
}
}
+54 -54
View File
@@ -39,26 +39,26 @@ namespace QuestPDF.UnitTests
// assert
var expected = new Container();
expected.Stack(stack =>
expected.Column(column =>
{
stack.Item().Row(row =>
column.Item().Row(row =>
{
row.RelativeColumn(6).Element(childA);
row.RelativeColumn(4).Element(childB);
row.RelativeColumn(2);
row.RelativeItem(6).Element(childA);
row.RelativeItem(4).Element(childB);
row.RelativeItem(2);
});
stack.Item().Row(row =>
column.Item().Row(row =>
{
row.RelativeColumn(4).Element(childC);
row.RelativeColumn(2).Element(childD);
row.RelativeColumn(6);
row.RelativeItem(4).Element(childC);
row.RelativeItem(2).Element(childD);
row.RelativeItem(6);
});
stack.Item().Row(row =>
column.Item().Row(row =>
{
row.RelativeColumn(8).Element(childE);
row.RelativeColumn(4);
row.RelativeItem(8).Element(childE);
row.RelativeItem(4);
});
});
@@ -93,29 +93,29 @@ namespace QuestPDF.UnitTests
// assert
var expected = new Container();
expected.Stack(stack =>
expected.Column(column =>
{
stack.Item().Row(row =>
column.Item().Row(row =>
{
row.RelativeColumn(1);
row.RelativeColumn(6).Element(childA);
row.RelativeColumn(4).Element(childB);
row.RelativeColumn(1);
row.RelativeItem(1);
row.RelativeItem(6).Element(childA);
row.RelativeItem(4).Element(childB);
row.RelativeItem(1);
});
stack.Item().Row(row =>
column.Item().Row(row =>
{
row.RelativeColumn(3);
row.RelativeColumn(4).Element(childC);
row.RelativeColumn(2).Element(childD);
row.RelativeColumn(3);
row.RelativeItem(3);
row.RelativeItem(4).Element(childC);
row.RelativeItem(2).Element(childD);
row.RelativeItem(3);
});
stack.Item().Row(row =>
column.Item().Row(row =>
{
row.RelativeColumn(2);
row.RelativeColumn(8).Element(childE);
row.RelativeColumn(2);
row.RelativeItem(2);
row.RelativeItem(8).Element(childE);
row.RelativeItem(2);
});
});
@@ -150,26 +150,26 @@ namespace QuestPDF.UnitTests
// assert
var expected = new Container();
expected.Stack(stack =>
expected.Column(column =>
{
stack.Item().Row(row =>
column.Item().Row(row =>
{
row.RelativeColumn(2);
row.RelativeColumn(6).Element(childA);
row.RelativeColumn(4).Element(childB);
row.RelativeItem(2);
row.RelativeItem(6).Element(childA);
row.RelativeItem(4).Element(childB);
});
stack.Item().Row(row =>
column.Item().Row(row =>
{
row.RelativeColumn(6);
row.RelativeColumn(4).Element(childC);
row.RelativeColumn(2).Element(childD);
row.RelativeItem(6);
row.RelativeItem(4).Element(childC);
row.RelativeItem(2).Element(childD);
});
stack.Item().Row(row =>
column.Item().Row(row =>
{
row.RelativeColumn(4);
row.RelativeColumn(8).Element(childE);
row.RelativeItem(4);
row.RelativeItem(8).Element(childE);
});
});
@@ -210,36 +210,36 @@ namespace QuestPDF.UnitTests
// assert
var expected = new Container();
expected.Stack(stack =>
expected.Column(column =>
{
stack.Spacing(20);
column.Spacing(20);
stack.Item().Row(row =>
column.Item().Row(row =>
{
row.Spacing(30);
row.RelativeColumn(3);
row.RelativeColumn(5).Element(childA);
row.RelativeColumn(5).Element(childB);
row.RelativeColumn(3);
row.RelativeItem(3);
row.RelativeItem(5).Element(childA);
row.RelativeItem(5).Element(childB);
row.RelativeItem(3);
});
stack.Item().Row(row =>
column.Item().Row(row =>
{
row.Spacing(30);
row.RelativeColumn(3);
row.RelativeColumn(10).Element(childC);
row.RelativeColumn(3);
row.RelativeItem(3);
row.RelativeItem(10).Element(childC);
row.RelativeItem(3);
});
stack.Item().Row(row =>
column.Item().Row(row =>
{
row.Spacing(30);
row.RelativeColumn(2);
row.RelativeColumn(12).Element(childD);
row.RelativeColumn(2);
row.RelativeItem(2);
row.RelativeItem(12).Element(childD);
row.RelativeItem(2);
});
});
+1 -1
View File
@@ -8,7 +8,7 @@ namespace QuestPDF.UnitTests
public class InternalLinkTests
{
[Test]
public void Measure() => SimpleContainerTests.Measure<InternalLink>();
public void Measure() => SimpleContainerTests.Measure<SectionLink>();
// TODO: consider tests for the Draw method
}
+1 -1
View File
@@ -8,7 +8,7 @@ namespace QuestPDF.UnitTests
public class InternalLocationTests
{
[Test]
public void Measure() => SimpleContainerTests.Measure<InternalLink>();
public void Measure() => SimpleContainerTests.Measure<SectionLink>();
// TODO: consider tests for the Draw method
}
-267
View File
@@ -1,267 +0,0 @@
using NUnit.Framework;
using QuestPDF.Drawing;
using QuestPDF.Elements;
using QuestPDF.Fluent;
using QuestPDF.Infrastructure;
using QuestPDF.UnitTests.TestEngine;
namespace QuestPDF.UnitTests
{
[TestFixture]
public class RowTests
{
#region Measure
[Test]
public void Measure_ReturnsWrap_WhenLeftChildReturnsWrap()
{
TestPlan
.For(x => new BinaryRow
{
Left = x.CreateChild("left"),
Right = x.CreateChild("right")
})
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("left", new Size(400, 300), SpacePlan.Wrap())
.CheckMeasureResult(SpacePlan.Wrap());
}
[Test]
public void Measure_ReturnsWrap_WhenRightChildReturnsWrap()
{
TestPlan
.For(x => new BinaryRow
{
Left = x.CreateChild("left"),
Right = x.CreateChild("right")
})
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("left", new Size(400, 300), SpacePlan.FullRender(250, 150))
.ExpectChildMeasure("right", new Size(150, 300), SpacePlan.Wrap())
.CheckMeasureResult(SpacePlan.Wrap());
}
[Test]
public void Measure_ReturnsPartialRender_WhenLeftChildReturnsPartialRender()
{
TestPlan
.For(x => new BinaryRow
{
Left = x.CreateChild("left"),
Right = x.CreateChild("right")
})
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("left", new Size(400, 300), SpacePlan.PartialRender(250, 150))
.ExpectChildMeasure("right", new Size(150, 300), SpacePlan.FullRender(100, 100))
.CheckMeasureResult(SpacePlan.PartialRender(350, 150));
}
[Test]
public void Measure_ReturnsPartialRender_WhenRightChildReturnsPartialRender()
{
TestPlan
.For(x => new BinaryRow
{
Left = x.CreateChild("left"),
Right = x.CreateChild("right")
})
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("left", new Size(400, 300), SpacePlan.FullRender(250, 150))
.ExpectChildMeasure("right", new Size(150, 300), SpacePlan.PartialRender(100, 100))
.CheckMeasureResult(SpacePlan.PartialRender(350, 150));
}
[Test]
public void Measure_ReturnsFullRender_WhenBothChildrenReturnFullRender()
{
TestPlan
.For(x => new BinaryRow
{
Left = x.CreateChild("left"),
Right = x.CreateChild("right")
})
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("left", new Size(400, 300), SpacePlan.FullRender(200, 150))
.ExpectChildMeasure("right", new Size(200, 300), SpacePlan.FullRender(100, 100))
.CheckMeasureResult(SpacePlan.FullRender(300, 150));
}
#endregion
#region Draw
[Test]
public void Draw()
{
TestPlan
.For(x => new BinaryRow
{
Left = x.CreateChild("left"),
Right = x.CreateChild("right")
})
.DrawElement(new Size(400, 300))
.ExpectChildMeasure("left", new Size(400, 300), SpacePlan.FullRender(250, 150))
.ExpectChildDraw("left", new Size(250, 300))
.ExpectCanvasTranslate(250, 0)
.ExpectChildDraw("right", new Size(150, 300))
.ExpectCanvasTranslate(-250, 0)
.CheckDrawResult();
}
#endregion
#region Structure
[Test]
public void Structure_RelativeColumnsHandling()
{
// arrange
var childA = TestPlan.CreateUniqueElement();
var childB = TestPlan.CreateUniqueElement();
var childC = TestPlan.CreateUniqueElement();
var childD = TestPlan.CreateUniqueElement();
var childE = TestPlan.CreateUniqueElement();
const int spacing = 25;
var availableSpace = new Size(1100, 400);
// act
var value = new Container();
value.Row(row =>
{
row.Spacing(spacing);
row.ConstantColumn(150).Element(childA);
row.ConstantColumn(250).Element(childB);
row.RelativeColumn(1).Element(childC);
row.RelativeColumn(2).Element(childD);
row.RelativeColumn(3).Element(childE);
});
// assert
var expected = new Container();
expected.Row(row =>
{
row.Spacing(spacing);
row.ConstantColumn(150).Element(childA);
row.ConstantColumn(250).Element(childB);
row.ConstantColumn(100).Element(childC);
row.ConstantColumn(200).Element(childD);
row.ConstantColumn(300).Element(childE);
});
TestPlan.CompareOperations(value, expected, availableSpace);
}
[Test]
public void Structure_Tree()
{
// arrange
var childA = TestPlan.CreateUniqueElement();
var childB = TestPlan.CreateUniqueElement();
var childC = TestPlan.CreateUniqueElement();
var childD = TestPlan.CreateUniqueElement();
var childE = TestPlan.CreateUniqueElement();
const int spacing = 25;
var availableSpace = new Size(1200, 400);
// act
var value = new Container();
value.Row(row =>
{
row.Spacing(spacing);
row.ConstantColumn(150).Element(childA);
row.ConstantColumn(200).Element(childB);
row.ConstantColumn(250).Element(childC);
row.RelativeColumn(2).Element(childD);
row.RelativeColumn(3).Element(childE);
});
// assert
var expected = new BinaryRow
{
Left = new BinaryRow
{
Left = new BinaryRow
{
Left = new Constrained
{
MinWidth = 150,
MaxWidth = 150,
Child = childA
},
Right = new Constrained
{
MinWidth = 25,
MaxWidth = 25
}
},
Right = new BinaryRow
{
Left = new Constrained
{
MinWidth = 200,
MaxWidth = 200,
Child = childB
},
Right = new Constrained
{
MinWidth = 25,
MaxWidth = 25
}
}
},
Right = new BinaryRow
{
Left = new BinaryRow
{
Left = new Constrained
{
MinWidth = 250,
MaxWidth = 250,
Child = childC
},
Right = new Constrained
{
MinWidth = 25,
MaxWidth = 25
}
},
Right = new BinaryRow
{
Left = new Constrained
{
MinWidth = 200,
MaxWidth = 200,
Child = childD
},
Right = new BinaryRow
{
Left = new Constrained
{
MinWidth = 25,
MaxWidth = 25
},
Right = new Constrained
{
MinWidth = 300,
MaxWidth = 300,
Child = childE
}
}
}
}
};
TestPlan.CompareOperations(value, expected, availableSpace);
}
#endregion
}
}
+4 -4
View File
@@ -20,9 +20,9 @@ namespace QuestPDF.UnitTests.TestEngine
public void DrawRectangle(Position vector, Size size, string color) => DrawRectFunc(vector, size, color);
public void DrawText(string text, Position position, TextStyle style) => DrawTextFunc(text, position, style);
public void DrawImage(SKImage image, Position position, Size size) => DrawImageFunc(image, position, size);
public void DrawExternalLink(string url, Size size) => throw new NotImplementedException();
public void DrawLocationLink(string locationName, Size size) => throw new NotImplementedException();
public void DrawLocation(string locationName) => throw new NotImplementedException();
public void DrawHyperlink(string url, Size size) => throw new NotImplementedException();
public void DrawSectionLink(string sectionName, Size size) => throw new NotImplementedException();
public void DrawSection(string sectionName) => throw new NotImplementedException();
}
}
@@ -18,8 +18,8 @@ namespace QuestPDF.UnitTests.TestEngine
public void DrawText(string text, Position position, TextStyle style) => Operations.Add(new CanvasDrawTextOperation(text, position, style));
public void DrawImage(SKImage image, Position position, Size size) => Operations.Add(new CanvasDrawImageOperation(position, size));
public void DrawExternalLink(string url, Size size) => throw new NotImplementedException();
public void DrawLocationLink(string locationName, Size size) => throw new NotImplementedException();
public void DrawLocation(string locationName) => throw new NotImplementedException();
public void DrawHyperlink(string url, Size size) => throw new NotImplementedException();
public void DrawSectionLink(string sectionName, Size size) => throw new NotImplementedException();
public void DrawSection(string sectionName) => throw new NotImplementedException();
}
}
+1 -1
View File
@@ -92,7 +92,7 @@ namespace QuestPDF.UnitTests.TestEngine
Assert.AreEqual(expected.Position.Y, position.Y, "Draw text: Y");
Assert.AreEqual(expected.Style.Color, style.Color, "Draw text: color");
Assert.AreEqual(expected.Style.FontType, style.FontType, "Draw text: font");
Assert.AreEqual(expected.Style.FontFamily, style.FontFamily, "Draw text: font");
Assert.AreEqual(expected.Style.Size, style.Size, "Draw text: size");
},
DrawImageFunc = (image, position, size) =>
+3 -3
View File
@@ -16,13 +16,13 @@ namespace QuestPDF.Drawing
var container = new Container();
container
.Stack(stack =>
.Column(column =>
{
Pages
.SelectMany(x => new List<Action>()
{
() => stack.Item().PageBreak(),
() => stack.Item().Component(x)
() => column.Item().PageBreak(),
() => column.Item().Component(x)
})
.Skip(1)
.ToList()
+4 -2
View File
@@ -18,14 +18,16 @@ namespace QuestPDF.Drawing
internal static void GeneratePdf(Stream stream, IDocument document)
{
var metadata = document.GetMetadata();
var canvas = new PdfCanvas(stream, metadata);
var writeOnlyStream = new WriteOnlyStream(stream);
var canvas = new PdfCanvas(writeOnlyStream, metadata);
RenderDocument(canvas, document);
}
internal static void GenerateXps(Stream stream, IDocument document)
{
var metadata = document.GetMetadata();
var canvas = new XpsCanvas(stream, metadata);
var writeOnlyStream = new WriteOnlyStream(stream);
var canvas = new XpsCanvas(writeOnlyStream, metadata);
RenderDocument(canvas, document);
}
+53 -17
View File
@@ -1,6 +1,7 @@
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Linq;
using QuestPDF.Infrastructure;
using SkiaSharp;
@@ -8,16 +9,41 @@ namespace QuestPDF.Drawing
{
public static class FontManager
{
private static ConcurrentDictionary<string, SKTypeface> Typefaces = new ConcurrentDictionary<string, SKTypeface>();
private static ConcurrentDictionary<string, SKFontMetrics> FontMetrics = new ConcurrentDictionary<string, SKFontMetrics>();
private static ConcurrentDictionary<string, SKPaint> Paints = new ConcurrentDictionary<string, SKPaint>();
private static ConcurrentDictionary<string, SKPaint> ColorPaint = new ConcurrentDictionary<string, SKPaint>();
private static ConcurrentDictionary<string, FontStyleSet> StyleSets = new();
private static ConcurrentDictionary<object, SKFontMetrics> FontMetrics = new();
private static ConcurrentDictionary<object, SKPaint> Paints = new();
private static ConcurrentDictionary<string, SKPaint> ColorPaint = new();
private static void RegisterFontType(SKData fontData, string? customName = null)
{
foreach (var index in Enumerable.Range(0, 256))
{
var typeface = SKTypeface.FromData(fontData, index);
if (typeface == null)
break;
var typefaceName = customName ?? typeface.FamilyName;
var fontStyleSet = StyleSets.GetOrAdd(typefaceName, _ => new FontStyleSet());
fontStyleSet.Add(typeface);
}
}
[Obsolete("Since version 2022.3, the FontManager class offers better font type matching support. Please use the RegisterFont(Stream stream) method.")]
public static void RegisterFontType(string fontName, Stream stream)
{
Typefaces.TryAdd(fontName, SKTypeface.FromStream(stream));
using var fontData = SKData.Create(stream);
RegisterFontType(fontData);
RegisterFontType(fontData, customName: fontName);
}
public static void RegisterFont(Stream stream)
{
using var fontData = SKData.Create(stream);
RegisterFontType(fontData);
}
internal static SKPaint ColorToPaint(this string color)
{
return ColorPaint.GetOrAdd(color, Convert);
@@ -30,37 +56,47 @@ namespace QuestPDF.Drawing
};
}
}
internal static SKPaint ToPaint(this TextStyle style)
{
return Paints.GetOrAdd(style.Key, key => Convert(style));
return Paints.GetOrAdd(style.PaintKey, key => Convert(style));
static SKPaint Convert(TextStyle style)
{
return new SKPaint
{
Color = SKColor.Parse(style.Color),
Typeface = GetTypeface(style),
TextSize = (style.Size ?? 12),
TextEncoding = SKTextEncoding.Utf32
TextSize = style.Size ?? 12
};
}
static SKTypeface GetTypeface(TextStyle style)
{
if (Typefaces.TryGetValue(style.FontType, out var result))
return result;
var weight = (SKFontStyleWeight)(style.FontWeight ?? FontWeight.Normal);
var slant = (style.IsItalic ?? false) ? SKFontStyleSlant.Italic : SKFontStyleSlant.Upright;
var fontStyle = new SKFontStyle(weight, SKFontStyleWidth.Normal, slant);
if (StyleSets.TryGetValue(style.FontFamily, out var fontStyleSet))
return fontStyleSet.Match(fontStyle);
var fontFromDefaultSource = SKFontManager.Default.MatchFamily(style.FontFamily, fontStyle);
return SKTypeface.FromFamilyName(style.FontType, (int)(style.FontWeight ?? FontWeight.Normal), (int)SKFontStyleWidth.Normal, slant)
?? throw new ArgumentException($"The typeface {style.FontType} could not be found.");
if (fontFromDefaultSource != null)
return fontFromDefaultSource;
throw new ArgumentException(
$"The typeface '{style.FontFamily}' could not be found. " +
$"Please consider the following options: " +
$"1) install the font on your operating system or execution environment. " +
$"2) load a font file specifically for QuestPDF usage via the QuestPDF.Drawing.FontManager.RegisterFontType(Stream fileContentStream) static method.");
}
}
internal static SKFontMetrics ToFontMetrics(this TextStyle style)
{
return FontMetrics.GetOrAdd(style.Key, key => style.ToPaint().FontMetrics);
return FontMetrics.GetOrAdd(style.FontMetricsKey, key => style.ToPaint().FontMetrics);
}
}
}
+132
View File
@@ -0,0 +1,132 @@
using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using SkiaSharp;
namespace QuestPDF.Drawing
{
internal class FontStyleSet
{
private ConcurrentDictionary<SKFontStyle, SKTypeface> Styles { get; } = new();
public void Add(SKTypeface typeface)
{
var style = typeface.FontStyle;
Styles.AddOrUpdate(style, _ => typeface, (_, _) => typeface);
}
public SKTypeface? Match(SKFontStyle target)
{
SKFontStyle? bestStyle = null;
SKTypeface? bestTypeface = null;
foreach (var entry in Styles)
{
if (IsBetterMatch(target, entry.Key, bestStyle))
{
bestStyle = entry.Key;
bestTypeface = entry.Value;
}
}
return bestTypeface;
}
private static Dictionary<SKFontStyleSlant, List<SKFontStyleSlant>> SlantFallbacks = new()
{
{ SKFontStyleSlant.Italic, new() { SKFontStyleSlant.Italic, SKFontStyleSlant.Oblique, SKFontStyleSlant.Upright } },
{ SKFontStyleSlant.Oblique, new() { SKFontStyleSlant.Oblique, SKFontStyleSlant.Italic, SKFontStyleSlant.Upright } },
{ SKFontStyleSlant.Upright, new() { SKFontStyleSlant.Upright, SKFontStyleSlant.Oblique, SKFontStyleSlant.Italic } },
};
// Checks whether style a is a better match for the target then style b. Uses the CSS font style matching algorithm
internal static bool IsBetterMatch(SKFontStyle? target, SKFontStyle? a, SKFontStyle? b)
{
// A font is better than no font
if (b == null)
return true;
if (a == null)
return false;
// First check font width
// For normal and condensed widths prefer smaller widths
// For expanded widths prefer larger widths
if (target.Width <= (int)SKFontStyleWidth.Normal)
{
if (a.Width <= target.Width && b.Width > target.Width)
return true;
if (a.Width > target.Width && b.Width <= target.Width)
return false;
}
else
{
if (a.Width >= target.Width && b.Width < target.Width)
return true;
if (a.Width < target.Width && b.Width >= target.Width)
return false;
}
// Prefer closest match
var widthDifferenceA = Math.Abs(a.Width - target.Width);
var widthDifferenceB = Math.Abs(b.Width - target.Width);
if (widthDifferenceA < widthDifferenceB)
return true;
if (widthDifferenceB < widthDifferenceA)
return false;
// Prefer closest slant based on provided fallback list
var slantFallback = SlantFallbacks[target.Slant];
var slantIndexA = slantFallback.IndexOf(a.Slant);
var slantIndexB = slantFallback.IndexOf(b.Slant);
if (slantIndexA < slantIndexB)
return true;
if (slantIndexB < slantIndexA)
return false;
// Check weight last
// For thin (<400) weights, prefer thinner weights
// For regular (400-500) weights, prefer other regular weights, then use rule for thin or bold
// For bold (>500) weights, prefer thicker weights
// Behavior for values other than multiples of 100 is not given in the specification
if (target.Weight >= 400 && target.Weight <= 500)
{
if ((a.Weight >= 400 && a.Weight <= 500) && !(b.Weight >= 400 && b.Weight <= 500))
return true;
if (!(a.Weight >= 400 && a.Weight <= 500) && (b.Weight >= 400 && b.Weight <= 500))
return false;
}
if (target.Weight < 450)
{
if (a.Weight <= target.Weight && b.Weight > target.Weight)
return true;
if (a.Weight > target.Weight && b.Weight <= target.Weight)
return false;
}
else
{
if (a.Weight >= target.Weight && b.Weight < target.Weight)
return true;
if (a.Weight < target.Weight && b.Weight >= target.Weight)
return false;
}
// Prefer closest weight
var weightDifferenceA = Math.Abs(a.Weight - target.Weight);
var weightDifferenceB = Math.Abs(b.Weight - target.Weight);
return weightDifferenceA < weightDifferenceB;
}
}
}
+3 -3
View File
@@ -51,17 +51,17 @@ namespace QuestPDF.Drawing
}
public void DrawExternalLink(string url, Size size)
public void DrawHyperlink(string url, Size size)
{
}
public void DrawLocationLink(string locationName, Size size)
public void DrawSectionLink(string sectionName, Size size)
{
}
public void DrawLocation(string locationName)
public void DrawSection(string sectionName)
{
}
+1 -1
View File
@@ -1,5 +1,5 @@
using System.IO;
using QuestPDF.Infrastructure;
using QuestPDF.Helpers;
using SkiaSharp;
namespace QuestPDF.Drawing
+5 -5
View File
@@ -37,19 +37,19 @@ namespace QuestPDF.Drawing
Canvas.DrawImage(image, new SKRect(vector.X, vector.Y, size.Width, size.Height));
}
public void DrawExternalLink(string url, Size size)
public void DrawHyperlink(string url, Size size)
{
Canvas.DrawUrlAnnotation(new SKRect(0, 0, size.Width, size.Height), url);
}
public void DrawLocationLink(string locationName, Size size)
public void DrawSectionLink(string sectionName, Size size)
{
Canvas.DrawLinkDestinationAnnotation(new SKRect(0, 0, size.Width, size.Height), locationName);
Canvas.DrawLinkDestinationAnnotation(new SKRect(0, 0, size.Width, size.Height), sectionName);
}
public void DrawLocation(string locationName)
public void DrawSection(string sectionName)
{
Canvas.DrawNamedDestinationAnnotation(new SKPoint(0, 0), locationName);
Canvas.DrawNamedDestinationAnnotation(new SKPoint(0, 0), sectionName);
}
public void Rotate(float angle)
+1 -1
View File
@@ -1,5 +1,5 @@
using System.IO;
using QuestPDF.Infrastructure;
using QuestPDF.Helpers;
using SkiaSharp;
namespace QuestPDF.Drawing
+123
View File
@@ -0,0 +1,123 @@
using System;
using System.Collections.Generic;
using System.Linq;
using QuestPDF.Drawing;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal class ColumnItem : Container
{
public bool IsRendered { get; set; }
}
internal class ColumnItemRenderingCommand
{
public ColumnItem ColumnItem { get; set; }
public SpacePlan Measurement { get; set; }
public Size Size { get; set; }
public Position Offset { get; set; }
}
internal class Column : Element, ICacheable, IStateResettable
{
internal List<ColumnItem> Items { get; } = new();
internal float Spacing { get; set; }
public void ResetState()
{
Items.ForEach(x => x.IsRendered = false);
}
internal override IEnumerable<Element?> GetChildren()
{
return Items;
}
internal override void CreateProxy(Func<Element?, Element?> create)
{
Items.ForEach(x => x.Child = create(x.Child));
}
internal override SpacePlan Measure(Size availableSpace)
{
if (!Items.Any())
return SpacePlan.FullRender(Size.Zero);
var renderingCommands = PlanLayout(availableSpace);
if (!renderingCommands.Any())
return SpacePlan.Wrap();
var width = renderingCommands.Max(x => x.Size.Width);
var height = renderingCommands.Last().Offset.Y + renderingCommands.Last().Size.Height;
var size = new Size(width, height);
if (width > availableSpace.Width + Size.Epsilon || height > availableSpace.Height + Size.Epsilon)
return SpacePlan.Wrap();
var totalRenderedItems = Items.Count(x => x.IsRendered) + renderingCommands.Count(x => x.Measurement.Type == SpacePlanType.FullRender);
var willBeFullyRendered = totalRenderedItems == Items.Count;
return willBeFullyRendered
? SpacePlan.FullRender(size)
: SpacePlan.PartialRender(size);
}
internal override void Draw(Size availableSpace)
{
var renderingCommands = PlanLayout(availableSpace);
foreach (var command in renderingCommands)
{
if (command.Measurement.Type == SpacePlanType.FullRender)
command.ColumnItem.IsRendered = true;
var targetSize = new Size(availableSpace.Width, command.Size.Height);
Canvas.Translate(command.Offset);
command.ColumnItem.Draw(targetSize);
Canvas.Translate(command.Offset.Reverse());
}
if (Items.All(x => x.IsRendered))
ResetState();
}
private ICollection<ColumnItemRenderingCommand> PlanLayout(Size availableSpace)
{
var topOffset = 0f;
var commands = new List<ColumnItemRenderingCommand>();
foreach (var item in Items)
{
if (item.IsRendered)
continue;
var itemSpace = new Size(availableSpace.Width, availableSpace.Height - topOffset);
var measurement = item.Measure(itemSpace);
if (measurement.Type == SpacePlanType.Wrap)
break;
commands.Add(new ColumnItemRenderingCommand
{
ColumnItem = item,
Size = measurement,
Measurement = measurement,
Offset = new Position(0, topOffset)
});
if (measurement.Type == SpacePlanType.PartialRender)
break;
topOffset += measurement.Height + Spacing;
}
var targetWidth = commands.Select(x => x.Size.Width).DefaultIfEmpty(0).Max();
commands.ForEach(x => x.Size = new Size(targetWidth, x.Size.Height));
return commands;
}
}
}
+4 -1
View File
@@ -30,7 +30,10 @@ namespace QuestPDF.Elements
.MinimalBox()
.Background(Colors.White)
.Padding(2)
.Text(Text, TextStyle.Default.Color(Color).FontType(Fonts.Consolas).Size(8));
.Text(Text)
.FontColor(Color)
.FontFamily(Fonts.Consolas)
.FontSize(8);
});
}
}
+78 -64
View File
@@ -1,98 +1,112 @@
using System;
using System.Collections.Generic;
using System.Linq;
using QuestPDF.Drawing;
using QuestPDF.Fluent;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal enum DecorationType
internal class DecorationItemRenderingCommand
{
Prepend,
Append
public Element Element { get; set; }
public SpacePlan Measurement { get; set; }
public Position Offset { get; set; }
}
internal class BinaryDecoration : Element, ICacheable
internal class Decoration : Element, ICacheable
{
public Element DecorationElement { get; set; } = Empty.Instance;
public Element ContentElement { get; set; } = Empty.Instance;
public DecorationType Type { get; set; }
internal Element Before { get; set; } = new Empty();
internal Element Content { get; set; } = new Empty();
internal Element After { get; set; } = new Empty();
internal override IEnumerable<Element?> GetChildren()
{
yield return DecorationElement;
yield return ContentElement;
yield return Before;
yield return Content;
yield return After;
}
internal override void CreateProxy(Func<Element, Element> create)
internal override void CreateProxy(Func<Element?, Element?> create)
{
DecorationElement = create(DecorationElement);
ContentElement = create(ContentElement);
Before = create(Before);
Content = create(Content);
After = create(After);
}
internal override SpacePlan Measure(Size availableSpace)
{
var decorationMeasure = DecorationElement.Measure(availableSpace);
if (decorationMeasure.Type == SpacePlanType.Wrap || decorationMeasure.Type == SpacePlanType.PartialRender)
var renderingCommands = PlanLayout(availableSpace).ToList();
if (renderingCommands.Any(x => x.Measurement.Type == SpacePlanType.Wrap))
return SpacePlan.Wrap();
var decorationSize = decorationMeasure;
var contentMeasure = ContentElement.Measure(new Size(availableSpace.Width, availableSpace.Height - decorationSize.Height));
var width = renderingCommands.Max(x => x.Measurement.Width);
var height = renderingCommands.Sum(x => x.Measurement.Height);
var size = new Size(width, height);
if (contentMeasure.Type == SpacePlanType.Wrap)
if (width > availableSpace.Width + Size.Epsilon || height > availableSpace.Height + Size.Epsilon)
return SpacePlan.Wrap();
var willBeFullyRendered = renderingCommands.All(x => x.Measurement.Type == SpacePlanType.FullRender);
var contentSize = contentMeasure;
var resultSize = new Size(availableSpace.Width, decorationSize.Height + contentSize.Height);
if (contentSize.Type == SpacePlanType.PartialRender)
return SpacePlan.PartialRender(resultSize);
if (contentSize.Type == SpacePlanType.FullRender)
return SpacePlan.FullRender(resultSize);
throw new NotSupportedException();
return willBeFullyRendered
? SpacePlan.FullRender(size)
: SpacePlan.PartialRender(size);
}
internal override void Draw(Size availableSpace)
{
var decorationSize = DecorationElement.Measure(availableSpace);
var contentSize = new Size(availableSpace.Width, availableSpace.Height - decorationSize.Height);
var translateHeight = Type == DecorationType.Prepend ? decorationSize.Height : contentSize.Height;
Action drawDecoration = () => DecorationElement?.Draw(new Size(availableSpace.Width, decorationSize.Height));
Action drawContent = () => ContentElement?.Draw(new Size (availableSpace.Width, contentSize.Height));
var first = Type == DecorationType.Prepend ? drawDecoration : drawContent;
var second = Type == DecorationType.Prepend ? drawContent : drawDecoration;
first();
Canvas.Translate(new Position(0, translateHeight));
second();
Canvas.Translate(new Position(0, -translateHeight));
}
}
internal class Decoration : IComponent
{
public Element Header { get; set; } = Empty.Instance;
public Element Content { get; set; } = Empty.Instance;
public Element Footer { get; set; } = Empty.Instance;
public void Compose(IContainer container)
{
container.Element(new BinaryDecoration
var renderingCommands = PlanLayout(availableSpace).ToList();
var width = renderingCommands.Max(x => x.Measurement.Width);
foreach (var command in renderingCommands)
{
Type = DecorationType.Prepend,
DecorationElement = Header,
ContentElement = new BinaryDecoration
{
Type = DecorationType.Append,
ContentElement = Content,
DecorationElement = Footer
}
});
var elementSize = new Size(width, command.Measurement.Height);
Canvas.Translate(command.Offset);
command.Element.Draw(elementSize);
Canvas.Translate(command.Offset.Reverse());
}
}
private IEnumerable<DecorationItemRenderingCommand> PlanLayout(Size availableSpace)
{
SpacePlan GetDecorationMeasurement(Element element)
{
var measurement = element.Measure(availableSpace);
return measurement.Type == SpacePlanType.FullRender
? measurement
: SpacePlan.Wrap();
}
var beforeMeasurement = GetDecorationMeasurement(Before);
var afterMeasurement = GetDecorationMeasurement(After);
var contentSpace = new Size(availableSpace.Width, availableSpace.Height - beforeMeasurement.Height - afterMeasurement.Height);
var contentMeasurement = Content.Measure(contentSpace);
yield return new DecorationItemRenderingCommand
{
Element = Before,
Measurement = beforeMeasurement,
Offset = Position.Zero
};
yield return new DecorationItemRenderingCommand
{
Element = Content,
Measurement = contentMeasurement,
Offset = new Position(0, beforeMeasurement.Height)
};
yield return new DecorationItemRenderingCommand
{
Element = After,
Measurement = afterMeasurement,
Offset = new Position(0, beforeMeasurement.Height + contentMeasurement.Height)
};
}
}
}
+6 -6
View File
@@ -27,12 +27,12 @@ namespace QuestPDF.Elements
{
ChildrenQueue = new Queue<GridElement>(Children);
container.Stack(stack =>
container.Column(column =>
{
stack.Spacing(VerticalSpacing);
column.Spacing(VerticalSpacing);
while (ChildrenQueue.Any())
stack.Item().Row(BuildRow);
column.Item().Row(BuildRow);
});
}
@@ -65,12 +65,12 @@ namespace QuestPDF.Elements
emptySpace /= 2;
if (hasEmptySpace && Alignment != HorizontalAlignment.Left)
row.RelativeColumn(emptySpace);
row.RelativeItem(emptySpace);
elements.ForEach(x => row.RelativeColumn(x.Columns).Element(x.Child));
elements.ForEach(x => row.RelativeItem(x.Columns).Element(x.Child));
if (hasEmptySpace && Alignment != HorizontalAlignment.Right)
row.RelativeColumn(emptySpace);
row.RelativeItem(emptySpace);
}
}
}
@@ -3,7 +3,7 @@ using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal class ExternalLink : ContainerElement
internal class Hyperlink : ContainerElement
{
public string Url { get; set; } = "https://www.questpdf.com";
@@ -14,7 +14,7 @@ namespace QuestPDF.Elements
if (targetSize.Type == SpacePlanType.Wrap)
return;
Canvas.DrawExternalLink(Url, targetSize);
Canvas.DrawHyperlink(Url, targetSize);
base.Draw(availableSpace);
}
}
+30 -43
View File
@@ -8,15 +8,7 @@ namespace QuestPDF.Elements
{
internal class InlinedElement : Container
{
public SpacePlan? MeasureCache { get; set; }
internal override SpacePlan Measure(Size availableSpace)
{
// TODO: once element caching proxy is introduces, this can be removed
MeasureCache ??= Child.Measure(Size.Max);
return MeasureCache.Value;
}
}
internal enum InlinedAlignment
@@ -112,8 +104,11 @@ namespace QuestPDF.Elements
foreach (var element in elements)
{
var size = element.Measure(Size.Max);
var size = (Size)element.Measure(Size.Max);
var baselineOffset = BaselineOffset(size, lineSize.Height);
if (size.Height == 0)
size = new Size(size.Width, lineSize.Height);
Canvas.Translate(new Position(0, baselineOffset));
element.Draw(size);
@@ -132,48 +127,39 @@ namespace QuestPDF.Elements
if (elements.Count == 1)
return 0;
if (ElementsAlignment == InlinedAlignment.Justify)
return difference / (elements.Count - 1);
if (ElementsAlignment == InlinedAlignment.SpaceAround)
return difference / (elements.Count + 1);
return HorizontalSpacing;
return ElementsAlignment switch
{
InlinedAlignment.Justify => difference / (elements.Count - 1),
InlinedAlignment.SpaceAround => difference / (elements.Count + 1),
_ => HorizontalSpacing
};
}
float AlignOffset()
{
if (ElementsAlignment == InlinedAlignment.Left)
return 0;
if (ElementsAlignment == InlinedAlignment.Justify)
return 0;
if (ElementsAlignment == InlinedAlignment.SpaceAround)
return elementOffset;
var difference = availableSpace.Width - lineSize.Width - (elements.Count - 1) * HorizontalSpacing;
if (ElementsAlignment == InlinedAlignment.Center)
return difference / 2;
if (ElementsAlignment == InlinedAlignment.Right)
return difference;
return 0;
return ElementsAlignment switch
{
InlinedAlignment.Left => 0,
InlinedAlignment.Justify => 0,
InlinedAlignment.SpaceAround => elementOffset,
InlinedAlignment.Center => difference / 2,
InlinedAlignment.Right => difference,
_ => 0
};
}
float BaselineOffset(Size elementSize, float lineHeight)
{
if (BaselineAlignment == VerticalAlignment.Top)
return 0;
var difference = lineHeight - elementSize.Height;
if (BaselineAlignment == VerticalAlignment.Middle)
return difference / 2;
return difference;
return BaselineAlignment switch
{
VerticalAlignment.Top => 0,
VerticalAlignment.Middle => difference / 2,
_ => difference
};
}
}
}
@@ -250,11 +236,12 @@ namespace QuestPDF.Elements
float GetInitialAlignmentOffset()
{
// this method makes sure that the spacing between elements is no lesser than configured
if (ElementsAlignment == InlinedAlignment.SpaceAround)
return HorizontalSpacing * 2;
return 0;
return ElementsAlignment switch
{
InlinedAlignment.SpaceAround => HorizontalSpacing * 2,
_ => 0
};
}
}
}
+46
View File
@@ -0,0 +1,46 @@
using QuestPDF.Drawing;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
public interface ILine
{
}
internal enum LineType
{
Vertical,
Horizontal
}
internal class Line : Element, ILine, ICacheable
{
public LineType Type { get; set; } = LineType.Vertical;
public string Color { get; set; } = Colors.Black;
public float Size { get; set; } = 1;
internal override SpacePlan Measure(Size availableSpace)
{
return Type switch
{
LineType.Vertical when availableSpace.Width >= Size => SpacePlan.FullRender(Size, 0),
LineType.Horizontal when availableSpace.Height >= Size => SpacePlan.FullRender(0, Size),
_ => SpacePlan.Wrap()
};
}
internal override void Draw(Size availableSpace)
{
if (Type == LineType.Vertical)
{
Canvas.DrawRectangle(new Position(-Size/2, 0), new Size(Size, availableSpace.Height), Color);
}
else if (Type == LineType.Horizontal)
{
Canvas.DrawRectangle(new Position(0, -Size/2), new Size(availableSpace.Width, Size), Color);
}
}
}
}
+48 -30
View File
@@ -20,6 +20,9 @@ namespace QuestPDF.Elements
public string BackgroundColor { get; set; } = Colors.Transparent;
public Element Background { get; set; } = Empty.Instance;
public Element Foreground { get; set; } = Empty.Instance;
public Element Header { get; set; } = Empty.Instance;
public Element Content { get; set; } = Empty.Instance;
public Element Footer { get; set; } = Empty.Instance;
@@ -27,39 +30,54 @@ namespace QuestPDF.Elements
public void Compose(IContainer container)
{
container
.MinWidth(MinSize.Width)
.MinHeight(MinSize.Height)
.MaxWidth(MaxSize.Width)
.MaxHeight(MaxSize.Height)
.Background(BackgroundColor)
.PaddingLeft(MarginLeft)
.PaddingRight(MarginRight)
.PaddingTop(MarginTop)
.PaddingBottom(MarginBottom)
.DefaultTextStyle(DefaultTextStyle)
.Decoration(decoration =>
.Layers(layers =>
{
decoration
.Header()
.DebugPointer("Page header")
.Element(Header);
layers
.Layer()
.DebugPointer("Page background layer")
.Element(Background);
decoration
.Content()
.Element(x => IsClose(MinSize.Width, MaxSize.Width) ? x.ExtendHorizontal() : x)
.Element(x => IsClose(MinSize.Height, MaxSize.Height) ? x.ExtendVertical() : x)
.DebugPointer("Page content")
.Element(Content);
layers
.PrimaryLayer()
.MinWidth(MinSize.Width)
.MinHeight(MinSize.Height)
.MaxWidth(MaxSize.Width)
.MaxHeight(MaxSize.Height)
.Background(BackgroundColor)
.PaddingLeft(MarginLeft)
.PaddingRight(MarginRight)
.PaddingTop(MarginTop)
.PaddingBottom(MarginBottom)
.DefaultTextStyle(DefaultTextStyle)
.Decoration(decoration =>
{
decoration
.Before()
.DebugPointer("Page header")
.Element(Header);
decoration
.Content()
.Element(x => IsClose(MinSize.Width, MaxSize.Width) ? x.ExtendHorizontal() : x)
.Element(x => IsClose(MinSize.Height, MaxSize.Height) ? x.ExtendVertical() : x)
.DebugPointer("Page content")
.Element(Content);
decoration
.After()
.DebugPointer("Page footer")
.Element(Footer);
});
decoration
.Footer()
.DebugPointer("Page footer")
.Element(Footer);
layers
.Layer()
.DebugPointer("Page foreground layer")
.Element(Foreground);
});
bool IsClose(float x, float y)
+1 -1
View File
@@ -26,7 +26,7 @@ namespace QuestPDF.Elements
if (string.IsNullOrWhiteSpace(Text))
x.MaxHeight(32).Image(ImageData, ImageScaling.FitArea);
else
x.Text(Text, TextStyle.Default.Size(14));
x.Text(Text).FontSize(14);
});
}
}
+119 -127
View File
@@ -1,4 +1,5 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using QuestPDF.Drawing;
@@ -6,171 +7,162 @@ using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal class RowElement : Constrained
internal enum RowItemType
{
public float ConstantSize { get; }
public float RelativeSize { get; }
public RowElement(float constantSize, float relativeSize)
{
ConstantSize = constantSize;
RelativeSize = relativeSize;
}
public void SetWidth(float width)
{
MinWidth = width;
MaxWidth = width;
}
Auto,
Constant,
Relative
}
internal class BinaryRow : Element, ICacheable, IStateResettable
internal class RowItem : Container
{
internal Element Left { get; set; }
internal Element Right { get; set; }
private bool IsLeftRendered { get; set; }
private bool IsRightRendered { get; set; }
public bool IsRendered { get; set; }
public float Width { get; set; }
public RowItemType Type { get; set; }
public float Size { get; set; }
}
internal class RowItemRenderingCommand
{
public RowItem RowItem { get; set; }
public SpacePlan Measurement { get; set; }
public Size Size { get; set; }
public Position Offset { get; set; }
}
internal class Row : Element, ICacheable, IStateResettable
{
internal List<RowItem> Items { get; } = new();
internal float Spacing { get; set; }
public void ResetState()
{
IsLeftRendered = false;
IsRightRendered = false;
Items.ForEach(x => x.IsRendered = false);
}
internal override IEnumerable<Element?> GetChildren()
{
yield return Left;
yield return Right;
return Items;
}
internal override void CreateProxy(Func<Element?, Element?> create)
{
Left = create(Left);
Right = create(Right);
Items.ForEach(x => x.Child = create(x.Child));
}
internal override SpacePlan Measure(Size availableSpace)
{
var leftMeasurement = Left.Measure(new Size(availableSpace.Width, availableSpace.Height));
if (!Items.Any())
return SpacePlan.FullRender(Size.Zero);
if (leftMeasurement.Type == SpacePlanType.Wrap)
UpdateItemsWidth(availableSpace.Width);
var renderingCommands = PlanLayout(availableSpace);
if (renderingCommands.Any(x => !x.RowItem.IsRendered && x.Measurement.Type == SpacePlanType.Wrap))
return SpacePlan.Wrap();
if (leftMeasurement.Type == SpacePlanType.FullRender)
IsLeftRendered = true;
var rightMeasurement = Right.Measure(new Size(availableSpace.Width - leftMeasurement.Width, availableSpace.Height));
var width = renderingCommands.Last().Offset.X + renderingCommands.Last().Size.Width;
var height = renderingCommands.Max(x => x.Size.Height);
var size = new Size(width, height);
if (rightMeasurement.Type == SpacePlanType.Wrap)
if (width > availableSpace.Width + Size.Epsilon || height > availableSpace.Height + Size.Epsilon)
return SpacePlan.Wrap();
if (leftMeasurement.Type == SpacePlanType.FullRender)
IsRightRendered = true;
var totalWidth = leftMeasurement.Width + rightMeasurement.Width;
var totalHeight = Math.Max(leftMeasurement.Height, rightMeasurement.Height);
if (renderingCommands.Any(x => !x.RowItem.IsRendered && x.Measurement.Type == SpacePlanType.PartialRender))
return SpacePlan.PartialRender(size);
var targetSize = new Size(totalWidth, totalHeight);
if ((!IsLeftRendered && leftMeasurement.Type == SpacePlanType.PartialRender) ||
(!IsRightRendered && rightMeasurement.Type == SpacePlanType.PartialRender))
return SpacePlan.PartialRender(targetSize);
return SpacePlan.FullRender(targetSize);
return SpacePlan.FullRender(size);
}
internal override void Draw(Size availableSpace)
{
var leftMeasurement = Left.Measure(new Size(availableSpace.Width, availableSpace.Height));
var leftWidth = leftMeasurement.Width;
Left.Draw(new Size(leftWidth, availableSpace.Height));
Canvas.Translate(new Position(leftWidth, 0));
Right.Draw(new Size(availableSpace.Width - leftWidth, availableSpace.Height));
Canvas.Translate(new Position(-leftWidth, 0));
}
}
internal class Row : Element
{
public float Spacing { get; set; } = 0;
public ICollection<RowElement> Items { get; internal set; } = new List<RowElement>();
private Element? RootElement { get; set; }
if (!Items.Any())
return;
internal override IEnumerable<Element?> GetChildren()
{
if (RootElement == null)
ComposeTree();
UpdateItemsWidth(availableSpace.Width);
var renderingCommands = PlanLayout(availableSpace);
yield return RootElement;
}
internal override SpacePlan Measure(Size availableSpace)
{
UpdateElementsWidth(availableSpace.Width);
return RootElement.Measure(availableSpace);
}
internal override void Draw(Size availableSpace)
{
UpdateElementsWidth(availableSpace.Width);
RootElement.Draw(availableSpace);
}
#region structure
private void ComposeTree()
{
Items = AddSpacing(Items, Spacing);
var elements = Items.Cast<Element>().ToArray();
RootElement = BuildTree(elements);
}
private void UpdateElementsWidth(float availableWidth)
{
var constantWidth = Items.Sum(x => x.ConstantSize);
var relativeWidth = Items.Sum(x => x.RelativeSize);
var widthPerRelativeUnit = (relativeWidth > 0) ? (availableWidth - constantWidth) / relativeWidth : 0;
foreach (var row in Items)
foreach (var command in renderingCommands)
{
row.SetWidth(row.ConstantSize + row.RelativeSize * widthPerRelativeUnit);
if (command.Measurement.Type == SpacePlanType.FullRender)
command.RowItem.IsRendered = true;
if (command.Measurement.Type == SpacePlanType.Wrap)
continue;
Canvas.Translate(command.Offset);
command.RowItem.Draw(command.Size);
Canvas.Translate(command.Offset.Reverse());
}
if (Items.All(x => x.IsRendered))
ResetState();
}
private void UpdateItemsWidth(float availableWidth)
{
HandleItemsWithAutoWidth();
var constantWidth = Items.Where(x => x.Type == RowItemType.Constant).Sum(x => x.Size);
var relativeWidth = Items.Where(x => x.Type == RowItemType.Relative).Sum(x => x.Size);
var spacingWidth = (Items.Count - 1) * Spacing;
foreach (var item in Items.Where(x => x.Type == RowItemType.Constant))
item.Width = item.Size;
if (relativeWidth <= 0)
return;
var widthPerRelativeUnit = (availableWidth - constantWidth - spacingWidth) / relativeWidth;
foreach (var item in Items.Where(x => x.Type == RowItemType.Relative))
item.Width = item.Size * widthPerRelativeUnit;
}
private void HandleItemsWithAutoWidth()
{
foreach (var rowItem in Items.Where(x => x.Type == RowItemType.Auto))
{
rowItem.Size = rowItem.Measure(Size.Max).Width;
rowItem.Type = RowItemType.Constant;
}
}
private static ICollection<RowElement> AddSpacing(ICollection<RowElement> elements, float spacing)
private ICollection<RowItemRenderingCommand> PlanLayout(Size availableSpace)
{
if (spacing < Size.Epsilon)
return elements;
return elements
.SelectMany(x => new[] { new RowElement(spacing, 0), x })
.Skip(1)
.ToList();
}
var leftOffset = 0f;
var renderingCommands = new List<RowItemRenderingCommand>();
private static Element BuildTree(Span<Element> elements)
{
if (elements.IsEmpty)
return Empty.Instance;
if (elements.Length == 1)
return elements[0];
var half = elements.Length / 2;
return new BinaryRow
foreach (var item in Items)
{
Left = BuildTree(elements.Slice(0, half)),
Right = BuildTree(elements.Slice(half))
};
var itemSpace = new Size(item.Width, availableSpace.Height);
var command = new RowItemRenderingCommand
{
RowItem = item,
Size = itemSpace,
Measurement = item.Measure(itemSpace),
Offset = new Position(leftOffset, 0)
};
renderingCommands.Add(command);
leftOffset += item.Width + Spacing;
}
var rowHeight = renderingCommands
.Where(x => !x.RowItem.IsRendered)
.Select(x => x.Measurement.Height)
.DefaultIfEmpty(0)
.Max();
foreach (var command in renderingCommands)
{
command.Size = new Size(command.Size.Width, rowHeight);
command.Measurement = command.RowItem.Measure(command.Size);
}
return renderingCommands;
}
#endregion
}
}
+78
View File
@@ -0,0 +1,78 @@
using System;
using System.Linq;
using QuestPDF.Drawing;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal class ScaleToFit : ContainerElement
{
internal override SpacePlan Measure(Size availableSpace)
{
if (Child == null)
return SpacePlan.FullRender(Size.Zero);
var perfectScale = FindPerfectScale(Child, availableSpace);
if (perfectScale == null)
return SpacePlan.Wrap();
var targetSpace = ScaleSize(availableSpace, perfectScale.Value);
return SpacePlan.FullRender(targetSpace);
}
internal override void Draw(Size availableSpace)
{
var perfectScale = FindPerfectScale(Child, availableSpace);
if (!perfectScale.HasValue)
return;
var targetScale = perfectScale.Value;
var targetSpace = ScaleSize(availableSpace, 1 / targetScale);
Canvas.Scale(targetScale, targetScale);
Child?.Draw(targetSpace);
Canvas.Scale(1 / targetScale, 1 / targetScale);
}
private static Size ScaleSize(Size size, float factor)
{
return new Size(size.Width * factor, size.Height * factor);
}
private static float? FindPerfectScale(Element child, Size availableSpace)
{
if (ChildFits(1))
return 1;
var maxScale = 1f;
var minScale = Size.Epsilon;
var lastWorkingScale = (float?)null;
foreach (var _ in Enumerable.Range(0, 8))
{
var halfScale = (maxScale + minScale) / 2;
if (ChildFits(halfScale))
{
minScale = halfScale;
lastWorkingScale = halfScale;
}
else
{
maxScale = halfScale;
}
}
return lastWorkingScale;
bool ChildFits(float scale)
{
var scaledSpace = ScaleSize(availableSpace, 1 / scale);
return child.Measure(scaledSpace).Type == SpacePlanType.FullRender;
}
}
}
}
@@ -2,7 +2,7 @@
namespace QuestPDF.Elements
{
internal class InternalLocation : ContainerElement, IStateResettable
internal class Section : ContainerElement, IStateResettable
{
public string LocationName { get; set; }
private bool IsRendered { get; set; }
@@ -16,12 +16,11 @@ namespace QuestPDF.Elements
{
if (!IsRendered)
{
PageContext.SetLocationPage(LocationName);
Canvas.DrawLocation(LocationName);
Canvas.DrawSection(LocationName);
IsRendered = true;
}
PageContext.SetSectionPage(LocationName);
base.Draw(availableSpace);
}
}
@@ -3,9 +3,9 @@ using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal class InternalLink : ContainerElement
internal class SectionLink : ContainerElement
{
public string LocationName { get; set; }
public string SectionName { get; set; }
internal override void Draw(Size availableSpace)
{
@@ -14,7 +14,7 @@ namespace QuestPDF.Elements
if (targetSize.Type == SpacePlanType.Wrap)
return;
Canvas.DrawLocationLink(LocationName, targetSize);
Canvas.DrawSectionLink(SectionName, targetSize);
base.Draw(availableSpace);
}
}
-143
View File
@@ -1,143 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using QuestPDF.Drawing;
using QuestPDF.Fluent;
using QuestPDF.Infrastructure;
using IComponent = QuestPDF.Infrastructure.IComponent;
using IContainer = QuestPDF.Infrastructure.IContainer;
namespace QuestPDF.Elements
{
internal class BinaryStack : Element, IStateResettable, ICacheable
{
internal Element First { get; set; } = Empty.Instance;
internal Element Second { get; set; } = Empty.Instance;
internal bool IsFirstRendered { get; set; } = false;
internal override IEnumerable<Element?> GetChildren()
{
yield return First;
yield return Second;
}
public void ResetState()
{
IsFirstRendered = false;
}
internal override void CreateProxy(Func<Element?, Element?> create)
{
First = create(First);
Second = create(Second);
}
internal override SpacePlan Measure(Size availableSpace)
{
var firstElement = IsFirstRendered ? Empty.Instance : First;
var firstSize = firstElement.Measure(availableSpace);
if (firstSize.Type == SpacePlanType.Wrap)
return SpacePlan.Wrap();
if (firstSize.Type == SpacePlanType.PartialRender)
return firstSize;
var spaceForSecond = new Size(availableSpace.Width, availableSpace.Height - firstSize.Height);
var secondSize = Second.Measure(spaceForSecond);
if (secondSize.Type == SpacePlanType.Wrap)
return SpacePlan.PartialRender(firstSize);
var totalWidth = Math.Max(firstSize.Width, secondSize.Width);
var totalHeight = firstSize.Height + secondSize.Height;
var targetSize = new Size(totalWidth, totalHeight);
if (secondSize.Type == SpacePlanType.PartialRender)
return SpacePlan.PartialRender(targetSize);
return SpacePlan.FullRender(targetSize);
}
internal override void Draw(Size availableSpace)
{
var firstElement = IsFirstRendered ? Empty.Instance : First;
var firstMeasurement = firstElement.Measure(availableSpace);
if (firstMeasurement.Type == SpacePlanType.FullRender)
IsFirstRendered = true;
var firstSize = firstMeasurement;
if (firstSize.Type != SpacePlanType.Wrap)
firstElement.Draw(new Size(availableSpace.Width, firstSize.Height));
if (firstMeasurement.Type == SpacePlanType.Wrap || firstMeasurement.Type == SpacePlanType.PartialRender)
return;
var firstHeight = firstSize.Height;
var spaceForSecond = new Size(availableSpace.Width, availableSpace.Height - firstHeight);
var secondMeasurement = Second.Measure(spaceForSecond);
if (secondMeasurement.Type == SpacePlanType.Wrap)
return;
Canvas.Translate(new Position(0, firstHeight));
Second.Draw(new Size(availableSpace.Width, secondMeasurement.Height));
Canvas.Translate(new Position(0, -firstHeight));
if (secondMeasurement.Type == SpacePlanType.FullRender)
IsFirstRendered = false;
}
}
internal class Stack : IComponent
{
public ICollection<Element> Items { get; } = new List<Element>();
public float Spacing { get; set; } = 0;
public void Compose(IContainer container)
{
var elements = AddSpacing(Spacing, Items);
container
.PaddingBottom(-Spacing)
.Element(BuildTree(elements.ToArray()));
}
static ICollection<Element> AddSpacing(float spacing, ICollection<Element> elements)
{
if (spacing < Size.Epsilon)
return elements;
return elements
.Where(x => !(x is Empty))
.Select(x => new Padding
{
Bottom = spacing,
Child = x
})
.Cast<Element>()
.ToList();
}
static Element BuildTree(Span<Element> elements)
{
if (elements.IsEmpty)
return Empty.Instance;
if (elements.Length == 1)
return elements[0];
var half = elements.Length / 2;
return new BinaryStack
{
First = BuildTree(elements.Slice(0, half)),
Second = BuildTree(elements.Slice(half))
};
}
}
}
+25
View File
@@ -0,0 +1,25 @@
using System;
using QuestPDF.Drawing;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal class StopPaging : ContainerElement
{
internal override SpacePlan Measure(Size availableSpace)
{
if (Child == null)
return SpacePlan.FullRender(Size.Zero);
var measurement = Child.Measure(availableSpace);
return measurement.Type switch
{
SpacePlanType.Wrap => SpacePlan.FullRender(Size.Zero),
SpacePlanType.PartialRender => SpacePlan.FullRender(measurement),
SpacePlanType.FullRender => measurement,
_ => throw new ArgumentOutOfRangeException()
};
}
}
}
+17 -35
View File
@@ -15,15 +15,19 @@ namespace QuestPDF.Elements.Table
public List<TableCell> Cells { get; set; } = new List<TableCell>();
public bool ExtendLastCellsToTableBottom { get; set; }
// cache for efficient cell finding
// index of first array - number of row
// nested array - collection of all cells starting at given row
private TableCell[][] OrderedCells { get; set; }
private int StartingRowsCount { get; set; }
private int RowsCount { get; set; }
private int CurrentRow { get; set; }
internal override void Initialize(IPageContext pageContext, ICanvas canvas)
{
StartingRowsCount = Cells.Select(x => x.Row).DefaultIfEmpty(0).Max();
RowsCount = Cells.Select(x => x.Row + x.RowSpan - 1).DefaultIfEmpty(0).Max();
Cells = Cells.OrderBy(x => x.Row).ThenBy(x => x.Column).ToList();
base.Initialize(pageContext, canvas);
}
internal override IEnumerable<Element?> GetChildren()
{
return Cells;
@@ -31,24 +35,6 @@ namespace QuestPDF.Elements.Table
public void ResetState()
{
if (StartingRowsCount == default)
StartingRowsCount = Cells.Select(x => x.Row).DefaultIfEmpty(0).Max();
if (RowsCount == default)
RowsCount = Cells.Select(x => x.Row + x.RowSpan - 1).DefaultIfEmpty(0).Max();
if (OrderedCells == default)
{
var groups = Cells
.GroupBy(x => x.Row)
.ToDictionary(x => x.Key, x => x.OrderBy(y => y.Column).ToArray());
OrderedCells = Enumerable
.Range(0, RowsCount + 1)
.Select(x => groups.TryGetValue(x, out var output) ? output : Array.Empty<TableCell>())
.ToArray();
}
Cells.ForEach(x => x.IsRendered = false);
CurrentRow = 1;
}
@@ -85,6 +71,9 @@ namespace QuestPDF.Elements.Table
{
if (command.Measurement.Type == SpacePlanType.FullRender)
command.Cell.IsRendered = true;
if (command.Measurement.Type == SpacePlanType.Wrap)
continue;
Canvas.Translate(command.Offset);
command.Cell.Draw(command.Size);
@@ -103,7 +92,7 @@ namespace QuestPDF.Elements.Table
{
return commands
.GroupBy(x => x.Cell.Row)
.Where(x => x.All(y => y.Measurement.Type == SpacePlanType.FullRender))
.Where(x => x.All(y => y.Cell.IsRendered || y.Measurement.Type == SpacePlanType.FullRender))
.Select(x => x.Key)
.DefaultIfEmpty(0)
.Max();
@@ -152,16 +141,12 @@ namespace QuestPDF.Elements.Table
ICollection<TableCellRenderingCommand> GetRenderingCommands()
{
var rowBottomOffsets = new DynamicDictionary<int, float>();
var commands = new List<TableCellRenderingCommand>();
var cellsToTry = Enumerable
.Range(CurrentRow, RowsCount - CurrentRow + 1)
.SelectMany(x => OrderedCells[x]);
var cellsToTry = Cells.Where(x => x.Row + x.RowSpan - 1 >= CurrentRow);
var currentRow = CurrentRow;
var maxRenderingRow = RowsCount;
var commands = new List<TableCellRenderingCommand>();
foreach (var cell in cellsToTry)
{
// update position of previous row
@@ -181,10 +166,7 @@ namespace QuestPDF.Elements.Table
// cell visibility optimizations
if (cell.Row > maxRenderingRow)
break;
if (cell.IsRendered)
continue;
// calculate cell position / size
var topOffset = rowBottomOffsets[cell.Row - 1];
@@ -274,4 +256,4 @@ namespace QuestPDF.Elements.Table
}
}
}
}
}
@@ -3,7 +3,7 @@ using QuestPDF.Infrastructure;
namespace QuestPDF.Elements.Text.Items
{
internal class TextBlockExternalLink : TextBlockSpan
internal class TextBlockHyperlink : TextBlockSpan
{
public string Url { get; set; }
@@ -15,7 +15,7 @@ namespace QuestPDF.Elements.Text.Items
public override void Draw(TextDrawingRequest request)
{
request.Canvas.Translate(new Position(0, request.TotalAscent));
request.Canvas.DrawExternalLink(Url, new Size(request.TextSize.Width, request.TextSize.Height));
request.Canvas.DrawHyperlink(Url, new Size(request.TextSize.Width, request.TextSize.Height));
request.Canvas.Translate(new Position(0, -request.TotalAscent));
base.Draw(request);
@@ -1,11 +1,13 @@
using QuestPDF.Elements.Text.Calculation;
using System;
using QuestPDF.Elements.Text.Calculation;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements.Text.Items
{
internal class TextBlockPageNumber : TextBlockSpan
{
public string SlotName { get; set; }
public const string PageNumberPlaceholder = "123";
public Func<IPageContext, string> Source { get; set; } = _ => PageNumberPlaceholder;
public override TextMeasurementResult? Measure(TextMeasurementRequest request)
{
@@ -21,13 +23,7 @@ namespace QuestPDF.Elements.Text.Items
private void SetPageNumber(IPageContext context)
{
var pageNumberPlaceholder = 123;
var pageNumber = context.GetRegisteredLocations().Contains(SlotName)
? context.GetLocationPage(SlotName)
: pageNumberPlaceholder;
Text = pageNumber.ToString();
Text = Source(context) ?? PageNumberPlaceholder;
}
}
}
@@ -3,9 +3,9 @@ using QuestPDF.Infrastructure;
namespace QuestPDF.Elements.Text.Items
{
internal class TextBlockInternalLink : TextBlockSpan
internal class TextBlockSectionlLink : TextBlockSpan
{
public string LocationName { get; set; }
public string SectionName { get; set; }
public override TextMeasurementResult? Measure(TextMeasurementRequest request)
{
@@ -15,7 +15,7 @@ namespace QuestPDF.Elements.Text.Items
public override void Draw(TextDrawingRequest request)
{
request.Canvas.Translate(new Position(0, request.TotalAscent));
request.Canvas.DrawLocationLink(LocationName, new Size(request.TextSize.Width, request.TextSize.Height));
request.Canvas.DrawSectionLink(SectionName, new Size(request.TextSize.Width, request.TextSize.Height));
request.Canvas.Translate(new Position(0, -request.TotalAscent));
base.Draw(request);
+20 -14
View File
@@ -14,39 +14,45 @@ namespace QuestPDF.Fluent
return element.Element(border);
}
public static IContainer Border(this IContainer element, float value)
public static IContainer Border(this IContainer element, float value, Unit unit = Unit.Point)
{
return element.BorderHorizontal(value).BorderVertical(value);
return element
.BorderHorizontal(value, unit)
.BorderVertical(value, unit);
}
public static IContainer BorderVertical(this IContainer element, float value)
public static IContainer BorderVertical(this IContainer element, float value, Unit unit = Unit.Point)
{
return element.BorderLeft(value).BorderRight(value);
return element
.BorderLeft(value, unit)
.BorderRight(value, unit);
}
public static IContainer BorderHorizontal(this IContainer element, float value)
public static IContainer BorderHorizontal(this IContainer element, float value, Unit unit = Unit.Point)
{
return element.BorderTop(value).BorderBottom(value);
return element
.BorderTop(value, unit)
.BorderBottom(value, unit);
}
public static IContainer BorderLeft(this IContainer element, float value)
public static IContainer BorderLeft(this IContainer element, float value, Unit unit = Unit.Point)
{
return element.Border(x => x.Left = value);
return element.Border(x => x.Left = value.ToPoints(unit));
}
public static IContainer BorderRight(this IContainer element, float value)
public static IContainer BorderRight(this IContainer element, float value, Unit unit = Unit.Point)
{
return element.Border(x => x.Right = value);
return element.Border(x => x.Right = value.ToPoints(unit));
}
public static IContainer BorderTop(this IContainer element, float value)
public static IContainer BorderTop(this IContainer element, float value, Unit unit = Unit.Point)
{
return element.Border(x => x.Top = value);
return element.Border(x => x.Top = value.ToPoints(unit));
}
public static IContainer BorderBottom(this IContainer element, float value)
public static IContainer BorderBottom(this IContainer element, float value, Unit unit = Unit.Point)
{
return element.Border(x => x.Bottom = value);
return element.Border(x => x.Bottom = value.ToPoints(unit));
}
public static IContainer BorderColor(this IContainer element, string color)
+44
View File
@@ -0,0 +1,44 @@
using System;
using QuestPDF.Elements;
using QuestPDF.Infrastructure;
namespace QuestPDF.Fluent
{
public class ColumnDescriptor
{
internal Column Column { get; } = new();
public void Spacing(float value, Unit unit = Unit.Point)
{
Column.Spacing = value.ToPoints(unit);
}
public IContainer Item()
{
var container = new Container();
Column.Items.Add(new ColumnItem
{
Child = container
});
return container;
}
}
public static class ColumnExtensions
{
[Obsolete("This element has been renamed since version 2022.2. Please use the 'Column' method.")]
public static void Stack(this IContainer element, Action<ColumnDescriptor> handler)
{
element.Column(handler);
}
public static void Column(this IContainer element, Action<ColumnDescriptor> handler)
{
var descriptor = new ColumnDescriptor();
handler(descriptor);
element.Element(descriptor.Column);
}
}
}
+16 -12
View File
@@ -14,34 +14,38 @@ namespace QuestPDF.Fluent
return element.Element(constrained);
}
public static IContainer Width(this IContainer element, float value)
public static IContainer Width(this IContainer element, float value, Unit unit = Unit.Point)
{
return element.MinWidth(value).MaxWidth(value);
return element
.MinWidth(value, unit)
.MaxWidth(value, unit);
}
public static IContainer MinWidth(this IContainer element, float value)
public static IContainer MinWidth(this IContainer element, float value, Unit unit = Unit.Point)
{
return element.Constrained(x => x.MinWidth = value);
return element.Constrained(x => x.MinWidth = value.ToPoints(unit));
}
public static IContainer MaxWidth(this IContainer element, float value)
public static IContainer MaxWidth(this IContainer element, float value, Unit unit = Unit.Point)
{
return element.Constrained(x => x.MaxWidth = value);
return element.Constrained(x => x.MaxWidth = value.ToPoints(unit));
}
public static IContainer Height(this IContainer element, float value)
public static IContainer Height(this IContainer element, float value, Unit unit = Unit.Point)
{
return element.MinHeight(value).MaxHeight(value);
return element
.MinHeight(value, unit)
.MaxHeight(value, unit);
}
public static IContainer MinHeight(this IContainer element, float value)
public static IContainer MinHeight(this IContainer element, float value, Unit unit = Unit.Point)
{
return element.Constrained(x => x.MinHeight = value);
return element.Constrained(x => x.MinHeight = value.ToPoints(unit));
}
public static IContainer MaxHeight(this IContainer element, float value)
public static IContainer MaxHeight(this IContainer element, float value, Unit unit = Unit.Point)
{
return element.Constrained(x => x.MaxHeight = value);
return element.Constrained(x => x.MaxHeight = value.ToPoints(unit));
}
}
}
+39 -7
View File
@@ -8,16 +8,16 @@ namespace QuestPDF.Fluent
{
internal Decoration Decoration { get; } = new Decoration();
public IContainer Header()
public IContainer Before()
{
var container = new Container();
Decoration.Header = container;
Decoration.Before = container;
return container;
}
public void Header(Action<IContainer> handler)
public void Before(Action<IContainer> handler)
{
handler?.Invoke(Header());
handler?.Invoke(Before());
}
public IContainer Content()
@@ -32,17 +32,49 @@ namespace QuestPDF.Fluent
handler?.Invoke(Content());
}
public IContainer Footer()
public IContainer After()
{
var container = new Container();
Decoration.Footer = container;
Decoration.After = container;
return container;
}
public void After(Action<IContainer> handler)
{
handler?.Invoke(After());
}
#region Obsolete
[Obsolete("This element has been renamed since version 2022.2. Please use the 'Before' method.")]
public IContainer Header()
{
var container = new Container();
Decoration.Before = container;
return container;
}
[Obsolete("This element has been renamed since version 2022.2. Please use the 'Before' method.")]
public void Header(Action<IContainer> handler)
{
handler?.Invoke(Header());
}
[Obsolete("This element has been renamed since version 2022.2. Please use the 'After' method.")]
public IContainer Footer()
{
var container = new Container();
Decoration.After = container;
return container;
}
[Obsolete("This element has been renamed since version 2022.2. Please use the 'After' method.")]
public void Footer(Action<IContainer> handler)
{
handler?.Invoke(Footer());
}
#endregion
}
public static class DecorationExtensions
@@ -52,7 +84,7 @@ namespace QuestPDF.Fluent
var descriptor = new DecorationDescriptor();
handler(descriptor);
element.Component(descriptor.Decoration);
element.Element(descriptor.Decoration);
}
}
}
+43 -8
View File
@@ -60,7 +60,7 @@ namespace QuestPDF.Fluent
public static void Placeholder(this IContainer element, string? text = null)
{
element.Component(new Elements.Placeholder
element.Component(new Placeholder
{
Text = text ?? string.Empty
});
@@ -99,27 +99,45 @@ namespace QuestPDF.Fluent
return element.Element(new Container());
}
[Obsolete("This element has been renamed since version 2022.3. Please use the Hyperlink method.")]
public static IContainer ExternalLink(this IContainer element, string url)
{
return element.Element(new ExternalLink
return element.Hyperlink(url);
}
public static IContainer Hyperlink(this IContainer element, string url)
{
return element.Element(new Hyperlink
{
Url = url
});
}
[Obsolete("This element has been renamed since version 2022.3. Please use the Section method.")]
public static IContainer Location(this IContainer element, string locationName)
{
return element.Element(new InternalLocation
return element.Section(locationName);
}
public static IContainer Section(this IContainer element, string sectionName)
{
return element.Element(new Section
{
LocationName = locationName
LocationName = sectionName
});
}
[Obsolete("This element has been renamed since version 2022.3. Please use the SectionLink method.")]
public static IContainer InternalLink(this IContainer element, string locationName)
{
return element.Element(new InternalLink
return element.SectionLink(locationName);
}
public static IContainer SectionLink(this IContainer element, string sectionName)
{
return element.Element(new SectionLink
{
LocationName = locationName
SectionName = sectionName
});
}
@@ -136,8 +154,7 @@ namespace QuestPDF.Fluent
});
}
// TODO: deprecated Box method in QuestPDF 2022.1
[Obsolete("This element has been renamed. Please use the MinimalBox method.")]
[Obsolete("This element has been renamed since version 2022.1. Please use the MinimalBox method.")]
public static IContainer Box(this IContainer element)
{
return element.Element(new MinimalBox());
@@ -160,5 +177,23 @@ namespace QuestPDF.Fluent
TextStyle = textStyle
});
}
public static IContainer DefaultTextStyle(this IContainer element, Func<TextStyle, TextStyle> handler)
{
return element.Element(new DefaultTextStyle
{
TextStyle = handler(TextStyle.Default)
});
}
public static IContainer StopPaging(this IContainer element)
{
return element.Element(new StopPaging());
}
public static IContainer ScaleToFit(this IContainer element)
{
return element.Element(new ScaleToFit());
}
}
}
+7 -7
View File
@@ -8,20 +8,20 @@ namespace QuestPDF.Fluent
{
internal Grid Grid { get; } = new Grid();
public void Spacing(float value)
public void Spacing(float value, Unit unit = Unit.Point)
{
VerticalSpacing(value);
HorizontalSpacing(value);
VerticalSpacing(value, unit);
HorizontalSpacing(value, unit);
}
public void VerticalSpacing(float value)
public void VerticalSpacing(float value, Unit unit = Unit.Point)
{
Grid.VerticalSpacing = value;
Grid.VerticalSpacing = value.ToPoints(unit);
}
public void HorizontalSpacing(float value)
public void HorizontalSpacing(float value, Unit unit = Unit.Point)
{
Grid.HorizontalSpacing = value;
Grid.HorizontalSpacing = value.ToPoints(unit);
}
public void Columns(int value = Grid.DefaultColumnsCount)
+12 -5
View File
@@ -10,14 +10,21 @@ namespace QuestPDF.Fluent
{
internal Inlined Inlined { get; } = new Inlined();
public void Spacing(float value)
public void Spacing(float value, Unit unit = Unit.Point)
{
VerticalSpacing(value);
HorizontalSpacing(value);
VerticalSpacing(value, unit);
HorizontalSpacing(value, unit);
}
public void VerticalSpacing(float value) => Inlined.VerticalSpacing = value;
public void HorizontalSpacing(float value) => Inlined.HorizontalSpacing = value;
public void VerticalSpacing(float value, Unit unit = Unit.Point)
{
Inlined.VerticalSpacing = value.ToPoints(unit);
}
public void HorizontalSpacing(float value, Unit unit = Unit.Point)
{
Inlined.HorizontalSpacing = value.ToPoints(unit);
}
public void BaselineTop() => Inlined.BaselineAlignment = VerticalAlignment.Top;
public void BaselineMiddle() => Inlined.BaselineAlignment = VerticalAlignment.Middle;
+36
View File
@@ -0,0 +1,36 @@
using System;
using QuestPDF.Elements;
using QuestPDF.Infrastructure;
namespace QuestPDF.Fluent
{
public static class LineExtensions
{
private static ILine Line(this IContainer element, LineType type, float size)
{
var line = new Line
{
Size = size,
Type = type
};
element.Element(line);
return line;
}
public static ILine LineVertical(this IContainer element, float size, Unit unit = Unit.Point)
{
return element.Line(LineType.Vertical, size.ToPoints(unit));
}
public static ILine LineHorizontal(this IContainer element, float size, Unit unit = Unit.Point)
{
return element.Line(LineType.Horizontal, size.ToPoints(unit));
}
public static void LineColor(this ILine descriptor, string value)
{
(descriptor as Line).Color = value;
}
}
}
+35
View File
@@ -0,0 +1,35 @@
using System;
using QuestPDF.Drawing;
using QuestPDF.Infrastructure;
namespace QuestPDF.Fluent
{
public class Document : IDocument
{
private Action<IDocumentContainer> ContentSource { get; }
private DocumentMetadata Metadata { get; set; } = DocumentMetadata.Default;
private Document(Action<IDocumentContainer> contentSource)
{
ContentSource = contentSource;
}
public static Document Create(Action<IDocumentContainer> handler)
{
return new Document(handler);
}
public Document WithMetadata(DocumentMetadata metadata)
{
Metadata = metadata ?? Metadata;
return this;
}
#region IDocument
public DocumentMetadata GetMetadata() => Metadata;
public void Compose(IDocumentContainer container) => ContentSource(container);
#endregion
}
}
+20 -14
View File
@@ -14,39 +14,45 @@ namespace QuestPDF.Fluent
return element.Element(padding);
}
public static IContainer Padding(this IContainer element, float value)
public static IContainer Padding(this IContainer element, float value, Unit unit = Unit.Point)
{
return element.PaddingVertical(value).PaddingHorizontal(value);
return element
.PaddingVertical(value, unit)
.PaddingHorizontal(value, unit);
}
public static IContainer PaddingHorizontal(this IContainer element, float value)
public static IContainer PaddingHorizontal(this IContainer element, float value, Unit unit = Unit.Point)
{
return element.PaddingLeft(value).PaddingRight(value);
return element
.PaddingLeft(value, unit)
.PaddingRight(value, unit);
}
public static IContainer PaddingVertical(this IContainer element, float value)
public static IContainer PaddingVertical(this IContainer element, float value, Unit unit = Unit.Point)
{
return element.PaddingTop(value).PaddingBottom(value);
return element
.PaddingTop(value, unit)
.PaddingBottom(value, unit);
}
public static IContainer PaddingTop(this IContainer element, float value)
public static IContainer PaddingTop(this IContainer element, float value, Unit unit = Unit.Point)
{
return element.Padding(x => x.Top = value);
return element.Padding(x => x.Top += value.ToPoints(unit));
}
public static IContainer PaddingBottom(this IContainer element, float value)
public static IContainer PaddingBottom(this IContainer element, float value, Unit unit = Unit.Point)
{
return element.Padding(x => x.Bottom = value);
return element.Padding(x => x.Bottom += value.ToPoints(unit));
}
public static IContainer PaddingLeft(this IContainer element, float value)
public static IContainer PaddingLeft(this IContainer element, float value, Unit unit = Unit.Point)
{
return element.Padding(x => x.Left = value);
return element.Padding(x => x.Left += value.ToPoints(unit));
}
public static IContainer PaddingRight(this IContainer element, float value)
public static IContainer PaddingRight(this IContainer element, float value, Unit unit = Unit.Point)
{
return element.Padding(x => x.Right = value);
return element.Padding(x => x.Right += value.ToPoints(unit));
}
}
}
+47 -20
View File
@@ -10,16 +10,24 @@ namespace QuestPDF.Fluent
{
internal Page Page { get; } = new Page();
public void Size(float width, float height, Unit unit = Unit.Inch)
{
var pageSize = new PageSize(width, height, unit);
MinSize(pageSize);
MaxSize(pageSize);
}
public void Size(PageSize pageSize)
{
MinSize(pageSize);
MaxSize(pageSize);
}
public void ContinuousSize(float width)
public void ContinuousSize(float width, Unit unit = Unit.Point)
{
MinSize(new PageSize(width, 0));
MaxSize(new PageSize(width, Infrastructure.Size.Max.Height));
MinSize(new PageSize(width.ToPoints(unit), 0));
MaxSize(new PageSize(width.ToPoints(unit), Infrastructure.Size.Max.Height));
}
public void MinSize(PageSize pageSize)
@@ -32,42 +40,42 @@ namespace QuestPDF.Fluent
Page.MaxSize = pageSize;
}
public void MarginLeft(float value)
public void MarginLeft(float value, Unit unit = Unit.Point)
{
Page.MarginLeft = value;
Page.MarginLeft = value.ToPoints(unit);
}
public void MarginRight(float value)
public void MarginRight(float value, Unit unit = Unit.Point)
{
Page.MarginRight = value;
Page.MarginRight = value.ToPoints(unit);
}
public void MarginTop(float value)
public void MarginTop(float value, Unit unit = Unit.Point)
{
Page.MarginTop = value;
Page.MarginTop = value.ToPoints(unit);
}
public void MarginBottom(float value)
public void MarginBottom(float value, Unit unit = Unit.Point)
{
Page.MarginBottom = value;
Page.MarginBottom = value.ToPoints(unit);
}
public void MarginVertical(float value)
public void MarginVertical(float value, Unit unit = Unit.Point)
{
MarginTop(value);
MarginBottom(value);
MarginTop(value, unit);
MarginBottom(value, unit);
}
public void MarginHorizontal(float value)
public void MarginHorizontal(float value, Unit unit = Unit.Point)
{
MarginLeft(value);
MarginRight(value);
MarginLeft(value, unit);
MarginRight(value, unit);
}
public void Margin(float value)
public void Margin(float value, Unit unit = Unit.Point)
{
MarginVertical(value);
MarginHorizontal(value);
MarginVertical(value, unit);
MarginHorizontal(value, unit);
}
public void DefaultTextStyle(TextStyle textStyle)
@@ -75,11 +83,30 @@ namespace QuestPDF.Fluent
Page.DefaultTextStyle = textStyle;
}
public void DefaultTextStyle(Func<TextStyle, TextStyle> handler)
{
DefaultTextStyle(handler(TextStyle.Default));
}
public void Background(string color)
{
Page.BackgroundColor = color;
}
public IContainer Background()
{
var container = new Container();
Page.Background = container;
return container;
}
public IContainer Foreground()
{
var container = new Container();
Page.Foreground = container;
return container;
}
public IContainer Header()
{
var container = new Container();
+35 -14
View File
@@ -6,30 +6,51 @@ namespace QuestPDF.Fluent
{
public class RowDescriptor
{
internal Row Row { get; } = new Row();
internal Row Row { get; } = new();
public void Spacing(float value)
{
Row.Spacing = value;
}
public IContainer ConstantColumn(float width)
private IContainer Item(RowItemType type, float size = 0)
{
return Column(constantWidth: width);
}
public IContainer RelativeColumn(float width = 1)
{
return Column(relativeWidth: width);
}
public IContainer Column(float constantWidth = 0, float relativeWidth = 0)
{
var element = new RowElement(constantWidth, relativeWidth);
var element = new RowItem
{
Type = type,
Size = size
};
Row.Items.Add(element);
return element;
}
[Obsolete("This element has been renamed since version 2022.2. Please use the RelativeItem method.")]
public IContainer RelativeColumn(float size = 1)
{
return Item(RowItemType.Relative, size);
}
[Obsolete("This element has been renamed since version 2022.2. Please use the ConstantItem method.")]
public IContainer ConstantColumn(float size)
{
return Item(RowItemType.Constant, size);
}
public IContainer RelativeItem(float size = 1)
{
return Item(RowItemType.Relative, size);
}
public IContainer ConstantItem(float size, Unit unit = Unit.Point)
{
return Item(RowItemType.Constant, size.ToPoints(unit));
}
public IContainer AutoItem()
{
return Item(RowItemType.Auto);
}
}
public static class RowExtensions
+2 -2
View File
@@ -21,7 +21,7 @@ namespace QuestPDF.Fluent
public static IContainer ScaleHorizontal(this IContainer element, float value)
{
return element.Scale(x => x.ScaleX = value);
return element.Scale(x => x.ScaleX *= value);
}
public static IContainer FlipHorizontal(this IContainer element)
@@ -31,7 +31,7 @@ namespace QuestPDF.Fluent
public static IContainer ScaleVertical(this IContainer element, float value)
{
return element.Scale(x => x.ScaleY = value);
return element.Scale(x => x.ScaleY *= value);
}
public static IContainer FlipVertical(this IContainer element)
-33
View File
@@ -1,33 +0,0 @@
using System;
using QuestPDF.Elements;
using QuestPDF.Infrastructure;
namespace QuestPDF.Fluent
{
public class StackDescriptor
{
internal Stack Stack { get; } = new Stack();
public void Spacing(float value)
{
Stack.Spacing = value;
}
public IContainer Item()
{
var container = new Container();
Stack.Items.Add(container);
return container;
}
}
public static class StackExtensions
{
public static void Stack(this IContainer element, Action<StackDescriptor> handler)
{
var descriptor = new StackDescriptor();
handler(descriptor);
element.Component(descriptor.Stack);
}
}
}
+5 -5
View File
@@ -14,9 +14,9 @@ namespace QuestPDF.Fluent
{
internal List<TableColumnDefinition> Columns { get; } = new();
public void ConstantColumn(float width)
public void ConstantColumn(float width, Unit unit = Unit.Point)
{
ComplexColumn(constantWidth: width);
ComplexColumn(constantWidth: width.ToPoints(unit));
}
public void RelativeColumn(float width = 1)
@@ -24,7 +24,7 @@ namespace QuestPDF.Fluent
ComplexColumn(relativeWidth: width);
}
public void ComplexColumn(float constantWidth = 0, float relativeWidth = 0)
private void ComplexColumn(float constantWidth = 0, float relativeWidth = 0)
{
var columnDefinition = new TableColumnDefinition(constantWidth, relativeWidth);
Columns.Add(columnDefinition);
@@ -101,9 +101,9 @@ namespace QuestPDF.Fluent
container
.Decoration(decoration =>
{
decoration.Header().Element(HeaderTable);
decoration.Before().Element(HeaderTable);
decoration.Content().Element(ContentTable);
decoration.Footer().Element(FooterTable);
decoration.After().Element(FooterTable);
});
return container;
+133 -51
View File
@@ -10,6 +10,34 @@ using static System.String;
namespace QuestPDF.Fluent
{
public class TextSpanDescriptor
{
internal TextStyle TextStyle { get; }
internal TextSpanDescriptor(TextStyle textStyle)
{
TextStyle = textStyle;
}
}
public delegate string PageNumberFormatter(int? pageNumber);
public class TextPageNumberDescriptor : TextSpanDescriptor
{
internal PageNumberFormatter FormatFunction { get; private set; } = x => (x ?? 123).ToString();
internal TextPageNumberDescriptor(TextStyle textStyle) : base(textStyle)
{
}
public TextPageNumberDescriptor Format(PageNumberFormatter formatter)
{
FormatFunction = formatter ?? FormatFunction;
return this;
}
}
public class TextDescriptor
{
private ICollection<TextBlock> TextBlocks { get; } = new List<TextBlock>();
@@ -22,6 +50,11 @@ namespace QuestPDF.Fluent
DefaultStyle = style;
}
public void DefaultTextStyle(Func<TextStyle, TextStyle> style)
{
DefaultStyle = style(TextStyle.Default);
}
public void AlignLeft()
{
Alignment = HorizontalAlignment.Left;
@@ -37,9 +70,9 @@ namespace QuestPDF.Fluent
Alignment = HorizontalAlignment.Right;
}
public void ParagraphSpacing(float value)
public void ParagraphSpacing(float value, Unit unit = Unit.Point)
{
Spacing = value;
Spacing = value.ToPoints(unit);
}
private void AddItemToLastTextBlock(ITextBlockItem item)
@@ -50,12 +83,15 @@ namespace QuestPDF.Fluent
TextBlocks.Last().Items.Add(item);
}
public void Span(string? text, TextStyle? style = null)
[Obsolete("This element has been renamed since version 2022.3. Please use the overload that returns a TextSpanDescriptor object which allows to specify text style.")]
public void Span(string? text, TextStyle style)
{
if (IsNullOrEmpty(text))
return;
style ??= TextStyle.Default;
Span(text).Style(style);
}
public TextSpanDescriptor Span(string? text)
{
var style = DefaultStyle.Clone();
var items = text
.Replace("\r", string.Empty)
@@ -77,85 +113,123 @@ namespace QuestPDF.Fluent
})
.ToList()
.ForEach(TextBlocks.Add);
return new TextSpanDescriptor(style);
}
public void Line(string? text, TextStyle? style = null)
public TextSpanDescriptor Line(string? text)
{
text ??= string.Empty;
Span(text + Environment.NewLine, style);
return Span(text + Environment.NewLine);
}
public void EmptyLine()
public TextSpanDescriptor EmptyLine()
{
Span(Environment.NewLine);
return Span(Environment.NewLine);
}
private void PageNumber(string slotName, TextStyle? style = null)
private TextPageNumberDescriptor PageNumber(Func<IPageContext, int?> pageNumber)
{
if (IsNullOrEmpty(slotName))
throw new ArgumentException(nameof(slotName));
var style = DefaultStyle.Clone();
var descriptor = new TextPageNumberDescriptor(DefaultStyle);
style ??= TextStyle.Default;
AddItemToLastTextBlock(new TextBlockPageNumber()
AddItemToLastTextBlock(new TextBlockPageNumber
{
Style = style,
SlotName = slotName
Source = context => descriptor.FormatFunction(pageNumber(context)),
Style = style
});
return descriptor;
}
public void CurrentPageNumber(TextStyle? style = null)
public TextPageNumberDescriptor CurrentPageNumber()
{
PageNumber(PageContext.CurrentPageSlot, style);
return PageNumber(x => x.CurrentPage);
}
public void TotalPages(TextStyle? style = null)
public TextPageNumberDescriptor TotalPages()
{
PageNumber(PageContext.TotalPagesSlot, style);
return PageNumber(x => x.GetLocation(PageContext.DocumentLocation)?.Length);
}
[Obsolete("This element has been renamed since version 2022.3. Please use the BeginPageNumberOfSection method.")]
public void PageNumberOfLocation(string locationName, TextStyle? style = null)
{
if (IsNullOrEmpty(locationName))
throw new ArgumentException(nameof(locationName));
PageNumber(locationName, style);
BeginPageNumberOfSection(locationName).Style(style);
}
public void InternalLocation(string? text, string locationName, TextStyle? style = null)
public TextPageNumberDescriptor BeginPageNumberOfSection(string locationName)
{
if (IsNullOrEmpty(text))
return;
if (IsNullOrEmpty(locationName))
throw new ArgumentException(nameof(locationName));
return PageNumber(x => x.GetLocation(locationName)?.PageStart);
}
public TextPageNumberDescriptor EndPageNumberOfSection(string locationName)
{
return PageNumber(x => x.GetLocation(locationName)?.PageEnd);
}
public TextPageNumberDescriptor PageNumberWithinSection(string locationName)
{
return PageNumber(x => x.CurrentPage + 1 - x.GetLocation(locationName)?.PageEnd);
}
public TextPageNumberDescriptor TotalPagesWithinSection(string locationName)
{
return PageNumber(x => x.GetLocation(locationName)?.Length);
}
public TextSpanDescriptor SectionLink(string? text, string sectionName)
{
if (IsNullOrEmpty(sectionName))
throw new ArgumentException(nameof(sectionName));
style ??= TextStyle.Default;
var style = DefaultStyle.Clone();
var descriptor = new TextSpanDescriptor(style);
AddItemToLastTextBlock(new TextBlockInternalLink
if (IsNullOrEmpty(text))
return descriptor;
AddItemToLastTextBlock(new TextBlockSectionlLink
{
Style = style,
Text = text,
LocationName = locationName
SectionName = sectionName
});
return descriptor;
}
public void ExternalLocation(string? text, string url, TextStyle? style = null)
[Obsolete("This element has been renamed since version 2022.3. Please use the SectionLink method.")]
public void InternalLocation(string? text, string locationName, TextStyle? style = null)
{
SectionLink(text, locationName).Style(style);
}
public TextSpanDescriptor Hyperlink(string? text, string url)
{
if (IsNullOrEmpty(text))
return;
if (IsNullOrEmpty(url))
throw new ArgumentException(nameof(url));
var style = DefaultStyle.Clone();
var descriptor = new TextSpanDescriptor(style);
if (IsNullOrEmpty(text))
return descriptor;
style ??= TextStyle.Default;
AddItemToLastTextBlock(new TextBlockExternalLink
AddItemToLastTextBlock(new TextBlockHyperlink
{
Style = style,
Text = text,
Url = url
});
return descriptor;
}
[Obsolete("This element has been renamed since version 2022.3. Please use the Hyperlink method.")]
public void ExternalLocation(string? text, string url, TextStyle? style = null)
{
Hyperlink(text, url).Style(style);
}
public IContainer Element()
@@ -174,12 +248,12 @@ namespace QuestPDF.Fluent
{
TextBlocks.ToList().ForEach(x => x.Alignment = Alignment);
container.DefaultTextStyle(DefaultStyle).Stack(stack =>
container.DefaultTextStyle(DefaultStyle).Column(column =>
{
stack.Spacing(Spacing);
column.Spacing(Spacing);
foreach (var textBlock in TextBlocks)
stack.Item().Element(textBlock);
column.Item().Element(textBlock);
});
}
}
@@ -197,9 +271,17 @@ namespace QuestPDF.Fluent
descriptor.Compose(element);
}
public static void Text(this IContainer element, object? text, TextStyle? style = null)
[Obsolete("This element has been renamed since version 2022.3. Please use the overload that returns a TextSpanDescriptor object which allows to specify text style.")]
public static void Text(this IContainer element, object? text, TextStyle style)
{
element.Text(x => x.Span(text?.ToString(), style));
element.Text(text).Style(style);
}
public static TextSpanDescriptor Text(this IContainer element, object? text)
{
var descriptor = (TextSpanDescriptor) null;
element.Text(x => descriptor = x.Span(text?.ToString()));
return descriptor;
}
}
}
@@ -0,0 +1,126 @@
using System;
using System.Runtime.CompilerServices;
using QuestPDF.Infrastructure;
namespace QuestPDF.Fluent
{
public static class TextSpanDescriptorExtensions
{
public static T Style<T>(this T descriptor, TextStyle style) where T : TextSpanDescriptor
{
if (style == null)
return descriptor;
descriptor.TextStyle.OverrideStyle(style);
return descriptor;
}
public static T FontColor<T>(this T descriptor, string value) where T : TextSpanDescriptor
{
descriptor.TextStyle.Color = value;
return descriptor;
}
public static T BackgroundColor<T>(this T descriptor, string value) where T : TextSpanDescriptor
{
descriptor.TextStyle.BackgroundColor = value;
return descriptor;
}
public static T FontFamily<T>(this T descriptor, string value) where T : TextSpanDescriptor
{
descriptor.TextStyle.FontFamily = value;
return descriptor;
}
public static T FontSize<T>(this T descriptor, float value) where T : TextSpanDescriptor
{
descriptor.TextStyle.Size = value;
return descriptor;
}
public static T LineHeight<T>(this T descriptor, float value) where T : TextSpanDescriptor
{
descriptor.TextStyle.LineHeight = value;
return descriptor;
}
public static T Italic<T>(this T descriptor, bool value = true) where T : TextSpanDescriptor
{
descriptor.TextStyle.IsItalic = value;
return descriptor;
}
public static T Strikethrough<T>(this T descriptor, bool value = true) where T : TextSpanDescriptor
{
descriptor.TextStyle.HasStrikethrough = value;
return descriptor;
}
public static T Underline<T>(this T descriptor, bool value = true) where T : TextSpanDescriptor
{
descriptor.TextStyle.HasUnderline = value;
return descriptor;
}
#region Weight
public static T Weight<T>(this T descriptor, FontWeight weight) where T : TextSpanDescriptor
{
descriptor.TextStyle.FontWeight = weight;
return descriptor;
}
public static T Thin<T>(this T descriptor) where T : TextSpanDescriptor
{
return descriptor.Weight(FontWeight.Thin);
}
public static T ExtraLight<T>(this T descriptor) where T : TextSpanDescriptor
{
return descriptor.Weight(FontWeight.ExtraLight);
}
public static T Light<T>(this T descriptor) where T : TextSpanDescriptor
{
return descriptor.Weight(FontWeight.Light);
}
public static T NormalWeight<T>(this T descriptor) where T : TextSpanDescriptor
{
return descriptor.Weight(FontWeight.Normal);
}
public static T Medium<T>(this T descriptor) where T : TextSpanDescriptor
{
return descriptor.Weight(FontWeight.Medium);
}
public static T SemiBold<T>(this T descriptor) where T : TextSpanDescriptor
{
return descriptor.Weight(FontWeight.SemiBold);
}
public static T Bold<T>(this T descriptor) where T : TextSpanDescriptor
{
return descriptor.Weight(FontWeight.Bold);
}
public static T ExtraBold<T>(this T descriptor) where T : TextSpanDescriptor
{
return descriptor.Weight(FontWeight.ExtraBold);
}
public static T Black<T>(this T descriptor) where T : TextSpanDescriptor
{
return descriptor.Weight(FontWeight.Black);
}
public static T ExtraBlack<T>(this T descriptor) where T : TextSpanDescriptor
{
return descriptor.Weight(FontWeight.ExtraBlack);
}
#endregion
}
}
+19 -1
View File
@@ -13,7 +13,13 @@ namespace QuestPDF.Fluent
return style;
}
[Obsolete("This element has been renamed since version 2022.3. Please use the FontColor method.")]
public static TextStyle Color(this TextStyle style, string value)
{
return style.FontColor(value);
}
public static TextStyle FontColor(this TextStyle style, string value)
{
return style.Mutate(x => x.Color = value);
}
@@ -23,12 +29,24 @@ namespace QuestPDF.Fluent
return style.Mutate(x => x.BackgroundColor = value);
}
[Obsolete("This element has been renamed since version 2022.3. Please use the FontFamily method.")]
public static TextStyle FontType(this TextStyle style, string value)
{
return style.Mutate(x => x.FontType = value);
return style.FontFamily(value);
}
public static TextStyle FontFamily(this TextStyle style, string value)
{
return style.Mutate(x => x.FontFamily = value);
}
[Obsolete("This element has been renamed since version 2022.3. Please use the FontSize method.")]
public static TextStyle Size(this TextStyle style, float value)
{
return style.FontSize(value);
}
public static TextStyle FontSize(this TextStyle style, float value)
{
return style.Mutate(x => x.Size = value);
}
+4 -4
View File
@@ -14,14 +14,14 @@ namespace QuestPDF.Fluent
return element.Element(translate);
}
public static IContainer TranslateX(this IContainer element, float value)
public static IContainer TranslateX(this IContainer element, float value, Unit unit = Unit.Point)
{
return element.Translate(x => x.TranslateX = value);
return element.Translate(x => x.TranslateX += value.ToPoints(unit));
}
public static IContainer TranslateY(this IContainer element, float value)
public static IContainer TranslateY(this IContainer element, float value, Unit unit = Unit.Point)
{
return element.Translate(x => x.TranslateY = value);
return element.Translate(x => x.TranslateY += value.ToPoints(unit));
}
}
}
+5 -5
View File
@@ -8,10 +8,10 @@ namespace QuestPDF.Helpers
public readonly float Width;
public readonly float Height;
public PageSize(float width, float height)
public PageSize(float width, float height, Unit unit = Unit.Point)
{
Width = width;
Height = height;
Width = width.ToPoints(unit);
Height = height.ToPoints(unit);
}
public static implicit operator Size(PageSize pageSize) => new Size(pageSize.Width, pageSize.Height);
@@ -50,8 +50,8 @@ namespace QuestPDF.Helpers
public static PageSize EnvDL => new PageSize(311.9f, 623.7f);
public static PageSize Executive => new PageSize(522, 756);
public static PageSize Legal => new PageSize(612.4f, 1009.3f);
public static PageSize Letter => new PageSize(612.4f, 791);
public static PageSize Legal => new PageSize(612, 1008);
public static PageSize Letter => new PageSize(612, 792);
public static PageSize ARCH_A => new PageSize(649.2f, 864.7f);
public static PageSize ARCH_B => new PageSize(864.7f, 1295.6f);
+1 -1
View File
@@ -7,7 +7,7 @@ namespace QuestPDF.Helpers
{
public static class Placeholders
{
public static readonly Random Random = new Random(3); // 3
public static readonly Random Random = new();
#region Word Cache
+56
View File
@@ -0,0 +1,56 @@
using System;
using System.IO;
namespace QuestPDF.Helpers
{
/// <summary>
/// SkiaSharp calls the Position property when generating target document file.
/// If the output stream does not support the Position property, the NullReferenceException is thrown.
/// This wrapper fixes this issue by providing cached Position value (always the end of the stream / current length).
/// Example stream affected: HttpContext.Response.Body
/// </summary>
internal class WriteOnlyStream : Stream
{
private readonly Stream InnerStream;
private long StreamLength { get; set; }
public WriteOnlyStream(Stream stream)
{
if (!stream.CanWrite)
throw new NotSupportedException("Stream cannot be written");
InnerStream = stream;
}
public override bool CanRead => false;
public override bool CanSeek => false;
public override bool CanWrite => true;
public override long Length => StreamLength;
public override long Position
{
get => StreamLength;
set => throw new NotImplementedException();
}
public override void Flush() => InnerStream.Flush();
public override int Read(byte[] buffer, int offset, int count)
=> throw new NotImplementedException();
public override long Seek(long offset, SeekOrigin origin)
=> throw new NotImplementedException();
public override void SetLength(long value)
=> throw new NotImplementedException();
public override void Write(byte[] buffer, int offset, int count)
{
InnerStream.Write(buffer, offset, count);
StreamLength += count;
}
}
}

Some files were not shown because too many files have changed in this diff Show More