Compare commits
448 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| eb61b19abf | |||
| e684b9ec6d | |||
| 6074ed0e7e | |||
| a560c6c864 | |||
| 327c412c7d | |||
| 4b5eeea896 | |||
| ef89be9710 | |||
| 176566801b | |||
| 22a696fab9 | |||
| 9809faccb6 | |||
| 6ad465b9fa | |||
| e6c7befac4 | |||
| be3e4b59ec | |||
| e751bfd2ff | |||
| b05f76f848 | |||
| b5d7dbb346 | |||
| 0ab73f4f2b | |||
| 458b379208 | |||
| c3c0d7631c | |||
| a3ea8bceb6 | |||
| 455d187897 | |||
| 65e5d36ded | |||
| ad5e8ab27d | |||
| a17197addc | |||
| a1e7cbbeb3 | |||
| c00ac5e9c7 | |||
| 0e0a8d5d69 | |||
| bc341da6dd | |||
| 6096771b51 | |||
| 4f097d0fed | |||
| 24beea80c7 | |||
| e1e5778ff0 | |||
| 8679391609 | |||
| c1b60287ca | |||
| 011e1b7ce9 | |||
| 5fd72cbb4b | |||
| 7284998657 | |||
| 30bd7420e2 | |||
| bd48688670 | |||
| 68e3efde59 | |||
| 805fd55d94 | |||
| b3a3deb354 | |||
| 68e4a10e81 | |||
| e15a21ab0e | |||
| f851221718 | |||
| 91c93e68b5 | |||
| 3dcc35be97 | |||
| 3f32c6e2c5 | |||
| f1f609ccc1 | |||
| 5c07485cc1 | |||
| 42684ec180 | |||
| 08f9f706ce | |||
| 1f199423b0 | |||
| d8e640fcf3 | |||
| fb02d97a10 | |||
| d5f8a884d6 | |||
| 5de5be6429 | |||
| 96d57b241e | |||
| 920e61fc5e | |||
| 1cc606261c | |||
| 0a6c021fe9 | |||
| b65ee32daf | |||
| e496917d84 | |||
| a5004b986c | |||
| 84d7fcf41f | |||
| 641cc6dee2 | |||
| 9dbed14559 | |||
| 6323a8dba2 | |||
| 356dbb020d | |||
| bad6be1a28 | |||
| 27c7383437 | |||
| ecac19ee46 | |||
| fd37070d3b | |||
| 3b433f3efb | |||
| b3d6a39a98 | |||
| 8d1a449e69 | |||
| 7f0be69ffc | |||
| 8553131b99 | |||
| 1bf9198a51 | |||
| 563b782f6a | |||
| edf8db878a | |||
| 5fd6488068 | |||
| d6a7c3703d | |||
| 5844d3bdba | |||
| 011d44304a | |||
| a087be8afe | |||
| 483cbb6d46 | |||
| ec76cbd696 | |||
| a83d8c43fe | |||
| 5f25e37f7f | |||
| cafd27a60d | |||
| 5082bbbdf2 | |||
| d2ba21821f | |||
| 9bef7d64d5 | |||
| ca4352addd | |||
| 2906315fa7 | |||
| 6f036388b9 | |||
| 9cd4fd740e | |||
| c97acada76 | |||
| 19ff9062ad | |||
| e4f8243c9b | |||
| 1060e4a44a | |||
| 983edd5f76 | |||
| 5315165cf5 | |||
| ee3d87010e | |||
| 3ac91c4971 | |||
| 8e450934ab | |||
| bf193eec23 | |||
| c9104540aa | |||
| af60d84abe | |||
| 4345ce857f | |||
| 823572bf25 | |||
| e6bd63b3ec | |||
| bbf3d71e7e | |||
| d56117647b | |||
| 117913693c | |||
| 2ba716a5ba | |||
| be2108f6fe | |||
| c08bb963a6 | |||
| 8ed8897fd1 | |||
| 2a0e91af68 | |||
| f647b79a0a | |||
| b1286bb536 | |||
| 43d1a55df0 | |||
| d1cd7b06db | |||
| 7bce01d6e6 | |||
| e3ff2960e1 | |||
| 5d9e6f327b | |||
| b895931eb9 | |||
| 86a35b7cd3 | |||
| 65990ebf59 | |||
| c348106b3f | |||
| 9d86e9efd3 | |||
| 9770575f9a | |||
| e6e6febf8b | |||
| 5cbacc7ea1 | |||
| 8214a8429d | |||
| 389b8ce304 | |||
| e1f7cff4aa | |||
| 6801425082 | |||
| ec31c9b063 | |||
| 8a57dceb9d | |||
| 6c8867e1b3 | |||
| 20d86cbfde | |||
| 16a164e5b8 | |||
| 261f087b46 | |||
| c876350b05 | |||
| 58f3932241 | |||
| 350abc2838 | |||
| 5d4c9a5dd2 | |||
| b31d097b61 | |||
| 9f6c091706 | |||
| af2229eabe | |||
| b86946b396 | |||
| e1ae97d3ca | |||
| a7ac747776 | |||
| fab0f5bfe1 | |||
| f5f90bd235 | |||
| 603dc13d0f | |||
| e277c6a8dc | |||
| 5ff9d03f20 | |||
| 9ce9ca85be | |||
| 553c8ab719 | |||
| e768e9b06d | |||
| fd913a777c | |||
| ed9e6daec5 | |||
| ee6249a658 | |||
| b307304f46 | |||
| f028f82e11 | |||
| 719a3385f6 | |||
| 2adff11400 | |||
| 34fed6d547 | |||
| db2df75624 | |||
| 6b535752df | |||
| 556f87ff25 | |||
| fbebbd85eb | |||
| 4650c2a4ea | |||
| 5390fc3f1b | |||
| 1ba01a1cf5 | |||
| d4448437ac | |||
| bc853f48fc | |||
| 0468dd5f02 | |||
| 3ba09ea826 | |||
| 92abd32aae | |||
| e17867d1f3 | |||
| bd71f30c78 | |||
| 6a78a4ebdb | |||
| 9fc721d66b | |||
| 7d62dead86 | |||
| 52ad0f5c24 | |||
| 53107073a6 | |||
| c71bb3ea59 | |||
| 6cced3c143 | |||
| 425ea59cfe | |||
| 4d326496c6 | |||
| b7b6488d16 | |||
| f0ba5fc32d | |||
| 04da32e0e7 | |||
| 5bdb338996 | |||
| 06b30f2ad6 | |||
| d39f68a974 | |||
| db9d8b5fcf | |||
| a31a588b24 | |||
| cb2a3c6363 | |||
| e775f62fab | |||
| 056e3f244c | |||
| cd3f7f5a25 | |||
| a5805f6e43 | |||
| abe7b2f14c | |||
| 3e485a572c | |||
| fd0bb5b089 | |||
| cc51b62b73 | |||
| 8e16b5ad4f | |||
| 71e2743409 | |||
| ba9d3aa04b | |||
| 2a0b133185 | |||
| 6058dac524 | |||
| 25b8f62245 | |||
| 2c31f3d5b5 | |||
| f7cd89ccf6 | |||
| 6914a9063a | |||
| b87e8d5d4e | |||
| d740d67a64 | |||
| a14e9ca61f | |||
| 44b3ff8d0d | |||
| df6b88e14a | |||
| d4103797aa | |||
| d894914e7e | |||
| 90d1ce7af7 | |||
| 5fa32a06cc | |||
| acdf1ed1aa | |||
| a0b32e5ecf | |||
| 539dc83303 | |||
| d97fa2cef1 | |||
| 6d122c784d | |||
| 28743a81d1 | |||
| 1047854f77 | |||
| c5fbf7263c | |||
| fae2205ff8 | |||
| 1cbe4b1dc8 | |||
| 32b94cea43 | |||
| 0ca55a91e0 | |||
| 09e6b5253f | |||
| bacb136a3f | |||
| b7bc640e31 | |||
| 6e6f884a5f | |||
| b47a8ac409 | |||
| e51082400c | |||
| 52cfa3ab7e | |||
| cd181b1e2e | |||
| e71601ad0c | |||
| 3041aa53cc | |||
| 0ebf2833af | |||
| 25e6d9fbca | |||
| f80149792d | |||
| 2beee1d22e | |||
| 1af7c0a144 | |||
| eeca67e5bc | |||
| 4da3ef5dfa | |||
| f3d4151273 | |||
| 9a7e397db2 | |||
| d81f21e658 | |||
| 78909c6edf | |||
| a2d81b6adf | |||
| 4a761c023f | |||
| 24bbd06dff | |||
| 44bd646bab | |||
| 5f0291b2c4 | |||
| 1b91015d8f | |||
| 2b6444463d | |||
| a2629575e8 | |||
| c8824021cc | |||
| e1101d870d | |||
| 152304b467 | |||
| 8a1be90c4f | |||
| bc8a9e5dc5 | |||
| e54e09de15 | |||
| c49a424f87 | |||
| c63b0658f3 | |||
| 82cd1d7835 | |||
| 3ffab6e933 | |||
| e4d55e0298 | |||
| a6f4fd7f49 | |||
| 048a4748b2 | |||
| 8cc2fb8696 | |||
| 8e7b528cd9 | |||
| 26193313b8 | |||
| 555825e8be | |||
| ddde8e7517 | |||
| d2c82b5988 | |||
| d82aae95b5 | |||
| 5c11a2ab6a | |||
| 03e3a80fd9 | |||
| 4692c5ce62 | |||
| 919f916219 | |||
| 7a32f774a1 | |||
| fba71e6392 | |||
| e27585a504 | |||
| 9be497ee41 | |||
| 0fc0db55d2 | |||
| 9e7a1ed49d | |||
| c137bcfcde | |||
| b80b3414d8 | |||
| 5f7cee3420 | |||
| 435c42ab9e | |||
| 7b4923b45d | |||
| 8732b7e50b | |||
| 6b31322f92 | |||
| 6155d21207 | |||
| a0909eb057 | |||
| 11ff53946d | |||
| 0711bca1e1 | |||
| 297ca48c70 | |||
| d2601e9e5a | |||
| 421a926780 | |||
| 307759d1b3 | |||
| 00bcb2c25b | |||
| e430484e85 | |||
| d24d887a3a | |||
| 404914ea7e | |||
| 545b76fd40 | |||
| c801894ec4 | |||
| c21744e7de | |||
| 734d116bfe | |||
| 0abb67b748 | |||
| 828bb86737 | |||
| 040c47e017 | |||
| ff95fe287a | |||
| 47e9a49fe5 | |||
| b2826a06b8 | |||
| 321cba32ce | |||
| 26fdbb714d | |||
| f0cec8988a | |||
| ea2102fe4c | |||
| 6cca3ed057 | |||
| 9afe4ec104 | |||
| 846679007f | |||
| 0946607154 | |||
| d5f86e0d70 | |||
| 346513ee85 | |||
| d18ee5f282 | |||
| a85bc0abc1 | |||
| 82e39df572 | |||
| 1311b6db32 | |||
| b5645bfbd5 | |||
| 8217b3b54a | |||
| 726759f47a | |||
| bbd9c3379e | |||
| 697c5e238d | |||
| 21a92be87e | |||
| b0cca1ff48 | |||
| 78c5049eda | |||
| 3d0ca07b43 | |||
| a67fa754af | |||
| 208235ad1e | |||
| f24416474a | |||
| 01c2e8d3f5 | |||
| b3cef238ec | |||
| aede62c135 | |||
| db9db95dcc | |||
| 92263685d7 | |||
| ba4be521aa | |||
| f6967dfe8b | |||
| ff3dfe7831 | |||
| 5a1990d857 | |||
| 1b9b164583 | |||
| 9c64a6bc8d | |||
| 5402e5fedc | |||
| 2c14f91ebe | |||
| b533a32b61 | |||
| 262bd3c182 | |||
| fdf8e3eb1a | |||
| fcff989ab2 | |||
| 054e7a5f20 | |||
| a92cc25fb1 | |||
| 8db283d559 | |||
| 456c7a735b | |||
| 5bf2a9a3d3 | |||
| 8a64abaac2 | |||
| 68df390d0e | |||
| d73c694452 | |||
| 69e268733c | |||
| 657bbe2aac | |||
| 4479a49ad0 | |||
| 63288dfb60 | |||
| fc325d8b76 | |||
| 5ffeaa3075 | |||
| fe431fbc12 | |||
| e0de408821 | |||
| 8ce6afef5a | |||
| 1c70b20028 | |||
| 3aef4ec4c9 | |||
| 3649607bd9 | |||
| 9a6b11ab85 | |||
| ac29ed4794 | |||
| 5f7da8b5b3 | |||
| 78eef45ce2 | |||
| 5424d9f0a0 | |||
| 946fe91a98 | |||
| b5839d3989 | |||
| 2ca6d1cb47 | |||
| 49a3371af7 | |||
| 3f3ada6d8a | |||
| ba409a8712 | |||
| b96c67a8af | |||
| b9fbb9267f | |||
| 272b0fc11a | |||
| b2cc7bbd36 | |||
| e3ce194fd1 | |||
| 9b892c86ed | |||
| c9833d410d | |||
| f8057a840c | |||
| a2ce31022c | |||
| 763fdfac0f | |||
| ba8b35b1c0 | |||
| 648a359d15 | |||
| 630d3389d3 | |||
| a7c3c88e81 | |||
| 9524cc6eb1 | |||
| 4152b67679 | |||
| 8020cb30cb | |||
| 9358fb8937 | |||
| eb297177ea | |||
| 7f651932b9 | |||
| caaae5fa1c | |||
| f76caa28b1 | |||
| ea92655ef5 | |||
| 3f8463e9b7 | |||
| 02e25b193d | |||
| 7cc272ff9e | |||
| b8ea30be23 | |||
| 06418238dc | |||
| ed73c5b82d | |||
| 96732b5856 | |||
| c7008ad940 | |||
| 940616cd3e | |||
| 01a80032b0 | |||
| 3e5e3843d2 | |||
| d3911d5687 | |||
| 82e4dd36f0 | |||
| 561d6a004c | |||
| 59b77e55d4 | |||
| ea398c562f | |||
| cb493562fc | |||
| 5f636beb9d | |||
| 5b85ae2627 | |||
| 10683776bb | |||
| f2c47c4fd3 |
@@ -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
|
||||
@@ -0,0 +1,54 @@
|
||||
name: Build And Create Nuget Package
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '3 0 * * 0'
|
||||
|
||||
jobs:
|
||||
main:
|
||||
runs-on: ${{ matrix.environment }}
|
||||
strategy:
|
||||
matrix:
|
||||
environment:
|
||||
- macos-latest
|
||||
- ubuntu-latest
|
||||
- windows-latest
|
||||
env:
|
||||
DOTNET_NOLOGO: 1
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: 1
|
||||
NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages
|
||||
UNIT_TEST_PROJECT: QuestPDF.UnitTests/QuestPDF.UnitTests.csproj
|
||||
|
||||
steps:
|
||||
- name: 📝 Fetch Sources 📝
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: ⚙ Setup .NET 6.0 SDK ⚙
|
||||
uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
dotnet-version: '6.0.x'
|
||||
|
||||
- name: 🔄 Restore Nuget Packages 🔄
|
||||
shell: bash
|
||||
run: dotnet restore
|
||||
working-directory: ./Source
|
||||
|
||||
- name: 🛠 Build Solution 🛠
|
||||
shell: bash
|
||||
run: dotnet build -c Release --no-restore
|
||||
working-directory: ./Source
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
if: ${{ matrix.environment == 'windows-latest' }}
|
||||
with:
|
||||
name: Build Package
|
||||
path: |
|
||||
**/*.nupkg
|
||||
**/*.snupkg
|
||||
!.nuget
|
||||
@@ -0,0 +1 @@
|
||||
github: QuestPDF
|
||||
@@ -1,31 +0,0 @@
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
[TestFixture]
|
||||
public class BarcodeExamples
|
||||
{
|
||||
[Test]
|
||||
public void Barcode()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(300, 300)
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Background("#FFF")
|
||||
.Padding(25)
|
||||
.Stack(stack =>
|
||||
{
|
||||
stack.Item().Border(1).Background(Colors.Grey.Lighten3).Padding(5).Text("Barcode Example");
|
||||
stack.Item().Border(1).Padding(5).AlignCenter().Text("*123456789*", TextStyle.Default.FontType("CarolinaBar-Demo-25E2").Size(20));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
using Microcharts;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class ChartExample
|
||||
{
|
||||
[Test]
|
||||
public void MicrochartChart()
|
||||
{
|
||||
var entries = new[]
|
||||
{
|
||||
new ChartEntry(212)
|
||||
{
|
||||
Label = "UWP",
|
||||
ValueLabel = "112",
|
||||
Color = SKColor.Parse("#2c3e50")
|
||||
},
|
||||
new ChartEntry(248)
|
||||
{
|
||||
Label = "Android",
|
||||
ValueLabel = "648",
|
||||
Color = SKColor.Parse("#77d065")
|
||||
},
|
||||
new ChartEntry(128)
|
||||
{
|
||||
Label = "iOS",
|
||||
ValueLabel = "428",
|
||||
Color = SKColor.Parse("#b455b6")
|
||||
},
|
||||
new ChartEntry(514)
|
||||
{
|
||||
Label = "Forms",
|
||||
ValueLabel = "214",
|
||||
Color = SKColor.Parse("#3498db")
|
||||
}
|
||||
};
|
||||
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(300, 300)
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container.Extend().Canvas((canvas, size) =>
|
||||
{
|
||||
var bar = new BarChart
|
||||
{
|
||||
Entries = entries,
|
||||
IsAnimated = false,
|
||||
};
|
||||
bar.Draw(canvas, (int)size.Width, (int)size.Height);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class DebuggingTesting
|
||||
{
|
||||
[Test]
|
||||
public void Stack()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(500, 360)
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Background(Colors.White)
|
||||
.Padding(15)
|
||||
.Grid(grid =>
|
||||
{
|
||||
grid.Spacing(15);
|
||||
|
||||
grid.Item().Background(Colors.Grey.Medium).Text(Placeholders.LoremIpsum());
|
||||
grid.Item().DebugPointer("Test").Background(Colors.Grey.Lighten1).Height(1000); // <-- problem
|
||||
grid.Item().Background(Colors.Grey.Lighten2).Height(150);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
using System.IO;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class ImageExamples
|
||||
{
|
||||
[Test]
|
||||
public void LoadingImage()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A5)
|
||||
.ProducePdf()
|
||||
.ShowResults()
|
||||
.Render(page =>
|
||||
{
|
||||
page.Padding(25).Stack(stack =>
|
||||
{
|
||||
stack.Spacing(25);
|
||||
|
||||
stack.Item().Image("logo.png");
|
||||
|
||||
var binaryData = File.ReadAllBytes("logo.png");
|
||||
stack.Item().Image(binaryData);
|
||||
|
||||
using var stream = new FileStream("logo.png", FileMode.Open);
|
||||
stack.Item().Image(stream);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Exception()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A5)
|
||||
.ProducePdf()
|
||||
.ShowResults()
|
||||
.Render(page => page.Image("non_existent.png"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,271 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class TextExamples
|
||||
{
|
||||
[Test]
|
||||
public void SimpleTextBlock()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(500, 300)
|
||||
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(5)
|
||||
.Box()
|
||||
.Border(1)
|
||||
.Padding(10)
|
||||
.Text(text =>
|
||||
{
|
||||
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());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ParagraphSpacing()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(500, 300)
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(5)
|
||||
.Box()
|
||||
.Border(1)
|
||||
.Padding(10)
|
||||
.Text(text =>
|
||||
{
|
||||
text.ParagraphSpacing(10);
|
||||
|
||||
foreach (var i in Enumerable.Range(1, 3))
|
||||
{
|
||||
text.Span($"Paragraph {i}: ", TextStyle.Default.SemiBold());
|
||||
text.Line(Placeholders.Paragraph());
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CustomElement()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(500, 200)
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(5)
|
||||
.Box()
|
||||
.Border(1)
|
||||
.Padding(10)
|
||||
.Text(text =>
|
||||
{
|
||||
text.DefaultTextStyle(TextStyle.Default.Size(20));
|
||||
text.Span("This is a random image aligned to the baseline: ");
|
||||
|
||||
text.Element()
|
||||
.PaddingBottom(-6)
|
||||
.Height(24)
|
||||
.Width(48)
|
||||
.Image(Placeholders.Image);
|
||||
|
||||
text.Span(".");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TextElements()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A4)
|
||||
.ProducePdf()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(20)
|
||||
.Padding(10)
|
||||
.Box()
|
||||
.Border(1)
|
||||
.Padding(5)
|
||||
.Padding(10)
|
||||
.Text(text =>
|
||||
{
|
||||
text.DefaultTextStyle(TextStyle.Default);
|
||||
text.AlignLeft();
|
||||
text.ParagraphSpacing(10);
|
||||
|
||||
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());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TextStack()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A4)
|
||||
.ProducePdf()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(20)
|
||||
.Padding(10)
|
||||
.Box()
|
||||
.Border(1)
|
||||
.Padding(5)
|
||||
.Padding(10)
|
||||
.Text(text =>
|
||||
{
|
||||
text.DefaultTextStyle(TextStyle.Default);
|
||||
text.AlignLeft();
|
||||
text.ParagraphSpacing(10);
|
||||
|
||||
foreach (var i in Enumerable.Range(1, 100))
|
||||
text.Line($"{i}: {Placeholders.Paragraph()}");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SpaceIssue()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A4)
|
||||
.ProducePdf()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(20)
|
||||
.Padding(10)
|
||||
.Box()
|
||||
.Border(1)
|
||||
.Padding(5)
|
||||
.Padding(10)
|
||||
.Text(text =>
|
||||
{
|
||||
text.DefaultTextStyle(TextStyle.Default);
|
||||
text.AlignLeft();
|
||||
text.ParagraphSpacing(10);
|
||||
|
||||
text.Span(Placeholders.LoremIpsum());
|
||||
|
||||
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.EmptyLine();
|
||||
|
||||
text.Span("The new text element also supports injecting custom content between words: ");
|
||||
text.Element().PaddingBottom(-10).Height(16).Width(32).Image(Placeholders.Image);
|
||||
text.Span(".");
|
||||
|
||||
text.EmptyLine();
|
||||
|
||||
text.Span("This is page number ");
|
||||
text.CurrentPageNumber();
|
||||
text.Span(" out of ");
|
||||
text.TotalPages();
|
||||
|
||||
text.EmptyLine();
|
||||
|
||||
text.ExternalLocation("Please visit QuestPDF website", "https://www.questpdf.com");
|
||||
|
||||
text.EmptyLine();
|
||||
|
||||
text.Span(Placeholders.Paragraphs());
|
||||
|
||||
|
||||
text.EmptyLine();
|
||||
|
||||
text.Span(Placeholders.Paragraphs(), TextStyle.Default.Italic());
|
||||
|
||||
text.Line("This is target text that does not show up. " + Placeholders.Paragraph());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void HugeList()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A4)
|
||||
.ProducePdf()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(20)
|
||||
.Padding(10)
|
||||
.Box()
|
||||
.Border(1)
|
||||
.Padding(5)
|
||||
.Padding(10)
|
||||
.Text(text =>
|
||||
{
|
||||
text.DefaultTextStyle(TextStyle.Default);
|
||||
text.AlignLeft();
|
||||
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("The new text element also supports injecting custom content between words: ");
|
||||
text.Element().PaddingBottom(-10).Height(16).Width(32).Image(Placeholders.Image);
|
||||
text.Span(".");
|
||||
|
||||
text.EmptyLine();
|
||||
|
||||
foreach (var i in Enumerable.Range(1, 100))
|
||||
{
|
||||
text.Line($"{i}: {Placeholders.Paragraph()}");
|
||||
|
||||
text.ExternalLocation("Please visit QuestPDF website", "https://www.questpdf.com");
|
||||
|
||||
text.Span("This is page number ");
|
||||
text.CurrentPageNumber();
|
||||
text.Span(" out of ");
|
||||
text.TotalPages();
|
||||
|
||||
text.EmptyLine();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace QuestPDF.ReportSample
|
||||
{
|
||||
public static class Helpers
|
||||
{
|
||||
public static Random Random { get; } = new Random(1);
|
||||
|
||||
public static string GetTestItem(string path) => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Resources", path);
|
||||
|
||||
public static byte[] GetImage(string name)
|
||||
{
|
||||
var photoPath = GetTestItem(name);
|
||||
return SKImage.FromEncodedData(photoPath).EncodedData.ToArray();
|
||||
}
|
||||
|
||||
public static Location RandomLocation()
|
||||
{
|
||||
return new Location
|
||||
{
|
||||
Longitude = Helpers.Random.NextDouble() * 360f - 180f,
|
||||
Latitude = Helpers.Random.NextDouble() * 180f - 90f
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.ReportSample.Layouts
|
||||
{
|
||||
public class TableOfContentsTemplate : IComponent
|
||||
{
|
||||
private List<ReportSection> Sections { get; }
|
||||
|
||||
public TableOfContentsTemplate(List<ReportSection> sections)
|
||||
{
|
||||
Sections = sections;
|
||||
}
|
||||
|
||||
public void Compose(IContainer container)
|
||||
{
|
||||
container
|
||||
.Decoration(decoration =>
|
||||
{
|
||||
decoration
|
||||
.Header()
|
||||
.PaddingBottom(5)
|
||||
.Text("Table of contents", Typography.Headline);
|
||||
|
||||
decoration.Content().Stack(stack =>
|
||||
{
|
||||
stack.Spacing(5);
|
||||
|
||||
for (var i = 0; i < Sections.Count; i++)
|
||||
stack.Item().Element(c => DrawLink(c, i+1, Sections[i].Title));
|
||||
|
||||
stack.Item().Element(c => DrawLink(c, Sections.Count+1, "Photos"));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void DrawLink(IContainer container, int number, string locationName)
|
||||
{
|
||||
container
|
||||
.InternalLink(locationName)
|
||||
.Row(row =>
|
||||
{
|
||||
row.ConstantColumn(25).Text($"{number}.");
|
||||
row.RelativeColumn().Text(locationName);
|
||||
row.ConstantColumn(150).AlignRight().Text(text => text.PageNumberOfLocation(locationName));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Configs;
|
||||
using BenchmarkDotNet.Engines;
|
||||
using BenchmarkDotNet.Running;
|
||||
using Microsoft.VisualStudio.TestPlatform.TestHost;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Drawing.Proxy;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Infrastructure;
|
||||
using QuestPDF.ReportSample.Layouts;
|
||||
|
||||
namespace QuestPDF.ReportSample
|
||||
{
|
||||
[SimpleJob(RunStrategy.Monitoring, launchCount: 0, warmupCount: 1, targetCount: 256)]
|
||||
[MinColumn, MaxColumn, MeanColumn, MedianColumn]
|
||||
public class PerformanceTests
|
||||
{
|
||||
private PageContext PageContext { get; set; }
|
||||
private DocumentMetadata Metadata { get; set; }
|
||||
private Container Content { get; set; }
|
||||
|
||||
[Test]
|
||||
public void Run()
|
||||
{
|
||||
ImagePlaceholder.Solid = true;
|
||||
|
||||
var configuration = ManualConfig
|
||||
.Create(DefaultConfig.Instance)
|
||||
.WithOptions(ConfigOptions.DisableOptimizationsValidator);
|
||||
|
||||
BenchmarkRunner.Run<PerformanceTests>(configuration);
|
||||
}
|
||||
|
||||
[IterationSetup]
|
||||
public void GenerateReportData()
|
||||
{
|
||||
ImagePlaceholder.Solid = true;
|
||||
|
||||
var model = DataSource.GetReport();
|
||||
var report = new StandardReport(model);
|
||||
Metadata = report.GetMetadata();
|
||||
|
||||
var documentContainer = new DocumentContainer();
|
||||
report.Compose(documentContainer);
|
||||
Content = documentContainer.Compose();
|
||||
|
||||
PageContext = new PageContext();
|
||||
DocumentGenerator.RenderPass(PageContext, new FreeCanvas(), Content, Metadata, null);
|
||||
|
||||
var sw = new Stopwatch();
|
||||
sw.Start();
|
||||
|
||||
Content.HandleVisitor(x =>
|
||||
{
|
||||
if (x is ICacheable)
|
||||
x.CreateProxy(y => new CacheProxy(y));
|
||||
});
|
||||
|
||||
sw.Stop();
|
||||
Console.WriteLine($"Creating cache took: {sw.ElapsedMilliseconds}");
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void GenerationTest()
|
||||
{
|
||||
DocumentGenerator.RenderPass(PageContext, new FreeCanvas(), Content, Metadata, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Infrastructure;
|
||||
using QuestPDF.UnitTests.TestEngine;
|
||||
|
||||
namespace QuestPDF.UnitTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class BoxTests
|
||||
{
|
||||
[Test]
|
||||
public void Measure() => SimpleContainerTests.Measure<Box>();
|
||||
|
||||
[Test]
|
||||
public void Draw_Wrap()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new Box
|
||||
{
|
||||
Child = x.CreateChild()
|
||||
})
|
||||
.DrawElement(new Size(400, 300))
|
||||
.ExpectChildMeasure(expectedInput: new Size(400, 300), returns: SpacePlan.Wrap())
|
||||
.CheckDrawResult();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_PartialRender()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new Box
|
||||
{
|
||||
Child = x.CreateChild()
|
||||
})
|
||||
.MeasureElement(new Size(400, 300))
|
||||
.ExpectChildMeasure(expectedInput: new Size(400, 300), returns: SpacePlan.PartialRender(200, 100))
|
||||
.ExpectChildDraw(new Size(200, 100))
|
||||
.CheckDrawResult();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_FullRender()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new Box
|
||||
{
|
||||
Child = x.CreateChild()
|
||||
})
|
||||
.MeasureElement(new Size(500, 400))
|
||||
.ExpectChildMeasure(expectedInput: new Size(500, 400), returns: SpacePlan.FullRender(300, 200))
|
||||
.ExpectChildDraw(new Size(300, 200))
|
||||
.CheckDrawResult();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Infrastructure;
|
||||
using QuestPDF.UnitTests.TestEngine;
|
||||
|
||||
namespace QuestPDF.UnitTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class DecorationTests
|
||||
{
|
||||
#region Measure
|
||||
|
||||
[Test]
|
||||
public void Measure_ReturnsWrap_WhenDecorationReturnsWrap()
|
||||
{
|
||||
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.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))
|
||||
.CheckMeasureResult(SpacePlan.Wrap());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_ReturnsWrap_WhenContentReturnsWrap()
|
||||
{
|
||||
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.FullRender(300, 100))
|
||||
.ExpectChildMeasure("content", new Size(400, 200), SpacePlan.Wrap())
|
||||
.CheckMeasureResult(SpacePlan.Wrap());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_ReturnsPartialRender_WhenContentReturnsPartialRender()
|
||||
{
|
||||
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.FullRender(300, 100))
|
||||
.ExpectChildMeasure("content", new Size(400, 200), SpacePlan.PartialRender(200, 150))
|
||||
.CheckMeasureResult(SpacePlan.PartialRender(400, 250));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_ReturnsFullRender_WhenContentReturnsFullRender()
|
||||
{
|
||||
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.FullRender(300, 100))
|
||||
.ExpectChildMeasure("content", new Size(400, 200), SpacePlan.FullRender(200, 150))
|
||||
.CheckMeasureResult(SpacePlan.FullRender(400, 250));
|
||||
}
|
||||
|
||||
#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")
|
||||
})
|
||||
.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)
|
||||
.CheckDrawResult();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
|
||||
[assembly: InternalsVisibleTo("QuestPDF.UnitTests")]
|
||||
[assembly: InternalsVisibleTo("QuestPDF.Examples")]
|
||||
[assembly: InternalsVisibleTo("QuestPDF.ReportSample")]
|
||||
@@ -1,172 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using QuestPDF.Drawing.Exceptions;
|
||||
using QuestPDF.Drawing.Proxy;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Elements.Text;
|
||||
using QuestPDF.Elements.Text.Items;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Drawing
|
||||
{
|
||||
static class DocumentGenerator
|
||||
{
|
||||
internal static void GeneratePdf(Stream stream, IDocument document)
|
||||
{
|
||||
var metadata = document.GetMetadata();
|
||||
var canvas = new PdfCanvas(stream, metadata);
|
||||
RenderDocument(canvas, document);
|
||||
}
|
||||
|
||||
internal static void GenerateXps(Stream stream, IDocument document)
|
||||
{
|
||||
var metadata = document.GetMetadata();
|
||||
var canvas = new XpsCanvas(stream, metadata);
|
||||
RenderDocument(canvas, document);
|
||||
}
|
||||
|
||||
internal static ICollection<byte[]> GenerateImages(IDocument document)
|
||||
{
|
||||
var metadata = document.GetMetadata();
|
||||
var canvas = new ImageCanvas(metadata);
|
||||
RenderDocument(canvas, document);
|
||||
|
||||
return canvas.Images;
|
||||
}
|
||||
|
||||
private static void RenderDocument<TCanvas>(TCanvas canvas, IDocument document)
|
||||
where TCanvas : ICanvas, IRenderingCanvas
|
||||
{
|
||||
var container = new DocumentContainer();
|
||||
document.Compose(container);
|
||||
var content = container.Compose();
|
||||
|
||||
var metadata = document.GetMetadata();
|
||||
var pageContext = new PageContext();
|
||||
|
||||
DebuggingState debuggingState = null;
|
||||
|
||||
if (System.Diagnostics.Debugger.IsAttached)
|
||||
{
|
||||
debuggingState = ApplyDebugging(content);
|
||||
}
|
||||
else
|
||||
{
|
||||
ApplyCaching(content);
|
||||
}
|
||||
|
||||
RenderPass(pageContext, new FreeCanvas(), content, metadata, debuggingState);
|
||||
RenderPass(pageContext, canvas, content, metadata, debuggingState);
|
||||
}
|
||||
|
||||
internal static void RenderPass<TCanvas>(PageContext pageContext, TCanvas canvas, Container content, DocumentMetadata documentMetadata, DebuggingState? debuggingState)
|
||||
where TCanvas : ICanvas, IRenderingCanvas
|
||||
{
|
||||
content.HandleVisitor(x => x?.Initialize(pageContext, canvas));
|
||||
content.HandleVisitor(x => (x as IStateResettable)?.ResetState());
|
||||
|
||||
canvas.BeginDocument();
|
||||
|
||||
var currentPage = 1;
|
||||
|
||||
while(true)
|
||||
{
|
||||
pageContext.SetPageNumber(currentPage);
|
||||
debuggingState?.Reset();
|
||||
|
||||
var spacePlan = content.Measure(Size.Max);
|
||||
|
||||
if (spacePlan.Type == SpacePlanType.Wrap)
|
||||
{
|
||||
canvas.EndDocument();
|
||||
ThrowLayoutException();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
canvas.BeginPage(spacePlan);
|
||||
content.Draw(spacePlan);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
canvas.EndDocument();
|
||||
throw new DocumentDrawingException("An exception occured during document drawing.", exception);
|
||||
}
|
||||
|
||||
canvas.EndPage();
|
||||
|
||||
if (currentPage >= documentMetadata.DocumentLayoutExceptionThreshold)
|
||||
{
|
||||
canvas.EndDocument();
|
||||
ThrowLayoutException();
|
||||
}
|
||||
|
||||
if (spacePlan.Type == SpacePlanType.FullRender)
|
||||
break;
|
||||
|
||||
currentPage++;
|
||||
}
|
||||
|
||||
canvas.EndDocument();
|
||||
|
||||
void ThrowLayoutException()
|
||||
{
|
||||
var message = $"Composed layout generates infinite document. This may happen in two cases. " +
|
||||
$"1) Your document and its layout configuration is correct but the content takes more than {documentMetadata.DocumentLayoutExceptionThreshold} pages. " +
|
||||
$"In this case, please increase the value {nameof(DocumentMetadata)}.{nameof(DocumentMetadata.DocumentLayoutExceptionThreshold)} property configured in the {nameof(IDocument.GetMetadata)} method. " +
|
||||
$"2) The layout configuration of your document is invalid. Some of the elements require more space than is provided." +
|
||||
$"Please analyze your documents structure to detect this element and fix its size constraints.";
|
||||
|
||||
throw new DocumentLayoutException(message)
|
||||
{
|
||||
ElementTrace = debuggingState?.BuildTrace() ?? "Debug trace is available only in the DEBUG mode."
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private static void ApplyCaching(Container content)
|
||||
{
|
||||
content.HandleVisitor(x =>
|
||||
{
|
||||
if (x is ICacheable)
|
||||
x.CreateProxy(y => new CacheProxy(y));
|
||||
});
|
||||
}
|
||||
|
||||
private static DebuggingState ApplyDebugging(Container content)
|
||||
{
|
||||
var debuggingState = new DebuggingState();
|
||||
|
||||
content.HandleVisitor(x =>
|
||||
{
|
||||
x.CreateProxy(y => new DebuggingProxy(debuggingState, y));
|
||||
});
|
||||
|
||||
return debuggingState;
|
||||
}
|
||||
|
||||
internal static void ApplyDefaultTextStyle(this Element content, TextStyle documentDefaultTextStyle)
|
||||
{
|
||||
documentDefaultTextStyle.ApplyGlobalStyle(TextStyle.LibraryDefault);
|
||||
|
||||
content.HandleVisitor(element =>
|
||||
{
|
||||
var text = element as TextBlock;
|
||||
|
||||
if (text == null)
|
||||
return;
|
||||
|
||||
foreach (var child in text.Children)
|
||||
{
|
||||
if (child is TextBlockSpan textSpan)
|
||||
textSpan.Style.ApplyGlobalStyle(documentDefaultTextStyle);
|
||||
|
||||
if (child is TextBlockElement textElement)
|
||||
ApplyDefaultTextStyle(textElement.Element, documentDefaultTextStyle);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace QuestPDF.Drawing
|
||||
{
|
||||
public class DocumentMetadata
|
||||
{
|
||||
public int ImageQuality { get; set; } = 101;
|
||||
public int RasterDpi { get; set; } = 72;
|
||||
public bool PdfA { get; set; }
|
||||
|
||||
public string? Title { get; set; }
|
||||
public string? Author { get; set; }
|
||||
public string? Subject { get; set; }
|
||||
public string? Keywords { get; set; }
|
||||
public string? Creator { get; set; }
|
||||
public string? Producer { get; set; }
|
||||
|
||||
public DateTime CreationDate { get; set; } = DateTime.Now;
|
||||
public DateTime ModifiedDate { get; set; } = DateTime.Now;
|
||||
|
||||
/// <summary>
|
||||
/// If the number of generated pages exceeds this threshold
|
||||
/// (likely due to infinite layout), the exception is thrown.
|
||||
/// </summary>
|
||||
public int DocumentLayoutExceptionThreshold { get; set; } = 250;
|
||||
|
||||
public static DocumentMetadata Default => new DocumentMetadata();
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace QuestPDF.Drawing.Exceptions
|
||||
{
|
||||
public class DocumentComposeException : Exception
|
||||
{
|
||||
public DocumentComposeException()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public DocumentComposeException(string message) : base(message)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public DocumentComposeException(string message, Exception inner) : base(message, inner)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace QuestPDF.Drawing.Exceptions
|
||||
{
|
||||
public class DocumentDrawingException : Exception
|
||||
{
|
||||
public DocumentDrawingException()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public DocumentDrawingException(string message) : base(message)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public DocumentDrawingException(string message, Exception inner) : base(message, inner)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace QuestPDF.Drawing.Exceptions
|
||||
{
|
||||
public class DocumentLayoutException : Exception
|
||||
{
|
||||
public string ElementTrace { get; set; }
|
||||
|
||||
public DocumentLayoutException()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public DocumentLayoutException(string message) : base(message)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public DocumentLayoutException(string message, Exception inner) : base(message, inner)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.IO;
|
||||
using QuestPDF.Infrastructure;
|
||||
using SkiaSharp;
|
||||
|
||||
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>();
|
||||
|
||||
public static void RegisterFontType(string fontName, Stream stream)
|
||||
{
|
||||
Typefaces.TryAdd(fontName, SKTypeface.FromStream(stream));
|
||||
}
|
||||
|
||||
internal static SKPaint ColorToPaint(this string color)
|
||||
{
|
||||
return ColorPaint.GetOrAdd(color, Convert);
|
||||
|
||||
static SKPaint Convert(string color)
|
||||
{
|
||||
return new SKPaint
|
||||
{
|
||||
Color = SKColor.Parse(color)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
internal static SKPaint ToPaint(this TextStyle style)
|
||||
{
|
||||
return Paints.GetOrAdd(style.Key, 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
|
||||
};
|
||||
}
|
||||
|
||||
static SKTypeface GetTypeface(TextStyle style)
|
||||
{
|
||||
if (Typefaces.TryGetValue(style.FontType, out var result))
|
||||
return result;
|
||||
|
||||
var slant = (style.IsItalic ?? false) ? SKFontStyleSlant.Italic : SKFontStyleSlant.Upright;
|
||||
|
||||
return SKTypeface.FromFamilyName(style.FontType, (int)(style.FontWeight ?? FontWeight.Normal), (int)SKFontStyleWidth.Normal, slant)
|
||||
?? throw new ArgumentException($"The typeface {style.FontType} could not be found.");
|
||||
}
|
||||
}
|
||||
|
||||
internal static SKFontMetrics ToFontMetrics(this TextStyle style)
|
||||
{
|
||||
return FontMetrics.GetOrAdd(style.Key, key => style.ToPaint().FontMetrics);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
namespace QuestPDF.Drawing
|
||||
{
|
||||
internal struct TextMeasurement
|
||||
{
|
||||
public int LineIndex { get; set; }
|
||||
public float FragmentWidth { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
using System.IO;
|
||||
using QuestPDF.Infrastructure;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace QuestPDF.Drawing
|
||||
{
|
||||
internal class XpsCanvas : SkiaDocumentCanvasBase
|
||||
{
|
||||
public XpsCanvas(Stream stream, DocumentMetadata documentMetadata)
|
||||
: base(SKDocument.CreateXps(stream, documentMetadata.RasterDpi))
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class Box : ContainerElement
|
||||
{
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
var targetSize = base.Measure(availableSpace);
|
||||
|
||||
if (targetSize.Type == SpacePlanType.Wrap)
|
||||
return;
|
||||
|
||||
base.Draw(targetSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
using System;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal enum DecorationType
|
||||
{
|
||||
Prepend,
|
||||
Append
|
||||
}
|
||||
|
||||
internal class BinaryDecoration : Element, ICacheable
|
||||
{
|
||||
public Element DecorationElement { get; set; } = Empty.Instance;
|
||||
public Element ContentElement { get; set; } = Empty.Instance;
|
||||
public DecorationType Type { get; set; }
|
||||
|
||||
internal override void HandleVisitor(Action<Element?> visit)
|
||||
{
|
||||
DecorationElement?.HandleVisitor(visit);
|
||||
ContentElement?.HandleVisitor(visit);
|
||||
|
||||
base.HandleVisitor(visit);
|
||||
}
|
||||
|
||||
internal override void CreateProxy(Func<Element, Element> create)
|
||||
{
|
||||
DecorationElement = create(DecorationElement);
|
||||
ContentElement = create(ContentElement);
|
||||
}
|
||||
|
||||
internal override SpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
var decorationMeasure = DecorationElement.Measure(availableSpace);
|
||||
|
||||
if (decorationMeasure.Type == SpacePlanType.Wrap || decorationMeasure.Type == SpacePlanType.PartialRender)
|
||||
return SpacePlan.Wrap();
|
||||
|
||||
var decorationSize = decorationMeasure;
|
||||
var contentMeasure = ContentElement.Measure(new Size(availableSpace.Width, availableSpace.Height - decorationSize.Height));
|
||||
|
||||
if (contentMeasure.Type == SpacePlanType.Wrap)
|
||||
return SpacePlan.Wrap();
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
Type = DecorationType.Prepend,
|
||||
DecorationElement = Header,
|
||||
ContentElement = new BinaryDecoration
|
||||
{
|
||||
Type = DecorationType.Append,
|
||||
ContentElement = Content,
|
||||
DecorationElement = Footer
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
using System;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class Page : IComponent
|
||||
{
|
||||
public TextStyle DefaultTextStyle { get; set; } = new TextStyle();
|
||||
|
||||
public Size MinSize { get; set; } = PageSizes.A4;
|
||||
public Size MaxSize { get; set; } = PageSizes.A4;
|
||||
|
||||
public float MarginLeft { get; set; }
|
||||
public float MarginRight { get; set; }
|
||||
public float MarginTop { get; set; }
|
||||
public float MarginBottom { get; set; }
|
||||
|
||||
public string BackgroundColor { get; set; } = Colors.Transparent;
|
||||
|
||||
public Element Header { get; set; } = Empty.Instance;
|
||||
public Element Content { get; set; } = Empty.Instance;
|
||||
public Element Footer { get; set; } = Empty.Instance;
|
||||
|
||||
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)
|
||||
|
||||
.Decoration(decoration =>
|
||||
{
|
||||
decoration
|
||||
.Header()
|
||||
.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
|
||||
.Footer()
|
||||
.DebugPointer("Page footer")
|
||||
.Element(Footer);
|
||||
});
|
||||
|
||||
(container as Element).ApplyDefaultTextStyle(DefaultTextStyle);
|
||||
|
||||
bool IsClose(float x, float y)
|
||||
{
|
||||
return Math.Abs(x - y) < Size.Epsilon;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,181 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal abstract class RowElement : Constrained
|
||||
{
|
||||
public float Size { get; protected set; }
|
||||
|
||||
public void SetWidth(float width)
|
||||
{
|
||||
MinWidth = width;
|
||||
MaxWidth = width;
|
||||
}
|
||||
}
|
||||
|
||||
internal class ConstantRowElement : RowElement
|
||||
{
|
||||
public ConstantRowElement(float size)
|
||||
{
|
||||
Size = size;
|
||||
SetWidth(size);
|
||||
}
|
||||
}
|
||||
|
||||
internal class RelativeRowElement : RowElement
|
||||
{
|
||||
public RelativeRowElement(float size)
|
||||
{
|
||||
Size = size;
|
||||
}
|
||||
}
|
||||
|
||||
internal class BinaryRow : Element, ICacheable
|
||||
{
|
||||
internal Element Left { get; set; }
|
||||
internal Element Right { get; set; }
|
||||
|
||||
internal override void HandleVisitor(Action<Element?> visit)
|
||||
{
|
||||
Left.HandleVisitor(visit);
|
||||
Right.HandleVisitor(visit);
|
||||
|
||||
base.HandleVisitor(visit);
|
||||
}
|
||||
|
||||
internal override void CreateProxy(Func<Element?, Element?> create)
|
||||
{
|
||||
Left = create(Left);
|
||||
Right = create(Right);
|
||||
}
|
||||
|
||||
internal override SpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
var leftMeasurement = Left.Measure(new Size(availableSpace.Width, availableSpace.Height));
|
||||
|
||||
if (leftMeasurement.Type == SpacePlanType.Wrap)
|
||||
return SpacePlan.Wrap();
|
||||
|
||||
var rightMeasurement = Right.Measure(new Size(availableSpace.Width - leftMeasurement.Width, availableSpace.Height));
|
||||
|
||||
if (rightMeasurement.Type == SpacePlanType.Wrap)
|
||||
return SpacePlan.Wrap();
|
||||
|
||||
var totalWidth = leftMeasurement.Width + rightMeasurement.Width;
|
||||
var totalHeight = Math.Max(leftMeasurement.Height, rightMeasurement.Height);
|
||||
|
||||
var targetSize = new Size(totalWidth, totalHeight);
|
||||
|
||||
if (leftMeasurement.Type == SpacePlanType.PartialRender || rightMeasurement.Type == SpacePlanType.PartialRender)
|
||||
return SpacePlan.PartialRender(targetSize);
|
||||
|
||||
return SpacePlan.FullRender(targetSize);
|
||||
}
|
||||
|
||||
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> Children { get; internal set; } = new List<RowElement>();
|
||||
private Element? RootElement { get; set; }
|
||||
|
||||
internal override void HandleVisitor(Action<Element?> visit)
|
||||
{
|
||||
if (RootElement == null)
|
||||
ComposeTree();
|
||||
|
||||
RootElement.HandleVisitor(visit);
|
||||
base.HandleVisitor(visit);
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
Children = AddSpacing(Children, Spacing);
|
||||
|
||||
var elements = Children.Cast<Element>().ToArray();
|
||||
RootElement = BuildTree(elements);
|
||||
}
|
||||
|
||||
private void UpdateElementsWidth(float availableWidth)
|
||||
{
|
||||
var constantWidth = Children
|
||||
.Where(x => x is ConstantRowElement)
|
||||
.Cast<ConstantRowElement>()
|
||||
.Sum(x => x.Size);
|
||||
|
||||
var relativeWidth = Children
|
||||
.Where(x => x is RelativeRowElement)
|
||||
.Cast<RelativeRowElement>()
|
||||
.Sum(x => x.Size);
|
||||
|
||||
var widthPerRelativeUnit = (availableWidth - constantWidth) / relativeWidth;
|
||||
|
||||
Children
|
||||
.Where(x => x is RelativeRowElement)
|
||||
.Cast<RelativeRowElement>()
|
||||
.ToList()
|
||||
.ForEach(x => x.SetWidth(x.Size * widthPerRelativeUnit));
|
||||
}
|
||||
|
||||
private static ICollection<RowElement> AddSpacing(ICollection<RowElement> elements, float spacing)
|
||||
{
|
||||
if (spacing < Size.Epsilon)
|
||||
return elements;
|
||||
|
||||
return elements
|
||||
.SelectMany(x => new[] { new ConstantRowElement(spacing), x })
|
||||
.Skip(1)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
Left = BuildTree(elements.Slice(0, half)),
|
||||
Right = BuildTree(elements.Slice(half))
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,145 +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 void HandleVisitor(Action<Element?> visit)
|
||||
{
|
||||
First?.HandleVisitor(visit);
|
||||
Second?.HandleVisitor(visit);
|
||||
|
||||
base.HandleVisitor(visit);
|
||||
}
|
||||
|
||||
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> Children { get; } = new List<Element>();
|
||||
public float Spacing { get; set; } = 0;
|
||||
|
||||
public void Compose(IContainer container)
|
||||
{
|
||||
var elements = AddSpacing(Spacing, Children);
|
||||
|
||||
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))
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
using QuestPDF.Elements.Text.Calculation;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements.Text.Items
|
||||
{
|
||||
internal class TextBlockPageNumber : TextBlockSpan
|
||||
{
|
||||
public string SlotName { get; set; }
|
||||
|
||||
public override TextMeasurementResult? Measure(TextMeasurementRequest request)
|
||||
{
|
||||
SetPageNumber(request.PageContext);
|
||||
return MeasureWithoutCache(request);
|
||||
}
|
||||
|
||||
public override void Draw(TextDrawingRequest request)
|
||||
{
|
||||
SetPageNumber(request.PageContext);
|
||||
base.Draw(request);
|
||||
}
|
||||
|
||||
private void SetPageNumber(IPageContext context)
|
||||
{
|
||||
var pageNumberPlaceholder = 123;
|
||||
|
||||
var pageNumber = context.GetRegisteredLocations().Contains(SlotName)
|
||||
? context.GetLocationPage(SlotName)
|
||||
: pageNumberPlaceholder;
|
||||
|
||||
Text = pageNumber.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Elements.Text.Calculation;
|
||||
using QuestPDF.Infrastructure;
|
||||
using Size = QuestPDF.Infrastructure.Size;
|
||||
|
||||
namespace QuestPDF.Elements.Text.Items
|
||||
{
|
||||
internal class TextBlockSpan : ITextBlockItem
|
||||
{
|
||||
public string Text { get; set; }
|
||||
public TextStyle Style { get; set; } = new TextStyle();
|
||||
|
||||
private Dictionary<(int startIndex, float availableWidth), TextMeasurementResult?> MeasureCache =
|
||||
new Dictionary<(int startIndex, float availableWidth), TextMeasurementResult?>();
|
||||
|
||||
public virtual TextMeasurementResult? Measure(TextMeasurementRequest request)
|
||||
{
|
||||
var cacheKey = (request.StartIndex, request.AvailableWidth);
|
||||
|
||||
if (!MeasureCache.ContainsKey(cacheKey))
|
||||
MeasureCache[cacheKey] = MeasureWithoutCache(request);
|
||||
|
||||
return MeasureCache[cacheKey];
|
||||
}
|
||||
|
||||
internal TextMeasurementResult? MeasureWithoutCache(TextMeasurementRequest request)
|
||||
{
|
||||
const char space = ' ';
|
||||
|
||||
var paint = Style.ToPaint();
|
||||
var fontMetrics = Style.ToFontMetrics();
|
||||
|
||||
var startIndex = request.StartIndex;
|
||||
|
||||
if (request.IsFirstLineElement)
|
||||
{
|
||||
while (startIndex + 1 < Text.Length && Text[startIndex] == space)
|
||||
startIndex++;
|
||||
}
|
||||
|
||||
if (Text.Length == 0)
|
||||
{
|
||||
return new TextMeasurementResult
|
||||
{
|
||||
Width = 0,
|
||||
|
||||
LineHeight = Style.LineHeight ?? 1,
|
||||
Ascent = fontMetrics.Ascent,
|
||||
Descent = fontMetrics.Descent
|
||||
};
|
||||
}
|
||||
|
||||
// start breaking text from requested position
|
||||
var text = Text.Substring(startIndex);
|
||||
|
||||
var textLength = (int)paint.BreakText(text, request.AvailableWidth);
|
||||
|
||||
if (textLength <= 0)
|
||||
return null;
|
||||
|
||||
if (textLength < text.Length && text[textLength] == space)
|
||||
textLength++;
|
||||
|
||||
// break text only on spaces
|
||||
if (textLength < text.Length)
|
||||
{
|
||||
var lastSpaceIndex = text.Substring(0, textLength).LastIndexOf(space) - 1;
|
||||
|
||||
if (lastSpaceIndex <= 0)
|
||||
{
|
||||
if (!request.IsFirstLineElement)
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
textLength = lastSpaceIndex + 1;
|
||||
}
|
||||
}
|
||||
|
||||
text = text.Substring(0, textLength);
|
||||
|
||||
var endIndex = startIndex + textLength;
|
||||
var nextIndex = endIndex;
|
||||
|
||||
while (nextIndex + 1 < Text.Length && Text[nextIndex] == space)
|
||||
nextIndex++;
|
||||
|
||||
// measure final text
|
||||
var width = paint.MeasureText(text);
|
||||
|
||||
return new TextMeasurementResult
|
||||
{
|
||||
Width = width,
|
||||
|
||||
Ascent = fontMetrics.Ascent,
|
||||
Descent = fontMetrics.Descent,
|
||||
|
||||
LineHeight = Style.LineHeight ?? 1,
|
||||
|
||||
StartIndex = startIndex,
|
||||
EndIndex = endIndex,
|
||||
NextIndex = nextIndex,
|
||||
TotalIndex = Text.Length
|
||||
};
|
||||
}
|
||||
|
||||
public virtual void Draw(TextDrawingRequest request)
|
||||
{
|
||||
var fontMetrics = Style.ToFontMetrics();
|
||||
|
||||
var text = Text.Substring(request.StartIndex, request.EndIndex - request.StartIndex);
|
||||
|
||||
request.Canvas.DrawRectangle(new Position(0, request.TotalAscent), new Size(request.TextSize.Width, request.TextSize.Height), Style.BackgroundColor);
|
||||
request.Canvas.DrawText(text, Position.Zero, Style);
|
||||
|
||||
// draw underline
|
||||
if ((Style.HasUnderline ?? false) && fontMetrics.UnderlinePosition.HasValue)
|
||||
DrawLine(fontMetrics.UnderlinePosition.Value, fontMetrics.UnderlineThickness ?? 1);
|
||||
|
||||
// draw stroke
|
||||
if ((Style.HasStrikethrough ?? false) && fontMetrics.StrikeoutPosition.HasValue)
|
||||
DrawLine(fontMetrics.StrikeoutPosition.Value, fontMetrics.StrikeoutThickness ?? 1);
|
||||
|
||||
void DrawLine(float offset, float thickness)
|
||||
{
|
||||
request.Canvas.DrawRectangle(new Position(0, offset - thickness / 2f), new Size(request.TextSize.Width, thickness), Style.Color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
using System;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Fluent
|
||||
{
|
||||
public class PageDescriptor
|
||||
{
|
||||
internal Page Page { get; } = new Page();
|
||||
|
||||
public void Size(PageSize pageSize)
|
||||
{
|
||||
MinSize(pageSize);
|
||||
MaxSize(pageSize);
|
||||
}
|
||||
|
||||
public void ContinuousSize(float width)
|
||||
{
|
||||
MinSize(new PageSize(width, 0));
|
||||
MaxSize(new PageSize(width, Infrastructure.Size.Max.Height));
|
||||
}
|
||||
|
||||
public void MinSize(PageSize pageSize)
|
||||
{
|
||||
Page.MinSize = pageSize;
|
||||
}
|
||||
|
||||
public void MaxSize(PageSize pageSize)
|
||||
{
|
||||
Page.MaxSize = pageSize;
|
||||
}
|
||||
|
||||
public void MarginLeft(float value)
|
||||
{
|
||||
Page.MarginLeft = value;
|
||||
}
|
||||
|
||||
public void MarginRight(float value)
|
||||
{
|
||||
Page.MarginRight = value;
|
||||
}
|
||||
|
||||
public void MarginTop(float value)
|
||||
{
|
||||
Page.MarginTop = value;
|
||||
}
|
||||
|
||||
public void MarginBottom(float value)
|
||||
{
|
||||
Page.MarginBottom = value;
|
||||
}
|
||||
|
||||
public void MarginVertical(float value)
|
||||
{
|
||||
MarginTop(value);
|
||||
MarginBottom(value);
|
||||
}
|
||||
|
||||
public void MarginHorizontal(float value)
|
||||
{
|
||||
MarginLeft(value);
|
||||
MarginRight(value);
|
||||
}
|
||||
|
||||
public void Margin(float value)
|
||||
{
|
||||
MarginVertical(value);
|
||||
MarginHorizontal(value);
|
||||
}
|
||||
|
||||
public void DefaultTextStyle(TextStyle textStyle)
|
||||
{
|
||||
Page.DefaultTextStyle = textStyle;
|
||||
}
|
||||
|
||||
public void Background(string color)
|
||||
{
|
||||
Page.BackgroundColor = color;
|
||||
}
|
||||
|
||||
public IContainer Header()
|
||||
{
|
||||
var container = new Container();
|
||||
Page.Header = container;
|
||||
return container;
|
||||
}
|
||||
|
||||
public IContainer Content()
|
||||
{
|
||||
var container = new Container();
|
||||
Page.Content = container;
|
||||
return container;
|
||||
}
|
||||
|
||||
public IContainer Footer()
|
||||
{
|
||||
var container = new Container();
|
||||
Page.Footer = container;
|
||||
return container;
|
||||
}
|
||||
}
|
||||
|
||||
public static class PageExtensions
|
||||
{
|
||||
public static IDocumentContainer Page(this IDocumentContainer document, Action<PageDescriptor> handler)
|
||||
{
|
||||
var descriptor = new PageDescriptor();
|
||||
handler(descriptor);
|
||||
|
||||
(document as DocumentContainer).Pages.Add(descriptor.Page);
|
||||
|
||||
return document;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
using System;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Fluent
|
||||
{
|
||||
public class RowDescriptor
|
||||
{
|
||||
internal Row Row { get; } = new Row();
|
||||
|
||||
public void Spacing(float value)
|
||||
{
|
||||
Row.Spacing = value;
|
||||
}
|
||||
|
||||
public IContainer ConstantColumn(float width)
|
||||
{
|
||||
var element = new ConstantRowElement(width);
|
||||
|
||||
Row.Children.Add(element);
|
||||
return element;
|
||||
}
|
||||
|
||||
public IContainer RelativeColumn(float width = 1)
|
||||
{
|
||||
var element = new RelativeRowElement(width);
|
||||
|
||||
Row.Children.Add(element);
|
||||
return element;
|
||||
}
|
||||
}
|
||||
|
||||
public static class RowExtensions
|
||||
{
|
||||
public static void Row(this IContainer element, Action<RowDescriptor> handler)
|
||||
{
|
||||
var descriptor = new RowDescriptor();
|
||||
handler(descriptor);
|
||||
element.Element(descriptor.Row);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.Children.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,208 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Elements.Text;
|
||||
using QuestPDF.Elements.Text.Items;
|
||||
using QuestPDF.Infrastructure;
|
||||
using static System.String;
|
||||
|
||||
namespace QuestPDF.Fluent
|
||||
{
|
||||
public class TextDescriptor
|
||||
{
|
||||
private ICollection<TextBlock> TextBlocks { get; } = new List<TextBlock>();
|
||||
private TextStyle DefaultStyle { get; set; } = TextStyle.Default;
|
||||
internal HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left;
|
||||
private float Spacing { get; set; } = 0f;
|
||||
|
||||
public void DefaultTextStyle(TextStyle style)
|
||||
{
|
||||
DefaultStyle = style;
|
||||
}
|
||||
|
||||
public void AlignLeft()
|
||||
{
|
||||
Alignment = HorizontalAlignment.Left;
|
||||
}
|
||||
|
||||
public void AlignCenter()
|
||||
{
|
||||
Alignment = HorizontalAlignment.Center;
|
||||
}
|
||||
|
||||
public void AlignRight()
|
||||
{
|
||||
Alignment = HorizontalAlignment.Right;
|
||||
}
|
||||
|
||||
public void ParagraphSpacing(float value)
|
||||
{
|
||||
Spacing = value;
|
||||
}
|
||||
|
||||
private void AddItemToLastTextBlock(ITextBlockItem item)
|
||||
{
|
||||
if (!TextBlocks.Any())
|
||||
TextBlocks.Add(new TextBlock());
|
||||
|
||||
TextBlocks.Last().Children.Add(item);
|
||||
}
|
||||
|
||||
public void Span(string? text, TextStyle? style = null)
|
||||
{
|
||||
if (IsNullOrEmpty(text))
|
||||
return;
|
||||
|
||||
style ??= TextStyle.Default;
|
||||
|
||||
var items = text
|
||||
.Replace("\r", string.Empty)
|
||||
.Split(new[] { '\n' }, StringSplitOptions.None)
|
||||
.Select(x => new TextBlockSpan
|
||||
{
|
||||
Text = x,
|
||||
Style = style
|
||||
})
|
||||
.ToList();
|
||||
|
||||
AddItemToLastTextBlock(items.First());
|
||||
|
||||
items
|
||||
.Skip(1)
|
||||
.Select(x => new TextBlock
|
||||
{
|
||||
Children = new List<ITextBlockItem> { x }
|
||||
})
|
||||
.ToList()
|
||||
.ForEach(TextBlocks.Add);
|
||||
}
|
||||
|
||||
public void Line(string? text, TextStyle? style = null)
|
||||
{
|
||||
text ??= string.Empty;
|
||||
Span(text + Environment.NewLine, style);
|
||||
}
|
||||
|
||||
public void EmptyLine()
|
||||
{
|
||||
Span(Environment.NewLine);
|
||||
}
|
||||
|
||||
private void PageNumber(string slotName, TextStyle? style = null)
|
||||
{
|
||||
if (IsNullOrEmpty(slotName))
|
||||
throw new ArgumentException(nameof(slotName));
|
||||
|
||||
style ??= TextStyle.Default;
|
||||
|
||||
AddItemToLastTextBlock(new TextBlockPageNumber()
|
||||
{
|
||||
Style = style,
|
||||
SlotName = slotName
|
||||
});
|
||||
}
|
||||
|
||||
public void CurrentPageNumber(TextStyle? style = null)
|
||||
{
|
||||
PageNumber(PageContext.CurrentPageSlot, style);
|
||||
}
|
||||
|
||||
public void TotalPages(TextStyle? style = null)
|
||||
{
|
||||
PageNumber(PageContext.TotalPagesSlot, style);
|
||||
}
|
||||
|
||||
public void PageNumberOfLocation(string locationName, TextStyle? style = null)
|
||||
{
|
||||
if (IsNullOrEmpty(locationName))
|
||||
throw new ArgumentException(nameof(locationName));
|
||||
|
||||
PageNumber(locationName, style);
|
||||
}
|
||||
|
||||
public void InternalLocation(string? text, string locationName, TextStyle? style = null)
|
||||
{
|
||||
if (IsNullOrEmpty(text))
|
||||
return;
|
||||
|
||||
if (IsNullOrEmpty(locationName))
|
||||
throw new ArgumentException(nameof(locationName));
|
||||
|
||||
style ??= TextStyle.Default;
|
||||
|
||||
AddItemToLastTextBlock(new TextBlockInternalLink
|
||||
{
|
||||
Style = style,
|
||||
Text = text,
|
||||
LocationName = locationName
|
||||
});
|
||||
}
|
||||
|
||||
public void ExternalLocation(string? text, string url, TextStyle? style = null)
|
||||
{
|
||||
if (IsNullOrEmpty(text))
|
||||
return;
|
||||
|
||||
if (IsNullOrEmpty(url))
|
||||
throw new ArgumentException(nameof(url));
|
||||
|
||||
style ??= TextStyle.Default;
|
||||
|
||||
AddItemToLastTextBlock(new TextBlockExternalLink
|
||||
{
|
||||
Style = style,
|
||||
Text = text,
|
||||
Url = url
|
||||
});
|
||||
}
|
||||
|
||||
public IContainer Element()
|
||||
{
|
||||
var container = new Container();
|
||||
|
||||
AddItemToLastTextBlock(new TextBlockElement
|
||||
{
|
||||
Element = container
|
||||
});
|
||||
|
||||
return container.AlignBottom().Box();
|
||||
}
|
||||
|
||||
internal void Compose(IContainer container)
|
||||
{
|
||||
TextBlocks.ToList().ForEach(x => x.Alignment = Alignment);
|
||||
|
||||
foreach (var textBlockSpan in TextBlocks.SelectMany(x => x.Children).Where(x => x is TextBlockSpan).Cast<TextBlockSpan>())
|
||||
textBlockSpan.Style.ApplyParentStyle(DefaultStyle);
|
||||
|
||||
container.Stack(stack =>
|
||||
{
|
||||
stack.Spacing(Spacing);
|
||||
|
||||
foreach (var textBlock in TextBlocks)
|
||||
stack.Item().Element(textBlock);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static class TextExtensions
|
||||
{
|
||||
public static void Text(this IContainer element, Action<TextDescriptor> content)
|
||||
{
|
||||
var descriptor = new TextDescriptor();
|
||||
|
||||
if (element is Alignment alignment)
|
||||
descriptor.Alignment = alignment.Horizontal;
|
||||
|
||||
content?.Invoke(descriptor);
|
||||
descriptor.Compose(element);
|
||||
}
|
||||
|
||||
public static void Text(this IContainer element, object? text, TextStyle? style = null)
|
||||
{
|
||||
element.Text(x => x.Span(text?.ToString(), style));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
using System;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Fluent
|
||||
{
|
||||
public static class TextStyleExtensions
|
||||
{
|
||||
private static TextStyle Mutate(this TextStyle style, Action<TextStyle> handler)
|
||||
{
|
||||
style = style.Clone();
|
||||
|
||||
handler(style);
|
||||
return style;
|
||||
}
|
||||
|
||||
public static TextStyle Color(this TextStyle style, string value)
|
||||
{
|
||||
return style.Mutate(x => x.Color = value);
|
||||
}
|
||||
|
||||
public static TextStyle BackgroundColor(this TextStyle style, string value)
|
||||
{
|
||||
return style.Mutate(x => x.BackgroundColor = value);
|
||||
}
|
||||
|
||||
public static TextStyle FontType(this TextStyle style, string value)
|
||||
{
|
||||
return style.Mutate(x => x.FontType = value);
|
||||
}
|
||||
|
||||
public static TextStyle Size(this TextStyle style, float value)
|
||||
{
|
||||
return style.Mutate(x => x.Size = value);
|
||||
}
|
||||
|
||||
public static TextStyle LineHeight(this TextStyle style, float value)
|
||||
{
|
||||
return style.Mutate(x => x.LineHeight = value);
|
||||
}
|
||||
|
||||
public static TextStyle Italic(this TextStyle style, bool value = true)
|
||||
{
|
||||
return style.Mutate(x => x.IsItalic = value);
|
||||
}
|
||||
|
||||
public static TextStyle Strikethrough(this TextStyle style, bool value = true)
|
||||
{
|
||||
return style.Mutate(x => x.HasStrikethrough = value);
|
||||
}
|
||||
|
||||
public static TextStyle Underline(this TextStyle style, bool value = true)
|
||||
{
|
||||
return style.Mutate(x => x.HasUnderline = value);
|
||||
}
|
||||
|
||||
#region Weight
|
||||
|
||||
public static TextStyle Weight(this TextStyle style, FontWeight weight)
|
||||
{
|
||||
return style.Mutate(x => x.FontWeight = weight);
|
||||
}
|
||||
|
||||
public static TextStyle Thin(this TextStyle style)
|
||||
{
|
||||
return style.Weight(FontWeight.Thin);
|
||||
}
|
||||
|
||||
public static TextStyle ExtraLight(this TextStyle style)
|
||||
{
|
||||
return style.Weight(FontWeight.ExtraLight);
|
||||
}
|
||||
|
||||
public static TextStyle Light(this TextStyle style)
|
||||
{
|
||||
return style.Weight(FontWeight.Light);
|
||||
}
|
||||
|
||||
public static TextStyle NormalWeight(this TextStyle style)
|
||||
{
|
||||
return style.Weight(FontWeight.Normal);
|
||||
}
|
||||
|
||||
public static TextStyle Medium(this TextStyle style)
|
||||
{
|
||||
return style.Weight(FontWeight.Medium);
|
||||
}
|
||||
|
||||
public static TextStyle SemiBold(this TextStyle style)
|
||||
{
|
||||
return style.Weight(FontWeight.SemiBold);
|
||||
}
|
||||
|
||||
public static TextStyle Bold(this TextStyle style)
|
||||
{
|
||||
return style.Weight(FontWeight.Bold);
|
||||
}
|
||||
|
||||
public static TextStyle ExtraBold(this TextStyle style)
|
||||
{
|
||||
return style.Weight(FontWeight.ExtraBold);
|
||||
}
|
||||
|
||||
public static TextStyle Black(this TextStyle style)
|
||||
{
|
||||
return style.Weight(FontWeight.Black);
|
||||
}
|
||||
|
||||
public static TextStyle ExtraBlack(this TextStyle style)
|
||||
{
|
||||
return style.Weight(FontWeight.ExtraBlack);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace QuestPDF.Infrastructure
|
||||
{
|
||||
public interface IPageContext
|
||||
{
|
||||
void SetLocationPage(string key);
|
||||
int GetLocationPage(string key);
|
||||
ICollection<string> GetRegisteredLocations();
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace QuestPDF.Infrastructure
|
||||
{
|
||||
public class PageContext : IPageContext
|
||||
{
|
||||
public const string CurrentPageSlot = "currentPage";
|
||||
public const string TotalPagesSlot = "totalPages";
|
||||
|
||||
private Dictionary<string, int> Locations { get; } = new Dictionary<string, int>();
|
||||
private int PageNumber { get; set; }
|
||||
|
||||
internal void SetPageNumber(int number)
|
||||
{
|
||||
PageNumber = number;
|
||||
Locations[CurrentPageSlot] = number;
|
||||
|
||||
if (!Locations.ContainsKey(TotalPagesSlot) || Locations[TotalPagesSlot] < number)
|
||||
Locations[TotalPagesSlot] = number;
|
||||
}
|
||||
|
||||
public void SetLocationPage(string key)
|
||||
{
|
||||
if (Locations.ContainsKey(key))
|
||||
return;
|
||||
|
||||
Locations[key] = PageNumber;
|
||||
}
|
||||
|
||||
public int GetLocationPage(string key)
|
||||
{
|
||||
if (!Locations.ContainsKey(key))
|
||||
throw new ArgumentException($"The location '{key}' does not exists.");
|
||||
|
||||
return Locations[key];
|
||||
}
|
||||
|
||||
public ICollection<string> GetRegisteredLocations()
|
||||
{
|
||||
return Locations.Keys;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
using System;
|
||||
using QuestPDF.Helpers;
|
||||
|
||||
namespace QuestPDF.Infrastructure
|
||||
{
|
||||
public class TextStyle
|
||||
{
|
||||
internal bool HasGlobalStyleApplied { get; private set; }
|
||||
|
||||
internal string? Color { get; set; }
|
||||
internal string? BackgroundColor { get; set; }
|
||||
internal string? FontType { get; set; }
|
||||
internal float? Size { get; set; }
|
||||
internal float? LineHeight { get; set; }
|
||||
internal FontWeight? FontWeight { get; set; }
|
||||
internal bool? IsItalic { get; set; }
|
||||
internal bool? HasStrikethrough { get; set; }
|
||||
internal bool? HasUnderline { get; set; }
|
||||
|
||||
internal string? Key { get; private set; }
|
||||
|
||||
internal static TextStyle LibraryDefault => new TextStyle
|
||||
{
|
||||
Color = Colors.Black,
|
||||
BackgroundColor = Colors.Transparent,
|
||||
FontType = Fonts.Calibri,
|
||||
Size = 12,
|
||||
LineHeight = 1.2f,
|
||||
FontWeight = Infrastructure.FontWeight.Normal,
|
||||
IsItalic = false,
|
||||
HasStrikethrough = false,
|
||||
HasUnderline = false
|
||||
};
|
||||
|
||||
public static TextStyle Default => new TextStyle();
|
||||
|
||||
internal void ApplyGlobalStyle(TextStyle globalStyle)
|
||||
{
|
||||
if (HasGlobalStyleApplied)
|
||||
return;
|
||||
|
||||
HasGlobalStyleApplied = true;
|
||||
|
||||
ApplyParentStyle(globalStyle);
|
||||
Key ??= $"{Color}|{BackgroundColor}|{FontType}|{Size}|{LineHeight}|{FontWeight}|{IsItalic}|{HasStrikethrough}|{HasUnderline}";
|
||||
}
|
||||
|
||||
internal void ApplyParentStyle(TextStyle parentStyle)
|
||||
{
|
||||
Color ??= parentStyle.Color;
|
||||
BackgroundColor ??= parentStyle.BackgroundColor;
|
||||
FontType ??= parentStyle.FontType;
|
||||
Size ??= parentStyle.Size;
|
||||
LineHeight ??= parentStyle.LineHeight;
|
||||
FontWeight ??= parentStyle.FontWeight;
|
||||
IsItalic ??= parentStyle.IsItalic;
|
||||
HasStrikethrough ??= parentStyle.HasStrikethrough;
|
||||
HasUnderline ??= parentStyle.HasUnderline;
|
||||
}
|
||||
|
||||
internal TextStyle Clone()
|
||||
{
|
||||
var clone = (TextStyle)MemberwiseClone();
|
||||
clone.HasGlobalStyleApplied = false;
|
||||
return clone;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Authors>MarcinZiabek</Authors>
|
||||
<Company>CodeFlint</Company>
|
||||
<PackageId>QuestPDF</PackageId>
|
||||
<Version>2021.12.0-alpha0</Version>
|
||||
<PackageDescription>QuestPDF is an open-source, modern and battle-tested library that can help you with generating PDF documents by offering friendly, discoverable and predictable C# fluent API.</PackageDescription>
|
||||
<PackageReleaseNotes>$([System.IO.File]::ReadAllText("$(MSBuildProjectDirectory)/Resources/ReleaseNotes.txt"))</PackageReleaseNotes>
|
||||
<LangVersion>8</LangVersion>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<PackageIcon>Logo.png</PackageIcon>
|
||||
<PackageIconUrl>https://www.questpdf.com/images/package-logo.png</PackageIconUrl>
|
||||
<PackageProjectUrl>https://www.questpdf.com/</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/QuestPDF/library.git</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<Copyright>Marcin Ziąbek, QuestPDF contributors</Copyright>
|
||||
<PackageTags>pdf report file export generate generation tool create creation render portable document format quest html library converter open source free standard core</PackageTags>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<Nullable>enable</Nullable>
|
||||
<TargetFrameworks>net462;netstandard2.0;netcoreapp2.0;netcoreapp3.0</TargetFrameworks>
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SkiaSharp" Version="2.80.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resources\ImagePlaceholder.png" />
|
||||
<EmbeddedResource Include="Resources\Logo.png" />
|
||||
<None Remove="ImagePlaceholder.png" />
|
||||
<None Include="Resources\Logo.png">
|
||||
<Pack>true</Pack>
|
||||
<Visible>false</Visible>
|
||||
<PackagePath>\</PackagePath>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,221 +0,0 @@
|
||||
## QuestPDF Overview
|
||||
|
||||
QuestPDF is an open-source, modern and battle-tested library that can help you with generating PDF documents by offering friendly, discoverable and predictable C# fluent API.
|
||||
|
||||
## Features
|
||||
|
||||
**Rely on solid fundamentals** - This library is created specifically for designing and arranging document layouts, with full paging support. Alternative solutions, such as HTML-based converters, are not designed for this purpose and therefore are often unpredictable and do not produce desired results.
|
||||
|
||||
**Work with organized self-explanatory code** - The entire process of implementing PDF document, takes place in your code. Free yourself from slow visual designers and strange technological limitations. Follow simple yet highly effective approaches to create maintainable, high-quality code.
|
||||
|
||||
**Compose simple components into complex documents** - Do you remember the feeling when your code just works? When your ideas are becoming real without any effort? Working with simple, easy to understand, self-explanatory and highly composable layout elements is the key here!
|
||||
|
||||
**Create and reuse components** - Feel no fear of complex documents! Create custom, reusable components and divide the document's layout into easy to maintain pieces. Inject data to customize content and use slots to enhance composability. Decide how complex approaches your solution needs and follow the best path.
|
||||
|
||||
**Prototype with ease** - We understand that document generation is often tricky and require multiple iterations. The library offers additional prototyping tools such as random text generator or image placeholder element. By following best practices, you can develop a document without having data.
|
||||
|
||||
**Enjoy fast PDF generation** - QuestPDF is created upon SkiaSharp, a well-known graphical library, and converts your data into PDF documents. It offers a highly optimized layouting engine capable of generating over 1000 PDF files per minute per core. The entire process is thread-safe.
|
||||
|
||||
## Learning resources
|
||||
|
||||
**[Release notes and roadmap](https://www.questpdf.com/documentation/releases.html)** - everything that is planned for future library iterations, description of new features and information about potential breaking changes.
|
||||
|
||||
**[Getting started tutorial](https://www.questpdf.com/documentation/getting-started.html)** - a short and easy to follow tutorial showing how to design an invoice document under 200 lines of code.
|
||||
|
||||
**[API Reference](https://www.questpdf.com/documentation/api-reference.html)** - a detailed description of behavior of all available components and how to use them with C# Fluent API.
|
||||
|
||||
**[Patterns and practices](https://www.questpdf.com/documentation/patterns-and-practices.html#document-metadata)** - everything that may help you designing great reports and reusable code that is easy to maintain.
|
||||
|
||||
## Example invoice
|
||||
|
||||
Do you believe that creating a complete invoice document can take less than 200 lines of code? We have prepared for you a step-by-step instruction that shows every detail of this implementation and describes the best patterns and practices.
|
||||
|
||||
For tutorial, documentation and API reference, please visit [the QuestPDF documentation](https://www.questpdf.com/documentation/getting-started.html).
|
||||
|
||||

|
||||
|
||||
Here you can find an example code showing how easy is to write and understand the fluent API.
|
||||
|
||||
|
||||
**General document structure** with header, content and footer:
|
||||
|
||||
```csharp
|
||||
public void Compose(IDocumentContainer container)
|
||||
{
|
||||
container
|
||||
.Page(page =>
|
||||
{
|
||||
page.Margin(50);
|
||||
|
||||
page.Header().Element(ComposeHeader);
|
||||
page.Content().Element(ComposeContent);
|
||||
|
||||
page.Footer().AlignCenter().Text(x =>
|
||||
{
|
||||
x.CurrentPageNumber();
|
||||
x.Span(" / ");
|
||||
x.TotalPages();
|
||||
});
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
**The header area** consists of basic invoice information along with a logo placeholder.
|
||||
|
||||
```csharp
|
||||
void ComposeHeader(IContainer container)
|
||||
{
|
||||
var titleTextStyle = TextStyle.Default.Size(20).SemiBold().Color(Colors.Blue.Medium);
|
||||
|
||||
container.Row(row =>
|
||||
{
|
||||
{
|
||||
stack.Item().Text($"Invoice #{Model.InvoiceNumber}", titleStyle);
|
||||
|
||||
stack.Item().Text(text =>
|
||||
{
|
||||
text.Span("Issue date: ", TextStyle.Default.SemiBold());
|
||||
text.Span($"{Model.IssueDate:d}");
|
||||
});
|
||||
|
||||
stack.Item().Text(text =>
|
||||
{
|
||||
text.Span("Due date: ", TextStyle.Default.SemiBold());
|
||||
text.Span($"{Model.DueDate:d}");
|
||||
});
|
||||
});
|
||||
|
||||
row.ConstantColumn(100).Height(50).Placeholder();
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Implementation of **the content area** that contains seller and customer details, then listing of all bought products, then a comments section.
|
||||
|
||||
```csharp
|
||||
void ComposeContent(IContainer container)
|
||||
{
|
||||
container.PaddingVertical(40).Stack(column =>
|
||||
{
|
||||
column.Spacing(20);
|
||||
|
||||
column.Item().Row(row =>
|
||||
{
|
||||
row.RelativeColumn().Component(new AddressComponent("From", Model.SellerAddress));
|
||||
row.ConstantColumn(50);
|
||||
row.RelativeColumn().Component(new AddressComponent("For", Model.CustomerAddress));
|
||||
});
|
||||
|
||||
column.Item().Element(ComposeTable);
|
||||
|
||||
var totalPrice = Model.Items.Sum(x => x.Price * x.Quantity);
|
||||
|
||||
column
|
||||
.Item()
|
||||
.PaddingRight(5)
|
||||
.AlignRight()
|
||||
.Text($"Grand total: {totalPrice}$", TextStyle.Default.SemiBold());
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(Model.Comments))
|
||||
column.Item().PaddingTop(25).Element(ComposeComments);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
**The table and comments** codes are extracted into separate methods to increase clarity:
|
||||
|
||||
```csharp
|
||||
void ComposeTable(IContainer container)
|
||||
{
|
||||
var headerStyle = TextStyle.Default.SemiBold();
|
||||
|
||||
container.Decoration(decoration =>
|
||||
{
|
||||
// header
|
||||
decoration.Header().BorderBottom(1).Padding(5).Row(row =>
|
||||
{
|
||||
row.ConstantColumn(25).Text("#", headerStyle);
|
||||
row.RelativeColumn(3).Text("Product", headerStyle);
|
||||
row.RelativeColumn().AlignRight().Text("Unit price", headerStyle);
|
||||
row.RelativeColumn().AlignRight().Text("Quantity", headerStyle);
|
||||
row.RelativeColumn().AlignRight().Text("Total", headerStyle);
|
||||
});
|
||||
|
||||
// content
|
||||
decoration
|
||||
.Content()
|
||||
.Stack(column =>
|
||||
{
|
||||
foreach (var item in Model.Items)
|
||||
{
|
||||
column
|
||||
.Item()
|
||||
.ShowEntire()
|
||||
.BorderBottom(1)
|
||||
.BorderColor(Colors.Grey.Lighten2)
|
||||
.Padding(5)
|
||||
.Row(row =>
|
||||
{
|
||||
row.ConstantColumn(25).Text(Model.Items.IndexOf(item) + 1);
|
||||
row.RelativeColumn(3).Text(item.Name);
|
||||
row.RelativeColumn().AlignRight().Text($"{item.Price}$");
|
||||
row.RelativeColumn().AlignRight().Text(item.Quantity);
|
||||
row.RelativeColumn().AlignRight().Text($"{item.Price * item.Quantity}$");
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
```csharp
|
||||
void ComposeComments(IContainer container)
|
||||
{
|
||||
container.ShowEntire().Background(Colors.Grey.Lighten3).Padding(10).Stack(message =>
|
||||
{
|
||||
message.Spacing(5);
|
||||
message.Item().Text("Comments", TextStyle.Default.Size(14).SemiBold());
|
||||
message.Item().Text(Model.Comments);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
**The address details section** is implemented using components. This way the code can be easily reused for both seller and customer:
|
||||
|
||||
```csharp
|
||||
public class AddressComponent : IComponent
|
||||
{
|
||||
private string Title { get; }
|
||||
private Address Address { get; }
|
||||
|
||||
public AddressComponent(string title, Address address)
|
||||
{
|
||||
Title = title;
|
||||
Address = address;
|
||||
}
|
||||
|
||||
public void Compose(IContainer container)
|
||||
{
|
||||
container.ShowEntire().Stack(column =>
|
||||
{
|
||||
column.Spacing(5);
|
||||
|
||||
column
|
||||
.Item()
|
||||
.BorderBottom(1)
|
||||
.PaddingBottom(5)
|
||||
.Text(Title, TextStyle.Default.SemiBold());
|
||||
|
||||
column.Item().Text(Address.CompanyName);
|
||||
column.Item().Text(Address.Street);
|
||||
column.Item().Text($"{Address.City}, {Address.State}");
|
||||
column.Item().Text(Address.Email);
|
||||
column.Item().Text(Address.Phone);
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -1 +0,0 @@
|
||||
- Added support for generating documents in the XPS file format.
|
||||
@@ -0,0 +1,91 @@
|
||||
<a href="https://www.questpdf.com/" target="_blank">
|
||||
<img src="https://github.com/QuestPDF/example-invoice/raw/main/images/logo.svg" width="400">
|
||||
</a>
|
||||
|
||||
---
|
||||
|
||||
[](https://www.questpdf.com)
|
||||
[](https://www.nuget.org/packages/QuestPDF/)
|
||||
[](https://github.com/QuestPDF/QuestPDF/stargazers)
|
||||
[](https://www.nuget.org/packages/QuestPDF/)
|
||||
[](https://www.nuget.org/packages/QuestPDF/)
|
||||
[](https://github.com/QuestPDF/QuestPDF/blob/main/LICENSE)
|
||||
|
||||
<br />
|
||||
|
||||
### QuestPDF is a modern open-source .NET library for PDF document generation. Offering comprehensive layout engine powered by concise and discoverable C# Fluent API.
|
||||
|
||||
<img src="https://github.com/QuestPDF/QuestPDF-Documentation/blob/main/docs/public/previewer/animation.gif?raw=true" width="100%">
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td>👨💻</td>
|
||||
<td>Design PDF documents using C# and employ a code-only approach. Utilize your version control system to its fullest potential.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>🧱</td>
|
||||
<td>Compose PDF document with a range of powerful and predictable structural elements, such as text, image, border, table, and many more.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>⚙️</td>
|
||||
<td>Utilize a comprehensive layout engine, specifically designed for PDF document generation and paging support.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>📖</td>
|
||||
<td>Write code using concise and easy-to-understand C# Fluent API. Utilize IntelliSense to quickly discover available options.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>🔗</td>
|
||||
<td>Don't be limited to any proprietary scripting language or format. Follow your experience and leverage all modern C# features.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>⌛</td>
|
||||
<td>Save time thanks to a hot-reload capability, allowing real-time PDF document preview without code recompilation.</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<br />
|
||||
|
||||
<br />
|
||||
|
||||
## Please help by giving a star
|
||||
|
||||
Choosing a project dependency could be difficult. We need to ensure stability and maintainability of our projects. Surveys show that GitHub stars count play an important factor when assessing library quality.
|
||||
|
||||
⭐ Please give this repository a star. It takes seconds and help thousands of developers! ⭐
|
||||
|
||||
<img src="https://user-images.githubusercontent.com/9263853/232912983-b20e441e-bd47-41df-afa4-f02481252d26.png" width="800" />
|
||||
|
||||
## Please share with the community
|
||||
|
||||
As an open-source project without funding, I cannot afford advertising QuestPDF in a typical way. Instead, the library relies on community interactions. Please consider sharing a post about QuestPDF and the value it provides. It really does help!
|
||||
|
||||
[](https://reddit.com/submit?url=https://github.com/QuestPDF/QuestPDF&title=Check%20out%20QuestPDF%20%F0%9F%8E%8A%20a%20modern%20open-source%20.NET%20library%20%20for%20PDF%20document%20generation%20%F0%9F%9A%80)
|
||||
[](https://twitter.com/share?url=https://github.com/QuestPDF/QuestPDF&text=Check%20out%20QuestPDF%20%F0%9F%8E%8A%20a%20modern%20open-source%20.NET%20library%20%20for%20PDF%20document%20generation%20%F0%9F%9A%80%20%23dotnet%20%23csharp%20%23questpdf)
|
||||
[](https://news.ycombinator.com/submitlink?u=https://github.com/QuestPDF/QuestPDF&t=QuestPDF%20-%20a%20modern%20open-source%20.NET%20library%20%20for%20PDF%20document%20generation)
|
||||
[](https://www.facebook.com/sharer/sharer.php?u=https://github.com/QuestPDF/QuestPDF)
|
||||
|
||||
<br />
|
||||
|
||||
## Let's get started
|
||||
|
||||
Begin exploring the QuestPDF library today. You are 250 lines of C# code away from creating a fully functional PDF invoice implementation.
|
||||
|
||||
Read the Getting Started tutorial to familiarize yourself with general library architecture, important layout structures as well as to better understand helpful patterns and practices. Easily start designing your PDF documents, reports, invoices and even more.
|
||||
|
||||
[](https://www.questpdf.com/getting-started.html)
|
||||
|
||||
<img src="https://github.com/QuestPDF/QuestPDF-Documentation/blob/main/docs/public/invoice-small.png?raw=true" width="400px">
|
||||
|
||||
<br />
|
||||
|
||||
## QuestPDF on JetBrains OSS Power-Ups
|
||||
|
||||
QuestPDF was presented on one of the episodes of OSS Power-Ups hosted by JetBrains. Huge thanks for Matthias Koch and entire JetBrains team for giving me a chance to show QuestPDF. You are the best!
|
||||
|
||||
<a href="https://www.youtube.com/watch?v=-iYvZvpLX0g">
|
||||
<img src="https://github.com/QuestPDF/QuestPDF-Documentation/blob/main/docs/public/jetbrains-oss-powerups-youtube.png?raw=true" width="600px">
|
||||
</a>
|
||||
|
||||
|
||||
[](https://www.youtube.com/watch?v=-iYvZvpLX0g)
|
||||
@@ -0,0 +1,131 @@
|
||||
# Rules in this file were initially inferred by Visual Studio IntelliCode from the D:\GithubRepos\QuestPDF codebase based on best match to current usage at 16.03.2022
|
||||
# You can modify the rules from these initially generated values to suit your own policies
|
||||
# You can learn more about editorconfig here: https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference
|
||||
[*.cs]
|
||||
|
||||
|
||||
#Core editorconfig formatting - indentation
|
||||
|
||||
#use soft tabs (spaces) for indentation
|
||||
indent_style = space
|
||||
|
||||
#Formatting - new line options
|
||||
|
||||
#place catch statements on a new line
|
||||
csharp_new_line_before_catch = true
|
||||
#place else statements on a new line
|
||||
csharp_new_line_before_else = true
|
||||
#require members of anonymous types to be on separate lines
|
||||
csharp_new_line_before_members_in_anonymous_types = true
|
||||
#require members of object intializers to be on separate lines
|
||||
csharp_new_line_before_members_in_object_initializers = true
|
||||
#require braces to be on a new line for object_collection_array_initializers, methods, anonymous_types, control_blocks, types, and lambdas (also known as "Allman" style)
|
||||
csharp_new_line_before_open_brace =all
|
||||
|
||||
#Formatting - organize using options
|
||||
|
||||
#sort System.* using directives alphabetically, and place them before other usings
|
||||
dotnet_sort_system_directives_first = true
|
||||
|
||||
#Formatting - spacing options
|
||||
|
||||
#require NO space between a cast and the value
|
||||
csharp_space_after_cast = false
|
||||
#require a space before the colon for bases or interfaces in a type declaration
|
||||
csharp_space_after_colon_in_inheritance_clause = true
|
||||
#require a space after a keyword in a control flow statement such as a for loop
|
||||
csharp_space_after_keywords_in_control_flow_statements = true
|
||||
#require a space before the colon for bases or interfaces in a type declaration
|
||||
csharp_space_before_colon_in_inheritance_clause = true
|
||||
#remove space within empty argument list parentheses
|
||||
csharp_space_between_method_call_empty_parameter_list_parentheses = false
|
||||
#remove space between method call name and opening parenthesis
|
||||
csharp_space_between_method_call_name_and_opening_parenthesis = false
|
||||
#do not place space characters after the opening parenthesis and before the closing parenthesis of a method call
|
||||
csharp_space_between_method_call_parameter_list_parentheses = false
|
||||
#remove space within empty parameter list parentheses for a method declaration
|
||||
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
|
||||
#place a space character after the opening parenthesis and before the closing parenthesis of a method declaration parameter list.
|
||||
csharp_space_between_method_declaration_parameter_list_parentheses = false
|
||||
|
||||
#Formatting - wrapping options
|
||||
|
||||
#leave code block on single line
|
||||
csharp_preserve_single_line_blocks = true
|
||||
|
||||
#Style - Code block preferences
|
||||
|
||||
#prefer no curly braces if allowed
|
||||
csharp_prefer_braces = false:suggestion
|
||||
|
||||
#Style - expression bodied member options
|
||||
|
||||
#prefer block bodies for constructors
|
||||
csharp_style_expression_bodied_constructors = false:suggestion
|
||||
#prefer block bodies for methods
|
||||
csharp_style_expression_bodied_methods = false:suggestion
|
||||
#prefer expression-bodied members for properties
|
||||
csharp_style_expression_bodied_properties = true:suggestion
|
||||
|
||||
#Style - expression level options
|
||||
|
||||
#prefer out variables to be declared inline in the argument list of a method call when possible
|
||||
csharp_style_inlined_variable_declaration = true:suggestion
|
||||
#prefer the language keyword for member access expressions, instead of the type name, for types that have a keyword to represent them
|
||||
dotnet_style_predefined_type_for_member_access = true:suggestion
|
||||
|
||||
#Style - Expression-level preferences
|
||||
|
||||
#prefer objects to be initialized using object initializers when possible
|
||||
dotnet_style_object_initializer = true:suggestion
|
||||
#prefer inferred anonymous type member names
|
||||
dotnet_style_prefer_inferred_anonymous_type_member_names = false:suggestion
|
||||
#prefer inferred tuple element names
|
||||
dotnet_style_prefer_inferred_tuple_names = true:suggestion
|
||||
|
||||
#Style - implicit and explicit types
|
||||
|
||||
#prefer var over explicit type in all cases, unless overridden by another code style rule
|
||||
csharp_style_var_elsewhere = true:suggestion
|
||||
#prefer var is used to declare variables with built-in system types such as int
|
||||
csharp_style_var_for_built_in_types = true:suggestion
|
||||
#prefer var when the type is already mentioned on the right-hand side of a declaration expression
|
||||
csharp_style_var_when_type_is_apparent = true:suggestion
|
||||
|
||||
#Style - language keyword and framework type options
|
||||
|
||||
#prefer the language keyword for local variables, method parameters, and class members, instead of the type name, for types that have a keyword to represent them
|
||||
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
|
||||
|
||||
#Style - Miscellaneous preferences
|
||||
|
||||
#prefer local functions over anonymous functions
|
||||
csharp_style_pattern_local_over_anonymous_function = true:suggestion
|
||||
|
||||
#Style - modifier options
|
||||
|
||||
#prefer accessibility modifiers to be declared except for public interface members. This will currently not differ from always and will act as future proofing for if C# adds default interface methods.
|
||||
dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
|
||||
|
||||
#Style - Modifier preferences
|
||||
|
||||
#when this rule is set to a list of modifiers, prefer the specified ordering.
|
||||
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:silent
|
||||
|
||||
#Style - Pattern matching
|
||||
|
||||
#prefer pattern matching instead of is expression with type casts
|
||||
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
|
||||
|
||||
#Style - qualification options
|
||||
|
||||
#prefer fields not to be prefaced with this. or Me. in Visual Basic
|
||||
dotnet_style_qualification_for_field = false:suggestion
|
||||
#prefer methods not to be prefaced with this. or Me. in Visual Basic
|
||||
dotnet_style_qualification_for_method = false:suggestion
|
||||
#prefer properties not to be prefaced with this. or Me. in Visual Basic
|
||||
dotnet_style_qualification_for_property = false:suggestion
|
||||
|
||||
[*.{cs,vb}]
|
||||
tab_width=4
|
||||
indent_size=4
|
||||
@@ -0,0 +1,29 @@
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class AlignmentExamples
|
||||
{
|
||||
[Test]
|
||||
public void Example()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(400, 200)
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(25)
|
||||
.Border(1)
|
||||
.AlignBottom()
|
||||
.Background(Colors.Grey.Lighten1)
|
||||
.Text("Test");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,16 +8,16 @@ using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class BarCode
|
||||
public class BarcodeExamples
|
||||
{
|
||||
[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);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
using Microcharts;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class CanvasExamples
|
||||
{
|
||||
[Test]
|
||||
public void BorderRadius()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(175, 100)
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Background(Colors.Grey.Lighten2)
|
||||
.Padding(25)
|
||||
.MinimalBox()
|
||||
.Layers(layers =>
|
||||
{
|
||||
layers.Layer().Canvas((canvas, size) =>
|
||||
{
|
||||
DrawRoundedRectangle(Colors.White, false);
|
||||
DrawRoundedRectangle(Colors.Blue.Darken2, true);
|
||||
|
||||
void DrawRoundedRectangle(string color, bool isStroke)
|
||||
{
|
||||
using var paint = new SKPaint
|
||||
{
|
||||
Color = SKColor.Parse(color),
|
||||
IsStroke = isStroke,
|
||||
StrokeWidth = 2,
|
||||
IsAntialias = true
|
||||
};
|
||||
|
||||
canvas.DrawRoundRect(0, 0, size.Width, size.Height, 20, 20, paint);
|
||||
}
|
||||
});
|
||||
|
||||
layers
|
||||
.PrimaryLayer()
|
||||
.PaddingVertical(10)
|
||||
.PaddingHorizontal(20)
|
||||
.Text("Sample text")
|
||||
.FontSize(16)
|
||||
.FontColor(Colors.Blue.Darken2)
|
||||
.SemiBold();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
using Microcharts;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class ChartExample
|
||||
{
|
||||
[Test]
|
||||
public void MicrochartChart()
|
||||
{
|
||||
var entries = new[]
|
||||
{
|
||||
new ChartEntry(212)
|
||||
{
|
||||
Label = "UWP",
|
||||
ValueLabel = "112",
|
||||
Color = SKColor.Parse("#2c3e50")
|
||||
},
|
||||
new ChartEntry(248)
|
||||
{
|
||||
Label = "Android",
|
||||
ValueLabel = "648",
|
||||
Color = SKColor.Parse("#77d065")
|
||||
},
|
||||
new ChartEntry(128)
|
||||
{
|
||||
Label = "iOS",
|
||||
ValueLabel = "428",
|
||||
Color = SKColor.Parse("#b455b6")
|
||||
},
|
||||
new ChartEntry(514)
|
||||
{
|
||||
Label = "Forms",
|
||||
ValueLabel = "214",
|
||||
Color = SKColor.Parse("#3498db")
|
||||
}
|
||||
};
|
||||
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(400, 600)
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Background(Colors.White)
|
||||
.Padding(25)
|
||||
.Column(column =>
|
||||
{
|
||||
column
|
||||
.Item()
|
||||
.PaddingBottom(10)
|
||||
.Text("Chart example")
|
||||
.FontSize(20)
|
||||
.SemiBold()
|
||||
.FontColor(Colors.Blue.Medium);
|
||||
|
||||
column
|
||||
.Item()
|
||||
.Border(1)
|
||||
.ExtendHorizontal()
|
||||
.Height(300)
|
||||
.Canvas((canvas, size) =>
|
||||
{
|
||||
var chart = new BarChart
|
||||
{
|
||||
Entries = entries,
|
||||
|
||||
LabelOrientation = Orientation.Horizontal,
|
||||
ValueLabelOrientation = Orientation.Horizontal,
|
||||
|
||||
IsAnimated = false,
|
||||
};
|
||||
|
||||
chart.DrawContent(canvas, (int)size.Width, (int)size.Height);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 => { });
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
-9
@@ -16,8 +16,7 @@ namespace QuestPDF.Examples
|
||||
.PageSize(PageSizes.A4)
|
||||
.ProducePdf()
|
||||
.ShowResults()
|
||||
.Render(x => x.Image(new byte[] { 1, 2, 3 }));
|
||||
//.Render(x => GenerateStructure(x, 16));
|
||||
.Render(x => GenerateStructure(x, 12));
|
||||
}
|
||||
|
||||
private void GenerateStructure(IContainer container, int level)
|
||||
@@ -33,23 +32,23 @@ namespace QuestPDF.Examples
|
||||
if (level % 3 == 0)
|
||||
{
|
||||
container
|
||||
.Border(level / 10f)
|
||||
.Border(level / 4f)
|
||||
.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
|
||||
{
|
||||
container
|
||||
.Border(level / 10f)
|
||||
.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));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,408 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Drawing.Exceptions;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class ContentDirectionExamples
|
||||
{
|
||||
private Action<IContainer> ContentDirectionTemplate(Action<IContainer> content)
|
||||
{
|
||||
return container =>
|
||||
{
|
||||
container
|
||||
.MinimalBox()
|
||||
.ExtendHorizontal()
|
||||
.Table(table =>
|
||||
{
|
||||
table.ColumnsDefinition(columns =>
|
||||
{
|
||||
columns.RelativeColumn();
|
||||
columns.ConstantColumn(1);
|
||||
columns.RelativeColumn();
|
||||
});
|
||||
|
||||
table.Header(header =>
|
||||
{
|
||||
header.Cell().ContentFromLeftToRight().Element(HeaderCell("Left-to-right"));
|
||||
header.Cell().LineVertical(1).LineColor(Colors.Grey.Medium);
|
||||
header.Cell().ContentFromRightToLeft().Element(HeaderCell("Right-to-left"));
|
||||
|
||||
static Action<IContainer> HeaderCell(string label)
|
||||
{
|
||||
return container => container
|
||||
.BorderColor(Colors.Grey.Medium)
|
||||
.Background(Colors.Grey.Lighten2)
|
||||
.PaddingHorizontal(15)
|
||||
.PaddingVertical(5)
|
||||
.Text(label)
|
||||
.FontSize(18)
|
||||
.SemiBold();
|
||||
}
|
||||
});
|
||||
|
||||
table.Cell().Element(TestCell).ContentFromLeftToRight().Element(content);
|
||||
table.Cell().LineVertical(1).LineColor(Colors.Grey.Medium);
|
||||
table.Cell().Element(TestCell).ContentFromRightToLeft().Element(content);
|
||||
|
||||
static IContainer TestCell(IContainer container)
|
||||
{
|
||||
return container.Padding(15);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Page()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.EnableDebugging()
|
||||
.RenderDocument(document =>
|
||||
{
|
||||
document.Page(page =>
|
||||
{
|
||||
page.Size(PageSizes.A5);
|
||||
page.Margin(20);
|
||||
page.PageColor(Colors.White);
|
||||
|
||||
page.DefaultTextStyle(x => x.FontFamily("Calibri").FontSize(20));
|
||||
page.ContentFromRightToLeft();
|
||||
|
||||
page.Content().Column(column =>
|
||||
{
|
||||
column.Spacing(20);
|
||||
|
||||
column.Item()
|
||||
.Text("مثال على الفاتورة") // example invoice
|
||||
.FontSize(32).FontColor(Colors.Blue.Darken2).SemiBold();
|
||||
|
||||
column.Item().Table(table =>
|
||||
{
|
||||
table.ColumnsDefinition(columns =>
|
||||
{
|
||||
columns.RelativeColumn();
|
||||
columns.ConstantColumn(75);
|
||||
columns.ConstantColumn(100);
|
||||
});
|
||||
|
||||
table.Cell().Element(HeaderStyle).Text("وصف السلعة"); // item description
|
||||
table.Cell().Element(HeaderStyle).Text("كمية"); // quantity
|
||||
table.Cell().Element(HeaderStyle).Text("سعر"); // price
|
||||
|
||||
var items = new[]
|
||||
{
|
||||
"دورة البرمجة", // programming course
|
||||
"دورة تصميم الرسومات", // graphics design course
|
||||
"تحليل وتصميم الخوارزميات", // analysis and design of algorithms
|
||||
};
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
var price = Placeholders.Random.NextDouble() * 100;
|
||||
|
||||
table.Cell().Text(item);
|
||||
table.Cell().Text(Placeholders.Random.Next(1, 10));
|
||||
table.Cell().Text($"USD${price:F2}");
|
||||
}
|
||||
|
||||
static IContainer HeaderStyle(IContainer x) => x.BorderBottom(1).PaddingVertical(5);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Column()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A4)
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.EnableDebugging()
|
||||
.Render(ContentDirectionTemplate(Content));
|
||||
|
||||
void Content(IContainer container)
|
||||
{
|
||||
container.Column(column =>
|
||||
{
|
||||
column.Spacing(5);
|
||||
|
||||
column.Item().Height(50).Width(50).Background(Colors.Red.Lighten1);
|
||||
column.Item().Height(50).Width(100).Background(Colors.Green.Lighten1);
|
||||
column.Item().Height(50).Width(150).Background(Colors.Blue.Lighten1);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Row()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A4)
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.EnableDebugging()
|
||||
.Render(ContentDirectionTemplate(Content));
|
||||
|
||||
void Content(IContainer container)
|
||||
{
|
||||
container.Row(row =>
|
||||
{
|
||||
row.Spacing(5);
|
||||
|
||||
row.AutoItem().Height(50).Width(50).Background(Colors.Red.Lighten1);
|
||||
row.AutoItem().Height(50).Width(50).Background(Colors.Green.Lighten1);
|
||||
row.AutoItem().Height(50).Width(75).Background(Colors.Blue.Lighten1);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Table()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A4)
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.EnableDebugging()
|
||||
.Render(ContentDirectionTemplate(Content));
|
||||
|
||||
void Content(IContainer container)
|
||||
{
|
||||
container.Table(table =>
|
||||
{
|
||||
table.ColumnsDefinition(columns =>
|
||||
{
|
||||
columns.RelativeColumn();
|
||||
columns.RelativeColumn();
|
||||
columns.RelativeColumn();
|
||||
});
|
||||
|
||||
table.Cell().Height(50).Background(Colors.Red.Lighten1);
|
||||
table.Cell().Height(50).Background(Colors.Green.Lighten1);
|
||||
table.Cell().Height(50).Background(Colors.Blue.Lighten1);
|
||||
table.Cell().ColumnSpan(2).Height(50).Background(Colors.Orange.Lighten1);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Constrained()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A4)
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.EnableDebugging()
|
||||
.Render(ContentDirectionTemplate(Content));
|
||||
|
||||
void Content(IContainer container)
|
||||
{
|
||||
container.Width(50).Height(50).Background(Colors.Red.Lighten1);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Unconstrained()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A4)
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.EnableDebugging()
|
||||
.Render(ContentDirectionTemplate(Content));
|
||||
|
||||
void Content(IContainer container)
|
||||
{
|
||||
container
|
||||
.Width(100)
|
||||
.Height(100)
|
||||
.Background(Colors.Grey.Lighten3)
|
||||
.AlignCenter()
|
||||
.AlignMiddle()
|
||||
|
||||
.Unconstrained()
|
||||
|
||||
.Width(50)
|
||||
.Height(50)
|
||||
.Background(Colors.Red.Lighten1);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Inlined()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A4)
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.EnableDebugging()
|
||||
.Render(ContentDirectionTemplate(Content));
|
||||
|
||||
void Content(IContainer container)
|
||||
{
|
||||
container.Column(column =>
|
||||
{
|
||||
column.Spacing(10);
|
||||
|
||||
column.Item().Text("Default alignment").FontSize(14).SemiBold();
|
||||
column.Item().Element(ContentWithAlignment(null));
|
||||
|
||||
column.Item().Text("Left alignment").FontSize(14).SemiBold();
|
||||
column.Item().Element(ContentWithAlignment(InlinedAlignment.Left));
|
||||
|
||||
column.Item().Text("Center alignment").FontSize(14).SemiBold();
|
||||
column.Item().Element(ContentWithAlignment(InlinedAlignment.Center));
|
||||
|
||||
column.Item().Text("Right alignment").FontSize(14).SemiBold();
|
||||
column.Item().Element(ContentWithAlignment(InlinedAlignment.Right));
|
||||
});
|
||||
|
||||
static Action<IContainer> ContentWithAlignment(InlinedAlignment? alignment)
|
||||
{
|
||||
return container =>
|
||||
{
|
||||
container.Inlined(inlined =>
|
||||
{
|
||||
inlined.Spacing(5);
|
||||
|
||||
inlined.Alignment(alignment);
|
||||
|
||||
inlined.Item().Height(50).Width(50).Background(Colors.Red.Lighten1);
|
||||
inlined.Item().Height(50).Width(75).Background(Colors.Green.Lighten1);
|
||||
inlined.Item().Height(50).Width(100).Background(Colors.Blue.Lighten1);
|
||||
inlined.Item().Height(50).Width(125).Background(Colors.Orange.Lighten1);
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Text()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A4)
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.EnableDebugging()
|
||||
.Render(ContentDirectionTemplate(Content));
|
||||
|
||||
void Content(IContainer container)
|
||||
{
|
||||
container.Column(column =>
|
||||
{
|
||||
column.Spacing(10);
|
||||
|
||||
column.Item().Text("Default alignment").FontSize(14).SemiBold();
|
||||
column.Item().Element(ContentWithAlignment(null));
|
||||
|
||||
column.Item().Text("Left alignment").FontSize(14).SemiBold();
|
||||
column.Item().Element(ContentWithAlignment(HorizontalAlignment.Left));
|
||||
|
||||
column.Item().Text("Center alignment").FontSize(14).SemiBold();
|
||||
column.Item().Element(ContentWithAlignment(HorizontalAlignment.Center));
|
||||
|
||||
column.Item().Text("Right alignment").FontSize(14).SemiBold();
|
||||
column.Item().Element(ContentWithAlignment(HorizontalAlignment.Right));
|
||||
});
|
||||
|
||||
static Action<IContainer> ContentWithAlignment(HorizontalAlignment? alignment)
|
||||
{
|
||||
return container =>
|
||||
{
|
||||
container.Text(text =>
|
||||
{
|
||||
text.Alignment = alignment; // internal API
|
||||
|
||||
text.Span("Lorem ipsum").Bold().FontColor(Colors.Red.Medium);
|
||||
text.Element().Width(5);
|
||||
text.Span(Placeholders.LoremIpsum());
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Decoration()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A4)
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.EnableDebugging()
|
||||
.Render(ContentDirectionTemplate(Content));
|
||||
|
||||
void Content(IContainer container)
|
||||
{
|
||||
container.Decoration(decoration =>
|
||||
{
|
||||
decoration.Before().Background(Colors.Green.Lighten1).Padding(5).Text("Before").FontSize(16);
|
||||
decoration.Content().Background(Colors.Green.Lighten2).Padding(5).Text("Content").FontSize(16);
|
||||
decoration.After().Background(Colors.Green.Lighten3).Padding(5).Text("After").FontSize(16);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Dynamic()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A4)
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.EnableDebugging()
|
||||
.Render(ContentDirectionTemplate(Content));
|
||||
|
||||
void Content(IContainer container)
|
||||
{
|
||||
container.Dynamic(new SimpleDynamic());
|
||||
}
|
||||
}
|
||||
|
||||
class SimpleDynamic : IDynamicComponent<int>
|
||||
{
|
||||
public int State { get; set; }
|
||||
|
||||
public DynamicComponentComposeResult Compose(DynamicContext context)
|
||||
{
|
||||
var content = context.CreateElement(container =>
|
||||
{
|
||||
container.Row(row =>
|
||||
{
|
||||
row.ConstantItem(50).Background(Colors.Red.Lighten2).Height(50);
|
||||
row.ConstantItem(75).Background(Colors.Green.Lighten2).Height(50);
|
||||
row.ConstantItem(100).Background(Colors.Blue.Lighten2).Height(50);
|
||||
});
|
||||
});
|
||||
|
||||
return new DynamicComponentComposeResult
|
||||
{
|
||||
Content = content,
|
||||
HasMoreContent = false
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
@@ -0,0 +1,76 @@
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Drawing.Exceptions;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class DebuggingTesting
|
||||
{
|
||||
[Test]
|
||||
public void Column()
|
||||
{
|
||||
Assert.Throws<DocumentLayoutException>(() =>
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(500, 360)
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(10)
|
||||
.Width(100)
|
||||
.Background(Colors.Grey.Lighten3)
|
||||
.DebugPointer("Example debug pointer")
|
||||
.Column(x =>
|
||||
{
|
||||
x.Item().Text("Test");
|
||||
x.Item().Width(150);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Simple()
|
||||
{
|
||||
Assert.Throws<DocumentLayoutException>(() =>
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(500, 360)
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(10)
|
||||
.Width(100)
|
||||
.Background(Colors.Grey.Lighten3)
|
||||
.Width(150)
|
||||
.Text("Test");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DebugPointer()
|
||||
{
|
||||
Assert.Throws<DocumentLayoutException>(() =>
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(500, 360)
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Background(Colors.Grey.Lighten3)
|
||||
.Padding(10)
|
||||
.Width(100)
|
||||
.DebugPointer("Example debug pointer")
|
||||
.Width(150)
|
||||
.Text("Example");
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
+6
-6
@@ -13,7 +13,7 @@ namespace QuestPDF.Examples
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProduceImages()
|
||||
.ProducePdf()
|
||||
.ShowResults()
|
||||
.RenderDocument(container =>
|
||||
{
|
||||
@@ -24,13 +24,13 @@ namespace QuestPDF.Examples
|
||||
|
||||
page.Margin(20);
|
||||
page.Size(PageSizes.A4);
|
||||
page.Background(Colors.White);
|
||||
page.PageColor(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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,55 @@
|
||||
using System.Drawing;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class DefaultTextStyleExamples
|
||||
{
|
||||
[Test]
|
||||
public void Placeholder()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(220, 270)
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.EnableDebugging()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(10)
|
||||
.DefaultTextStyle(TextStyle.Default.Bold().Underline())
|
||||
.Column(column =>
|
||||
{
|
||||
column.Item().Text("Default style applies to all children");
|
||||
column.Item().Text("You can override certain styles").Underline(false).FontColor(Colors.Green.Darken2);
|
||||
|
||||
column.Item().PaddingTop(10).Border(1).Grid(grid =>
|
||||
{
|
||||
grid.Columns(4);
|
||||
|
||||
foreach (var i in Enumerable.Range(1, 16))
|
||||
{
|
||||
grid.Item()
|
||||
.Border(1)
|
||||
.BorderColor(Colors.Grey.Lighten1)
|
||||
.Background(Colors.Grey.Lighten3)
|
||||
.Width(50)
|
||||
.Height(50)
|
||||
.AlignCenter()
|
||||
.AlignMiddle()
|
||||
.Text(i.ToString(CultureInfo.InvariantCulture))
|
||||
.FontSize(16 + i / 4);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class DifferentHeaderOnFirstPageExample
|
||||
{
|
||||
[Test]
|
||||
public void Placeholder()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A6)
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.EnableDebugging()
|
||||
.RenderDocument(container =>
|
||||
{
|
||||
container.Page(page =>
|
||||
{
|
||||
page.Size(PageSizes.A6);
|
||||
page.Margin(5);
|
||||
page.PageColor(Colors.White);
|
||||
|
||||
page.Header().Column(column =>
|
||||
{
|
||||
column.Item().ShowOnce().Background(Colors.Blue.Lighten2).Height(60);
|
||||
column.Item().SkipOnce().Background(Colors.Green.Lighten2).Height(40);
|
||||
});
|
||||
|
||||
page.Content().PaddingVertical(10).Column(column =>
|
||||
{
|
||||
column.Spacing(10);
|
||||
|
||||
foreach (var _ in Enumerable.Range(0, 13))
|
||||
column.Item().Background(Colors.Grey.Lighten2).Height(40);
|
||||
});
|
||||
|
||||
page.Footer().AlignCenter().Text(text =>
|
||||
{
|
||||
text.DefaultTextStyle(TextStyle.Default.Size(16));
|
||||
|
||||
text.CurrentPageNumber();
|
||||
text.Span(" / ");
|
||||
text.TotalPages();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public struct FibonacciHeaderState
|
||||
{
|
||||
public int Previous { get; set; }
|
||||
public int Current { get; set; }
|
||||
}
|
||||
|
||||
public class FibonacciHeader : IDynamicComponent<FibonacciHeaderState>
|
||||
{
|
||||
public FibonacciHeaderState State { get; set; }
|
||||
|
||||
public static readonly string[] ColorsTable =
|
||||
{
|
||||
Colors.Red.Lighten2,
|
||||
Colors.Orange.Lighten2,
|
||||
Colors.Green.Lighten2,
|
||||
};
|
||||
|
||||
public FibonacciHeader(int previous, int current)
|
||||
{
|
||||
State = new FibonacciHeaderState
|
||||
{
|
||||
Previous = previous,
|
||||
Current = current
|
||||
};
|
||||
}
|
||||
|
||||
public DynamicComponentComposeResult Compose(DynamicContext context)
|
||||
{
|
||||
var content = context.CreateElement(container =>
|
||||
{
|
||||
var colorIndex = State.Current % ColorsTable.Length;
|
||||
var color = ColorsTable[colorIndex];
|
||||
|
||||
var ratio = (float)State.Current / State.Previous;
|
||||
|
||||
container
|
||||
.Background(color)
|
||||
.Height(50)
|
||||
.AlignMiddle()
|
||||
.AlignCenter()
|
||||
.Text($"{State.Current} / {State.Previous} = {ratio:N5}");
|
||||
});
|
||||
|
||||
State = new FibonacciHeaderState
|
||||
{
|
||||
Previous = State.Current,
|
||||
Current = State.Previous + State.Current
|
||||
};
|
||||
|
||||
return new DynamicComponentComposeResult
|
||||
{
|
||||
Content = content,
|
||||
HasMoreContent = false
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static class DynamicFibonacci
|
||||
{
|
||||
[Test]
|
||||
public static void Dynamic()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ShowResults()
|
||||
.MaxPages(100)
|
||||
.ProduceImages()
|
||||
.RenderDocument(container =>
|
||||
{
|
||||
container.Page(page =>
|
||||
{
|
||||
page.Size(PageSizes.A6);
|
||||
page.PageColor(Colors.White);
|
||||
page.Margin(1, Unit.Centimetre);
|
||||
page.DefaultTextStyle(x => x.FontSize(18));
|
||||
|
||||
page.Header().Dynamic(new FibonacciHeader(17, 19));
|
||||
|
||||
page.Content().Column(column =>
|
||||
{
|
||||
foreach (var i in Enumerable.Range(0, 50))
|
||||
column.Item().PaddingTop(25).Background(Colors.Grey.Lighten2).Height(50);
|
||||
});
|
||||
|
||||
page.Footer().AlignCenter().Text(text =>
|
||||
{
|
||||
text.CurrentPageNumber();
|
||||
text.Span(" / ");
|
||||
text.TotalPages();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class OrderItem
|
||||
{
|
||||
public string ItemName { get; set; } = Placeholders.Label();
|
||||
public int Price { get; set; } = Placeholders.Random.Next(1, 11) * 10;
|
||||
public int Count { get; set; } = Placeholders.Random.Next(1, 11);
|
||||
}
|
||||
|
||||
public struct OrdersTableState
|
||||
{
|
||||
public int ShownItemsCount { get; set; }
|
||||
}
|
||||
|
||||
public class OptimizedOrdersTable : IDynamicComponent<OrdersTableState>
|
||||
{
|
||||
private ICollection<OrderItem> Items { get; }
|
||||
public OrdersTableState State { get; set; }
|
||||
|
||||
public OptimizedOrdersTable(ICollection<OrderItem> items)
|
||||
{
|
||||
Items = items;
|
||||
|
||||
State = new OrdersTableState
|
||||
{
|
||||
ShownItemsCount = 0
|
||||
};
|
||||
}
|
||||
|
||||
public DynamicComponentComposeResult Compose(DynamicContext context)
|
||||
{
|
||||
var header = ComposeHeader(context);
|
||||
var sampleFooter = ComposeFooter(context, Enumerable.Empty<OrderItem>());
|
||||
var decorationHeight = header.Size.Height + sampleFooter.Size.Height;
|
||||
|
||||
var rows = GetItemsForPage(context, decorationHeight).ToList();
|
||||
var footer = ComposeFooter(context, rows.Select(x => x.Item));
|
||||
|
||||
var content = context.CreateElement(container =>
|
||||
{
|
||||
container.MinimalBox().Decoration(decoration =>
|
||||
{
|
||||
decoration.Before().Element(header);
|
||||
|
||||
decoration.Content().Column(column =>
|
||||
{
|
||||
foreach (var row in rows)
|
||||
column.Item().Element(row.Element);
|
||||
});
|
||||
|
||||
decoration.After().Element(footer);
|
||||
});
|
||||
});
|
||||
|
||||
State = new OrdersTableState
|
||||
{
|
||||
ShownItemsCount = State.ShownItemsCount + rows.Count
|
||||
};
|
||||
|
||||
return new DynamicComponentComposeResult
|
||||
{
|
||||
Content = content,
|
||||
HasMoreContent = State.ShownItemsCount < Items.Count
|
||||
};
|
||||
}
|
||||
|
||||
private IDynamicElement ComposeHeader(DynamicContext context)
|
||||
{
|
||||
return context.CreateElement(element =>
|
||||
{
|
||||
element
|
||||
.Width(context.AvailableSize.Width)
|
||||
.BorderBottom(1)
|
||||
.BorderColor(Colors.Grey.Darken2)
|
||||
.Padding(5)
|
||||
.DefaultTextStyle(TextStyle.Default.SemiBold())
|
||||
.Row(row =>
|
||||
{
|
||||
row.ConstantItem(30).Text("#");
|
||||
row.RelativeItem().Text("Item name");
|
||||
row.ConstantItem(50).AlignRight().Text("Count");
|
||||
row.ConstantItem(50).AlignRight().Text("Price");
|
||||
row.ConstantItem(50).AlignRight().Text("Total");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private IDynamicElement ComposeFooter(DynamicContext context, IEnumerable<OrderItem> items)
|
||||
{
|
||||
var total = items.Sum(x => x.Count * x.Price);
|
||||
|
||||
return context.CreateElement(element =>
|
||||
{
|
||||
element
|
||||
.Width(context.AvailableSize.Width)
|
||||
.Padding(5)
|
||||
.AlignRight()
|
||||
.DefaultTextStyle(TextStyle.Default.FontSize(14).SemiBold())
|
||||
.Text($"Subtotal: {total}$");
|
||||
});
|
||||
}
|
||||
|
||||
private IEnumerable<(OrderItem Item, IDynamicElement Element)> GetItemsForPage(DynamicContext context, float decorationHeight)
|
||||
{
|
||||
var totalHeight = decorationHeight;
|
||||
|
||||
foreach (var index in Enumerable.Range(State.ShownItemsCount, Items.Count - State.ShownItemsCount))
|
||||
{
|
||||
var item = Items.ElementAt(index);
|
||||
|
||||
var element = context.CreateElement(content =>
|
||||
{
|
||||
content
|
||||
.Width(context.AvailableSize.Width)
|
||||
.BorderBottom(1)
|
||||
.BorderColor(Colors.Grey.Lighten2)
|
||||
.Padding(5)
|
||||
.Row(row =>
|
||||
{
|
||||
row.ConstantItem(30).Text((index + 1).ToString(CultureInfo.InvariantCulture));
|
||||
row.RelativeItem().Text(item.ItemName);
|
||||
row.ConstantItem(50).AlignRight().Text(item.Count.ToString(CultureInfo.InvariantCulture));
|
||||
row.ConstantItem(50).AlignRight().Text($"{item.Price}$");
|
||||
row.ConstantItem(50).AlignRight().Text($"{item.Count*item.Price}$");
|
||||
});
|
||||
});
|
||||
|
||||
var elementHeight = element.Size.Height;
|
||||
|
||||
if (totalHeight + elementHeight > context.AvailableSize.Height)
|
||||
break;
|
||||
|
||||
totalHeight += elementHeight;
|
||||
yield return (item, element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class DynamicOptimizedExamples
|
||||
{
|
||||
[Test]
|
||||
public static void Dynamic()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A5)
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
var items = Enumerable.Range(0, 25).Select(x => new OrderItem()).ToList();
|
||||
|
||||
container
|
||||
.Background(Colors.White)
|
||||
.Padding(25)
|
||||
.Decoration(decoration =>
|
||||
{
|
||||
decoration
|
||||
.Before()
|
||||
.PaddingBottom(5)
|
||||
.Text(text =>
|
||||
{
|
||||
text.DefaultTextStyle(TextStyle.Default.SemiBold().FontColor(Colors.Blue.Darken2).FontSize(16));
|
||||
text.Span("Page ");
|
||||
text.CurrentPageNumber();
|
||||
text.Span(" of ");
|
||||
text.TotalPages();
|
||||
});
|
||||
|
||||
decoration
|
||||
.Content()
|
||||
.Dynamic(new OptimizedOrdersTable(items));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class FooterWithAlternatingAlignment : IDynamicComponent<int>
|
||||
{
|
||||
public int State { get; set; }
|
||||
|
||||
public DynamicComponentComposeResult Compose(DynamicContext context)
|
||||
{
|
||||
var content = context.CreateElement(element =>
|
||||
{
|
||||
element
|
||||
.Element(x => context.PageNumber % 2 == 0 ? x.AlignLeft() : x.AlignRight())
|
||||
.Text(x =>
|
||||
{
|
||||
x.CurrentPageNumber();
|
||||
x.Span(" / ");
|
||||
x.TotalPages();
|
||||
});
|
||||
});
|
||||
|
||||
return new DynamicComponentComposeResult()
|
||||
{
|
||||
Content = content,
|
||||
HasMoreContent = false
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static class DynamicPageNumberLeftRightExamples
|
||||
{
|
||||
[Test]
|
||||
public static void Dynamic()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A5)
|
||||
.MaxPages(100)
|
||||
.ShowResults()
|
||||
.ProduceImages()
|
||||
.RenderDocument(container =>
|
||||
{
|
||||
container.Page(page =>
|
||||
{
|
||||
page.Size(PageSizes.A6);
|
||||
page.PageColor(Colors.White);
|
||||
page.Margin(1, Unit.Centimetre);
|
||||
page.DefaultTextStyle(x => x.FontSize(18));
|
||||
|
||||
page.Content().Column(column =>
|
||||
{
|
||||
foreach (var i in Enumerable.Range(0, 50))
|
||||
column.Item().PaddingTop(25).Background(Colors.Grey.Lighten2).Height(50);
|
||||
});
|
||||
|
||||
page.Footer().Dynamic(new FooterWithAlternatingAlignment());
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class ProgressHeader : IDynamicComponent<int>
|
||||
{
|
||||
public int State { get; set; }
|
||||
|
||||
public DynamicComponentComposeResult Compose(DynamicContext context)
|
||||
{
|
||||
var content = context.CreateElement(container =>
|
||||
{
|
||||
var width = context.AvailableSize.Width * context.PageNumber / context.TotalPages;
|
||||
|
||||
container
|
||||
.Background(Colors.Blue.Lighten2)
|
||||
.Height(25)
|
||||
.Width(width)
|
||||
.Background(Colors.Blue.Darken1);
|
||||
});
|
||||
|
||||
return new DynamicComponentComposeResult
|
||||
{
|
||||
Content = content,
|
||||
HasMoreContent = false
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static class DynamicProgressHeader
|
||||
{
|
||||
[Test]
|
||||
public static void Dynamic()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ShowResults()
|
||||
.MaxPages(100)
|
||||
.ProduceImages()
|
||||
.RenderDocument(container =>
|
||||
{
|
||||
container.Page(page =>
|
||||
{
|
||||
page.Size(PageSizes.A6);
|
||||
page.Margin(1, Unit.Centimetre);
|
||||
page.PageColor(Colors.White);
|
||||
page.DefaultTextStyle(x => x.FontSize(20));
|
||||
|
||||
page.Header().Dynamic(new ProgressHeader());
|
||||
|
||||
page.Content().Column(column =>
|
||||
{
|
||||
foreach (var i in Enumerable.Range(0, 100))
|
||||
column.Item().PaddingTop(25).Background(Colors.Grey.Lighten2).Height(50);
|
||||
});
|
||||
|
||||
page.Footer().AlignCenter().Text(text =>
|
||||
{
|
||||
text.DefaultTextStyle(x => x.FontSize(20));
|
||||
|
||||
text.CurrentPageNumber();
|
||||
text.Span(" / ");
|
||||
text.TotalPages();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class OrdersTable : IDynamicComponent<OrdersTableState>
|
||||
{
|
||||
private IList<OrderItem> Items { get; }
|
||||
public OrdersTableState State { get; set; }
|
||||
|
||||
public OrdersTable(IList<OrderItem> items)
|
||||
{
|
||||
Items = items;
|
||||
|
||||
State = new OrdersTableState
|
||||
{
|
||||
ShownItemsCount = 0
|
||||
};
|
||||
}
|
||||
|
||||
public DynamicComponentComposeResult Compose(DynamicContext context)
|
||||
{
|
||||
var possibleItems = Enumerable
|
||||
.Range(1, Items.Count - State.ShownItemsCount)
|
||||
.Select(itemsToDisplay => ComposeContent(context, itemsToDisplay))
|
||||
.TakeWhile(x => x.Size.Height <= context.AvailableSize.Height)
|
||||
.ToList();
|
||||
|
||||
State = new OrdersTableState
|
||||
{
|
||||
ShownItemsCount = State.ShownItemsCount + possibleItems.Count
|
||||
};
|
||||
|
||||
return new DynamicComponentComposeResult
|
||||
{
|
||||
Content = possibleItems.Last(),
|
||||
HasMoreContent = State.ShownItemsCount < Items.Count
|
||||
};
|
||||
}
|
||||
|
||||
private IDynamicElement ComposeContent(DynamicContext context, int itemsToDisplay)
|
||||
{
|
||||
var total = Items.Skip(State.ShownItemsCount).Take(itemsToDisplay).Sum(x => x.Count * x.Price);
|
||||
|
||||
return context.CreateElement(container =>
|
||||
{
|
||||
container
|
||||
.MinimalBox()
|
||||
.Width(context.AvailableSize.Width)
|
||||
.Table(table =>
|
||||
{
|
||||
table.ColumnsDefinition(columns =>
|
||||
{
|
||||
columns.ConstantColumn(30);
|
||||
columns.RelativeColumn();
|
||||
columns.ConstantColumn(50);
|
||||
columns.ConstantColumn(50);
|
||||
columns.ConstantColumn(50);
|
||||
});
|
||||
|
||||
table.Header(header =>
|
||||
{
|
||||
header.Cell().Element(Style).Text("#");
|
||||
header.Cell().Element(Style).Text("Item name");
|
||||
header.Cell().Element(Style).AlignRight().Text("Count");
|
||||
header.Cell().Element(Style).AlignRight().Text("Price");
|
||||
header.Cell().Element(Style).AlignRight().Text("Total");
|
||||
|
||||
IContainer Style(IContainer container)
|
||||
{
|
||||
return container
|
||||
.DefaultTextStyle(x => x.SemiBold())
|
||||
.BorderBottom(1)
|
||||
.BorderColor(Colors.Grey.Darken2)
|
||||
.Padding(5);
|
||||
}
|
||||
});
|
||||
|
||||
table.Footer(footer =>
|
||||
{
|
||||
footer
|
||||
.Cell().ColumnSpan(5)
|
||||
.AlignRight()
|
||||
.PaddingTop(10)
|
||||
.Text($"Subtotal: {total}$", TextStyle.Default.Bold());
|
||||
});
|
||||
|
||||
foreach (var index in Enumerable.Range(State.ShownItemsCount, itemsToDisplay))
|
||||
{
|
||||
var item = Items[index];
|
||||
|
||||
table.Cell().Element(Style).Text((index + 1).ToString(CultureInfo.InvariantCulture));
|
||||
table.Cell().Element(Style).Text(item.ItemName);
|
||||
table.Cell().Element(Style).AlignRight().Text(item.Count.ToString(CultureInfo.InvariantCulture));
|
||||
table.Cell().Element(Style).AlignRight().Text($"{item.Price}$");
|
||||
table.Cell().Element(Style).AlignRight().Text($"{item.Count*item.Price}$");
|
||||
|
||||
IContainer Style(IContainer container)
|
||||
{
|
||||
return container
|
||||
.BorderBottom(1)
|
||||
.BorderColor(Colors.Grey.Lighten2)
|
||||
.Padding(5);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static class DynamicSimpleTableExample
|
||||
{
|
||||
[Test]
|
||||
public static void Dynamic()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A5)
|
||||
.ShowResults()
|
||||
.ProduceImages()
|
||||
.Render(container =>
|
||||
{
|
||||
var items = Enumerable.Range(0, 15).Select(x => new OrderItem()).ToList();
|
||||
|
||||
container
|
||||
.Background(Colors.White)
|
||||
.Padding(25)
|
||||
.DefaultTextStyle(x => x.FontSize(16))
|
||||
.Decoration(decoration =>
|
||||
{
|
||||
decoration
|
||||
.Header()
|
||||
.PaddingBottom(5)
|
||||
.Text(text =>
|
||||
{
|
||||
text.DefaultTextStyle(TextStyle.Default.SemiBold().FontColor(Colors.Blue.Darken2));
|
||||
text.Span("Page ");
|
||||
text.CurrentPageNumber();
|
||||
text.Span(" / ");
|
||||
text.TotalPages();
|
||||
});
|
||||
|
||||
decoration
|
||||
.Content()
|
||||
.Dynamic(new OrdersTable(items));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
+74
-62
@@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -474,10 +481,10 @@ namespace QuestPDF.Examples
|
||||
.Padding(15)
|
||||
.Border(4)
|
||||
.BorderColor(Colors.Blue.Medium)
|
||||
//.Box()
|
||||
//.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);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -542,7 +551,7 @@ namespace QuestPDF.Examples
|
||||
{
|
||||
container
|
||||
.Background("#FFF")
|
||||
.Box()
|
||||
.MinimalBox()
|
||||
|
||||
.Padding(25)
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -588,10 +598,11 @@ namespace QuestPDF.Examples
|
||||
|
||||
return element;
|
||||
})
|
||||
.Box()
|
||||
.MinimalBox()
|
||||
.Background(Colors.White)
|
||||
.Padding(10)
|
||||
.Text($"Rotated {turns * 90}°", TextStyle.Default.Size(16));
|
||||
.Text($"Rotated {turns * 90}°")
|
||||
.FontSize(16);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -686,10 +697,11 @@ namespace QuestPDF.Examples
|
||||
|
||||
return element;
|
||||
})
|
||||
.Box()
|
||||
.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());
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -730,12 +742,12 @@ namespace QuestPDF.Examples
|
||||
{
|
||||
container
|
||||
.Padding(25)
|
||||
.PaddingLeft(75)
|
||||
.Stack(stack =>
|
||||
.PaddingLeft(50)
|
||||
.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");
|
||||
});
|
||||
});
|
||||
});
|
||||
+25
-8
@@ -20,7 +20,11 @@ namespace QuestPDF.Examples.Engine
|
||||
private Size Size { get; set; }
|
||||
private int? MaxPagesThreshold { get; set; }
|
||||
private bool ShowResult { get; set; }
|
||||
private bool ApplyCaching { get; set; }
|
||||
private bool ApplyDebugging { get; set; }
|
||||
private RenderingTestResult ResultType { get; set; } = RenderingTestResult.Images;
|
||||
|
||||
private bool ShowingResultsEnabled = true;
|
||||
|
||||
private RenderingTest()
|
||||
{
|
||||
@@ -67,6 +71,24 @@ namespace QuestPDF.Examples.Engine
|
||||
return this;
|
||||
}
|
||||
|
||||
public RenderingTest MaxPages(int value)
|
||||
{
|
||||
MaxPagesThreshold = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RenderingTest EnableCaching(bool value = true)
|
||||
{
|
||||
ApplyCaching = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RenderingTest EnableDebugging(bool value = true)
|
||||
{
|
||||
ApplyDebugging = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public void Render(Action<IContainer> content)
|
||||
{
|
||||
RenderDocument(container =>
|
||||
@@ -78,16 +100,11 @@ namespace QuestPDF.Examples.Engine
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public void MaxPages(int value)
|
||||
{
|
||||
MaxPagesThreshold = value;
|
||||
}
|
||||
|
||||
public void RenderDocument(Action<IDocumentContainer> content)
|
||||
{
|
||||
MaxPagesThreshold ??= ResultType == RenderingTestResult.Pdf ? 1000 : 10;
|
||||
var document = new SimpleDocument(content, MaxPagesThreshold.Value);
|
||||
var document = new SimpleDocument(content, MaxPagesThreshold.Value, ApplyCaching, ApplyDebugging);
|
||||
|
||||
Render(document);
|
||||
}
|
||||
@@ -99,7 +116,7 @@ namespace QuestPDF.Examples.Engine
|
||||
Func<int, string> fileNameSchema = i => $"{FileNamePrefix}-${i}.png";
|
||||
document.GenerateImages(fileNameSchema);
|
||||
|
||||
if (ShowResult)
|
||||
if (ShowResult && ShowingResultsEnabled)
|
||||
Process.Start("explorer", fileNameSchema(0));
|
||||
}
|
||||
|
||||
@@ -108,7 +125,7 @@ namespace QuestPDF.Examples.Engine
|
||||
var fileName = $"{FileNamePrefix}.pdf";
|
||||
document.GeneratePdf(fileName);
|
||||
|
||||
if (ShowResult)
|
||||
if (ShowResult && ShowingResultsEnabled)
|
||||
Process.Start("explorer", fileName);
|
||||
}
|
||||
}
|
||||
+8
-2
@@ -13,11 +13,15 @@ namespace QuestPDF.Examples.Engine
|
||||
|
||||
private Action<IDocumentContainer> Content { get; }
|
||||
private int MaxPages { get; }
|
||||
private bool ApplyCaching { get; }
|
||||
private bool ApplyDebugging { get; }
|
||||
|
||||
public SimpleDocument(Action<IDocumentContainer> content, int maxPages)
|
||||
public SimpleDocument(Action<IDocumentContainer> content, int maxPages, bool applyCaching, bool applyDebugging)
|
||||
{
|
||||
Content = content;
|
||||
MaxPages = maxPages;
|
||||
ApplyCaching = applyCaching;
|
||||
ApplyDebugging = applyDebugging;
|
||||
}
|
||||
|
||||
public DocumentMetadata GetMetadata()
|
||||
@@ -25,7 +29,9 @@ namespace QuestPDF.Examples.Engine
|
||||
return new DocumentMetadata()
|
||||
{
|
||||
RasterDpi = PageSizes.PointsPerInch * ImageScalingFactor,
|
||||
DocumentLayoutExceptionThreshold = MaxPages
|
||||
DocumentLayoutExceptionThreshold = MaxPages,
|
||||
ApplyCaching = ApplyCaching,
|
||||
ApplyDebugging = ApplyDebugging
|
||||
};
|
||||
}
|
||||
|
||||
+5
-5
@@ -21,19 +21,19 @@ namespace QuestPDF.Examples
|
||||
{
|
||||
page.Margin(20);
|
||||
page.Size(PageSizes.A7.Landscape());
|
||||
page.Background(Colors.White);
|
||||
page.PageColor(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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,12 +12,14 @@ namespace QuestPDF.Examples
|
||||
{
|
||||
return container
|
||||
.Border(1)
|
||||
.Background(dark ? Colors.Grey.Lighten2 : Colors.White)
|
||||
.Padding(10);
|
||||
.BorderColor(Colors.Grey.Medium)
|
||||
.Background(dark ? Colors.Grey.Lighten3 : Colors.White)
|
||||
.Padding(5);
|
||||
}
|
||||
|
||||
public static void LabelCell(this IContainer container, string text) => container.Cell(true).Text(text, TextStyle.Default.Medium());
|
||||
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);
|
||||
}
|
||||
|
||||
public class FrameExample
|
||||
@@ -34,14 +36,14 @@ namespace QuestPDF.Examples
|
||||
container
|
||||
.Background("#FFF")
|
||||
.Padding(25)
|
||||
.Stack(stack =>
|
||||
.Column(column =>
|
||||
{
|
||||
for(var i=1; i<=4; i++)
|
||||
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());
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,146 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class ProcessRunningTime
|
||||
{
|
||||
public TimeSpan FluentTime { get; set; }
|
||||
public TimeSpan GenerationTime { get; set; }
|
||||
public float Size { get; set; }
|
||||
}
|
||||
|
||||
public class GenerationBenchmark
|
||||
{
|
||||
public const int TestSize = 4096;
|
||||
|
||||
[Test]
|
||||
public void BenchmarkAsync()
|
||||
{
|
||||
RunTest(() => Enumerable
|
||||
.Range(0, TestSize)
|
||||
.AsParallel() // difference
|
||||
.Select(GenerateAndCollect)
|
||||
.ToList());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void BenchmarkSync()
|
||||
{
|
||||
RunTest(() => Enumerable
|
||||
.Range(0, TestSize)
|
||||
.Select(GenerateAndCollect)
|
||||
.ToList());
|
||||
}
|
||||
|
||||
public void RunTest(Func<IEnumerable<ProcessRunningTime>> handler)
|
||||
{
|
||||
var totalFluentTime = TimeSpan.Zero;
|
||||
var totalGenerationTime = TimeSpan.Zero;
|
||||
|
||||
var stopWatch = new Stopwatch();
|
||||
|
||||
stopWatch.Start();
|
||||
var results = handler();
|
||||
stopWatch.Stop();
|
||||
|
||||
foreach (var result in results)
|
||||
{
|
||||
totalFluentTime += result.FluentTime;
|
||||
totalGenerationTime += result.GenerationTime;
|
||||
}
|
||||
|
||||
Console.WriteLine($"Fluent: {totalFluentTime:g}");
|
||||
Console.WriteLine($"Generation: {totalGenerationTime:g}");
|
||||
Console.WriteLine($"Total: {stopWatch.Elapsed:g}");
|
||||
}
|
||||
|
||||
static ProcessRunningTime GenerateAndCollect(int attemptNumber)
|
||||
{
|
||||
var stopwatch = new Stopwatch();
|
||||
stopwatch.Start();
|
||||
|
||||
var container = new Container();
|
||||
|
||||
container
|
||||
.Padding(10)
|
||||
.MinimalBox()
|
||||
.Border(1)
|
||||
.Column(column =>
|
||||
{
|
||||
column.Item().Text($"Attempts {attemptNumber}");
|
||||
|
||||
const int numberOfRows = 100;
|
||||
const int numberOfColumns = 10;
|
||||
|
||||
for (var y = 0; y < numberOfRows; y++)
|
||||
{
|
||||
column.Item().Row(row =>
|
||||
{
|
||||
for (var x = 0; x < numberOfColumns; x++)
|
||||
{
|
||||
row.RelativeItem()
|
||||
|
||||
.Background(Colors.Red.Lighten5)
|
||||
.Padding(3)
|
||||
|
||||
.Background(Colors.Red.Lighten4)
|
||||
.Padding(3)
|
||||
|
||||
.Background(Colors.Red.Lighten3)
|
||||
.Padding(3)
|
||||
|
||||
.Background(Colors.Red.Lighten2)
|
||||
.Padding(3)
|
||||
|
||||
.Background(Colors.Red.Lighten1)
|
||||
.Padding(3)
|
||||
|
||||
.Background(Colors.Red.Medium)
|
||||
.Padding(3)
|
||||
|
||||
.Background(Colors.Red.Darken1)
|
||||
.Padding(3)
|
||||
|
||||
.Background(Colors.Red.Darken2)
|
||||
.Padding(3)
|
||||
|
||||
.Background(Colors.Red.Darken3)
|
||||
.Padding(3)
|
||||
|
||||
.Background(Colors.Red.Darken4)
|
||||
.Height(3);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
var fluentTime = stopwatch.Elapsed;
|
||||
|
||||
stopwatch.Reset();
|
||||
stopwatch.Start();
|
||||
|
||||
var size = Document
|
||||
.Create(x => x.Page(page => page.Content().Element(container)))
|
||||
.GeneratePdf()
|
||||
.Length;
|
||||
|
||||
var generationTime = stopwatch.Elapsed;
|
||||
|
||||
return new ProcessRunningTime
|
||||
{
|
||||
FluentTime = fluentTime,
|
||||
GenerationTime = generationTime,
|
||||
Size = size
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class GridBenchmark
|
||||
{
|
||||
[Test]
|
||||
public void Benchmark()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProducePdf()
|
||||
.PageSize(PageSizes.A4)
|
||||
.ShowResults()
|
||||
.MaxPages(10_000)
|
||||
.EnableCaching(true)
|
||||
.EnableDebugging(false)
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(10)
|
||||
.MinimalBox()
|
||||
.Border(1)
|
||||
.Grid(grid =>
|
||||
{
|
||||
const int numberOfRows = 100_000;
|
||||
const int numberOfColumns = 10;
|
||||
|
||||
grid.Columns(numberOfColumns);
|
||||
|
||||
foreach (var row in Enumerable.Range(0, numberOfRows))
|
||||
foreach (var column in Enumerable.Range(0, numberOfColumns))
|
||||
grid.Item().Background(Placeholders.BackgroundColor()).Padding(5).Text($"{row}_{column}");
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Drawing.Exceptions;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class ImageExamples
|
||||
{
|
||||
[Test]
|
||||
public void LoadingImage()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A5)
|
||||
.ProducePdf()
|
||||
.ShowResults()
|
||||
.Render(page =>
|
||||
{
|
||||
page.Padding(25).Column(column =>
|
||||
{
|
||||
column.Spacing(25);
|
||||
|
||||
column.Item().Image("logo.png");
|
||||
|
||||
var binaryData = File.ReadAllBytes("logo.png");
|
||||
column.Item().Image(binaryData);
|
||||
|
||||
using var stream = new FileStream("logo.png", FileMode.Open);
|
||||
column.Item().Image(stream);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DynamicImage()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(450, 350)
|
||||
.ProducePdf()
|
||||
.ShowResults()
|
||||
.Render(page =>
|
||||
{
|
||||
page.Padding(25)
|
||||
.Image(Placeholders.Image);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Exception()
|
||||
{
|
||||
Assert.Throws<DocumentComposeException>(() =>
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A2)
|
||||
.ProducePdf()
|
||||
.ShowResults()
|
||||
.Render(page => page.Image("non_existent.png"));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ReusingTheSameImageFileShouldBePossible()
|
||||
{
|
||||
var fileName = Path.GetTempFileName() + ".jpg";
|
||||
|
||||
try
|
||||
{
|
||||
var image = Placeholders.Image(300, 100);
|
||||
|
||||
using var file = File.Create(fileName);
|
||||
file.Write(image);
|
||||
file.Dispose();
|
||||
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProducePdf()
|
||||
.PageSize(PageSizes.A4)
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(20)
|
||||
.Column(column =>
|
||||
{
|
||||
column.Spacing(20);
|
||||
|
||||
column.Item().Image(fileName);
|
||||
column.Item().Image(fileName);
|
||||
column.Item().Image(fileName);
|
||||
});
|
||||
});
|
||||
}
|
||||
finally
|
||||
{
|
||||
File.Delete(fileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace QuestPDF.Examples
|
||||
decoration
|
||||
.Content()
|
||||
.PaddingTop(25)
|
||||
//.Box()
|
||||
//.MinimalBox()
|
||||
.Border(1)
|
||||
.Background(Colors.Grey.Lighten2)
|
||||
.Inlined(inlined =>
|
||||
@@ -74,7 +74,8 @@ namespace QuestPDF.Examples
|
||||
.PrimaryLayer()
|
||||
.AlignCenter()
|
||||
.AlignMiddle()
|
||||
.Text(sizeText, TextStyle.Default.Size(15));
|
||||
.Text(sizeText)
|
||||
.FontSize(15);
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -94,7 +95,7 @@ namespace QuestPDF.Examples
|
||||
{
|
||||
container
|
||||
.Padding(20)
|
||||
.Box()
|
||||
.MinimalBox()
|
||||
.Border(1)
|
||||
.Background(Colors.Grey.Lighten4)
|
||||
.Inlined(inlined =>
|
||||
@@ -119,5 +120,42 @@ namespace QuestPDF.Examples
|
||||
.Background(Placeholders.BackgroundColor());
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RepeatingInlinedInHeader_Test()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.RenderDocument(document =>
|
||||
{
|
||||
document.Page(page =>
|
||||
{
|
||||
page.Size(PageSizes.A4);
|
||||
page.Margin(1, Unit.Inch);
|
||||
page.PageColor(Colors.White);
|
||||
|
||||
page.Header()
|
||||
.Inlined(inlined =>
|
||||
{
|
||||
inlined.Spacing(10);
|
||||
|
||||
foreach (var i in Enumerable.Range(5, 5))
|
||||
inlined.Item().Width(i * 10).Height(20).Background(Colors.Red.Medium);
|
||||
});
|
||||
|
||||
page.Content()
|
||||
.PaddingVertical(20)
|
||||
.Column(column =>
|
||||
{
|
||||
column.Spacing(25);
|
||||
|
||||
foreach (var i in Enumerable.Range(10, 20))
|
||||
column.Item().Width(i * 10).Height(50).Background(Colors.Grey.Lighten2);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
+7
-3
@@ -19,10 +19,12 @@ namespace QuestPDF.Examples
|
||||
{
|
||||
var url = "https://picsum.photos/300/200";
|
||||
|
||||
if(Greyscale)
|
||||
if (Greyscale)
|
||||
url += "?grayscale";
|
||||
|
||||
using var client = new WebClient();
|
||||
client.Headers.Add("user-agent", "QuestPDF/1.0 Unit Testing");
|
||||
|
||||
var response = client.DownloadData(url);
|
||||
container.Image(response);
|
||||
}
|
||||
@@ -36,12 +38,14 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(350, 280)
|
||||
.ProducePdf()
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Background("#FFF")
|
||||
.Padding(25)
|
||||
.Stack(column =>
|
||||
.Column(column =>
|
||||
{
|
||||
column.Spacing(10);
|
||||
|
||||
@@ -57,4 +61,4 @@ namespace QuestPDF.Examples
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
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.PageColor(Colors.White);
|
||||
page.DefaultTextStyle(x => x.FontSize(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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,57 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class PageBackgroundForegroundExample
|
||||
{
|
||||
[Test]
|
||||
public void PageBackgroundForeground()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProduceImages()
|
||||
.MaxPages(100)
|
||||
.ShowResults()
|
||||
.RenderDocument(document =>
|
||||
{
|
||||
document.Page(page =>
|
||||
{
|
||||
page.Size(PageSizes.A4);
|
||||
page.Margin(1, Unit.Inch);
|
||||
page.DefaultTextStyle(TextStyle.Default.FontSize(16));
|
||||
page.PageColor(Colors.White);
|
||||
|
||||
const string transparentBlue = "#662196f3";
|
||||
|
||||
page.Background()
|
||||
.AlignTop()
|
||||
.ExtendHorizontal()
|
||||
.Height(200)
|
||||
.Background(transparentBlue);
|
||||
|
||||
page.Foreground()
|
||||
.AlignBottom()
|
||||
.ExtendHorizontal()
|
||||
.Height(250)
|
||||
.Background(transparentBlue);
|
||||
|
||||
page.Header().Text("Background and foreground").Bold().FontColor(Colors.Blue.Darken2).FontSize(36);
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
+9
-4
@@ -6,11 +6,13 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.13.2" />
|
||||
<PackageReference Include="microcharts" Version="0.9.5.9" />
|
||||
<PackageReference Include="nunit" Version="3.13.2" />
|
||||
<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="nunit" Version="3.13.3" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.3.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.0" />
|
||||
<PackageReference Include="SkiaSharp" Version="2.88.3" />
|
||||
<PackageReference Include="Svg.Skia" Version="0.5.18" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -27,6 +29,9 @@
|
||||
<None Update="logo.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="pdf-icon.svg">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,145 @@
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class RowExamples
|
||||
{
|
||||
[Test]
|
||||
public void ItemTypes()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProducePdf()
|
||||
.PageSize(650, 300)
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(25)
|
||||
.MinimalBox()
|
||||
.Border(1)
|
||||
.Column(column =>
|
||||
{
|
||||
column.Item().LabelCell("Total width: 600px");
|
||||
|
||||
column.Item().Row(row =>
|
||||
{
|
||||
row.ConstantItem(150).ValueCell("150px");
|
||||
row.ConstantItem(100).ValueCell("100px");
|
||||
row.RelativeItem(4).ValueCell("200px");
|
||||
row.RelativeItem(3).ValueCell("150px");
|
||||
});
|
||||
|
||||
column.Item().Row(row =>
|
||||
{
|
||||
row.ConstantItem(100).ValueCell("100px");
|
||||
row.ConstantItem(50).ValueCell("50px");
|
||||
row.RelativeItem(2).ValueCell("100px");
|
||||
row.RelativeItem(1).ValueCell("50px");
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Stability()
|
||||
{
|
||||
// up to version 2021.12, this code would always result with the infinite layout exception
|
||||
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProducePdf()
|
||||
.MaxPages(100)
|
||||
.PageSize(250, 150)
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(25)
|
||||
.Row(row =>
|
||||
{
|
||||
row.RelativeItem().Column(column =>
|
||||
{
|
||||
column.Item().ShowOnce().Element(CreateBox).Text("X");
|
||||
column.Item().Element(CreateBox).Text("1");
|
||||
column.Item().Element(CreateBox).Text("2");
|
||||
});
|
||||
|
||||
row.RelativeItem().Column(column =>
|
||||
{
|
||||
column.Item().Element(CreateBox).Text("1");
|
||||
column.Item().Element(CreateBox).Text("2");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
static IContainer CreateBox(IContainer container)
|
||||
{
|
||||
return container
|
||||
.ExtendHorizontal()
|
||||
.ExtendVertical()
|
||||
.Background(Colors.Grey.Lighten4)
|
||||
.Border(1)
|
||||
.AlignCenter()
|
||||
.AlignMiddle()
|
||||
.ShowOnce();
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Stability_NoItems()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProducePdf()
|
||||
.MaxPages(100)
|
||||
.PageSize(250, 150)
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(25)
|
||||
.Row(row => { });
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RowElementForRelativeHeightDivision()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.MaxPages(100)
|
||||
.PageSize(250, 400)
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(25)
|
||||
.AlignLeft()
|
||||
.RotateRight()
|
||||
.Row(row =>
|
||||
{
|
||||
row.Spacing(20);
|
||||
|
||||
row.RelativeItem(1).Element(Content);
|
||||
row.RelativeItem(2).Element(Content);
|
||||
row.RelativeItem(3).Element(Content);
|
||||
|
||||
void Content(IContainer container)
|
||||
{
|
||||
container
|
||||
.RotateLeft()
|
||||
.Border(1)
|
||||
.Background(Placeholders.BackgroundColor())
|
||||
.Padding(5)
|
||||
.Text(Placeholders.Label());
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,20 +21,20 @@ namespace QuestPDF.Examples
|
||||
{
|
||||
page.Margin(20);
|
||||
page.Size(PageSizes.A7.Landscape());
|
||||
page.Background(Colors.White);
|
||||
page.PageColor(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());
|
||||
@@ -21,17 +21,18 @@ namespace QuestPDF.Examples
|
||||
{
|
||||
page.Margin(20);
|
||||
page.Size(PageSizes.A7.Landscape());
|
||||
page.Background(Colors.White);
|
||||
page.PageColor(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 =>
|
||||
{
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class TableBenchmark
|
||||
{
|
||||
[Test]
|
||||
public void Benchmark()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProducePdf()
|
||||
.PageSize(PageSizes.A4)
|
||||
.ShowResults()
|
||||
.MaxPages(10_000)
|
||||
.EnableCaching(true)
|
||||
.EnableDebugging(false)
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(10)
|
||||
.MinimalBox()
|
||||
.Border(1)
|
||||
.Table(table =>
|
||||
{
|
||||
const int numberOfRows = 100_000;
|
||||
const int numberOfColumns = 10;
|
||||
|
||||
table.ColumnsDefinition(columns =>
|
||||
{
|
||||
foreach (var _ in Enumerable.Range(0, numberOfColumns))
|
||||
columns.RelativeColumn();
|
||||
});
|
||||
|
||||
foreach (var row in Enumerable.Range(0, numberOfRows))
|
||||
foreach (var column in Enumerable.Range(0, numberOfColumns))
|
||||
table.Cell().Background(Placeholders.BackgroundColor()).Padding(5).Text($"{row}_{column}");
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,554 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
using IContainer = QuestPDF.Infrastructure.IContainer;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class TableExamples
|
||||
{
|
||||
[Test]
|
||||
public void BasicPlacement()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProduceImages()
|
||||
.PageSize(220, 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().Row(1).Column(4).Element(Block).Text("A");
|
||||
table.Cell().Row(2).Column(2).Element(Block).Text("B");
|
||||
table.Cell().Row(3).Column(3).Element(Block).Text("C");
|
||||
table.Cell().Row(4).Column(1).Element(Block).Text("D");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[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()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProduceImages()
|
||||
.PageSize(220, 120)
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(10)
|
||||
.MinimalBox()
|
||||
.Border(1)
|
||||
.DefaultTextStyle(TextStyle.Default.Size(16))
|
||||
.Table(table =>
|
||||
{
|
||||
table.ColumnsDefinition(columns =>
|
||||
{
|
||||
columns.RelativeColumn();
|
||||
columns.RelativeColumn();
|
||||
columns.RelativeColumn();
|
||||
columns.RelativeColumn();
|
||||
});
|
||||
|
||||
table.Cell().Row(1).Column(1).Element(Block).Text("A");
|
||||
table.Cell().Row(2).Column(2).Element(Block).Text("B");
|
||||
table.Cell().Row(1).Column(3).Element(Block).Text("C");
|
||||
table.Cell().Row(2).Column(4).Element(Block).Text("D");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ColumnsDefinition()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProduceImages()
|
||||
.PageSize(320, 80)
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(10)
|
||||
.Table(table =>
|
||||
{
|
||||
table.ColumnsDefinition(columns =>
|
||||
{
|
||||
columns.ConstantColumn(50);
|
||||
columns.ConstantColumn(100);
|
||||
columns.RelativeColumn(2);
|
||||
columns.RelativeColumn(3);
|
||||
});
|
||||
|
||||
table.Cell().ColumnSpan(4).LabelCell("Total width: 300px");
|
||||
table.Cell().ValueCell("50px");
|
||||
table.Cell().ValueCell("100px");
|
||||
table.Cell().ValueCell("100px");
|
||||
table.Cell().ValueCell("150px");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PartialAutoPlacement()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProduceImages()
|
||||
.PageSize(220, 220)
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(10)
|
||||
.MinimalBox()
|
||||
.Border(1)
|
||||
.Table(table =>
|
||||
{
|
||||
table.ColumnsDefinition(columns =>
|
||||
{
|
||||
columns.RelativeColumn();
|
||||
columns.RelativeColumn();
|
||||
columns.RelativeColumn();
|
||||
columns.RelativeColumn();
|
||||
});
|
||||
|
||||
table.Cell().Element(Block).Text("A");
|
||||
table.Cell().Row(2).Column(2).Element(Block).Text("B");
|
||||
table.Cell().Element(Block).Text("C");
|
||||
table.Cell().Row(3).Column(3).Element(Block).Text("D");
|
||||
table.Cell().ColumnSpan(2).Element(Block).Text("E");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ExtendLastCellsToTableBottom()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProduceImages()
|
||||
.PageSize(220, 170)
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(10)
|
||||
.MinimalBox()
|
||||
.Border(1)
|
||||
.Table(table =>
|
||||
{
|
||||
table.ColumnsDefinition(columns =>
|
||||
{
|
||||
columns.RelativeColumn();
|
||||
columns.RelativeColumn();
|
||||
columns.RelativeColumn();
|
||||
columns.RelativeColumn();
|
||||
});
|
||||
|
||||
table.ExtendLastCellsToTableBottom();
|
||||
|
||||
table.Cell().Row(1).Column(1).Element(Block).Text("A");
|
||||
table.Cell().Row(3).Column(1).Element(Block).Text("B");
|
||||
table.Cell().Row(2).Column(2).Element(Block).Text("C");
|
||||
table.Cell().Row(3).Column(3).Element(Block).Text("D");
|
||||
table.Cell().Row(2).RowSpan(2).Column(4).Element(Block).Text("E");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Overlapping()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProduceImages()
|
||||
.PageSize(170, 170)
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(10)
|
||||
.MinimalBox()
|
||||
.Border(1)
|
||||
.Table(table =>
|
||||
{
|
||||
table.ColumnsDefinition(columns =>
|
||||
{
|
||||
columns.RelativeColumn();
|
||||
columns.RelativeColumn();
|
||||
columns.RelativeColumn();
|
||||
});
|
||||
|
||||
table.Cell().Row(1).RowSpan(3).Column(1).ColumnSpan(3).Background(Colors.Grey.Lighten3).MinHeight(150);
|
||||
table.Cell().Row(1).RowSpan(2).Column(1).ColumnSpan(2).Background(Colors.Grey.Lighten1).MinHeight(100);
|
||||
table.Cell().Row(3).Column(3).Background(Colors.Grey.Darken1).MinHeight(50);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Spans()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProduceImages()
|
||||
.PageSize(220, 170)
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(10)
|
||||
.Table(table =>
|
||||
{
|
||||
table.ColumnsDefinition(columns =>
|
||||
{
|
||||
columns.RelativeColumn();
|
||||
columns.RelativeColumn();
|
||||
columns.RelativeColumn();
|
||||
columns.RelativeColumn();
|
||||
});
|
||||
|
||||
table.Cell().RowSpan(2).ColumnSpan(2).Element(Block).Text("1");
|
||||
table.Cell().ColumnSpan(2).Element(Block).Text("2");
|
||||
table.Cell().Element(Block).Text("3");
|
||||
table.Cell().Element(Block).Text("4");
|
||||
table.Cell().RowSpan(2).Element(Block).Text("5");
|
||||
table.Cell().ColumnSpan(2).Element(Block).Text("6");
|
||||
table.Cell().RowSpan(2).Element(Block).Text("7");
|
||||
table.Cell().Element(Block).Text("8");
|
||||
table.Cell().Element(Block).Text("9");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Stability()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProduceImages()
|
||||
.PageSize(300, 300)
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(10)
|
||||
.Table(table =>
|
||||
{
|
||||
table.ColumnsDefinition(columns =>
|
||||
{
|
||||
columns.RelativeColumn();
|
||||
columns.RelativeColumn();
|
||||
columns.RelativeColumn();
|
||||
columns.RelativeColumn();
|
||||
columns.RelativeColumn();
|
||||
columns.RelativeColumn();
|
||||
});
|
||||
|
||||
var image = Placeholders.Image(400, 300);
|
||||
|
||||
table.Cell().Image(image);
|
||||
table.Cell().Image(image);
|
||||
table.Cell().Image(image);
|
||||
table.Cell().Image(image);
|
||||
table.Cell().Image(image);
|
||||
table.Cell().Image(image);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Bug_RowSpanWorksIncorrectly()
|
||||
{
|
||||
// https://github.com/QuestPDF/QuestPDF/issues/552
|
||||
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProduceImages()
|
||||
.PageSize(PageSizes.A5.Landscape())
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container.Padding(20).Table(table =>
|
||||
{
|
||||
table.ColumnsDefinition(columns =>
|
||||
{
|
||||
columns.RelativeColumn(1);
|
||||
columns.RelativeColumn(2);
|
||||
columns.RelativeColumn(2);
|
||||
columns.RelativeColumn(2);
|
||||
});
|
||||
|
||||
foreach (var i in Enumerable.Range(6, 9))
|
||||
{
|
||||
table.Cell().Element(CellStyleMainTable).Text($"{i:00}:00");
|
||||
|
||||
foreach (var j in Enumerable.Range(1, 3))
|
||||
table.Cell().Element(CellStyleMainTable).Text("");
|
||||
}
|
||||
|
||||
static IContainer CellStyleMainTable(IContainer container)
|
||||
{
|
||||
return container
|
||||
.Border(0.5f).BorderColor(Colors.Blue.Lighten4)
|
||||
.Background(Colors.Blue.Lighten5)
|
||||
.PaddingVertical(5);
|
||||
}
|
||||
|
||||
table.Cell().Row(1).RowSpan(3).Column(2).Element(BlockAccepted).Text("3 rows");
|
||||
table.Cell().Row(1).RowSpan(6).Column(3).Element(BlockAccepted).Text("6 rows");
|
||||
table.Cell().Row(3).RowSpan(5).Column(4).Element(BlockAccepted).Text("5 rows");
|
||||
|
||||
static IContainer BlockAccepted(IContainer container)
|
||||
{
|
||||
return container
|
||||
.Border(1.5f).BorderColor(Colors.Green.Lighten2)
|
||||
.Background(Colors.Green.Lighten4);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TableHeader()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProduceImages()
|
||||
.PageSize(500, 200)
|
||||
.ShowResults()
|
||||
.EnableDebugging()
|
||||
.Render(container =>
|
||||
{
|
||||
var pageSizes = new List<(string name, double width, double height)>()
|
||||
{
|
||||
("Letter (ANSI A)", 8.5f, 11),
|
||||
("Legal", 8.5f, 14),
|
||||
("Ledger (ANSI B)", 11, 17),
|
||||
("Tabloid (ANSI B)", 17, 11),
|
||||
("ANSI C", 22, 17),
|
||||
("ANSI D", 34, 22),
|
||||
("ANSI E", 44, 34)
|
||||
};
|
||||
|
||||
const int inchesToPoints = 72;
|
||||
|
||||
container
|
||||
.Padding(10)
|
||||
.MinimalBox()
|
||||
.Border(1)
|
||||
.Table(table =>
|
||||
{
|
||||
IContainer DefaultCellStyle(IContainer container, string backgroundColor)
|
||||
{
|
||||
return container
|
||||
.Border(1)
|
||||
.BorderColor(Colors.Grey.Lighten1)
|
||||
.Background(backgroundColor)
|
||||
.PaddingVertical(5)
|
||||
.PaddingHorizontal(10)
|
||||
.AlignCenter()
|
||||
.AlignMiddle();
|
||||
}
|
||||
|
||||
table.ColumnsDefinition(columns =>
|
||||
{
|
||||
columns.RelativeColumn();
|
||||
|
||||
columns.ConstantColumn(75);
|
||||
columns.ConstantColumn(75);
|
||||
|
||||
columns.ConstantColumn(75);
|
||||
columns.ConstantColumn(75);
|
||||
});
|
||||
|
||||
table.Header(header =>
|
||||
{
|
||||
header.Cell().RowSpan(2).Element(CellStyle).ExtendHorizontal().AlignLeft().Text("Document type");
|
||||
|
||||
header.Cell().ColumnSpan(2).Element(CellStyle).Text("Inches");
|
||||
header.Cell().ColumnSpan(2).Element(CellStyle).Text("Points");
|
||||
|
||||
header.Cell().Element(CellStyle).Text("Width");
|
||||
header.Cell().Element(CellStyle).Text("Height");
|
||||
|
||||
header.Cell().Element(CellStyle).Text("Width");
|
||||
header.Cell().Element(CellStyle).Text("Height");
|
||||
|
||||
// you can extend already existing styles by creating additional methods
|
||||
IContainer CellStyle(IContainer container) => DefaultCellStyle(container, Colors.Grey.Lighten3);
|
||||
});
|
||||
|
||||
foreach (var page in pageSizes)
|
||||
{
|
||||
table.Cell().Element(CellStyle).ExtendHorizontal().AlignLeft().Text(page.name);
|
||||
|
||||
// inches
|
||||
table.Cell().Element(CellStyle).Text(page.width.ToString(CultureInfo.InvariantCulture));
|
||||
table.Cell().Element(CellStyle).Text(page.height.ToString(CultureInfo.InvariantCulture));
|
||||
|
||||
// points
|
||||
table.Cell().Element(CellStyle).Text((page.width * inchesToPoints).ToString(CultureInfo.InvariantCulture));
|
||||
table.Cell().Element(CellStyle).Text((page.height * inchesToPoints).ToString(CultureInfo.InvariantCulture));
|
||||
|
||||
IContainer CellStyle(IContainer container) => DefaultCellStyle(container, Colors.White);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PerformanceText_TemperatureReport()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProducePdf()
|
||||
.PageSize(PageSizes.A4)
|
||||
.MaxPages(10_000)
|
||||
.EnableCaching()
|
||||
.EnableDebugging(false)
|
||||
.ShowResults()
|
||||
.Render(container => GeneratePerformanceStructure(container, 250));
|
||||
}
|
||||
|
||||
public static void GeneratePerformanceStructure(IContainer container, int repeats)
|
||||
{
|
||||
container
|
||||
.Padding(25)
|
||||
//.Background(Colors.Blue.Lighten2)
|
||||
.MinimalBox()
|
||||
.Border(1)
|
||||
//.Background(Colors.Red.Lighten2)
|
||||
.Table(table =>
|
||||
{
|
||||
table.ColumnsDefinition(columns =>
|
||||
{
|
||||
columns.ConstantColumn(100);
|
||||
columns.RelativeColumn();
|
||||
columns.ConstantColumn(100);
|
||||
columns.RelativeColumn();
|
||||
});
|
||||
|
||||
table.ExtendLastCellsToTableBottom();
|
||||
|
||||
foreach (var i in Enumerable.Range(0, repeats))
|
||||
{
|
||||
table.Cell().RowSpan(3).LabelCell("Project");
|
||||
table.Cell().RowSpan(3).ShowEntire().ValueCell(Placeholders.Sentence());
|
||||
|
||||
table.Cell().LabelCell("Report number");
|
||||
table.Cell().ValueCell(i.ToString());
|
||||
|
||||
table.Cell().LabelCell("Date");
|
||||
table.Cell().ValueCell(Placeholders.ShortDate());
|
||||
|
||||
table.Cell().LabelCell("Inspector");
|
||||
table.Cell().ValueCell("Marcin Ziąbek");
|
||||
|
||||
table.Cell().ColumnSpan(2).LabelCell("Morning weather");
|
||||
table.Cell().ColumnSpan(2).LabelCell("Evening weather");
|
||||
|
||||
table.Cell().ValueCell("Time");
|
||||
table.Cell().ValueCell("7:13");
|
||||
|
||||
table.Cell().ValueCell("Time");
|
||||
table.Cell().ValueCell("18:25");
|
||||
|
||||
table.Cell().ValueCell("Description");
|
||||
table.Cell().ValueCell("Sunny");
|
||||
|
||||
table.Cell().ValueCell("Description");
|
||||
table.Cell().ValueCell("Windy");
|
||||
|
||||
table.Cell().ValueCell("Wind");
|
||||
table.Cell().ValueCell("Mild");
|
||||
|
||||
table.Cell().ValueCell("Wind");
|
||||
table.Cell().ValueCell("Strong");
|
||||
|
||||
table.Cell().ValueCell("Temperature");
|
||||
table.Cell().ValueCell("17°C");
|
||||
|
||||
table.Cell().ValueCell("Temperature");
|
||||
table.Cell().ValueCell("32°C");
|
||||
|
||||
table.Cell().LabelCell("Remarks");
|
||||
table.Cell().ColumnSpan(3).ValueCell(Placeholders.Paragraph());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// this method uses a higher order function to define a custom and dynamic style
|
||||
static IContainer Block(IContainer container)
|
||||
{
|
||||
return container
|
||||
.Border(1)
|
||||
.Background(Colors.Grey.Lighten3)
|
||||
.ShowOnce()
|
||||
.MinWidth(50)
|
||||
.MinHeight(50)
|
||||
.Padding(10)
|
||||
.AlignCenter()
|
||||
.AlignMiddle();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ namespace QuestPDF.Examples
|
||||
.PageSize(PageSizes.A4)
|
||||
.ProducePdf()
|
||||
.ShowResults()
|
||||
.Render(x => ComposeBook(x, chapters));
|
||||
.RenderDocument(x => ComposeBook(x, chapters));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -31,7 +31,7 @@ namespace QuestPDF.Examples
|
||||
{
|
||||
var chapters = GetBookChapters().ToList();
|
||||
|
||||
var results = PerformTest(16).ToList();
|
||||
var results = PerformTest(128).ToList();
|
||||
|
||||
Console.WriteLine($"Min: {results.Min():F}");
|
||||
Console.WriteLine($"Max: {results.Max():F}");
|
||||
@@ -42,8 +42,8 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(PageSizes.A4)
|
||||
.ProducePdf()
|
||||
.Render(x => ComposeBook(x, chapters));
|
||||
.ProducePdf()
|
||||
.RenderDocument(x => ComposeBook(x, chapters));
|
||||
}
|
||||
|
||||
IEnumerable<float> PerformTest(int attempts)
|
||||
@@ -102,112 +102,105 @@ namespace QuestPDF.Examples
|
||||
}
|
||||
}
|
||||
|
||||
private void ComposeBook(IContainer container, ICollection<BookChapter> chapters)
|
||||
private void ComposeBook(IDocumentContainer container, ICollection<BookChapter> chapters)
|
||||
{
|
||||
var subtitleStyle = TextStyle.Default.Size(24).SemiBold().Color(Colors.Blue.Medium);
|
||||
var normalStyle = TextStyle.Default.Size(14);
|
||||
|
||||
ComposePage(container);
|
||||
|
||||
void ComposePage(IContainer container)
|
||||
container.Page(page =>
|
||||
{
|
||||
container
|
||||
.Padding(50)
|
||||
.Decoration(decoration =>
|
||||
{
|
||||
decoration
|
||||
.Content()
|
||||
.Stack(stack =>
|
||||
{
|
||||
stack.Item().Element(Title);
|
||||
stack.Item().PageBreak();
|
||||
stack.Item().Element(TableOfContents);
|
||||
stack.Item().PageBreak();
|
||||
page.Margin(50);
|
||||
|
||||
page.Content().PaddingVertical(10).Column(column =>
|
||||
{
|
||||
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);
|
||||
});
|
||||
|
||||
page.Footer().Element(Footer);
|
||||
});
|
||||
|
||||
decoration.Footer().Element(Footer);
|
||||
});
|
||||
}
|
||||
|
||||
void Title(IContainer container)
|
||||
{
|
||||
container
|
||||
.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)
|
||||
File diff suppressed because it is too large
Load Diff
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
@@ -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 "PDF":
|
||||
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 |
@@ -0,0 +1,62 @@
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
using QuestPDF.Previewer;
|
||||
using QuestPDF.ReportSample;
|
||||
using QuestPDF.ReportSample.Layouts;
|
||||
|
||||
//ImagePlaceholder.Solid = true;
|
||||
|
||||
// var model = DataSource.GetReport();
|
||||
// var report = new StandardReport(model);
|
||||
// report.ShowInPreviewer();
|
||||
//
|
||||
// return;
|
||||
|
||||
Document
|
||||
.Create(container =>
|
||||
{
|
||||
container.Page(page =>
|
||||
{
|
||||
page.Size(PageSizes.A4);
|
||||
page.Margin(2, Unit.Centimetre);
|
||||
page.PageColor(Colors.White);
|
||||
page.DefaultTextStyle(x => x.FontSize(20));
|
||||
|
||||
page.Header()
|
||||
.Text("Hot Reload!")
|
||||
.SemiBold().FontSize(36).FontColor(Colors.Blue.Darken2);
|
||||
|
||||
page.Content()
|
||||
.PaddingVertical(1, Unit.Centimetre)
|
||||
.Column(x =>
|
||||
{
|
||||
x.Spacing(20);
|
||||
|
||||
x.Item().Table(t =>
|
||||
{
|
||||
t.ColumnsDefinition(c =>
|
||||
{
|
||||
c.RelativeColumn();
|
||||
c.RelativeColumn(3);
|
||||
});
|
||||
|
||||
t.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(5).Text("Visual Studio");
|
||||
t.Cell().Border(1).Padding(5).Text("Start in debug mode with 'Hot Reload on Save' enabled.");
|
||||
t.Cell().Border(1).Background(Colors.Grey.Lighten3).Padding(5).Text("Command line");
|
||||
t.Cell().Border(1).Padding(5).Text("Run 'dotnet watch'.");
|
||||
});
|
||||
|
||||
x.Item().Text("Modify this line and the preview should show your changes instantly.");
|
||||
});
|
||||
|
||||
page.Footer()
|
||||
.AlignCenter()
|
||||
.Text(x =>
|
||||
{
|
||||
x.Span("Page ");
|
||||
x.CurrentPageNumber();
|
||||
});
|
||||
});
|
||||
})
|
||||
.ShowInPreviewer();
|
||||
@@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\QuestPDF.ReportSample\QuestPDF.ReportSample.csproj" />
|
||||
<ProjectReference Include="..\QuestPDF\QuestPDF.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,79 @@
|
||||
using System.Text.Json;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace QuestPDF.Previewer;
|
||||
|
||||
class CommunicationService
|
||||
{
|
||||
public static CommunicationService Instance { get; } = new ();
|
||||
|
||||
public event Action<ICollection<PreviewPage>>? OnDocumentRefreshed;
|
||||
|
||||
private WebApplication? Application { get; set; }
|
||||
|
||||
private readonly JsonSerializerOptions JsonSerializerOptions = new()
|
||||
{
|
||||
PropertyNameCaseInsensitive = true
|
||||
};
|
||||
|
||||
private CommunicationService()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public Task Start(int port)
|
||||
{
|
||||
var builder = WebApplication.CreateBuilder();
|
||||
builder.Services.AddLogging(x => x.ClearProviders());
|
||||
builder.WebHost.UseKestrel(options => options.Limits.MaxRequestBodySize = null);
|
||||
Application = builder.Build();
|
||||
|
||||
Application.MapGet("ping", HandlePing);
|
||||
Application.MapGet("version", HandleVersion);
|
||||
Application.MapPost("update/preview", HandleUpdatePreview);
|
||||
|
||||
return Application.RunAsync($"http://localhost:{port}/");
|
||||
}
|
||||
|
||||
public async Task Stop()
|
||||
{
|
||||
await Application.StopAsync();
|
||||
await Application.DisposeAsync();
|
||||
}
|
||||
|
||||
private async Task<IResult> HandlePing()
|
||||
{
|
||||
return OnDocumentRefreshed == null
|
||||
? Results.StatusCode(StatusCodes.Status503ServiceUnavailable)
|
||||
: Results.Ok();
|
||||
}
|
||||
|
||||
private async Task<IResult> HandleVersion()
|
||||
{
|
||||
return Results.Json(GetType().Assembly.GetName().Version);
|
||||
}
|
||||
|
||||
private async Task<IResult> HandleUpdatePreview(HttpRequest request)
|
||||
{
|
||||
var command = JsonSerializer.Deserialize<DocumentSnapshot>(request.Form["command"], JsonSerializerOptions);
|
||||
|
||||
var pages = command
|
||||
.Pages
|
||||
.Select(page =>
|
||||
{
|
||||
using var stream = request.Form.Files[page.Id].OpenReadStream();
|
||||
var picture = SKPicture.Deserialize(stream);
|
||||
|
||||
return new PreviewPage(picture, page.Width, page.Height);
|
||||
})
|
||||
.ToList();
|
||||
|
||||
Task.Run(() => OnDocumentRefreshed(pages));
|
||||
return Results.Ok();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using SkiaSharp;
|
||||
|
||||
namespace QuestPDF.Previewer;
|
||||
|
||||
class Helpers
|
||||
{
|
||||
public static void GeneratePdfFromDocumentSnapshots(string filePath, ICollection<PreviewPage> pages)
|
||||
{
|
||||
using var stream = File.Create(filePath);
|
||||
|
||||
using var document = SKDocument.CreatePdf(stream);
|
||||
|
||||
foreach (var page in pages)
|
||||
{
|
||||
using var canvas = document.BeginPage(page.Width, page.Height);
|
||||
canvas.DrawPicture(page.Picture);
|
||||
document.EndPage();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Platform;
|
||||
using Avalonia.Rendering.SceneGraph;
|
||||
using Avalonia.Skia;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace QuestPDF.Previewer;
|
||||
|
||||
class InteractiveCanvas : ICustomDrawOperation
|
||||
{
|
||||
public Rect Bounds { get; set; }
|
||||
public ICollection<PreviewPage> Pages { get; set; }
|
||||
|
||||
private float Width => (float)Bounds.Width;
|
||||
private float Height => (float)Bounds.Height;
|
||||
|
||||
public float Scale { get; private set; } = 1;
|
||||
public float TranslateX { get; set; }
|
||||
public float TranslateY { get; set; }
|
||||
|
||||
private const float MinScale = 0.1f;
|
||||
private const float MaxScale = 10f;
|
||||
|
||||
private const float PageSpacing = 25f;
|
||||
private const float SafeZone = 25f;
|
||||
|
||||
public float TotalPagesHeight => Pages.Sum(x => x.Height) + (Pages.Count - 1) * PageSpacing;
|
||||
public float TotalHeight => TotalPagesHeight + SafeZone * 2 / Scale;
|
||||
public float MaxWidth => Pages.Any() ? Pages.Max(x => x.Width) : 0;
|
||||
|
||||
public float MaxTranslateY => TotalHeight - Height / Scale;
|
||||
|
||||
public float ScrollPercentY
|
||||
{
|
||||
get
|
||||
{
|
||||
return TranslateY / MaxTranslateY;
|
||||
}
|
||||
set
|
||||
{
|
||||
TranslateY = value * MaxTranslateY;
|
||||
}
|
||||
}
|
||||
|
||||
public float ScrollViewportSizeY
|
||||
{
|
||||
get
|
||||
{
|
||||
var viewPortSize = Height / Scale / TotalHeight;
|
||||
return Math.Clamp(viewPortSize, 0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
#region transformations
|
||||
|
||||
private void LimitScale()
|
||||
{
|
||||
Scale = Math.Max(Scale, MinScale);
|
||||
Scale = Math.Min(Scale, MaxScale);
|
||||
}
|
||||
|
||||
private void LimitTranslate()
|
||||
{
|
||||
if (TotalPagesHeight > Height / Scale)
|
||||
{
|
||||
TranslateY = Math.Min(TranslateY, MaxTranslateY);
|
||||
TranslateY = Math.Max(TranslateY, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
TranslateY = (TotalPagesHeight - Height / Scale) / 2;
|
||||
}
|
||||
|
||||
if (Width / Scale < MaxWidth)
|
||||
{
|
||||
var maxTranslateX = (Width / 2 - SafeZone) / Scale - MaxWidth / 2;
|
||||
|
||||
TranslateX = Math.Min(TranslateX, -maxTranslateX);
|
||||
TranslateX = Math.Max(TranslateX, maxTranslateX);
|
||||
}
|
||||
else
|
||||
{
|
||||
TranslateX = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void TranslateWithCurrentScale(float x, float y)
|
||||
{
|
||||
TranslateX += x / Scale;
|
||||
TranslateY += y / Scale;
|
||||
|
||||
LimitTranslate();
|
||||
}
|
||||
|
||||
public void ZoomToPoint(float x, float y, float factor)
|
||||
{
|
||||
var oldScale = Scale;
|
||||
Scale *= factor;
|
||||
|
||||
LimitScale();
|
||||
|
||||
TranslateX -= x / Scale - x / oldScale;
|
||||
TranslateY -= y / Scale - y / oldScale;
|
||||
|
||||
LimitTranslate();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region rendering
|
||||
|
||||
public void Render(IDrawingContextImpl context)
|
||||
{
|
||||
if (Pages.Count <= 0)
|
||||
return;
|
||||
|
||||
LimitScale();
|
||||
LimitTranslate();
|
||||
|
||||
var canvas = (context as ISkiaDrawingContextImpl)?.SkCanvas;
|
||||
|
||||
if (canvas == null)
|
||||
throw new InvalidOperationException($"Context needs to be ISkiaDrawingContextImpl but got {nameof(context)}");
|
||||
|
||||
var originalMatrix = canvas.TotalMatrix;
|
||||
|
||||
canvas.Translate(Width / 2, 0);
|
||||
|
||||
canvas.Scale(Scale);
|
||||
canvas.Translate(TranslateX, -TranslateY + SafeZone / Scale);
|
||||
|
||||
foreach (var page in Pages)
|
||||
{
|
||||
canvas.Translate(-page.Width / 2f, 0);
|
||||
DrawBlankPage(canvas, page.Width, page.Height);
|
||||
canvas.DrawPicture(page.Picture);
|
||||
canvas.Translate(page.Width / 2f, page.Height + PageSpacing);
|
||||
}
|
||||
|
||||
canvas.SetMatrix(originalMatrix);
|
||||
DrawInnerGradient(canvas);
|
||||
}
|
||||
|
||||
public void Dispose() { }
|
||||
public bool Equals(ICustomDrawOperation? other) => false;
|
||||
public bool HitTest(Point p) => true;
|
||||
|
||||
#endregion
|
||||
|
||||
#region blank page
|
||||
|
||||
private static SKPaint BlankPagePaint = new SKPaint
|
||||
{
|
||||
Color = SKColors.White
|
||||
};
|
||||
|
||||
private static SKPaint BlankPageShadowPaint = new SKPaint
|
||||
{
|
||||
ImageFilter = SKImageFilter.CreateBlendMode(
|
||||
SKBlendMode.Overlay,
|
||||
SKImageFilter.CreateDropShadowOnly(0, 6, 6, 6, SKColors.Black.WithAlpha(64)),
|
||||
SKImageFilter.CreateDropShadowOnly(0, 10, 14, 14, SKColors.Black.WithAlpha(32)))
|
||||
};
|
||||
|
||||
private void DrawBlankPage(SKCanvas canvas, float width, float height)
|
||||
{
|
||||
canvas.DrawRect(0, 0, width, height, BlankPageShadowPaint);
|
||||
canvas.DrawRect(0, 0, width, height, BlankPagePaint);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region inner viewport gradient
|
||||
|
||||
private const int InnerGradientSize = (int)SafeZone;
|
||||
private static readonly SKColor InnerGradientColor = SKColor.Parse("#666");
|
||||
|
||||
private void DrawInnerGradient(SKCanvas canvas)
|
||||
{
|
||||
// gamma correction
|
||||
var colors = Enumerable
|
||||
.Range(0, InnerGradientSize)
|
||||
.Select(x => 1f - x / (float) InnerGradientSize)
|
||||
.Select(x => Math.Pow(x, 2f))
|
||||
.Select(x => (byte)(x * 255))
|
||||
.Select(x => InnerGradientColor.WithAlpha(x))
|
||||
.ToArray();
|
||||
|
||||
using var fogPaint = new SKPaint
|
||||
{
|
||||
Shader = SKShader.CreateLinearGradient(
|
||||
new SKPoint(0, 0),
|
||||
new SKPoint(0, InnerGradientSize),
|
||||
colors,
|
||||
SKShaderTileMode.Clamp)
|
||||
};
|
||||
|
||||
canvas.DrawRect(0, 0, Width, InnerGradientSize, fogPaint);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
using SkiaSharp;
|
||||
|
||||
namespace QuestPDF.Previewer
|
||||
{
|
||||
record PreviewPage(SKPicture Picture, float Width, float Height);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<Application x:Class="QuestPDF.Previewer.PreviewerApp"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Name="QuestPDF Document Preview">
|
||||
<Application.Styles>
|
||||
<FluentTheme Mode="Dark" />
|
||||
</Application.Styles>
|
||||
</Application>
|
||||
@@ -0,0 +1,27 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace QuestPDF.Previewer
|
||||
{
|
||||
internal class PreviewerApp : Application
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public override void OnFrameworkInitializationCompleted()
|
||||
{
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
desktop.MainWindow = new PreviewerWindow()
|
||||
{
|
||||
DataContext = new PreviewerWindowViewModel()
|
||||
};
|
||||
}
|
||||
|
||||
base.OnFrameworkInitializationCompleted();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Media;
|
||||
|
||||
namespace QuestPDF.Previewer
|
||||
{
|
||||
class PreviewerControl : Control
|
||||
{
|
||||
private InteractiveCanvas InteractiveCanvas { get; set; } = new ();
|
||||
|
||||
public static readonly StyledProperty<ObservableCollection<PreviewPage>> PagesProperty =
|
||||
AvaloniaProperty.Register<PreviewerControl, ObservableCollection<PreviewPage>>(nameof(Pages));
|
||||
|
||||
public ObservableCollection<PreviewPage>? Pages
|
||||
{
|
||||
get => GetValue(PagesProperty);
|
||||
set => SetValue(PagesProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<float> CurrentScrollProperty = AvaloniaProperty.Register<PreviewerControl, float>(nameof(CurrentScroll));
|
||||
|
||||
public float CurrentScroll
|
||||
{
|
||||
get => GetValue(CurrentScrollProperty);
|
||||
set => SetValue(CurrentScrollProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<float> ScrollViewportSizeProperty = AvaloniaProperty.Register<PreviewerControl, float>(nameof(ScrollViewportSize));
|
||||
|
||||
public float ScrollViewportSize
|
||||
{
|
||||
get => GetValue(ScrollViewportSizeProperty);
|
||||
set => SetValue(ScrollViewportSizeProperty, value);
|
||||
}
|
||||
|
||||
public PreviewerControl()
|
||||
{
|
||||
PagesProperty.Changed.Subscribe(x =>
|
||||
{
|
||||
InteractiveCanvas.Pages = x.NewValue.Value;
|
||||
InvalidateVisual();
|
||||
});
|
||||
|
||||
CurrentScrollProperty.Changed.Subscribe(x =>
|
||||
{
|
||||
InteractiveCanvas.ScrollPercentY = x.NewValue.Value;
|
||||
InvalidateVisual();
|
||||
});
|
||||
|
||||
ClipToBounds = true;
|
||||
}
|
||||
|
||||
protected override void OnPointerWheelChanged(PointerWheelEventArgs e)
|
||||
{
|
||||
base.OnPointerWheelChanged(e);
|
||||
|
||||
if ((e.KeyModifiers & KeyModifiers.Control) != 0)
|
||||
{
|
||||
var scaleFactor = 1 + e.Delta.Y / 10f;
|
||||
var point = new Point(Bounds.Center.X, Bounds.Top) - e.GetPosition(this);
|
||||
|
||||
InteractiveCanvas.ZoomToPoint((float)point.X, -(float)point.Y, (float)scaleFactor);
|
||||
}
|
||||
|
||||
if (e.KeyModifiers == KeyModifiers.None)
|
||||
{
|
||||
var translation = (float)e.Delta.Y * 25;
|
||||
InteractiveCanvas.TranslateWithCurrentScale(0, -translation);
|
||||
}
|
||||
|
||||
InvalidateVisual();
|
||||
}
|
||||
|
||||
private bool IsMousePressed { get; set; }
|
||||
private Vector MousePosition { get; set; }
|
||||
|
||||
protected override void OnPointerMoved(PointerEventArgs e)
|
||||
{
|
||||
base.OnPointerMoved(e);
|
||||
|
||||
if (IsMousePressed)
|
||||
{
|
||||
var currentPosition = e.GetPosition(this);
|
||||
var translation = currentPosition - MousePosition;
|
||||
InteractiveCanvas.TranslateWithCurrentScale((float)translation.X, -(float)translation.Y);
|
||||
|
||||
InvalidateVisual();
|
||||
}
|
||||
|
||||
MousePosition = e.GetPosition(this);
|
||||
}
|
||||
|
||||
protected override void OnPointerPressed(PointerPressedEventArgs e)
|
||||
{
|
||||
base.OnPointerPressed(e);
|
||||
IsMousePressed = true;
|
||||
}
|
||||
|
||||
protected override void OnPointerReleased(PointerReleasedEventArgs e)
|
||||
{
|
||||
base.OnPointerReleased(e);
|
||||
IsMousePressed = false;
|
||||
}
|
||||
|
||||
public override void Render(DrawingContext context)
|
||||
{
|
||||
CurrentScroll = InteractiveCanvas.ScrollPercentY;
|
||||
ScrollViewportSize = InteractiveCanvas.ScrollViewportSizeY;
|
||||
|
||||
InteractiveCanvas.Bounds = new Rect(0, 0, Bounds.Width, Bounds.Height);
|
||||
|
||||
context.Custom(InteractiveCanvas);
|
||||
base.Render(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user