Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1b9b164583 | |||
| 2c14f91ebe | |||
| b533a32b61 | |||
| 262bd3c182 | |||
| fcff989ab2 | |||
| 054e7a5f20 | |||
| a92cc25fb1 | |||
| 8db283d559 | |||
| 8a64abaac2 | |||
| 68df390d0e | |||
| d73c694452 | |||
| 4479a49ad0 | |||
| 63288dfb60 | |||
| fc325d8b76 | |||
| 5ffeaa3075 | |||
| fe431fbc12 | |||
| e0de408821 | |||
| 8ce6afef5a | |||
| 1c70b20028 | |||
| 3aef4ec4c9 | |||
| 3649607bd9 | |||
| 78eef45ce2 | |||
| ba409a8712 | |||
| e3ce194fd1 | |||
| 9b892c86ed | |||
| c9833d410d | |||
| 763fdfac0f |
@@ -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
|
||||
@@ -13,11 +13,11 @@ namespace QuestPDF.Examples
|
||||
[Test]
|
||||
public void Example()
|
||||
{
|
||||
FontManager.RegisterFontType("LibreBarcode39", File.OpenRead("LibreBarcode39-Regular.ttf"));
|
||||
FontManager.RegisterFont(File.OpenRead("LibreBarcode39-Regular.ttf"));
|
||||
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(400, 100)
|
||||
.PageSize(400, 200)
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
@@ -25,7 +25,9 @@ namespace QuestPDF.Examples
|
||||
.Background(Colors.White)
|
||||
.AlignCenter()
|
||||
.AlignMiddle()
|
||||
.Text("*QuestPDF*", TextStyle.Default.FontType("LibreBarcode39").Size(64));
|
||||
.Text("*QuestPDF*")
|
||||
.FontFamily("Libre Barcode 39")
|
||||
.FontSize(64);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,10 @@ namespace QuestPDF.Examples
|
||||
.PrimaryLayer()
|
||||
.PaddingVertical(10)
|
||||
.PaddingHorizontal(20)
|
||||
.Text("Sample text", TextStyle.Default.Size(16).Color(Colors.Blue.Darken2).SemiBold());
|
||||
.Text("Sample text")
|
||||
.FontSize(16)
|
||||
.FontColor(Colors.Blue.Darken2)
|
||||
.SemiBold();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -57,7 +57,10 @@ namespace QuestPDF.Examples
|
||||
column
|
||||
.Item()
|
||||
.PaddingBottom(10)
|
||||
.Text("Chart example", TextStyle.Default.Size(20).SemiBold().Color(Colors.Blue.Medium));
|
||||
.Text("Chart example")
|
||||
.FontSize(20)
|
||||
.SemiBold()
|
||||
.FontColor(Colors.Blue.Medium);
|
||||
|
||||
column
|
||||
.Item()
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using NUnit.Framework;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
@@ -20,18 +22,35 @@ namespace QuestPDF.Examples
|
||||
{
|
||||
container.Column(column =>
|
||||
{
|
||||
column.Item().Element(Block);
|
||||
foreach (var i in Enumerable.Range(0, 10))
|
||||
column.Item().Element(Block);
|
||||
|
||||
static void Block(IContainer container)
|
||||
{
|
||||
container
|
||||
.Width(72)
|
||||
.Height(3.5f)
|
||||
.Height(1.5f)
|
||||
.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 => { });
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,7 @@ namespace QuestPDF.Examples
|
||||
page.Content().PaddingVertical(10).Border(1).Padding(10).Column(column =>
|
||||
{
|
||||
foreach (var index in Enumerable.Range(1, 100))
|
||||
column.Item().Text($"Line {index}", TextStyle.Default.Color(Placeholders.Color()));
|
||||
column.Item().Text($"Line {index}").FontColor(Placeholders.Color());
|
||||
});
|
||||
|
||||
page.Footer().Text("Footer");
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -26,8 +26,8 @@ namespace QuestPDF.Examples
|
||||
.DefaultTextStyle(TextStyle.Default.Bold().Underline())
|
||||
.Column(column =>
|
||||
{
|
||||
column.Item().Text("Default style applies to all children", TextStyle.Default);
|
||||
column.Item().Text("You can override certain styles", TextStyle.Default.Underline(false).Color(Colors.Green.Darken2));
|
||||
column.Item().Text("Default style applies to all children");
|
||||
column.Item().Text("You can override certain styles").Underline(false).FontColor(Colors.Green.Darken2);
|
||||
|
||||
column.Item().PaddingTop(10).Border(1).Grid(grid =>
|
||||
{
|
||||
@@ -43,7 +43,8 @@ namespace QuestPDF.Examples
|
||||
.Height(50)
|
||||
.AlignCenter()
|
||||
.AlignMiddle()
|
||||
.Text(i, TextStyle.Default.Size(16 + i / 4));
|
||||
.Text(i)
|
||||
.FontSize(16 + i / 4);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -43,7 +43,9 @@ namespace QuestPDF.Examples
|
||||
.Before()
|
||||
.Background(Colors.Grey.Medium)
|
||||
.Padding(10)
|
||||
.Text("Notes", TextStyle.Default.Size(16).Color("#FFF"));
|
||||
.Text("Notes")
|
||||
.FontSize(16)
|
||||
.FontColor("#FFF");
|
||||
|
||||
decoration
|
||||
.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));
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -456,7 +463,7 @@ namespace QuestPDF.Examples
|
||||
});
|
||||
|
||||
layers.Layer().Background("#8F00").Extend();
|
||||
layers.Layer().PaddingTop(40).Text("It works!", TextStyle.Default.Size(24));
|
||||
layers.Layer().PaddingTop(40).Text("It works!").FontSize(24);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -477,7 +484,7 @@ namespace QuestPDF.Examples
|
||||
//.MinimalBox()
|
||||
.Background(Colors.Grey.Lighten2)
|
||||
.Padding(15)
|
||||
.Text("Test of the \n box element", TextStyle.Default.Size(20));
|
||||
.Text("Test of the \n box element").FontSize(20);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -503,7 +510,8 @@ namespace QuestPDF.Examples
|
||||
decoration
|
||||
.Before()
|
||||
.PaddingBottom(10)
|
||||
.Text("Example: scale component", headerFontStyle);
|
||||
.Text("Example: scale component")
|
||||
.Style(headerFontStyle);
|
||||
|
||||
decoration
|
||||
.Content()
|
||||
@@ -525,7 +533,8 @@ namespace QuestPDF.Examples
|
||||
.Background(fontColor)
|
||||
.Scale(scale)
|
||||
.Padding(5)
|
||||
.Text($"Content with {scale} scale.", fontStyle);
|
||||
.Text($"Content with {scale} scale.")
|
||||
.Style(fontStyle);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -555,7 +564,8 @@ namespace QuestPDF.Examples
|
||||
.BorderColor(Colors.Green.Darken1)
|
||||
|
||||
.Padding(50)
|
||||
.Text("Moved text", TextStyle.Default.Size(25));
|
||||
.Text("Moved text")
|
||||
.FontSize(25);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -591,7 +601,8 @@ namespace QuestPDF.Examples
|
||||
.MinimalBox()
|
||||
.Background(Colors.White)
|
||||
.Padding(10)
|
||||
.Text($"Rotated {turns * 90}°", TextStyle.Default.Size(16));
|
||||
.Text($"Rotated {turns * 90}°")
|
||||
.FontSize(16);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -689,7 +700,8 @@ namespace QuestPDF.Examples
|
||||
.MinimalBox()
|
||||
.Background(Colors.White)
|
||||
.Padding(10)
|
||||
.Text($"Flipped {turns}", TextStyle.Default.Size(16));
|
||||
.Text($"Flipped {turns}")
|
||||
.FontSize(16);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace QuestPDF.Examples
|
||||
page.Size(PageSizes.A7.Landscape());
|
||||
page.Background(Colors.White);
|
||||
|
||||
page.Header().Text("With ensure space", TextStyle.Default.SemiBold());
|
||||
page.Header().Text("With ensure space").SemiBold();
|
||||
|
||||
page.Content().Column(column =>
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,9 +17,9 @@ namespace QuestPDF.Examples
|
||||
.Padding(5);
|
||||
}
|
||||
|
||||
public static void LabelCell(this IContainer container, string text) => container.Cell(true).Text(text, TextStyle.Default.SemiBold());
|
||||
public static void LabelCell(this IContainer container, string text) => container.Cell(true).Text(text).SemiBold();
|
||||
public static IContainer ValueCell(this IContainer container) => container.Cell(false);
|
||||
public static void ValueCell(this IContainer container, string text) => container.ValueCell().Text(text, TextStyle.Default);
|
||||
public static void ValueCell(this IContainer container, string text) => container.ValueCell().Text(text);
|
||||
}
|
||||
|
||||
public class FrameExample
|
||||
|
||||
@@ -74,7 +74,8 @@ namespace QuestPDF.Examples
|
||||
.PrimaryLayer()
|
||||
.AlignCenter()
|
||||
.AlignMiddle()
|
||||
.Text(sizeText, TextStyle.Default.Size(15));
|
||||
.Text(sizeText)
|
||||
.FontSize(15);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
using System.Diagnostics;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class MinimalApiExamples
|
||||
{
|
||||
[Test]
|
||||
public void MinimalApi()
|
||||
{
|
||||
Document
|
||||
.Create(container =>
|
||||
{
|
||||
container.Page(page =>
|
||||
{
|
||||
page.Size(PageSizes.A4);
|
||||
page.Margin(2, Unit.Centimetre);
|
||||
page.Background(Colors.White);
|
||||
page.DefaultTextStyle(TextStyle.Default.Size(20));
|
||||
|
||||
page.Header()
|
||||
.Text("Hello PDF!").SemiBold().FontSize(36).FontColor(Colors.Blue.Medium);
|
||||
|
||||
page.Content()
|
||||
.PaddingVertical(1, Unit.Centimetre)
|
||||
.Column(x =>
|
||||
{
|
||||
x.Spacing(20);
|
||||
|
||||
x.Item().Text(Placeholders.LoremIpsum());
|
||||
x.Item().Image(Placeholders.Image(200, 100));
|
||||
});
|
||||
|
||||
page.Footer()
|
||||
.AlignCenter()
|
||||
.Text(x =>
|
||||
{
|
||||
x.Span("Page ");
|
||||
x.CurrentPageNumber();
|
||||
});
|
||||
});
|
||||
})
|
||||
.GeneratePdf("hello.pdf");
|
||||
|
||||
Process.Start("explorer.exe", "hello.pdf");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MinimalApi2()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProduceImages()
|
||||
.ShowResults()
|
||||
.RenderDocument(container =>
|
||||
{
|
||||
container.Page(page =>
|
||||
{
|
||||
page.Size(PageSizes.A4);
|
||||
page.Margin(2, Unit.Centimetre);
|
||||
page.Background(Colors.White);
|
||||
page.DefaultTextStyle(TextStyle.Default.Size(20));
|
||||
|
||||
page.Header()
|
||||
.Text("Hello PDF!")
|
||||
.SemiBold()
|
||||
.FontSize(36)
|
||||
.FontColor(Colors.Blue.Medium);
|
||||
|
||||
page.Content()
|
||||
.PaddingVertical(1, Unit.Centimetre)
|
||||
.Column(x =>
|
||||
{
|
||||
x.Spacing(20);
|
||||
|
||||
x.Item().Text(Placeholders.LoremIpsum());
|
||||
x.Item().Image(Placeholders.Image(200, 100));
|
||||
});
|
||||
|
||||
page.Footer()
|
||||
.AlignCenter()
|
||||
.Text(x =>
|
||||
{
|
||||
x.Span("Page ");
|
||||
x.CurrentPageNumber();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Examples.Engine;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Examples
|
||||
{
|
||||
public class PageBackgroundForeground
|
||||
{
|
||||
[Test]
|
||||
public void Test()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.PageSize(550, 400)
|
||||
.ProducePdf()
|
||||
.ShowResults()
|
||||
.RenderDocument(document =>
|
||||
{
|
||||
document.Page(page =>
|
||||
{
|
||||
page.Size(PageSizes.A4);
|
||||
page.Margin(1, Unit.Inch);
|
||||
page.DefaultTextStyle(TextStyle.Default.FontSize(16));
|
||||
|
||||
page.Foreground()
|
||||
.AlignMiddle()
|
||||
.AlignCenter()
|
||||
.Text("Watermark")
|
||||
.FontSize(64)
|
||||
.FontColor(Colors.Blue.Lighten3);
|
||||
|
||||
page.Header().Text("Background and foreground").Bold().FontColor(Colors.Blue.Medium).FontSize(24);
|
||||
|
||||
page.Content().PaddingVertical(25).Column(column =>
|
||||
{
|
||||
column.Spacing(25);
|
||||
|
||||
foreach (var i in Enumerable.Range(0, 100))
|
||||
column.Item().Background(Colors.Grey.Lighten2).Height(75);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
|
||||
<PackageReference Include="SkiaSharp" Version="2.80.3" />
|
||||
<PackageReference Include="Svg.Skia" Version="0.5.10" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -28,6 +29,9 @@
|
||||
<None Update="logo.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="pdf-icon.svg">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -89,5 +89,21 @@ namespace QuestPDF.Examples
|
||||
.ShowOnce();
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Stability_NoItems()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProducePdf()
|
||||
.MaxPages(100)
|
||||
.PageSize(250, 150)
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(25)
|
||||
.Row(row => { });
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ namespace QuestPDF.Examples
|
||||
page.Size(PageSizes.A7.Landscape());
|
||||
page.Background(Colors.White);
|
||||
|
||||
page.Header().Text("With show once", TextStyle.Default.SemiBold());
|
||||
page.Header().Text("With show once").SemiBold();
|
||||
|
||||
page.Content().PaddingVertical(5).Row(row =>
|
||||
{
|
||||
|
||||
@@ -31,7 +31,8 @@ namespace QuestPDF.Examples
|
||||
|
||||
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,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);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -49,6 +49,39 @@ namespace QuestPDF.Examples
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PagingSupport()
|
||||
{
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProducePdf()
|
||||
.PageSize(420, 220)
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
container
|
||||
.Padding(10)
|
||||
.MinimalBox()
|
||||
.Border(1)
|
||||
.Table(table =>
|
||||
{
|
||||
table.ColumnsDefinition(columns =>
|
||||
{
|
||||
columns.RelativeColumn();
|
||||
columns.RelativeColumn();
|
||||
columns.RelativeColumn();
|
||||
columns.RelativeColumn();
|
||||
});
|
||||
|
||||
// by using custom 'Element' method, we can reuse visual configuration
|
||||
table.Cell().Element(Block).Text(Placeholders.Label());
|
||||
table.Cell().Element(Block).Text(Placeholders.Label());
|
||||
table.Cell().Element(Block).Text(Placeholders.Paragraph());
|
||||
table.Cell().Element(Block).Text(Placeholders.Label());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DefaultCellStyle()
|
||||
{
|
||||
@@ -217,7 +250,7 @@ namespace QuestPDF.Examples
|
||||
RenderingTest
|
||||
.Create()
|
||||
.ProduceImages()
|
||||
.PageSize(220, 220)
|
||||
.PageSize(220, 170)
|
||||
.ShowResults()
|
||||
.Render(container =>
|
||||
{
|
||||
@@ -380,7 +413,7 @@ namespace QuestPDF.Examples
|
||||
.EnableCaching()
|
||||
.EnableDebugging(false)
|
||||
.ShowResults()
|
||||
.Render(container => GeneratePerformanceStructure(container, 1000));
|
||||
.Render(container => GeneratePerformanceStructure(container, 250));
|
||||
}
|
||||
|
||||
public static void GeneratePerformanceStructure(IContainer container, int repeats)
|
||||
@@ -456,8 +489,10 @@ namespace QuestPDF.Examples
|
||||
return container
|
||||
.Border(1)
|
||||
.Background(Colors.Grey.Lighten3)
|
||||
.ShowOnce()
|
||||
.MinWidth(50)
|
||||
.MinHeight(50)
|
||||
.Padding(10)
|
||||
.AlignCenter()
|
||||
.AlignMiddle();
|
||||
}
|
||||
|
||||
@@ -141,8 +141,8 @@ namespace QuestPDF.Examples
|
||||
.AlignBottom()
|
||||
.Column(column =>
|
||||
{
|
||||
column.Item().Text("Quo Vadis", TextStyle.Default.Size(72).Bold().Color(Colors.Blue.Darken2));
|
||||
column.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);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -156,8 +156,8 @@ namespace QuestPDF.Examples
|
||||
{
|
||||
column.Item().InternalLink(chapter.Title).Row(row =>
|
||||
{
|
||||
row.RelativeItem().Text(chapter.Title, normalStyle);
|
||||
row.ConstantItem(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));
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -180,7 +180,7 @@ namespace QuestPDF.Examples
|
||||
column.Item().Text(text =>
|
||||
{
|
||||
text.ParagraphSpacing(5);
|
||||
text.Span(content, normalStyle);
|
||||
text.Span(content).Style(normalStyle);
|
||||
});
|
||||
|
||||
column.Item().PageBreak();
|
||||
@@ -198,7 +198,7 @@ namespace QuestPDF.Examples
|
||||
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!");
|
||||
});
|
||||
});
|
||||
@@ -206,7 +206,7 @@ namespace QuestPDF.Examples
|
||||
|
||||
void SectionTitle(ColumnDescriptor column, string text)
|
||||
{
|
||||
column.Item().Location(text).Text(text, subtitleStyle);
|
||||
column.Item().Location(text).Text(text).Style(subtitleStyle);
|
||||
column.Item().PaddingTop(10).PaddingBottom(50).BorderBottom(1).BorderColor(Colors.Grey.Lighten2).ExtendHorizontal();
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace QuestPDF.Examples
|
||||
{
|
||||
text.DefaultTextStyle(TextStyle.Default.Size(20));
|
||||
text.Span("This is a normal text, followed by an ");
|
||||
text.Span("underlined red text.", TextStyle.Default.Size(20).Color(Colors.Red.Medium).Underline());
|
||||
text.Span("underlined red text.").FontSize(20).FontColor(Colors.Red.Medium).Underline();
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -57,7 +57,7 @@ namespace QuestPDF.Examples
|
||||
|
||||
foreach (var i in Enumerable.Range(1, 3))
|
||||
{
|
||||
text.Span($"Paragraph {i}: ", TextStyle.Default.SemiBold());
|
||||
text.Span($"Paragraph {i}: ").SemiBold();
|
||||
text.Line(Placeholders.Paragraph());
|
||||
}
|
||||
});
|
||||
@@ -120,7 +120,7 @@ namespace QuestPDF.Examples
|
||||
|
||||
text.Line(Placeholders.LoremIpsum());
|
||||
|
||||
text.Span($"This is target text that should show up. {DateTime.UtcNow:T} > This is a short sentence that will be wrapped into second line hopefully, right? <", TextStyle.Default.Underline());
|
||||
text.Span($"This is target text that should show up. {DateTime.UtcNow:T} > This is a short sentence that will be wrapped into second line hopefully, right? <").Underline();
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -173,6 +173,8 @@ namespace QuestPDF.Examples
|
||||
.Padding(10)
|
||||
.Text(text =>
|
||||
{
|
||||
text.DefaultTextStyle(x => x.Bold());
|
||||
|
||||
text.DefaultTextStyle(TextStyle.Default);
|
||||
text.AlignLeft();
|
||||
text.ParagraphSpacing(10);
|
||||
@@ -182,9 +184,9 @@ namespace QuestPDF.Examples
|
||||
text.EmptyLine();
|
||||
|
||||
text.Span("This text is a normal text, ");
|
||||
text.Span("this is a bold text, ", TextStyle.Default.Bold());
|
||||
text.Span("this is a red and underlined text, ", TextStyle.Default.Color(Colors.Red.Medium).Underline());
|
||||
text.Span("and this is slightly bigger text.", TextStyle.Default.Size(16));
|
||||
text.Span("this is a bold text, ").Bold();
|
||||
text.Span("this is a red and underlined text, ").FontColor(Colors.Red.Medium).Underline();
|
||||
text.Span("and this is slightly bigger text.").FontSize(16);
|
||||
|
||||
text.EmptyLine();
|
||||
|
||||
@@ -210,7 +212,7 @@ namespace QuestPDF.Examples
|
||||
|
||||
text.EmptyLine();
|
||||
|
||||
text.Span(Placeholders.Paragraphs(), TextStyle.Default.Italic());
|
||||
text.Span(Placeholders.Paragraphs()).Italic();
|
||||
|
||||
text.Line("This is target text that does not show up. " + Placeholders.Paragraph());
|
||||
});
|
||||
@@ -241,9 +243,9 @@ namespace QuestPDF.Examples
|
||||
text.ParagraphSpacing(10);
|
||||
|
||||
text.Span("This text is a normal text, ");
|
||||
text.Span("this is a bold text, ", TextStyle.Default.Bold());
|
||||
text.Span("this is a red and underlined text, ", TextStyle.Default.Color(Colors.Red.Medium).Underline());
|
||||
text.Span("and this is slightly bigger text.", TextStyle.Default.Size(16));
|
||||
text.Span("this is a bold text, ").Bold();
|
||||
text.Span("this is a red and underlined text, ").FontColor(Colors.Red.Medium).Underline();
|
||||
text.Span("and this is slightly bigger text.").FontSize(16);
|
||||
|
||||
text.Span("The new text element also supports injecting custom content between words: ");
|
||||
text.Element().PaddingBottom(-10).Height(16).Width(32).Image(Placeholders.Image);
|
||||
|
||||
@@ -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 |
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.IO;
|
||||
using SkiaSharp;
|
||||
|
||||
@@ -24,5 +25,32 @@ namespace QuestPDF.ReportSample
|
||||
Latitude = Helpers.Random.NextDouble() * 180f - 90f
|
||||
};
|
||||
}
|
||||
|
||||
private static readonly ConcurrentDictionary<int, string> RomanNumeralCache = new ConcurrentDictionary<int, string>();
|
||||
|
||||
public static string FormatAsRomanNumeral(this int number)
|
||||
{
|
||||
if (number < 0 || number > 3999)
|
||||
throw new ArgumentOutOfRangeException("Number should be in range from 1 to 3999");
|
||||
|
||||
return RomanNumeralCache.GetOrAdd(number, x =>
|
||||
{
|
||||
if (x >= 1000) return "M" + FormatAsRomanNumeral(x - 1000);
|
||||
if (x >= 900) return "CM" + FormatAsRomanNumeral(x - 900);
|
||||
if (x >= 500) return "D" + FormatAsRomanNumeral(x - 500);
|
||||
if (x >= 400) return "CD" + FormatAsRomanNumeral(x - 400);
|
||||
if (x >= 100) return "C" + FormatAsRomanNumeral(x - 100);
|
||||
if (x >= 90) return "XC" + FormatAsRomanNumeral(x - 90);
|
||||
if (x >= 50) return "L" + FormatAsRomanNumeral(x - 50);
|
||||
if (x >= 40) return "XL" + FormatAsRomanNumeral(x - 40);
|
||||
if (x >= 10) return "X" + FormatAsRomanNumeral(x - 10);
|
||||
if (x >= 9) return "IX" + FormatAsRomanNumeral(x - 9);
|
||||
if (x >= 5) return "V" + FormatAsRomanNumeral(x - 5);
|
||||
if (x >= 4) return "IV" + FormatAsRomanNumeral(x - 4);
|
||||
if (x >= 1) return "I" + FormatAsRomanNumeral(x - 1);
|
||||
|
||||
return string.Empty;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,12 +30,12 @@ namespace QuestPDF.ReportSample.Layouts
|
||||
{
|
||||
column.Item().ShowOnce().Padding(5).AlignMiddle().Row(row =>
|
||||
{
|
||||
row.RelativeItem(2).AlignMiddle().Text("PRIMARY HEADER", TextStyle.Default.Color(Colors.Grey.Darken3).Size(30).Bold());
|
||||
row.RelativeItem(2).AlignMiddle().Text("PRIMARY HEADER").FontColor(Colors.Grey.Darken3).FontSize(30).Bold();
|
||||
row.RelativeItem(1).AlignRight().MinimalBox().AlignMiddle().Background(Colors.Blue.Darken2).Padding(30);
|
||||
});
|
||||
column.Item().SkipOnce().Padding(5).Row(row =>
|
||||
{
|
||||
row.RelativeItem(2).Text("SECONDARY HEADER", TextStyle.Default.Color(Colors.Grey.Darken3).Size(30).Bold());
|
||||
row.RelativeItem(2).Text("SECONDARY HEADER").FontColor(Colors.Grey.Darken3).FontSize(30).Bold();
|
||||
row.RelativeItem(1).AlignRight().MinimalBox().Background(Colors.Blue.Lighten4).Padding(15);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -23,7 +23,8 @@ namespace QuestPDF.ReportSample.Layouts
|
||||
decoration
|
||||
.Before()
|
||||
.PaddingBottom(5)
|
||||
.Text(Model.Title, Typography.Headline);
|
||||
.Text(Model.Title)
|
||||
.Style(Typography.Headline);
|
||||
|
||||
decoration.Content().Border(0.75f).BorderColor(Colors.Grey.Medium).Column(column =>
|
||||
{
|
||||
@@ -69,7 +70,7 @@ namespace QuestPDF.ReportSample.Layouts
|
||||
{
|
||||
if (model.PhotoCount == 0)
|
||||
{
|
||||
container.Text("No photos", Typography.Normal);
|
||||
container.Text("No photos").Style(Typography.Normal);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -39,9 +39,9 @@ namespace QuestPDF.ReportSample.Layouts
|
||||
|
||||
page.Footer().AlignCenter().Text(text =>
|
||||
{
|
||||
text.CurrentPageNumber();
|
||||
text.CurrentPageNumber().Format(x => x?.FormatAsRomanNumeral() ?? "-----");
|
||||
text.Span(" / ");
|
||||
text.TotalPages();
|
||||
text.TotalPages().Format(x => x?.FormatAsRomanNumeral() ?? "-----");
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -54,7 +54,7 @@ namespace QuestPDF.ReportSample.Layouts
|
||||
{
|
||||
row.Spacing(50);
|
||||
|
||||
row.RelativeItem().PaddingTop(-10).Text(Model.Title, Typography.Title);
|
||||
row.RelativeItem().PaddingTop(-10).Text(Model.Title).Style(Typography.Title);
|
||||
row.ConstantItem(90).ExternalLink("https://www.questpdf.com").MaxHeight(30).Component<ImagePlaceholder>();
|
||||
});
|
||||
|
||||
@@ -69,7 +69,7 @@ namespace QuestPDF.ReportSample.Layouts
|
||||
{
|
||||
grid.Item().Text(text =>
|
||||
{
|
||||
text.Span($"{field.Label}: ", TextStyle.Default.SemiBold());
|
||||
text.Span($"{field.Label}: ").SemiBold();
|
||||
text.Span(field.Value);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.ReportSample.Layouts
|
||||
@@ -21,7 +23,8 @@ namespace QuestPDF.ReportSample.Layouts
|
||||
decoration
|
||||
.Before()
|
||||
.PaddingBottom(5)
|
||||
.Text("Table of contents", Typography.Headline);
|
||||
.Text("Table of contents")
|
||||
.Style(Typography.Headline);
|
||||
|
||||
decoration.Content().Column(column =>
|
||||
{
|
||||
@@ -43,7 +46,18 @@ namespace QuestPDF.ReportSample.Layouts
|
||||
{
|
||||
row.ConstantItem(25).Text($"{number}.");
|
||||
row.RelativeItem().Text(locationName);
|
||||
row.ConstantItem(150).AlignRight().Text(text => text.PageNumberOfLocation(locationName));
|
||||
row.ConstantItem(150).AlignRight().Text(text =>
|
||||
{
|
||||
text.BeginPageNumberOfSection(locationName);
|
||||
text.Span(" - ");
|
||||
text.EndPageNumberOfSection(locationName);
|
||||
|
||||
var lengthStyle = TextStyle.Default.Color(Colors.Grey.Medium);
|
||||
|
||||
text.Span(" (").Style(lengthStyle);
|
||||
text.TotalPagesWithinSection(locationName).Style(lengthStyle).Format(x => x == 1 ? "1 page long" : $"{x} pages long");
|
||||
text.Span(")").Style(lengthStyle);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace QuestPDF.UnitTests
|
||||
public class ExternalLinkTests
|
||||
{
|
||||
[Test]
|
||||
public void Measure() => SimpleContainerTests.Measure<ExternalLink>();
|
||||
public void Measure() => SimpleContainerTests.Measure<Hyperlink>();
|
||||
|
||||
// TODO: consider tests for the Draw method
|
||||
}
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Drawing;
|
||||
using SkiaSharp;
|
||||
using static SkiaSharp.SKFontStyleSlant;
|
||||
|
||||
namespace QuestPDF.UnitTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class FontStyleSetTests
|
||||
{
|
||||
private void ExpectComparisonOrder(SKFontStyle target, SKFontStyle[] styles)
|
||||
{
|
||||
for (var i = 0; i < styles.Length - 1; i++)
|
||||
{
|
||||
var currentStyle = styles[i];
|
||||
var nextStyle = styles[i + 1];
|
||||
|
||||
FontStyleSet.IsBetterMatch(target, currentStyle, nextStyle).Should().BeTrue();
|
||||
FontStyleSet.IsBetterMatch(target, nextStyle, currentStyle).Should().BeFalse();
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FontStyleSet_IsBetterMatch_CondensedWidth()
|
||||
{
|
||||
var styles = new[]
|
||||
{
|
||||
new SKFontStyle(500, 5, Upright),
|
||||
new SKFontStyle(500, 4, Upright),
|
||||
new SKFontStyle(500, 3, Upright),
|
||||
new SKFontStyle(500, 6, Upright)
|
||||
};
|
||||
|
||||
ExpectComparisonOrder(new SKFontStyle(500, 5, Upright), styles);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FontStyleSet_IsBetterMatch_ExpandedWidth()
|
||||
{
|
||||
var styles = new[]
|
||||
{
|
||||
new SKFontStyle(500, 6, Upright),
|
||||
new SKFontStyle(500, 7, Upright),
|
||||
new SKFontStyle(500, 8, Upright),
|
||||
new SKFontStyle(500, 5, Upright)
|
||||
};
|
||||
|
||||
ExpectComparisonOrder(new SKFontStyle(500, 6, Upright), styles);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FontStyleSet_IsBetterMatch_ItalicSlant()
|
||||
{
|
||||
var styles = new[]
|
||||
{
|
||||
new SKFontStyle(500, 5, Italic),
|
||||
new SKFontStyle(500, 5, Oblique),
|
||||
new SKFontStyle(500, 5, Upright)
|
||||
};
|
||||
|
||||
ExpectComparisonOrder(new SKFontStyle(500, 5, Italic), styles);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FontStyleSet_IsBetterMatch_ObliqueSlant()
|
||||
{
|
||||
var styles = new[]
|
||||
{
|
||||
new SKFontStyle(500, 5, Oblique),
|
||||
new SKFontStyle(500, 5, Italic),
|
||||
new SKFontStyle(500, 5, Upright)
|
||||
};
|
||||
|
||||
ExpectComparisonOrder(new SKFontStyle(500, 5, Oblique), styles);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FontStyleSet_IsBetterMatch_UprightSlant()
|
||||
{
|
||||
var styles = new[]
|
||||
{
|
||||
new SKFontStyle(500, 5, Upright),
|
||||
new SKFontStyle(500, 5, Oblique),
|
||||
new SKFontStyle(500, 5, Italic)
|
||||
};
|
||||
|
||||
ExpectComparisonOrder(new SKFontStyle(500, 5, Upright), styles);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FontStyleSet_IsBetterMatch_ThinWeight()
|
||||
{
|
||||
var styles = new[]
|
||||
{
|
||||
new SKFontStyle(300, 5, Upright),
|
||||
new SKFontStyle(200, 5, Upright),
|
||||
new SKFontStyle(100, 5, Upright),
|
||||
new SKFontStyle(400, 5, Upright)
|
||||
};
|
||||
|
||||
ExpectComparisonOrder(new SKFontStyle(300, 5, Upright), styles);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FontStyleSet_IsBetterMatch_RegularWeight()
|
||||
{
|
||||
var styles = new[]
|
||||
{
|
||||
new SKFontStyle(500, 5, Upright),
|
||||
new SKFontStyle(300, 5, Upright),
|
||||
new SKFontStyle(100, 5, Upright),
|
||||
new SKFontStyle(600, 5, Upright)
|
||||
};
|
||||
|
||||
ExpectComparisonOrder(new SKFontStyle(400, 5, Upright), styles);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FontStyleSet_IsBetterMatch_BoldWeight()
|
||||
{
|
||||
var styles = new[]
|
||||
{
|
||||
new SKFontStyle(600, 5, Upright),
|
||||
new SKFontStyle(700, 5, Upright),
|
||||
new SKFontStyle(800, 5, Upright),
|
||||
new SKFontStyle(500, 5, Upright)
|
||||
};
|
||||
|
||||
ExpectComparisonOrder(new SKFontStyle(600, 5, Upright), styles);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FontStyleSet_RespectsPriority()
|
||||
{
|
||||
var styles = new[]
|
||||
{
|
||||
new SKFontStyle(600, 5, Italic),
|
||||
new SKFontStyle(600, 6, Upright),
|
||||
new SKFontStyle(500, 6, Italic)
|
||||
};
|
||||
|
||||
ExpectComparisonOrder(new SKFontStyle(500, 5, Upright), styles);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ namespace QuestPDF.UnitTests
|
||||
public class InternalLinkTests
|
||||
{
|
||||
[Test]
|
||||
public void Measure() => SimpleContainerTests.Measure<InternalLink>();
|
||||
public void Measure() => SimpleContainerTests.Measure<SectionLink>();
|
||||
|
||||
// TODO: consider tests for the Draw method
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace QuestPDF.UnitTests
|
||||
public class InternalLocationTests
|
||||
{
|
||||
[Test]
|
||||
public void Measure() => SimpleContainerTests.Measure<InternalLink>();
|
||||
public void Measure() => SimpleContainerTests.Measure<SectionLink>();
|
||||
|
||||
// TODO: consider tests for the Draw method
|
||||
}
|
||||
|
||||
@@ -20,9 +20,9 @@ namespace QuestPDF.UnitTests.TestEngine
|
||||
public void DrawRectangle(Position vector, Size size, string color) => DrawRectFunc(vector, size, color);
|
||||
public void DrawText(string text, Position position, TextStyle style) => DrawTextFunc(text, position, style);
|
||||
public void DrawImage(SKImage image, Position position, Size size) => DrawImageFunc(image, position, size);
|
||||
|
||||
public void DrawExternalLink(string url, Size size) => throw new NotImplementedException();
|
||||
public void DrawLocationLink(string locationName, Size size) => throw new NotImplementedException();
|
||||
public void DrawLocation(string locationName) => throw new NotImplementedException();
|
||||
|
||||
public void DrawHyperlink(string url, Size size) => throw new NotImplementedException();
|
||||
public void DrawSectionLink(string sectionName, Size size) => throw new NotImplementedException();
|
||||
public void DrawSection(string sectionName) => throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -18,8 +18,8 @@ namespace QuestPDF.UnitTests.TestEngine
|
||||
public void DrawText(string text, Position position, TextStyle style) => Operations.Add(new CanvasDrawTextOperation(text, position, style));
|
||||
public void DrawImage(SKImage image, Position position, Size size) => Operations.Add(new CanvasDrawImageOperation(position, size));
|
||||
|
||||
public void DrawExternalLink(string url, Size size) => throw new NotImplementedException();
|
||||
public void DrawLocationLink(string locationName, Size size) => throw new NotImplementedException();
|
||||
public void DrawLocation(string locationName) => throw new NotImplementedException();
|
||||
public void DrawHyperlink(string url, Size size) => throw new NotImplementedException();
|
||||
public void DrawSectionLink(string sectionName, Size size) => throw new NotImplementedException();
|
||||
public void DrawSection(string sectionName) => throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -92,7 +92,7 @@ namespace QuestPDF.UnitTests.TestEngine
|
||||
Assert.AreEqual(expected.Position.Y, position.Y, "Draw text: Y");
|
||||
|
||||
Assert.AreEqual(expected.Style.Color, style.Color, "Draw text: color");
|
||||
Assert.AreEqual(expected.Style.FontType, style.FontType, "Draw text: font");
|
||||
Assert.AreEqual(expected.Style.FontFamily, style.FontFamily, "Draw text: font");
|
||||
Assert.AreEqual(expected.Style.Size, style.Size, "Draw text: size");
|
||||
},
|
||||
DrawImageFunc = (image, position, size) =>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using QuestPDF.Infrastructure;
|
||||
using SkiaSharp;
|
||||
|
||||
@@ -8,16 +9,41 @@ namespace QuestPDF.Drawing
|
||||
{
|
||||
public static class FontManager
|
||||
{
|
||||
private static ConcurrentDictionary<string, SKTypeface> Typefaces = new ConcurrentDictionary<string, SKTypeface>();
|
||||
private static ConcurrentDictionary<string, SKFontMetrics> FontMetrics = new ConcurrentDictionary<string, SKFontMetrics>();
|
||||
private static ConcurrentDictionary<string, SKPaint> Paints = new ConcurrentDictionary<string, SKPaint>();
|
||||
private static ConcurrentDictionary<string, SKPaint> ColorPaint = new ConcurrentDictionary<string, SKPaint>();
|
||||
private static ConcurrentDictionary<string, FontStyleSet> StyleSets = new();
|
||||
private static ConcurrentDictionary<object, SKFontMetrics> FontMetrics = new();
|
||||
private static ConcurrentDictionary<object, SKPaint> Paints = new();
|
||||
private static ConcurrentDictionary<string, SKPaint> ColorPaint = new();
|
||||
|
||||
private static void RegisterFontType(SKData fontData, string? customName = null)
|
||||
{
|
||||
foreach (var index in Enumerable.Range(0, 256))
|
||||
{
|
||||
var typeface = SKTypeface.FromData(fontData, index);
|
||||
|
||||
if (typeface == null)
|
||||
break;
|
||||
|
||||
var typefaceName = customName ?? typeface.FamilyName;
|
||||
|
||||
var fontStyleSet = StyleSets.GetOrAdd(typefaceName, _ => new FontStyleSet());
|
||||
fontStyleSet.Add(typeface);
|
||||
}
|
||||
}
|
||||
|
||||
[Obsolete("Since version 2022.3, the FontManager class offers better font type matching support. Please use the RegisterFont(Stream stream) method.")]
|
||||
public static void RegisterFontType(string fontName, Stream stream)
|
||||
{
|
||||
Typefaces.TryAdd(fontName, SKTypeface.FromStream(stream));
|
||||
using var fontData = SKData.Create(stream);
|
||||
RegisterFontType(fontData);
|
||||
RegisterFontType(fontData, customName: fontName);
|
||||
}
|
||||
|
||||
|
||||
public static void RegisterFont(Stream stream)
|
||||
{
|
||||
using var fontData = SKData.Create(stream);
|
||||
RegisterFontType(fontData);
|
||||
}
|
||||
|
||||
internal static SKPaint ColorToPaint(this string color)
|
||||
{
|
||||
return ColorPaint.GetOrAdd(color, Convert);
|
||||
@@ -30,37 +56,47 @@ namespace QuestPDF.Drawing
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal static SKPaint ToPaint(this TextStyle style)
|
||||
{
|
||||
return Paints.GetOrAdd(style.Key, key => Convert(style));
|
||||
|
||||
return Paints.GetOrAdd(style.PaintKey, key => Convert(style));
|
||||
|
||||
static SKPaint Convert(TextStyle style)
|
||||
{
|
||||
return new SKPaint
|
||||
{
|
||||
Color = SKColor.Parse(style.Color),
|
||||
Typeface = GetTypeface(style),
|
||||
TextSize = (style.Size ?? 12),
|
||||
TextEncoding = SKTextEncoding.Utf32
|
||||
TextSize = style.Size ?? 12
|
||||
};
|
||||
}
|
||||
|
||||
static SKTypeface GetTypeface(TextStyle style)
|
||||
{
|
||||
if (Typefaces.TryGetValue(style.FontType, out var result))
|
||||
return result;
|
||||
|
||||
var weight = (SKFontStyleWeight)(style.FontWeight ?? FontWeight.Normal);
|
||||
var slant = (style.IsItalic ?? false) ? SKFontStyleSlant.Italic : SKFontStyleSlant.Upright;
|
||||
|
||||
var fontStyle = new SKFontStyle(weight, SKFontStyleWidth.Normal, slant);
|
||||
|
||||
if (StyleSets.TryGetValue(style.FontFamily, out var fontStyleSet))
|
||||
return fontStyleSet.Match(fontStyle);
|
||||
|
||||
var fontFromDefaultSource = SKFontManager.Default.MatchFamily(style.FontFamily, fontStyle);
|
||||
|
||||
return SKTypeface.FromFamilyName(style.FontType, (int)(style.FontWeight ?? FontWeight.Normal), (int)SKFontStyleWidth.Normal, slant)
|
||||
?? throw new ArgumentException($"The typeface {style.FontType} could not be found.");
|
||||
if (fontFromDefaultSource != null)
|
||||
return fontFromDefaultSource;
|
||||
|
||||
throw new ArgumentException(
|
||||
$"The typeface '{style.FontFamily}' could not be found. " +
|
||||
$"Please consider the following options: " +
|
||||
$"1) install the font on your operating system or execution environment. " +
|
||||
$"2) load a font file specifically for QuestPDF usage via the QuestPDF.Drawing.FontManager.RegisterFontType(Stream fileContentStream) static method.");
|
||||
}
|
||||
}
|
||||
|
||||
internal static SKFontMetrics ToFontMetrics(this TextStyle style)
|
||||
{
|
||||
return FontMetrics.GetOrAdd(style.Key, key => style.ToPaint().FontMetrics);
|
||||
return FontMetrics.GetOrAdd(style.FontMetricsKey, key => style.ToPaint().FontMetrics);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Concurrent;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace QuestPDF.Drawing
|
||||
{
|
||||
internal class FontStyleSet
|
||||
{
|
||||
private ConcurrentDictionary<SKFontStyle, SKTypeface> Styles { get; } = new();
|
||||
|
||||
public void Add(SKTypeface typeface)
|
||||
{
|
||||
var style = typeface.FontStyle;
|
||||
Styles.AddOrUpdate(style, _ => typeface, (_, _) => typeface);
|
||||
}
|
||||
|
||||
public SKTypeface? Match(SKFontStyle target)
|
||||
{
|
||||
SKFontStyle? bestStyle = null;
|
||||
SKTypeface? bestTypeface = null;
|
||||
|
||||
foreach (var entry in Styles)
|
||||
{
|
||||
if (IsBetterMatch(target, entry.Key, bestStyle))
|
||||
{
|
||||
bestStyle = entry.Key;
|
||||
bestTypeface = entry.Value;
|
||||
}
|
||||
}
|
||||
|
||||
return bestTypeface;
|
||||
}
|
||||
|
||||
private static Dictionary<SKFontStyleSlant, List<SKFontStyleSlant>> SlantFallbacks = new()
|
||||
{
|
||||
{ SKFontStyleSlant.Italic, new() { SKFontStyleSlant.Italic, SKFontStyleSlant.Oblique, SKFontStyleSlant.Upright } },
|
||||
{ SKFontStyleSlant.Oblique, new() { SKFontStyleSlant.Oblique, SKFontStyleSlant.Italic, SKFontStyleSlant.Upright } },
|
||||
{ SKFontStyleSlant.Upright, new() { SKFontStyleSlant.Upright, SKFontStyleSlant.Oblique, SKFontStyleSlant.Italic } },
|
||||
};
|
||||
|
||||
// Checks whether style a is a better match for the target then style b. Uses the CSS font style matching algorithm
|
||||
internal static bool IsBetterMatch(SKFontStyle? target, SKFontStyle? a, SKFontStyle? b)
|
||||
{
|
||||
// A font is better than no font
|
||||
if (b == null)
|
||||
return true;
|
||||
|
||||
if (a == null)
|
||||
return false;
|
||||
|
||||
// First check font width
|
||||
// For normal and condensed widths prefer smaller widths
|
||||
// For expanded widths prefer larger widths
|
||||
if (target.Width <= (int)SKFontStyleWidth.Normal)
|
||||
{
|
||||
if (a.Width <= target.Width && b.Width > target.Width)
|
||||
return true;
|
||||
|
||||
if (a.Width > target.Width && b.Width <= target.Width)
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (a.Width >= target.Width && b.Width < target.Width)
|
||||
return true;
|
||||
|
||||
if (a.Width < target.Width && b.Width >= target.Width)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Prefer closest match
|
||||
var widthDifferenceA = Math.Abs(a.Width - target.Width);
|
||||
var widthDifferenceB = Math.Abs(b.Width - target.Width);
|
||||
|
||||
if (widthDifferenceA < widthDifferenceB)
|
||||
return true;
|
||||
|
||||
if (widthDifferenceB < widthDifferenceA)
|
||||
return false;
|
||||
|
||||
// Prefer closest slant based on provided fallback list
|
||||
var slantFallback = SlantFallbacks[target.Slant];
|
||||
var slantIndexA = slantFallback.IndexOf(a.Slant);
|
||||
var slantIndexB = slantFallback.IndexOf(b.Slant);
|
||||
|
||||
if (slantIndexA < slantIndexB)
|
||||
return true;
|
||||
|
||||
if (slantIndexB < slantIndexA)
|
||||
return false;
|
||||
|
||||
// Check weight last
|
||||
// For thin (<400) weights, prefer thinner weights
|
||||
// For regular (400-500) weights, prefer other regular weights, then use rule for thin or bold
|
||||
// For bold (>500) weights, prefer thicker weights
|
||||
// Behavior for values other than multiples of 100 is not given in the specification
|
||||
|
||||
if (target.Weight >= 400 && target.Weight <= 500)
|
||||
{
|
||||
if ((a.Weight >= 400 && a.Weight <= 500) && !(b.Weight >= 400 && b.Weight <= 500))
|
||||
return true;
|
||||
|
||||
if (!(a.Weight >= 400 && a.Weight <= 500) && (b.Weight >= 400 && b.Weight <= 500))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (target.Weight < 450)
|
||||
{
|
||||
if (a.Weight <= target.Weight && b.Weight > target.Weight)
|
||||
return true;
|
||||
|
||||
if (a.Weight > target.Weight && b.Weight <= target.Weight)
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (a.Weight >= target.Weight && b.Weight < target.Weight)
|
||||
return true;
|
||||
|
||||
if (a.Weight < target.Weight && b.Weight >= target.Weight)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Prefer closest weight
|
||||
var weightDifferenceA = Math.Abs(a.Weight - target.Weight);
|
||||
var weightDifferenceB = Math.Abs(b.Weight - target.Weight);
|
||||
|
||||
return weightDifferenceA < weightDifferenceB;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,17 +51,17 @@ namespace QuestPDF.Drawing
|
||||
|
||||
}
|
||||
|
||||
public void DrawExternalLink(string url, Size size)
|
||||
public void DrawHyperlink(string url, Size size)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void DrawLocationLink(string locationName, Size size)
|
||||
public void DrawSectionLink(string sectionName, Size size)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void DrawLocation(string locationName)
|
||||
public void DrawSection(string sectionName)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@@ -37,19 +37,19 @@ namespace QuestPDF.Drawing
|
||||
Canvas.DrawImage(image, new SKRect(vector.X, vector.Y, size.Width, size.Height));
|
||||
}
|
||||
|
||||
public void DrawExternalLink(string url, Size size)
|
||||
public void DrawHyperlink(string url, Size size)
|
||||
{
|
||||
Canvas.DrawUrlAnnotation(new SKRect(0, 0, size.Width, size.Height), url);
|
||||
}
|
||||
|
||||
public void DrawLocationLink(string locationName, Size size)
|
||||
public void DrawSectionLink(string sectionName, Size size)
|
||||
{
|
||||
Canvas.DrawLinkDestinationAnnotation(new SKRect(0, 0, size.Width, size.Height), locationName);
|
||||
Canvas.DrawLinkDestinationAnnotation(new SKRect(0, 0, size.Width, size.Height), sectionName);
|
||||
}
|
||||
|
||||
public void DrawLocation(string locationName)
|
||||
public void DrawSection(string sectionName)
|
||||
{
|
||||
Canvas.DrawNamedDestinationAnnotation(new SKPoint(0, 0), locationName);
|
||||
Canvas.DrawNamedDestinationAnnotation(new SKPoint(0, 0), sectionName);
|
||||
}
|
||||
|
||||
public void Rotate(float angle)
|
||||
|
||||
@@ -41,6 +41,9 @@ namespace QuestPDF.Elements
|
||||
|
||||
internal override SpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
if (!Items.Any())
|
||||
return SpacePlan.FullRender(Size.Zero);
|
||||
|
||||
var renderingCommands = PlanLayout(availableSpace);
|
||||
|
||||
if (!renderingCommands.Any())
|
||||
|
||||
@@ -30,7 +30,10 @@ namespace QuestPDF.Elements
|
||||
.MinimalBox()
|
||||
.Background(Colors.White)
|
||||
.Padding(2)
|
||||
.Text(Text, TextStyle.Default.Color(Color).FontType(Fonts.Consolas).Size(8));
|
||||
.Text(Text)
|
||||
.FontColor(Color)
|
||||
.FontFamily(Fonts.Consolas)
|
||||
.FontSize(8);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class ExternalLink : ContainerElement
|
||||
internal class Hyperlink : ContainerElement
|
||||
{
|
||||
public string Url { get; set; } = "https://www.questpdf.com";
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace QuestPDF.Elements
|
||||
if (targetSize.Type == SpacePlanType.Wrap)
|
||||
return;
|
||||
|
||||
Canvas.DrawExternalLink(Url, targetSize);
|
||||
Canvas.DrawHyperlink(Url, targetSize);
|
||||
base.Draw(availableSpace);
|
||||
}
|
||||
}
|
||||
+48
-30
@@ -20,6 +20,9 @@ namespace QuestPDF.Elements
|
||||
|
||||
public string BackgroundColor { get; set; } = Colors.Transparent;
|
||||
|
||||
public Element Background { get; set; } = Empty.Instance;
|
||||
public Element Foreground { get; set; } = Empty.Instance;
|
||||
|
||||
public Element Header { get; set; } = Empty.Instance;
|
||||
public Element Content { get; set; } = Empty.Instance;
|
||||
public Element Footer { get; set; } = Empty.Instance;
|
||||
@@ -27,39 +30,54 @@ namespace QuestPDF.Elements
|
||||
public void Compose(IContainer container)
|
||||
{
|
||||
container
|
||||
.MinWidth(MinSize.Width)
|
||||
.MinHeight(MinSize.Height)
|
||||
|
||||
.MaxWidth(MaxSize.Width)
|
||||
.MaxHeight(MaxSize.Height)
|
||||
|
||||
.Background(BackgroundColor)
|
||||
|
||||
.PaddingLeft(MarginLeft)
|
||||
.PaddingRight(MarginRight)
|
||||
.PaddingTop(MarginTop)
|
||||
.PaddingBottom(MarginBottom)
|
||||
|
||||
.DefaultTextStyle(DefaultTextStyle)
|
||||
|
||||
.Decoration(decoration =>
|
||||
.Layers(layers =>
|
||||
{
|
||||
decoration
|
||||
.Before()
|
||||
.DebugPointer("Page header")
|
||||
.Element(Header);
|
||||
layers
|
||||
.Layer()
|
||||
.DebugPointer("Page background layer")
|
||||
.Element(Background);
|
||||
|
||||
decoration
|
||||
.Content()
|
||||
.Element(x => IsClose(MinSize.Width, MaxSize.Width) ? x.ExtendHorizontal() : x)
|
||||
.Element(x => IsClose(MinSize.Height, MaxSize.Height) ? x.ExtendVertical() : x)
|
||||
.DebugPointer("Page content")
|
||||
.Element(Content);
|
||||
layers
|
||||
.PrimaryLayer()
|
||||
.MinWidth(MinSize.Width)
|
||||
.MinHeight(MinSize.Height)
|
||||
|
||||
.MaxWidth(MaxSize.Width)
|
||||
.MaxHeight(MaxSize.Height)
|
||||
|
||||
.Background(BackgroundColor)
|
||||
|
||||
.PaddingLeft(MarginLeft)
|
||||
.PaddingRight(MarginRight)
|
||||
.PaddingTop(MarginTop)
|
||||
.PaddingBottom(MarginBottom)
|
||||
|
||||
.DefaultTextStyle(DefaultTextStyle)
|
||||
|
||||
.Decoration(decoration =>
|
||||
{
|
||||
decoration
|
||||
.Before()
|
||||
.DebugPointer("Page header")
|
||||
.Element(Header);
|
||||
|
||||
decoration
|
||||
.Content()
|
||||
.Element(x => IsClose(MinSize.Width, MaxSize.Width) ? x.ExtendHorizontal() : x)
|
||||
.Element(x => IsClose(MinSize.Height, MaxSize.Height) ? x.ExtendVertical() : x)
|
||||
.DebugPointer("Page content")
|
||||
.Element(Content);
|
||||
|
||||
decoration
|
||||
.After()
|
||||
.DebugPointer("Page footer")
|
||||
.Element(Footer);
|
||||
});
|
||||
|
||||
decoration
|
||||
.After()
|
||||
.DebugPointer("Page footer")
|
||||
.Element(Footer);
|
||||
layers
|
||||
.Layer()
|
||||
.DebugPointer("Page foreground layer")
|
||||
.Element(Foreground);
|
||||
});
|
||||
|
||||
bool IsClose(float x, float y)
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace QuestPDF.Elements
|
||||
if (string.IsNullOrWhiteSpace(Text))
|
||||
x.MaxHeight(32).Image(ImageData, ImageScaling.FitArea);
|
||||
else
|
||||
x.Text(Text, TextStyle.Default.Size(14));
|
||||
x.Text(Text).FontSize(14);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,9 @@ namespace QuestPDF.Elements
|
||||
|
||||
internal override SpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
if (!Items.Any())
|
||||
return SpacePlan.FullRender(Size.Zero);
|
||||
|
||||
UpdateItemsWidth(availableSpace.Width);
|
||||
var renderingCommands = PlanLayout(availableSpace);
|
||||
|
||||
@@ -74,6 +77,9 @@ namespace QuestPDF.Elements
|
||||
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
if (!Items.Any())
|
||||
return;
|
||||
|
||||
UpdateItemsWidth(availableSpace.Width);
|
||||
var renderingCommands = PlanLayout(availableSpace);
|
||||
|
||||
@@ -143,8 +149,12 @@ namespace QuestPDF.Elements
|
||||
renderingCommands.Add(command);
|
||||
leftOffset += item.Width + Spacing;
|
||||
}
|
||||
|
||||
var rowHeight = renderingCommands.Where(x => !x.RowItem.IsRendered).Max(x => x.Measurement.Height);
|
||||
|
||||
var rowHeight = renderingCommands
|
||||
.Where(x => !x.RowItem.IsRendered)
|
||||
.Select(x => x.Measurement.Height)
|
||||
.DefaultIfEmpty(0)
|
||||
.Max();
|
||||
|
||||
foreach (var command in renderingCommands)
|
||||
{
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class InternalLocation : ContainerElement, IStateResettable
|
||||
internal class Section : ContainerElement, IStateResettable
|
||||
{
|
||||
public string LocationName { get; set; }
|
||||
private bool IsRendered { get; set; }
|
||||
@@ -16,12 +16,11 @@ namespace QuestPDF.Elements
|
||||
{
|
||||
if (!IsRendered)
|
||||
{
|
||||
PageContext.SetLocationPage(LocationName);
|
||||
|
||||
Canvas.DrawLocation(LocationName);
|
||||
Canvas.DrawSection(LocationName);
|
||||
IsRendered = true;
|
||||
}
|
||||
|
||||
PageContext.SetSectionPage(LocationName);
|
||||
base.Draw(availableSpace);
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,9 @@ using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class InternalLink : ContainerElement
|
||||
internal class SectionLink : ContainerElement
|
||||
{
|
||||
public string LocationName { get; set; }
|
||||
public string SectionName { get; set; }
|
||||
|
||||
internal override void Draw(Size availableSpace)
|
||||
{
|
||||
@@ -14,7 +14,7 @@ namespace QuestPDF.Elements
|
||||
if (targetSize.Type == SpacePlanType.Wrap)
|
||||
return;
|
||||
|
||||
Canvas.DrawLocationLink(LocationName, targetSize);
|
||||
Canvas.DrawSectionLink(SectionName, targetSize);
|
||||
base.Draw(availableSpace);
|
||||
}
|
||||
}
|
||||
@@ -15,15 +15,19 @@ namespace QuestPDF.Elements.Table
|
||||
public List<TableCell> Cells { get; set; } = new List<TableCell>();
|
||||
public bool ExtendLastCellsToTableBottom { get; set; }
|
||||
|
||||
// cache for efficient cell finding
|
||||
// index of first array - number of row
|
||||
// nested array - collection of all cells starting at given row
|
||||
private TableCell[][] OrderedCells { get; set; }
|
||||
|
||||
private int StartingRowsCount { get; set; }
|
||||
private int RowsCount { get; set; }
|
||||
private int CurrentRow { get; set; }
|
||||
|
||||
internal override void Initialize(IPageContext pageContext, ICanvas canvas)
|
||||
{
|
||||
StartingRowsCount = Cells.Select(x => x.Row).DefaultIfEmpty(0).Max();
|
||||
RowsCount = Cells.Select(x => x.Row + x.RowSpan - 1).DefaultIfEmpty(0).Max();
|
||||
Cells = Cells.OrderBy(x => x.Row).ThenBy(x => x.Column).ToList();
|
||||
|
||||
base.Initialize(pageContext, canvas);
|
||||
}
|
||||
|
||||
internal override IEnumerable<Element?> GetChildren()
|
||||
{
|
||||
return Cells;
|
||||
@@ -31,24 +35,6 @@ namespace QuestPDF.Elements.Table
|
||||
|
||||
public void ResetState()
|
||||
{
|
||||
if (StartingRowsCount == default)
|
||||
StartingRowsCount = Cells.Select(x => x.Row).DefaultIfEmpty(0).Max();
|
||||
|
||||
if (RowsCount == default)
|
||||
RowsCount = Cells.Select(x => x.Row + x.RowSpan - 1).DefaultIfEmpty(0).Max();
|
||||
|
||||
if (OrderedCells == default)
|
||||
{
|
||||
var groups = Cells
|
||||
.GroupBy(x => x.Row)
|
||||
.ToDictionary(x => x.Key, x => x.OrderBy(y => y.Column).ToArray());
|
||||
|
||||
OrderedCells = Enumerable
|
||||
.Range(0, RowsCount + 1)
|
||||
.Select(x => groups.TryGetValue(x, out var output) ? output : Array.Empty<TableCell>())
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
Cells.ForEach(x => x.IsRendered = false);
|
||||
CurrentRow = 1;
|
||||
}
|
||||
@@ -85,6 +71,9 @@ namespace QuestPDF.Elements.Table
|
||||
{
|
||||
if (command.Measurement.Type == SpacePlanType.FullRender)
|
||||
command.Cell.IsRendered = true;
|
||||
|
||||
if (command.Measurement.Type == SpacePlanType.Wrap)
|
||||
continue;
|
||||
|
||||
Canvas.Translate(command.Offset);
|
||||
command.Cell.Draw(command.Size);
|
||||
@@ -103,7 +92,7 @@ namespace QuestPDF.Elements.Table
|
||||
{
|
||||
return commands
|
||||
.GroupBy(x => x.Cell.Row)
|
||||
.Where(x => x.All(y => y.Measurement.Type == SpacePlanType.FullRender))
|
||||
.Where(x => x.All(y => y.Cell.IsRendered || y.Measurement.Type == SpacePlanType.FullRender))
|
||||
.Select(x => x.Key)
|
||||
.DefaultIfEmpty(0)
|
||||
.Max();
|
||||
@@ -152,16 +141,12 @@ namespace QuestPDF.Elements.Table
|
||||
ICollection<TableCellRenderingCommand> GetRenderingCommands()
|
||||
{
|
||||
var rowBottomOffsets = new DynamicDictionary<int, float>();
|
||||
var commands = new List<TableCellRenderingCommand>();
|
||||
|
||||
var cellsToTry = Enumerable
|
||||
.Range(CurrentRow, RowsCount - CurrentRow + 1)
|
||||
.SelectMany(x => OrderedCells[x]);
|
||||
|
||||
var cellsToTry = Cells.Where(x => x.Row + x.RowSpan - 1 >= CurrentRow);
|
||||
var currentRow = CurrentRow;
|
||||
var maxRenderingRow = RowsCount;
|
||||
|
||||
var commands = new List<TableCellRenderingCommand>();
|
||||
|
||||
foreach (var cell in cellsToTry)
|
||||
{
|
||||
// update position of previous row
|
||||
@@ -181,10 +166,7 @@ namespace QuestPDF.Elements.Table
|
||||
// cell visibility optimizations
|
||||
if (cell.Row > maxRenderingRow)
|
||||
break;
|
||||
|
||||
if (cell.IsRendered)
|
||||
continue;
|
||||
|
||||
|
||||
// calculate cell position / size
|
||||
var topOffset = rowBottomOffsets[cell.Row - 1];
|
||||
|
||||
|
||||
+2
-2
@@ -3,7 +3,7 @@ using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements.Text.Items
|
||||
{
|
||||
internal class TextBlockExternalLink : TextBlockSpan
|
||||
internal class TextBlockHyperlink : TextBlockSpan
|
||||
{
|
||||
public string Url { get; set; }
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace QuestPDF.Elements.Text.Items
|
||||
public override void Draw(TextDrawingRequest request)
|
||||
{
|
||||
request.Canvas.Translate(new Position(0, request.TotalAscent));
|
||||
request.Canvas.DrawExternalLink(Url, new Size(request.TextSize.Width, request.TextSize.Height));
|
||||
request.Canvas.DrawHyperlink(Url, new Size(request.TextSize.Width, request.TextSize.Height));
|
||||
request.Canvas.Translate(new Position(0, -request.TotalAscent));
|
||||
|
||||
base.Draw(request);
|
||||
@@ -1,11 +1,13 @@
|
||||
using QuestPDF.Elements.Text.Calculation;
|
||||
using System;
|
||||
using QuestPDF.Elements.Text.Calculation;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements.Text.Items
|
||||
{
|
||||
internal class TextBlockPageNumber : TextBlockSpan
|
||||
{
|
||||
public string SlotName { get; set; }
|
||||
public const string PageNumberPlaceholder = "123";
|
||||
public Func<IPageContext, string> Source { get; set; } = _ => PageNumberPlaceholder;
|
||||
|
||||
public override TextMeasurementResult? Measure(TextMeasurementRequest request)
|
||||
{
|
||||
@@ -21,13 +23,7 @@ namespace QuestPDF.Elements.Text.Items
|
||||
|
||||
private void SetPageNumber(IPageContext context)
|
||||
{
|
||||
var pageNumberPlaceholder = 123;
|
||||
|
||||
var pageNumber = context.GetRegisteredLocations().Contains(SlotName)
|
||||
? context.GetLocationPage(SlotName)
|
||||
: pageNumberPlaceholder;
|
||||
|
||||
Text = pageNumber.ToString();
|
||||
Text = Source(context) ?? PageNumberPlaceholder;
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -3,9 +3,9 @@ using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements.Text.Items
|
||||
{
|
||||
internal class TextBlockInternalLink : TextBlockSpan
|
||||
internal class TextBlockSectionlLink : TextBlockSpan
|
||||
{
|
||||
public string LocationName { get; set; }
|
||||
public string SectionName { get; set; }
|
||||
|
||||
public override TextMeasurementResult? Measure(TextMeasurementRequest request)
|
||||
{
|
||||
@@ -15,7 +15,7 @@ namespace QuestPDF.Elements.Text.Items
|
||||
public override void Draw(TextDrawingRequest request)
|
||||
{
|
||||
request.Canvas.Translate(new Position(0, request.TotalAscent));
|
||||
request.Canvas.DrawLocationLink(LocationName, new Size(request.TextSize.Width, request.TextSize.Height));
|
||||
request.Canvas.DrawSectionLink(SectionName, new Size(request.TextSize.Width, request.TextSize.Height));
|
||||
request.Canvas.Translate(new Position(0, -request.TotalAscent));
|
||||
|
||||
base.Draw(request);
|
||||
@@ -60,7 +60,7 @@ namespace QuestPDF.Fluent
|
||||
|
||||
public static void Placeholder(this IContainer element, string? text = null)
|
||||
{
|
||||
element.Component(new Elements.Placeholder
|
||||
element.Component(new Placeholder
|
||||
{
|
||||
Text = text ?? string.Empty
|
||||
});
|
||||
@@ -99,27 +99,45 @@ namespace QuestPDF.Fluent
|
||||
return element.Element(new Container());
|
||||
}
|
||||
|
||||
[Obsolete("This element has been renamed since version 2022.3. Please use the Hyperlink method.")]
|
||||
public static IContainer ExternalLink(this IContainer element, string url)
|
||||
{
|
||||
return element.Element(new ExternalLink
|
||||
return element.Hyperlink(url);
|
||||
}
|
||||
|
||||
public static IContainer Hyperlink(this IContainer element, string url)
|
||||
{
|
||||
return element.Element(new Hyperlink
|
||||
{
|
||||
Url = url
|
||||
});
|
||||
}
|
||||
|
||||
[Obsolete("This element has been renamed since version 2022.3. Please use the Section method.")]
|
||||
public static IContainer Location(this IContainer element, string locationName)
|
||||
{
|
||||
return element.Element(new InternalLocation
|
||||
return element.Section(locationName);
|
||||
}
|
||||
|
||||
public static IContainer Section(this IContainer element, string sectionName)
|
||||
{
|
||||
return element.Element(new Section
|
||||
{
|
||||
LocationName = locationName
|
||||
LocationName = sectionName
|
||||
});
|
||||
}
|
||||
|
||||
[Obsolete("This element has been renamed since version 2022.3. Please use the SectionLink method.")]
|
||||
public static IContainer InternalLink(this IContainer element, string locationName)
|
||||
{
|
||||
return element.Element(new InternalLink
|
||||
return element.SectionLink(locationName);
|
||||
}
|
||||
|
||||
public static IContainer SectionLink(this IContainer element, string sectionName)
|
||||
{
|
||||
return element.Element(new SectionLink
|
||||
{
|
||||
LocationName = locationName
|
||||
SectionName = sectionName
|
||||
});
|
||||
}
|
||||
|
||||
@@ -159,7 +177,15 @@ namespace QuestPDF.Fluent
|
||||
TextStyle = textStyle
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public static IContainer DefaultTextStyle(this IContainer element, Func<TextStyle, TextStyle> handler)
|
||||
{
|
||||
return element.Element(new DefaultTextStyle
|
||||
{
|
||||
TextStyle = handler(TextStyle.Default)
|
||||
});
|
||||
}
|
||||
|
||||
public static IContainer StopPaging(this IContainer element)
|
||||
{
|
||||
return element.Element(new StopPaging());
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Fluent
|
||||
{
|
||||
public class Document : IDocument
|
||||
{
|
||||
private Action<IDocumentContainer> ContentSource { get; }
|
||||
private DocumentMetadata Metadata { get; set; } = DocumentMetadata.Default;
|
||||
|
||||
private Document(Action<IDocumentContainer> contentSource)
|
||||
{
|
||||
ContentSource = contentSource;
|
||||
}
|
||||
|
||||
public static Document Create(Action<IDocumentContainer> handler)
|
||||
{
|
||||
return new Document(handler);
|
||||
}
|
||||
|
||||
public Document WithMetadata(DocumentMetadata metadata)
|
||||
{
|
||||
Metadata = metadata ?? Metadata;
|
||||
return this;
|
||||
}
|
||||
|
||||
#region IDocument
|
||||
|
||||
public DocumentMetadata GetMetadata() => Metadata;
|
||||
public void Compose(IDocumentContainer container) => ContentSource(container);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -83,11 +83,30 @@ namespace QuestPDF.Fluent
|
||||
Page.DefaultTextStyle = textStyle;
|
||||
}
|
||||
|
||||
public void DefaultTextStyle(Func<TextStyle, TextStyle> handler)
|
||||
{
|
||||
DefaultTextStyle(handler(TextStyle.Default));
|
||||
}
|
||||
|
||||
public void Background(string color)
|
||||
{
|
||||
Page.BackgroundColor = color;
|
||||
}
|
||||
|
||||
public IContainer Background()
|
||||
{
|
||||
var container = new Container();
|
||||
Page.Background = container;
|
||||
return container;
|
||||
}
|
||||
|
||||
public IContainer Foreground()
|
||||
{
|
||||
var container = new Container();
|
||||
Page.Foreground = container;
|
||||
return container;
|
||||
}
|
||||
|
||||
public IContainer Header()
|
||||
{
|
||||
var container = new Container();
|
||||
|
||||
@@ -10,6 +10,34 @@ using static System.String;
|
||||
|
||||
namespace QuestPDF.Fluent
|
||||
{
|
||||
public class TextSpanDescriptor
|
||||
{
|
||||
internal TextStyle TextStyle { get; }
|
||||
|
||||
internal TextSpanDescriptor(TextStyle textStyle)
|
||||
{
|
||||
TextStyle = textStyle;
|
||||
}
|
||||
}
|
||||
|
||||
public delegate string PageNumberFormatter(int? pageNumber);
|
||||
|
||||
public class TextPageNumberDescriptor : TextSpanDescriptor
|
||||
{
|
||||
internal PageNumberFormatter FormatFunction { get; private set; } = x => (x ?? 123).ToString();
|
||||
|
||||
internal TextPageNumberDescriptor(TextStyle textStyle) : base(textStyle)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public TextPageNumberDescriptor Format(PageNumberFormatter formatter)
|
||||
{
|
||||
FormatFunction = formatter ?? FormatFunction;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public class TextDescriptor
|
||||
{
|
||||
private ICollection<TextBlock> TextBlocks { get; } = new List<TextBlock>();
|
||||
@@ -22,6 +50,11 @@ namespace QuestPDF.Fluent
|
||||
DefaultStyle = style;
|
||||
}
|
||||
|
||||
public void DefaultTextStyle(Func<TextStyle, TextStyle> style)
|
||||
{
|
||||
DefaultStyle = style(TextStyle.Default);
|
||||
}
|
||||
|
||||
public void AlignLeft()
|
||||
{
|
||||
Alignment = HorizontalAlignment.Left;
|
||||
@@ -50,12 +83,15 @@ namespace QuestPDF.Fluent
|
||||
TextBlocks.Last().Items.Add(item);
|
||||
}
|
||||
|
||||
public void Span(string? text, TextStyle? style = null)
|
||||
[Obsolete("This element has been renamed since version 2022.3. Please use the overload that returns a TextSpanDescriptor object which allows to specify text style.")]
|
||||
public void Span(string? text, TextStyle style)
|
||||
{
|
||||
if (IsNullOrEmpty(text))
|
||||
return;
|
||||
|
||||
style ??= TextStyle.Default;
|
||||
Span(text).Style(style);
|
||||
}
|
||||
|
||||
public TextSpanDescriptor Span(string? text)
|
||||
{
|
||||
var style = DefaultStyle.Clone();
|
||||
|
||||
var items = text
|
||||
.Replace("\r", string.Empty)
|
||||
@@ -77,85 +113,123 @@ namespace QuestPDF.Fluent
|
||||
})
|
||||
.ToList()
|
||||
.ForEach(TextBlocks.Add);
|
||||
|
||||
return new TextSpanDescriptor(style);
|
||||
}
|
||||
|
||||
public void Line(string? text, TextStyle? style = null)
|
||||
public TextSpanDescriptor Line(string? text)
|
||||
{
|
||||
text ??= string.Empty;
|
||||
Span(text + Environment.NewLine, style);
|
||||
return Span(text + Environment.NewLine);
|
||||
}
|
||||
|
||||
public void EmptyLine()
|
||||
public TextSpanDescriptor EmptyLine()
|
||||
{
|
||||
Span(Environment.NewLine);
|
||||
return Span(Environment.NewLine);
|
||||
}
|
||||
|
||||
private void PageNumber(string slotName, TextStyle? style = null)
|
||||
|
||||
private TextPageNumberDescriptor PageNumber(Func<IPageContext, int?> pageNumber)
|
||||
{
|
||||
if (IsNullOrEmpty(slotName))
|
||||
throw new ArgumentException(nameof(slotName));
|
||||
var style = DefaultStyle.Clone();
|
||||
var descriptor = new TextPageNumberDescriptor(DefaultStyle);
|
||||
|
||||
style ??= TextStyle.Default;
|
||||
|
||||
AddItemToLastTextBlock(new TextBlockPageNumber()
|
||||
AddItemToLastTextBlock(new TextBlockPageNumber
|
||||
{
|
||||
Style = style,
|
||||
SlotName = slotName
|
||||
Source = context => descriptor.FormatFunction(pageNumber(context)),
|
||||
Style = style
|
||||
});
|
||||
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
public void CurrentPageNumber(TextStyle? style = null)
|
||||
|
||||
public TextPageNumberDescriptor CurrentPageNumber()
|
||||
{
|
||||
PageNumber(PageContext.CurrentPageSlot, style);
|
||||
return PageNumber(x => x.CurrentPage);
|
||||
}
|
||||
|
||||
public void TotalPages(TextStyle? style = null)
|
||||
public TextPageNumberDescriptor TotalPages()
|
||||
{
|
||||
PageNumber(PageContext.TotalPagesSlot, style);
|
||||
return PageNumber(x => x.GetLocation(PageContext.DocumentLocation)?.Length);
|
||||
}
|
||||
|
||||
|
||||
[Obsolete("This element has been renamed since version 2022.3. Please use the BeginPageNumberOfSection method.")]
|
||||
public void PageNumberOfLocation(string locationName, TextStyle? style = null)
|
||||
{
|
||||
if (IsNullOrEmpty(locationName))
|
||||
throw new ArgumentException(nameof(locationName));
|
||||
|
||||
PageNumber(locationName, style);
|
||||
BeginPageNumberOfSection(locationName).Style(style);
|
||||
}
|
||||
|
||||
public void InternalLocation(string? text, string locationName, TextStyle? style = null)
|
||||
public TextPageNumberDescriptor BeginPageNumberOfSection(string locationName)
|
||||
{
|
||||
if (IsNullOrEmpty(text))
|
||||
return;
|
||||
|
||||
if (IsNullOrEmpty(locationName))
|
||||
throw new ArgumentException(nameof(locationName));
|
||||
return PageNumber(x => x.GetLocation(locationName)?.PageStart);
|
||||
}
|
||||
|
||||
public TextPageNumberDescriptor EndPageNumberOfSection(string locationName)
|
||||
{
|
||||
return PageNumber(x => x.GetLocation(locationName)?.PageEnd);
|
||||
}
|
||||
|
||||
public TextPageNumberDescriptor PageNumberWithinSection(string locationName)
|
||||
{
|
||||
return PageNumber(x => x.CurrentPage + 1 - x.GetLocation(locationName)?.PageEnd);
|
||||
}
|
||||
|
||||
public TextPageNumberDescriptor TotalPagesWithinSection(string locationName)
|
||||
{
|
||||
return PageNumber(x => x.GetLocation(locationName)?.Length);
|
||||
}
|
||||
|
||||
public TextSpanDescriptor SectionLink(string? text, string sectionName)
|
||||
{
|
||||
if (IsNullOrEmpty(sectionName))
|
||||
throw new ArgumentException(nameof(sectionName));
|
||||
|
||||
style ??= TextStyle.Default;
|
||||
var style = DefaultStyle.Clone();
|
||||
var descriptor = new TextSpanDescriptor(style);
|
||||
|
||||
AddItemToLastTextBlock(new TextBlockInternalLink
|
||||
if (IsNullOrEmpty(text))
|
||||
return descriptor;
|
||||
|
||||
AddItemToLastTextBlock(new TextBlockSectionlLink
|
||||
{
|
||||
Style = style,
|
||||
Text = text,
|
||||
LocationName = locationName
|
||||
SectionName = sectionName
|
||||
});
|
||||
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
public void ExternalLocation(string? text, string url, TextStyle? style = null)
|
||||
[Obsolete("This element has been renamed since version 2022.3. Please use the SectionLink method.")]
|
||||
public void InternalLocation(string? text, string locationName, TextStyle? style = null)
|
||||
{
|
||||
SectionLink(text, locationName).Style(style);
|
||||
}
|
||||
|
||||
public TextSpanDescriptor Hyperlink(string? text, string url)
|
||||
{
|
||||
if (IsNullOrEmpty(text))
|
||||
return;
|
||||
|
||||
if (IsNullOrEmpty(url))
|
||||
throw new ArgumentException(nameof(url));
|
||||
|
||||
var style = DefaultStyle.Clone();
|
||||
var descriptor = new TextSpanDescriptor(style);
|
||||
|
||||
if (IsNullOrEmpty(text))
|
||||
return descriptor;
|
||||
|
||||
style ??= TextStyle.Default;
|
||||
|
||||
AddItemToLastTextBlock(new TextBlockExternalLink
|
||||
AddItemToLastTextBlock(new TextBlockHyperlink
|
||||
{
|
||||
Style = style,
|
||||
Text = text,
|
||||
Url = url
|
||||
});
|
||||
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
[Obsolete("This element has been renamed since version 2022.3. Please use the Hyperlink method.")]
|
||||
public void ExternalLocation(string? text, string url, TextStyle? style = null)
|
||||
{
|
||||
Hyperlink(text, url).Style(style);
|
||||
}
|
||||
|
||||
public IContainer Element()
|
||||
@@ -197,9 +271,17 @@ namespace QuestPDF.Fluent
|
||||
descriptor.Compose(element);
|
||||
}
|
||||
|
||||
public static void Text(this IContainer element, object? text, TextStyle? style = null)
|
||||
[Obsolete("This element has been renamed since version 2022.3. Please use the overload that returns a TextSpanDescriptor object which allows to specify text style.")]
|
||||
public static void Text(this IContainer element, object? text, TextStyle style)
|
||||
{
|
||||
element.Text(x => x.Span(text?.ToString(), style));
|
||||
element.Text(text).Style(style);
|
||||
}
|
||||
|
||||
public static TextSpanDescriptor Text(this IContainer element, object? text)
|
||||
{
|
||||
var descriptor = (TextSpanDescriptor) null;
|
||||
element.Text(x => descriptor = x.Span(text?.ToString()));
|
||||
return descriptor;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Fluent
|
||||
{
|
||||
public static class TextSpanDescriptorExtensions
|
||||
{
|
||||
public static T Style<T>(this T descriptor, TextStyle style) where T : TextSpanDescriptor
|
||||
{
|
||||
if (style == null)
|
||||
return descriptor;
|
||||
|
||||
descriptor.TextStyle.OverrideStyle(style);
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
public static T FontColor<T>(this T descriptor, string value) where T : TextSpanDescriptor
|
||||
{
|
||||
descriptor.TextStyle.Color = value;
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
public static T BackgroundColor<T>(this T descriptor, string value) where T : TextSpanDescriptor
|
||||
{
|
||||
descriptor.TextStyle.BackgroundColor = value;
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
public static T FontFamily<T>(this T descriptor, string value) where T : TextSpanDescriptor
|
||||
{
|
||||
descriptor.TextStyle.FontFamily = value;
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
public static T FontSize<T>(this T descriptor, float value) where T : TextSpanDescriptor
|
||||
{
|
||||
descriptor.TextStyle.Size = value;
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
public static T LineHeight<T>(this T descriptor, float value) where T : TextSpanDescriptor
|
||||
{
|
||||
descriptor.TextStyle.LineHeight = value;
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
public static T Italic<T>(this T descriptor, bool value = true) where T : TextSpanDescriptor
|
||||
{
|
||||
descriptor.TextStyle.IsItalic = value;
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
public static T Strikethrough<T>(this T descriptor, bool value = true) where T : TextSpanDescriptor
|
||||
{
|
||||
descriptor.TextStyle.HasStrikethrough = value;
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
public static T Underline<T>(this T descriptor, bool value = true) where T : TextSpanDescriptor
|
||||
{
|
||||
descriptor.TextStyle.HasUnderline = value;
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
#region Weight
|
||||
|
||||
public static T Weight<T>(this T descriptor, FontWeight weight) where T : TextSpanDescriptor
|
||||
{
|
||||
descriptor.TextStyle.FontWeight = weight;
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
public static T Thin<T>(this T descriptor) where T : TextSpanDescriptor
|
||||
{
|
||||
return descriptor.Weight(FontWeight.Thin);
|
||||
}
|
||||
|
||||
public static T ExtraLight<T>(this T descriptor) where T : TextSpanDescriptor
|
||||
{
|
||||
return descriptor.Weight(FontWeight.ExtraLight);
|
||||
}
|
||||
|
||||
public static T Light<T>(this T descriptor) where T : TextSpanDescriptor
|
||||
{
|
||||
return descriptor.Weight(FontWeight.Light);
|
||||
}
|
||||
|
||||
public static T NormalWeight<T>(this T descriptor) where T : TextSpanDescriptor
|
||||
{
|
||||
return descriptor.Weight(FontWeight.Normal);
|
||||
}
|
||||
|
||||
public static T Medium<T>(this T descriptor) where T : TextSpanDescriptor
|
||||
{
|
||||
return descriptor.Weight(FontWeight.Medium);
|
||||
}
|
||||
|
||||
public static T SemiBold<T>(this T descriptor) where T : TextSpanDescriptor
|
||||
{
|
||||
return descriptor.Weight(FontWeight.SemiBold);
|
||||
}
|
||||
|
||||
public static T Bold<T>(this T descriptor) where T : TextSpanDescriptor
|
||||
{
|
||||
return descriptor.Weight(FontWeight.Bold);
|
||||
}
|
||||
|
||||
public static T ExtraBold<T>(this T descriptor) where T : TextSpanDescriptor
|
||||
{
|
||||
return descriptor.Weight(FontWeight.ExtraBold);
|
||||
}
|
||||
|
||||
public static T Black<T>(this T descriptor) where T : TextSpanDescriptor
|
||||
{
|
||||
return descriptor.Weight(FontWeight.Black);
|
||||
}
|
||||
|
||||
public static T ExtraBlack<T>(this T descriptor) where T : TextSpanDescriptor
|
||||
{
|
||||
return descriptor.Weight(FontWeight.ExtraBlack);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,13 @@ namespace QuestPDF.Fluent
|
||||
return style;
|
||||
}
|
||||
|
||||
[Obsolete("This element has been renamed since version 2022.3. Please use the FontColor method.")]
|
||||
public static TextStyle Color(this TextStyle style, string value)
|
||||
{
|
||||
return style.FontColor(value);
|
||||
}
|
||||
|
||||
public static TextStyle FontColor(this TextStyle style, string value)
|
||||
{
|
||||
return style.Mutate(x => x.Color = value);
|
||||
}
|
||||
@@ -23,12 +29,24 @@ namespace QuestPDF.Fluent
|
||||
return style.Mutate(x => x.BackgroundColor = value);
|
||||
}
|
||||
|
||||
[Obsolete("This element has been renamed since version 2022.3. Please use the FontFamily method.")]
|
||||
public static TextStyle FontType(this TextStyle style, string value)
|
||||
{
|
||||
return style.Mutate(x => x.FontType = value);
|
||||
return style.FontFamily(value);
|
||||
}
|
||||
|
||||
public static TextStyle FontFamily(this TextStyle style, string value)
|
||||
{
|
||||
return style.Mutate(x => x.FontFamily = value);
|
||||
}
|
||||
|
||||
[Obsolete("This element has been renamed since version 2022.3. Please use the FontSize method.")]
|
||||
public static TextStyle Size(this TextStyle style, float value)
|
||||
{
|
||||
return style.FontSize(value);
|
||||
}
|
||||
|
||||
public static TextStyle FontSize(this TextStyle style, float value)
|
||||
{
|
||||
return style.Mutate(x => x.Size = value);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace QuestPDF.Infrastructure
|
||||
yield break;
|
||||
}
|
||||
|
||||
internal void Initialize(IPageContext pageContext, ICanvas canvas)
|
||||
internal virtual void Initialize(IPageContext pageContext, ICanvas canvas)
|
||||
{
|
||||
PageContext = pageContext;
|
||||
Canvas = canvas;
|
||||
|
||||
@@ -10,9 +10,9 @@ namespace QuestPDF.Infrastructure
|
||||
void DrawText(string text, Position position, TextStyle style);
|
||||
void DrawImage(SKImage image, Position position, Size size);
|
||||
|
||||
void DrawExternalLink(string url, Size size);
|
||||
void DrawLocationLink(string locationName, Size size);
|
||||
void DrawLocation(string locationName);
|
||||
void DrawHyperlink(string url, Size size);
|
||||
void DrawSectionLink(string sectionName, Size size);
|
||||
void DrawSection(string sectionName);
|
||||
|
||||
void Rotate(float angle);
|
||||
void Scale(float scaleX, float scaleY);
|
||||
|
||||
@@ -2,10 +2,18 @@
|
||||
|
||||
namespace QuestPDF.Infrastructure
|
||||
{
|
||||
public interface IPageContext
|
||||
internal class DocumentLocation
|
||||
{
|
||||
void SetLocationPage(string key);
|
||||
int GetLocationPage(string key);
|
||||
ICollection<string> GetRegisteredLocations();
|
||||
public string Name { get; set; }
|
||||
public int PageStart { get; set; }
|
||||
public int PageEnd { get; set; }
|
||||
public int Length => PageEnd - PageStart + 1;
|
||||
}
|
||||
|
||||
internal interface IPageContext
|
||||
{
|
||||
int CurrentPage { get; }
|
||||
void SetSectionPage(string name);
|
||||
DocumentLocation? GetLocation(string name);
|
||||
}
|
||||
}
|
||||
@@ -1,44 +1,45 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace QuestPDF.Infrastructure
|
||||
{
|
||||
public class PageContext : IPageContext
|
||||
internal class PageContext : IPageContext
|
||||
{
|
||||
public const string CurrentPageSlot = "currentPage";
|
||||
public const string TotalPagesSlot = "totalPages";
|
||||
public const string DocumentLocation = "document";
|
||||
|
||||
private Dictionary<string, int> Locations { get; } = new Dictionary<string, int>();
|
||||
private int PageNumber { get; set; }
|
||||
private List<DocumentLocation> Locations { get; } = new();
|
||||
public int CurrentPage { get; private set; }
|
||||
|
||||
internal void SetPageNumber(int number)
|
||||
{
|
||||
PageNumber = number;
|
||||
Locations[CurrentPageSlot] = number;
|
||||
|
||||
if (!Locations.ContainsKey(TotalPagesSlot) || Locations[TotalPagesSlot] < number)
|
||||
Locations[TotalPagesSlot] = number;
|
||||
CurrentPage = number;
|
||||
SetSectionPage(DocumentLocation);
|
||||
}
|
||||
|
||||
public void SetLocationPage(string key)
|
||||
public void SetSectionPage(string name)
|
||||
{
|
||||
if (Locations.ContainsKey(key))
|
||||
return;
|
||||
|
||||
Locations[key] = PageNumber;
|
||||
var location = GetLocation(name);
|
||||
|
||||
if (location == null)
|
||||
{
|
||||
location = new DocumentLocation
|
||||
{
|
||||
Name = name,
|
||||
PageStart = CurrentPage,
|
||||
PageEnd = CurrentPage
|
||||
};
|
||||
|
||||
Locations.Add(location);
|
||||
}
|
||||
|
||||
if (location.PageEnd < CurrentPage)
|
||||
location.PageEnd = CurrentPage;
|
||||
}
|
||||
|
||||
public int GetLocationPage(string key)
|
||||
public DocumentLocation? GetLocation(string name)
|
||||
{
|
||||
if (!Locations.ContainsKey(key))
|
||||
throw new ArgumentException($"The location '{key}' does not exists.");
|
||||
|
||||
return Locations[key];
|
||||
}
|
||||
|
||||
public ICollection<string> GetRegisteredLocations()
|
||||
{
|
||||
return Locations.Keys;
|
||||
return Locations.FirstOrDefault(x => x.Name == name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ namespace QuestPDF.Infrastructure
|
||||
|
||||
internal string? Color { get; set; }
|
||||
internal string? BackgroundColor { get; set; }
|
||||
internal string? FontType { get; set; }
|
||||
internal string? FontFamily { get; set; }
|
||||
internal float? Size { get; set; }
|
||||
internal float? LineHeight { get; set; }
|
||||
internal FontWeight? FontWeight { get; set; }
|
||||
@@ -17,13 +17,14 @@ namespace QuestPDF.Infrastructure
|
||||
internal bool? HasStrikethrough { get; set; }
|
||||
internal bool? HasUnderline { get; set; }
|
||||
|
||||
internal string? Key { get; private set; }
|
||||
internal object PaintKey { get; private set; }
|
||||
internal object FontMetricsKey { get; private set; }
|
||||
|
||||
internal static TextStyle LibraryDefault => new TextStyle
|
||||
{
|
||||
Color = Colors.Black,
|
||||
BackgroundColor = Colors.Transparent,
|
||||
FontType = Fonts.Calibri,
|
||||
FontFamily = Fonts.Calibri,
|
||||
Size = 12,
|
||||
LineHeight = 1.2f,
|
||||
FontWeight = Infrastructure.FontWeight.Normal,
|
||||
@@ -42,14 +43,15 @@ namespace QuestPDF.Infrastructure
|
||||
HasGlobalStyleApplied = true;
|
||||
|
||||
ApplyParentStyle(globalStyle);
|
||||
Key ??= $"{Color}|{BackgroundColor}|{FontType}|{Size}|{LineHeight}|{FontWeight}|{IsItalic}|{HasStrikethrough}|{HasUnderline}";
|
||||
PaintKey ??= (FontFamily, Size, FontWeight, IsItalic, Color);
|
||||
FontMetricsKey ??= (FontFamily, Size, FontWeight, IsItalic);
|
||||
}
|
||||
|
||||
internal void ApplyParentStyle(TextStyle parentStyle)
|
||||
{
|
||||
Color ??= parentStyle.Color;
|
||||
BackgroundColor ??= parentStyle.BackgroundColor;
|
||||
FontType ??= parentStyle.FontType;
|
||||
FontFamily ??= parentStyle.FontFamily;
|
||||
Size ??= parentStyle.Size;
|
||||
LineHeight ??= parentStyle.LineHeight;
|
||||
FontWeight ??= parentStyle.FontWeight;
|
||||
@@ -58,6 +60,19 @@ namespace QuestPDF.Infrastructure
|
||||
HasUnderline ??= parentStyle.HasUnderline;
|
||||
}
|
||||
|
||||
internal void OverrideStyle(TextStyle parentStyle)
|
||||
{
|
||||
Color = parentStyle.Color ?? Color;
|
||||
BackgroundColor = parentStyle.BackgroundColor ?? BackgroundColor;
|
||||
FontFamily = parentStyle.FontFamily ?? FontFamily;
|
||||
Size = parentStyle.Size ?? Size;
|
||||
LineHeight = parentStyle.LineHeight ?? LineHeight;
|
||||
FontWeight = parentStyle.FontWeight ?? FontWeight;
|
||||
IsItalic = parentStyle.IsItalic ?? IsItalic;
|
||||
HasStrikethrough = parentStyle.HasStrikethrough ?? HasStrikethrough;
|
||||
HasUnderline = parentStyle.HasUnderline ?? HasUnderline;
|
||||
}
|
||||
|
||||
internal TextStyle Clone()
|
||||
{
|
||||
var clone = (TextStyle)MemberwiseClone();
|
||||
|
||||
@@ -13,6 +13,10 @@ namespace QuestPDF.Infrastructure
|
||||
|
||||
Feet,
|
||||
Inch,
|
||||
|
||||
/// <summary>
|
||||
/// 1/1000th of inch
|
||||
/// </summary>
|
||||
Mill
|
||||
}
|
||||
|
||||
@@ -32,7 +36,7 @@ namespace QuestPDF.Infrastructure
|
||||
Point => 1,
|
||||
Meter => 100 / InchToCentimetre * InchToPoints,
|
||||
Centimetre => 1 / InchToCentimetre * InchToPoints,
|
||||
Millimetre => 10 / InchToCentimetre * InchToPoints,
|
||||
Millimetre => 0.1f / InchToCentimetre * InchToPoints,
|
||||
Feet => 12 * InchToPoints,
|
||||
Inch => InchToPoints,
|
||||
Mill => InchToPoints / 1000f,
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<Authors>MarcinZiabek</Authors>
|
||||
<Company>CodeFlint</Company>
|
||||
<PackageId>QuestPDF</PackageId>
|
||||
<Version>2022.2.0</Version>
|
||||
<Version>2022.3.0</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>9</LangVersion>
|
||||
|
||||
@@ -132,41 +132,42 @@ void ComposeTable(IContainer container)
|
||||
{
|
||||
var headerStyle = TextStyle.Default.SemiBold();
|
||||
|
||||
container.Decoration(decoration =>
|
||||
container.Table(table =>
|
||||
{
|
||||
// header
|
||||
decoration.Header().BorderBottom(1).Padding(5).Row(row =>
|
||||
table.ColumnsDefinition(columns =>
|
||||
{
|
||||
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);
|
||||
columns.ConstantColumn(25);
|
||||
columns.RelativeColumn(3);
|
||||
columns.RelativeColumn();
|
||||
columns.RelativeColumn();
|
||||
columns.RelativeColumn();
|
||||
});
|
||||
|
||||
// content
|
||||
decoration
|
||||
.Content()
|
||||
.column(column =>
|
||||
|
||||
table.Header(header =>
|
||||
{
|
||||
header.Cell().Text("#", headerStyle);
|
||||
header.Cell().Text("Product", headerStyle);
|
||||
header.Cell().AlignRight().Text("Unit price", headerStyle);
|
||||
header.Cell().AlignRight().Text("Quantity", headerStyle);
|
||||
header.Cell().AlignRight().Text("Total", headerStyle);
|
||||
|
||||
header.Cell().ColumnSpan(5)
|
||||
.PaddingVertical(5).BorderBottom(1).BorderColor(Colors.Black);
|
||||
});
|
||||
|
||||
foreach (var item in Model.Items)
|
||||
{
|
||||
table.Cell().Element(CellStyle).Text(Model.Items.IndexOf(item) + 1);
|
||||
table.Cell().Element(CellStyle).Text(item.Name);
|
||||
table.Cell().Element(CellStyle).AlignRight().Text($"{item.Price}$");
|
||||
table.Cell().Element(CellStyle).AlignRight().Text(item.Quantity);
|
||||
table.Cell().Element(CellStyle).AlignRight().Text($"{item.Price * item.Quantity}$");
|
||||
|
||||
static IContainer CellStyle(IContainer container)
|
||||
{
|
||||
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}$");
|
||||
});
|
||||
}
|
||||
});
|
||||
container.BorderBottom(1).BorderColor(Colors.Grey.Lighten2).PaddingVertical(5);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
- Added a `ScaleToFit` element - scales its child down so it fits in the provided space,
|
||||
- Added a `ScaleToFit` element - scales its child down so it fits in the provided space,
|
||||
- Added a `StopPaging` element - when its child requires more than one page to fully render, only the first page is shown,
|
||||
- Added a 'LineVertical' and a 'LineHorizontal' elements - those will simplify your code a lot, there is no need to use the `Border` element anymore!
|
||||
- Renaming: the `Stack` element was renamed to the `Column` element,
|
||||
|
||||
@@ -2,235 +2,90 @@
|
||||
<img src="https://github.com/QuestPDF/example-invoice/raw/main/images/logo.svg" width="400px">
|
||||
</a>
|
||||
|
||||
## Overview
|
||||
QuestPDF presents a new approach to PDF document generation. Unlike other libraries, it does not rely on the HTML-to-PDF conversion which in many cases is not reliable. Instead, it implements its own layouting engine that is optimized to cover all paging-related requirements. Then, everything is rendered using the SkiaSharp library (a Skia port for .NET, used in Chrome, Android, MAUI, etc.).
|
||||
|
||||
**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.
|
||||
I have designed this layouting engine with full paging support in mind. The document consists of many simple elements (e.g. border, background, image, text, padding, table, grid etc.) that are composed together to create more complex structures. This way, as a developer, you can understand the behaviour of every element and use them with full confidence. Additionally, the document and all its elements support paging functionality. For example, an element can be moved to the next page (if there is not enough space) or even be split between pages like table's rows.
|
||||
|
||||
## Support QuestPDF
|
||||
|
||||
All great frameworks and libraries started from zero. Please help us to make QuestPDF a commonly known library and an obvious choice in case of generating PDF documents. It can be as easy as:
|
||||
- Giving this repository a star ⭐ so more people will know about it,
|
||||
- Observing 🤩 the library to know about each new realease,
|
||||
- Trying our sample project to see how easy it is to create an invoice 📊,
|
||||
- Sharing your thoughts 💬 with us and your colleagues,
|
||||
- Simply using it 👨💻 and suggesting new features,
|
||||
- Creating new features 🆕 for everybody.
|
||||
All great frameworks and libraries started from zero. Please help me make QuestPDF a commonly known library and an obvious choice for generating PDF documents.
|
||||
|
||||
- ⭐ Give this repository a star,
|
||||
- 💬 Share it with your team members.
|
||||
|
||||
## Installation
|
||||
|
||||
The library is available as a nuget package. You can install it as any other nuget package from your IDE, try to search by `QuestPDF`. You can find package details [on this webpage](https://www.nuget.org/packages/QuestPDF/).
|
||||
|
||||
<a href="https://www.nuget.org/packages/QuestPDF/">
|
||||
<img src="https://github.com/QuestPDF/example-invoice/raw/main/images/nuget.svg" width="200px">
|
||||
</a>
|
||||
```c#
|
||||
// Package Manager
|
||||
Install-Package QuestPDF
|
||||
|
||||
// .NET CLI
|
||||
dotnet add package QuestPDF
|
||||
|
||||
// Package reference in .csproj file
|
||||
<PackageReference Include="QuestPDF" Version="2022.2.5" />
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
**[Release notes and roadmap](https://www.questpdf.com/documentation/releases.html)** - everything that is planned for future library iterations, description of new features and information about potential breaking changes.
|
||||
**[🚀 Getting started tutorial](https://www.questpdf.com/documentation/getting-started.html)** - a short and easy to follow tutorial showing how to design an invoice document under 200 lines of code.
|
||||
|
||||
**[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.
|
||||
|
||||
**[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 create reusable code that is easy to maintain.
|
||||
|
||||
**[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.
|
||||
## Simplicity is the key
|
||||
|
||||
## 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).
|
||||
|
||||
<a href="https://github.com/QuestPDF/example-invoice">
|
||||
<img src="https://github.com/QuestPDF/example-invoice/raw/main/images/invoice.png" width="595px">
|
||||
</a>
|
||||
|
||||
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:
|
||||
How easy it is to start and prototype with QuestPDF? Really easy thanks to its minimal API! Please analyse the code below:
|
||||
|
||||
```csharp
|
||||
public void Compose(IDocumentContainer container)
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
// code in your main method
|
||||
Document.Create(container =>
|
||||
{
|
||||
container
|
||||
.Page(page =>
|
||||
{
|
||||
page.Margin(50);
|
||||
|
||||
page.Header().Element(ComposeHeader);
|
||||
page.Content().Element(ComposeContent);
|
||||
|
||||
page.Footer().AlignCenter().Text(x =>
|
||||
container.Page(page =>
|
||||
{
|
||||
page.Size(PageSizes.A4);
|
||||
page.Margin(2, Unit.Centimetre);
|
||||
page.Background(Colors.White);
|
||||
page.DefaultTextStyle(TextStyle.Default.Size(20));
|
||||
|
||||
page.Header()
|
||||
.Text("Hello PDF!", TextStyle.Default.SemiBold().Size(36).Color(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();
|
||||
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 =>
|
||||
{
|
||||
row.RelativeColumn().Stack(stack =>
|
||||
{
|
||||
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();
|
||||
});
|
||||
}
|
||||
})
|
||||
.GeneratePdf("hello.pdf");
|
||||
```
|
||||
|
||||
Implementation of **the content area** that contains seller and customer details, then listing of all bought products, then a comments section.
|
||||
And compare it to the produced PDF file:
|
||||
|
||||
```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());
|
||||
## Are you ready for more?
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(Model.Comments))
|
||||
column.Item().PaddingTop(25).Element(ComposeComments);
|
||||
});
|
||||
}
|
||||
```
|
||||
The Fluent API of QuestPDF scales really well. It is easy to create and maintain even most complex documents. Read [the Getting started tutorial](https://www.questpdf.com/documentation/getting-started.html) to learn QuestPDF basics and implement an invoice under 200 lines of code. You can also investigate and play with the code from [the example repository](https://github.com/QuestPDF/example-invoice).
|
||||
|
||||
**The table and comments** codes are extracted into separate methods to increase clarity:
|
||||
|
||||
```csharp
|
||||
void ComposeTable(IContainer container)
|
||||
{
|
||||
var headerStyle = TextStyle.Default.SemiBold();
|
||||
|
||||
container.Table(table =>
|
||||
{
|
||||
table.ColumnsDefinition(columns =>
|
||||
{
|
||||
columns.ConstantColumn(25);
|
||||
columns.RelativeColumn(3);
|
||||
columns.RelativeColumn();
|
||||
columns.RelativeColumn();
|
||||
columns.RelativeColumn();
|
||||
});
|
||||
|
||||
table.Header(header =>
|
||||
{
|
||||
header.Cell().Text("#", headerStyle);
|
||||
header.Cell().Text("Product", headerStyle);
|
||||
header.Cell().AlignRight().Text("Unit price", headerStyle);
|
||||
header.Cell().AlignRight().Text("Quantity", headerStyle);
|
||||
header.Cell().AlignRight().Text("Total", headerStyle);
|
||||
|
||||
header.Cell().ColumnSpan(5)
|
||||
.PaddingVertical(5).BorderBottom(1).BorderColor(Colors.Black);
|
||||
});
|
||||
|
||||
foreach (var item in Model.Items)
|
||||
{
|
||||
table.Cell().Text(Model.Items.IndexOf(item) + 1);
|
||||
table.Cell().Text(item.Name);
|
||||
table.Cell().AlignRight().Text($"{item.Price}$");
|
||||
table.Cell().AlignRight().Text(item.Quantity);
|
||||
table.Cell().AlignRight().Text($"{item.Price * item.Quantity}$");
|
||||
|
||||
table.Cell().ColumnSpan(5)
|
||||
.PaddingVertical(5).LineHorizontal(1).LineColor(Colors.Grey.Lighten2);
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
```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);
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
<img src="https://github.com/QuestPDF/example-invoice/raw/main/images/invoice.png" width="595px">
|
||||
Reference in New Issue
Block a user