Compare commits

..

26 Commits

Author SHA1 Message Date
Marcin Ziąbek 51508bbe0b Table: added support for headers and footers 2022-01-06 21:10:54 +01:00
Marcin Ziąbek 62b5054a2f Updated release notes 2022-01-06 00:40:32 +01:00
Marcin Ziąbek 51dac22422 2022.1.0-beta3 Renaming Box() to MinimalBox() 2022-01-06 00:22:07 +01:00
Marcin Ziąbek 01980e5635 Table: row height fixes for overlapping cells. Added code examples 2022-01-06 00:06:24 +01:00
Marcin Ziąbek 6e59fa6e3d Table: improved repeating+paging support, added example 2022-01-05 20:30:23 +01:00
Marcin Ziąbek 4003337d01 Fixed: traversing elements graph 2022-01-05 01:35:29 +01:00
Marcin Ziąbek 5931b12b90 VisitChildren -> GetChildren traversal refactorization 2022-01-05 01:10:05 +01:00
Marcin Ziąbek 935c575221 Table: added default style, fixed bugs, added examples 2022-01-05 00:52:28 +01:00
Marcin Ziąbek 18a710e2a3 Table: fixed cross-cell size adjusting 2022-01-03 17:13:57 +01:00
Marcin Ziąbek d39e93b924 Update Table.cs 2022-01-02 22:27:48 +01:00
MarcinZiabek c8cc968705 Typo fixes 2021-12-30 21:53:42 +01:00
MarcinZiabek ffccbe37a4 2022.1.0-beta0 2021-12-30 21:43:07 +01:00
MarcinZiabek a6439c1883 2022.1.0-alpha0 2021-12-30 21:28:09 +01:00
MarcinZiabek c0e4ec6df1 Border radius: added example 2021-12-30 21:07:34 +01:00
MarcinZiabek a80258aa58 Improved readibility of the table layout algorithm 2021-12-30 20:29:32 +01:00
MarcinZiabek 48c5771abb Table layout: refactorization 2021-12-30 19:56:51 +01:00
MarcinZiabek b79122aca5 Table: layouting improvements for paging 2021-12-30 19:42:40 +01:00
MarcinZiabek 2fcf543817 Fixed: corner cases of paging support 2021-12-30 18:40:54 +01:00
MarcinZiabek 7f72e50eb8 Improved wrapping support 2021-12-29 22:51:01 +01:00
MarcinZiabek f718cb3324 Table: optimized rendering performance 2021-12-27 18:24:37 +01:00
MarcinZiabek efdc8903a5 Table: minor optimizations + performance test 2021-12-26 17:04:59 +01:00
MarcinZiabek 647b1fd0f4 Rendering algorithm partially working 2021-12-25 11:53:39 +01:00
MarcinZiabek 8a62591940 Table: refactorization, cell placement algorithm 2021-12-24 14:40:54 +01:00
Marcin Ziąbek 6da3075e7a Improved table paging support 2021-12-19 05:11:23 +01:00
Marcin Ziąbek 0a27b80b73 Row: added support for mixed columns (constant + relational), good when performing visual column merging 2021-12-18 16:54:35 +01:00
Marcin Ziąbek 656d1a6ecf Improved Row element stability 2021-12-16 01:39:26 +01:00
214 changed files with 2600 additions and 8392 deletions
-131
View File
@@ -1,131 +0,0 @@
# Rules in this file were initially inferred by Visual Studio IntelliCode from the D:\GithubRepos\QuestPDF codebase based on best match to current usage at 16.03.2022
# You can modify the rules from these initially generated values to suit your own policies
# You can learn more about editorconfig here: https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference
[*.cs]
#Core editorconfig formatting - indentation
#use soft tabs (spaces) for indentation
indent_style = space
#Formatting - new line options
#place catch statements on a new line
csharp_new_line_before_catch = true
#place else statements on a new line
csharp_new_line_before_else = true
#require members of anonymous types to be on separate lines
csharp_new_line_before_members_in_anonymous_types = true
#require members of object intializers to be on separate lines
csharp_new_line_before_members_in_object_initializers = true
#require braces to be on a new line for object_collection_array_initializers, methods, anonymous_types, control_blocks, types, and lambdas (also known as "Allman" style)
csharp_new_line_before_open_brace =all
#Formatting - organize using options
#sort System.* using directives alphabetically, and place them before other usings
dotnet_sort_system_directives_first = true
#Formatting - spacing options
#require NO space between a cast and the value
csharp_space_after_cast = false
#require a space before the colon for bases or interfaces in a type declaration
csharp_space_after_colon_in_inheritance_clause = true
#require a space after a keyword in a control flow statement such as a for loop
csharp_space_after_keywords_in_control_flow_statements = true
#require a space before the colon for bases or interfaces in a type declaration
csharp_space_before_colon_in_inheritance_clause = true
#remove space within empty argument list parentheses
csharp_space_between_method_call_empty_parameter_list_parentheses = false
#remove space between method call name and opening parenthesis
csharp_space_between_method_call_name_and_opening_parenthesis = false
#do not place space characters after the opening parenthesis and before the closing parenthesis of a method call
csharp_space_between_method_call_parameter_list_parentheses = false
#remove space within empty parameter list parentheses for a method declaration
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
#place a space character after the opening parenthesis and before the closing parenthesis of a method declaration parameter list.
csharp_space_between_method_declaration_parameter_list_parentheses = false
#Formatting - wrapping options
#leave code block on single line
csharp_preserve_single_line_blocks = true
#Style - Code block preferences
#prefer no curly braces if allowed
csharp_prefer_braces = false:suggestion
#Style - expression bodied member options
#prefer block bodies for constructors
csharp_style_expression_bodied_constructors = false:suggestion
#prefer block bodies for methods
csharp_style_expression_bodied_methods = false:suggestion
#prefer expression-bodied members for properties
csharp_style_expression_bodied_properties = true:suggestion
#Style - expression level options
#prefer out variables to be declared inline in the argument list of a method call when possible
csharp_style_inlined_variable_declaration = true:suggestion
#prefer the language keyword for member access expressions, instead of the type name, for types that have a keyword to represent them
dotnet_style_predefined_type_for_member_access = true:suggestion
#Style - Expression-level preferences
#prefer objects to be initialized using object initializers when possible
dotnet_style_object_initializer = true:suggestion
#prefer inferred anonymous type member names
dotnet_style_prefer_inferred_anonymous_type_member_names = false:suggestion
#prefer inferred tuple element names
dotnet_style_prefer_inferred_tuple_names = true:suggestion
#Style - implicit and explicit types
#prefer var over explicit type in all cases, unless overridden by another code style rule
csharp_style_var_elsewhere = true:suggestion
#prefer var is used to declare variables with built-in system types such as int
csharp_style_var_for_built_in_types = true:suggestion
#prefer var when the type is already mentioned on the right-hand side of a declaration expression
csharp_style_var_when_type_is_apparent = true:suggestion
#Style - language keyword and framework type options
#prefer the language keyword for local variables, method parameters, and class members, instead of the type name, for types that have a keyword to represent them
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
#Style - Miscellaneous preferences
#prefer local functions over anonymous functions
csharp_style_pattern_local_over_anonymous_function = true:suggestion
#Style - modifier options
#prefer accessibility modifiers to be declared except for public interface members. This will currently not differ from always and will act as future proofing for if C# adds default interface methods.
dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
#Style - Modifier preferences
#when this rule is set to a list of modifiers, prefer the specified ordering.
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:silent
#Style - Pattern matching
#prefer pattern matching instead of is expression with type casts
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
#Style - qualification options
#prefer fields not to be prefaced with this. or Me. in Visual Basic
dotnet_style_qualification_for_field = false:suggestion
#prefer methods not to be prefaced with this. or Me. in Visual Basic
dotnet_style_qualification_for_method = false:suggestion
#prefer properties not to be prefaced with this. or Me. in Visual Basic
dotnet_style_qualification_for_property = false:suggestion
[*.{cs,vb}]
tab_width=4
indent_size=4
-70
View File
@@ -1,70 +0,0 @@
# 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
-1
View File
@@ -1 +0,0 @@
github: QuestPDF
+3 -5
View File
@@ -13,11 +13,11 @@ namespace QuestPDF.Examples
[Test]
public void Example()
{
FontManager.RegisterFont(File.OpenRead("LibreBarcode39-Regular.ttf"));
FontManager.RegisterFontType("LibreBarcode39", File.OpenRead("LibreBarcode39-Regular.ttf"));
RenderingTest
.Create()
.PageSize(400, 200)
.PageSize(400, 100)
.ShowResults()
.Render(container =>
{
@@ -25,9 +25,7 @@ namespace QuestPDF.Examples
.Background(Colors.White)
.AlignCenter()
.AlignMiddle()
.Text("*QuestPDF*")
.FontFamily("Libre Barcode 39")
.FontSize(64);
.Text("*QuestPDF*", TextStyle.Default.FontType("LibreBarcode39").Size(64));
});
}
}
+1 -4
View File
@@ -49,10 +49,7 @@ namespace QuestPDF.Examples
.PrimaryLayer()
.PaddingVertical(10)
.PaddingHorizontal(20)
.Text("Sample text")
.FontSize(16)
.FontColor(Colors.Blue.Darken2)
.SemiBold();
.Text("Sample text", TextStyle.Default.Size(16).Color(Colors.Blue.Darken2).SemiBold());
});
});
}
+4 -7
View File
@@ -52,17 +52,14 @@ namespace QuestPDF.Examples
container
.Background(Colors.White)
.Padding(25)
.Column(column =>
.Stack(stack =>
{
column
stack
.Item()
.PaddingBottom(10)
.Text("Chart example")
.FontSize(20)
.SemiBold()
.FontColor(Colors.Blue.Medium);
.Text("Chart example", TextStyle.Default.Size(20).SemiBold().Color(Colors.Blue.Medium));
column
stack
.Item()
.Border(1)
.ExtendHorizontal()
-56
View File
@@ -1,56 +0,0 @@
using System;
using System.Linq;
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class ColumnExamples
{
[Test]
public void Column()
{
RenderingTest
.Create()
.PageSize(PageSizes.A4)
.ShowResults()
.ProducePdf()
.Render(container =>
{
container.Column(column =>
{
foreach (var i in Enumerable.Range(0, 10))
column.Item().Element(Block);
static void Block(IContainer container)
{
container
.Width(72)
.Height(3.5f, Unit.Inch)
.Height(1.5f, Unit.Inch)
.Background(Placeholders.BackgroundColor());
}
});
});
}
[Test]
public void Stability_NoItems()
{
RenderingTest
.Create()
.ProducePdf()
.MaxPages(100)
.PageSize(250, 150)
.Render(container =>
{
container
.Padding(25)
.Column(column => { });
});
}
}
}
+6 -6
View File
@@ -16,7 +16,7 @@ namespace QuestPDF.Examples
.PageSize(PageSizes.A4)
.ProducePdf()
.ShowResults()
.Render(x => GenerateStructure(x, 12));
.Render(x => GenerateStructure(x, 16));
}
private void GenerateStructure(IContainer container, int level)
@@ -36,8 +36,8 @@ namespace QuestPDF.Examples
.BorderColor(Colors.Black)
.Row(row =>
{
row.RelativeItem().Element(x => GenerateStructure(x, level));
row.RelativeItem().Element(x => GenerateStructure(x, level));
row.RelativeColumn().Element(x => GenerateStructure(x, level));
row.RelativeColumn().Element(x => GenerateStructure(x, level));
});
}
else
@@ -45,10 +45,10 @@ namespace QuestPDF.Examples
container
.Border(level / 4f)
.BorderColor(Colors.Black)
.Column(column =>
.Stack(stack =>
{
column.Item().Element(x => GenerateStructure(x, level));
column.Item().Element(x => GenerateStructure(x, level));
stack.Item().Element(x => GenerateStructure(x, level));
stack.Item().Element(x => GenerateStructure(x, level));
});
}
}
@@ -1,408 +0,0 @@
using System;
using System.Linq;
using NUnit.Framework;
using QuestPDF.Drawing.Exceptions;
using QuestPDF.Elements;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class ContentDirectionExamples
{
private Action<IContainer> ContentDirectionTemplate(Action<IContainer> content)
{
return container =>
{
container
.MinimalBox()
.ExtendHorizontal()
.Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.RelativeColumn();
columns.ConstantColumn(1);
columns.RelativeColumn();
});
table.Header(header =>
{
header.Cell().ContentFromLeftToRight().Element(HeaderCell("Left-to-right"));
header.Cell().LineVertical(1).LineColor(Colors.Grey.Medium);
header.Cell().ContentFromRightToLeft().Element(HeaderCell("Right-to-left"));
static Action<IContainer> HeaderCell(string label)
{
return container => container
.BorderColor(Colors.Grey.Medium)
.Background(Colors.Grey.Lighten2)
.PaddingHorizontal(15)
.PaddingVertical(5)
.Text(label)
.FontSize(18)
.SemiBold();
}
});
table.Cell().Element(TestCell).ContentFromLeftToRight().Element(content);
table.Cell().LineVertical(1).LineColor(Colors.Grey.Medium);
table.Cell().Element(TestCell).ContentFromRightToLeft().Element(content);
static IContainer TestCell(IContainer container)
{
return container.Padding(15);
}
});
};
}
[Test]
public void Page()
{
RenderingTest
.Create()
.ProduceImages()
.ShowResults()
.EnableDebugging()
.RenderDocument(document =>
{
document.Page(page =>
{
page.Size(PageSizes.A5);
page.Margin(20);
page.PageColor(Colors.White);
page.DefaultTextStyle(x => x.FontFamily("Calibri").FontSize(20));
page.ContentFromRightToLeft();
page.Content().Column(column =>
{
column.Spacing(20);
column.Item()
.Text("مثال على الفاتورة") // example invoice
.FontSize(32).FontColor(Colors.Blue.Darken2).SemiBold();
column.Item().Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.RelativeColumn();
columns.ConstantColumn(75);
columns.ConstantColumn(100);
});
table.Cell().Element(HeaderStyle).Text("وصف السلعة"); // item description
table.Cell().Element(HeaderStyle).Text("كمية"); // quantity
table.Cell().Element(HeaderStyle).Text("سعر"); // price
var items = new[]
{
"دورة البرمجة", // programming course
"دورة تصميم الرسومات", // graphics design course
"تحليل وتصميم الخوارزميات", // analysis and design of algorithms
};
foreach (var item in items)
{
var price = Placeholders.Random.NextDouble() * 100;
table.Cell().Text(item);
table.Cell().Text(Placeholders.Random.Next(1, 10));
table.Cell().Text($"USD${price:F2}");
}
static IContainer HeaderStyle(IContainer x) => x.BorderBottom(1).PaddingVertical(5);
});
});
});
});
}
[Test]
public void Column()
{
RenderingTest
.Create()
.PageSize(PageSizes.A4)
.ProduceImages()
.ShowResults()
.EnableDebugging()
.Render(ContentDirectionTemplate(Content));
void Content(IContainer container)
{
container.Column(column =>
{
column.Spacing(5);
column.Item().Height(50).Width(50).Background(Colors.Red.Lighten1);
column.Item().Height(50).Width(100).Background(Colors.Green.Lighten1);
column.Item().Height(50).Width(150).Background(Colors.Blue.Lighten1);
});
}
}
[Test]
public void Row()
{
RenderingTest
.Create()
.PageSize(PageSizes.A4)
.ProduceImages()
.ShowResults()
.EnableDebugging()
.Render(ContentDirectionTemplate(Content));
void Content(IContainer container)
{
container.Row(row =>
{
row.Spacing(5);
row.AutoItem().Height(50).Width(50).Background(Colors.Red.Lighten1);
row.AutoItem().Height(50).Width(50).Background(Colors.Green.Lighten1);
row.AutoItem().Height(50).Width(75).Background(Colors.Blue.Lighten1);
});
}
}
[Test]
public void Table()
{
RenderingTest
.Create()
.PageSize(PageSizes.A4)
.ProduceImages()
.ShowResults()
.EnableDebugging()
.Render(ContentDirectionTemplate(Content));
void Content(IContainer container)
{
container.Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.RelativeColumn();
columns.RelativeColumn();
columns.RelativeColumn();
});
table.Cell().Height(50).Background(Colors.Red.Lighten1);
table.Cell().Height(50).Background(Colors.Green.Lighten1);
table.Cell().Height(50).Background(Colors.Blue.Lighten1);
table.Cell().ColumnSpan(2).Height(50).Background(Colors.Orange.Lighten1);
});
}
}
[Test]
public void Constrained()
{
RenderingTest
.Create()
.PageSize(PageSizes.A4)
.ProduceImages()
.ShowResults()
.EnableDebugging()
.Render(ContentDirectionTemplate(Content));
void Content(IContainer container)
{
container.Width(50).Height(50).Background(Colors.Red.Lighten1);
}
}
[Test]
public void Unconstrained()
{
RenderingTest
.Create()
.PageSize(PageSizes.A4)
.ProduceImages()
.ShowResults()
.EnableDebugging()
.Render(ContentDirectionTemplate(Content));
void Content(IContainer container)
{
container
.Width(100)
.Height(100)
.Background(Colors.Grey.Lighten3)
.AlignCenter()
.AlignMiddle()
.Unconstrained()
.Width(50)
.Height(50)
.Background(Colors.Red.Lighten1);
}
}
[Test]
public void Inlined()
{
RenderingTest
.Create()
.PageSize(PageSizes.A4)
.ProduceImages()
.ShowResults()
.EnableDebugging()
.Render(ContentDirectionTemplate(Content));
void Content(IContainer container)
{
container.Column(column =>
{
column.Spacing(10);
column.Item().Text("Default alignment").FontSize(14).SemiBold();
column.Item().Element(ContentWithAlignment(null));
column.Item().Text("Left alignment").FontSize(14).SemiBold();
column.Item().Element(ContentWithAlignment(InlinedAlignment.Left));
column.Item().Text("Center alignment").FontSize(14).SemiBold();
column.Item().Element(ContentWithAlignment(InlinedAlignment.Center));
column.Item().Text("Right alignment").FontSize(14).SemiBold();
column.Item().Element(ContentWithAlignment(InlinedAlignment.Right));
});
static Action<IContainer> ContentWithAlignment(InlinedAlignment? alignment)
{
return container =>
{
container.Inlined(inlined =>
{
inlined.Spacing(5);
inlined.Alignment(alignment);
inlined.Item().Height(50).Width(50).Background(Colors.Red.Lighten1);
inlined.Item().Height(50).Width(75).Background(Colors.Green.Lighten1);
inlined.Item().Height(50).Width(100).Background(Colors.Blue.Lighten1);
inlined.Item().Height(50).Width(125).Background(Colors.Orange.Lighten1);
});
};
}
}
}
[Test]
public void Text()
{
RenderingTest
.Create()
.PageSize(PageSizes.A4)
.ProduceImages()
.ShowResults()
.EnableDebugging()
.Render(ContentDirectionTemplate(Content));
void Content(IContainer container)
{
container.Column(column =>
{
column.Spacing(10);
column.Item().Text("Default alignment").FontSize(14).SemiBold();
column.Item().Element(ContentWithAlignment(null));
column.Item().Text("Left alignment").FontSize(14).SemiBold();
column.Item().Element(ContentWithAlignment(HorizontalAlignment.Left));
column.Item().Text("Center alignment").FontSize(14).SemiBold();
column.Item().Element(ContentWithAlignment(HorizontalAlignment.Center));
column.Item().Text("Right alignment").FontSize(14).SemiBold();
column.Item().Element(ContentWithAlignment(HorizontalAlignment.Right));
});
static Action<IContainer> ContentWithAlignment(HorizontalAlignment? alignment)
{
return container =>
{
container.Text(text =>
{
text.Alignment = alignment; // internal API
text.Span("Lorem ipsum").Bold().FontColor(Colors.Red.Medium);
text.Element().Width(5);
text.Span(Placeholders.LoremIpsum());
});
};
}
}
}
[Test]
public void Decoration()
{
RenderingTest
.Create()
.PageSize(PageSizes.A4)
.ProduceImages()
.ShowResults()
.EnableDebugging()
.Render(ContentDirectionTemplate(Content));
void Content(IContainer container)
{
container.Decoration(decoration =>
{
decoration.Before().Background(Colors.Green.Lighten1).Padding(5).Text("Before").FontSize(16);
decoration.Content().Background(Colors.Green.Lighten2).Padding(5).Text("Content").FontSize(16);
decoration.After().Background(Colors.Green.Lighten3).Padding(5).Text("After").FontSize(16);
});
}
}
[Test]
public void Dynamic()
{
RenderingTest
.Create()
.PageSize(PageSizes.A4)
.ProduceImages()
.ShowResults()
.EnableDebugging()
.Render(ContentDirectionTemplate(Content));
void Content(IContainer container)
{
container.Dynamic(new SimpleDynamic());
}
}
class SimpleDynamic : IDynamicComponent<int>
{
public int State { get; set; }
public DynamicComponentComposeResult Compose(DynamicContext context)
{
var content = context.CreateElement(container =>
{
container.Row(row =>
{
row.ConstantItem(50).Background(Colors.Red.Lighten2).Height(50);
row.ConstantItem(75).Background(Colors.Green.Lighten2).Height(50);
row.ConstantItem(100).Background(Colors.Blue.Lighten2).Height(50);
});
});
return new DynamicComponentComposeResult
{
Content = content,
HasMoreContent = false
};
}
}
}
}
+2 -2
View File
@@ -22,10 +22,10 @@ namespace QuestPDF.Examples
page.Header().Text("Header");
page.Content().PaddingVertical(10).Border(1).Padding(10).Column(column =>
page.Content().PaddingVertical(10).Border(1).Padding(10).Stack(stack =>
{
foreach (var index in Enumerable.Range(1, 100))
column.Item().Text($"Line {index}").FontColor(Placeholders.Color());
stack.Item().Text($"Line {index}", TextStyle.Default.Color(Placeholders.Color()));
});
page.Footer().Text("Footer");
+2 -2
View File
@@ -9,7 +9,7 @@ namespace QuestPDF.Examples
public class DebuggingTesting
{
[Test]
public void Column()
public void Stack()
{
Assert.Throws<DocumentLayoutException>(() =>
{
@@ -23,7 +23,7 @@ namespace QuestPDF.Examples
.Width(100)
.Background(Colors.Grey.Lighten3)
.DebugPointer("Example debug pointer")
.Column(x =>
.Stack(x =>
{
x.Item().Text("Test");
x.Item().Width(150);
+6 -6
View File
@@ -13,7 +13,7 @@ namespace QuestPDF.Examples
{
RenderingTest
.Create()
.ProducePdf()
.ProduceImages()
.ShowResults()
.RenderDocument(container =>
{
@@ -24,13 +24,13 @@ namespace QuestPDF.Examples
page.Margin(20);
page.Size(PageSizes.A4);
page.PageColor(Colors.White);
page.Background(Colors.White);
page.Content().Column(column =>
page.Content().Stack(stack =>
{
column.Item().Text(Placeholders.Sentence());
stack.Item().Text(Placeholders.Sentence());
column.Item().Text(text =>
stack.Item().Text(text =>
{
// text in this block is additionally semibold
text.DefaultTextStyle(TextStyle.Default.SemiBold());
@@ -38,7 +38,7 @@ namespace QuestPDF.Examples
text.Line(Placeholders.Sentence());
// this text has size 20 but also semibold and red
text.Span(Placeholders.Sentence()).FontColor(Colors.Red.Medium);
text.Span(Placeholders.Sentence(), TextStyle.Default.Color(Colors.Red.Medium));
});
});
});
@@ -24,12 +24,12 @@ namespace QuestPDF.Examples
container
.Padding(10)
.DefaultTextStyle(TextStyle.Default.Bold().Underline())
.Column(column =>
.Stack(stack =>
{
column.Item().Text("Default style applies to all children");
column.Item().Text("You can override certain styles").Underline(false).FontColor(Colors.Green.Darken2);
stack.Item().Text("Default style applies to all children", TextStyle.Default);
stack.Item().Text("You can override certain styles", TextStyle.Default.Underline(false).Color(Colors.Green.Darken2));
column.Item().PaddingTop(10).Border(1).Grid(grid =>
stack.Item().PaddingTop(10).Border(1).Grid(grid =>
{
grid.Columns(4);
@@ -43,8 +43,7 @@ namespace QuestPDF.Examples
.Height(50)
.AlignCenter()
.AlignMiddle()
.Text(i)
.FontSize(16 + i / 4);
.Text(i, TextStyle.Default.Size(16 + i / 4));
}
});
});
@@ -23,21 +23,21 @@ namespace QuestPDF.Examples
container.Page(page =>
{
page.Size(PageSizes.A6);
page.Margin(5);
page.PageColor(Colors.White);
page.Margin(30);
page.Background(Colors.White);
page.Header().Column(column =>
page.Header().Stack(stack =>
{
column.Item().ShowOnce().Background(Colors.Blue.Lighten2).Height(60);
column.Item().SkipOnce().Background(Colors.Green.Lighten2).Height(40);
stack.Item().ShowOnce().Background(Colors.Blue.Lighten2).Height(60);
stack.Item().SkipOnce().Background(Colors.Green.Lighten2).Height(40);
});
page.Content().PaddingVertical(10).Column(column =>
page.Content().PaddingVertical(10).Stack(stack =>
{
column.Spacing(10);
stack.Spacing(10);
foreach (var _ in Enumerable.Range(0, 13))
column.Item().Background(Colors.Grey.Lighten2).Height(40);
stack.Item().Background(Colors.Grey.Lighten2).Height(40);
});
page.Footer().AlignCenter().Text(text =>
-106
View File
@@ -1,106 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using QuestPDF.Elements;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public struct FibonacciHeaderState
{
public int Previous { get; set; }
public int Current { get; set; }
}
public class FibonacciHeader : IDynamicComponent<FibonacciHeaderState>
{
public FibonacciHeaderState State { get; set; }
public static readonly string[] ColorsTable =
{
Colors.Red.Lighten2,
Colors.Orange.Lighten2,
Colors.Green.Lighten2,
};
public FibonacciHeader(int previous, int current)
{
State = new FibonacciHeaderState
{
Previous = previous,
Current = current
};
}
public DynamicComponentComposeResult Compose(DynamicContext context)
{
var content = context.CreateElement(container =>
{
var colorIndex = State.Current % ColorsTable.Length;
var color = ColorsTable[colorIndex];
var ratio = (float)State.Current / State.Previous;
container
.Background(color)
.Height(50)
.AlignMiddle()
.AlignCenter()
.Text($"{State.Current} / {State.Previous} = {ratio:N5}");
});
State = new FibonacciHeaderState
{
Previous = State.Current,
Current = State.Previous + State.Current
};
return new DynamicComponentComposeResult
{
Content = content,
HasMoreContent = false
};
}
}
public static class DynamicFibonacci
{
[Test]
public static void Dynamic()
{
RenderingTest
.Create()
.ShowResults()
.MaxPages(100)
.ProduceImages()
.RenderDocument(container =>
{
container.Page(page =>
{
page.Size(PageSizes.A6);
page.PageColor(Colors.White);
page.Margin(1, Unit.Centimetre);
page.DefaultTextStyle(x => x.FontSize(18));
page.Header().Dynamic(new FibonacciHeader(17, 19));
page.Content().Column(column =>
{
foreach (var i in Enumerable.Range(0, 50))
column.Item().PaddingTop(25).Background(Colors.Grey.Lighten2).Height(50);
});
page.Footer().AlignCenter().Text(text =>
{
text.CurrentPageNumber();
text.Span(" / ");
text.TotalPages();
});
});
});
}
}
}
@@ -1,185 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using QuestPDF.Elements;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class OrderItem
{
public string ItemName { get; set; } = Placeholders.Label();
public int Price { get; set; } = Placeholders.Random.Next(1, 11) * 10;
public int Count { get; set; } = Placeholders.Random.Next(1, 11);
}
public struct OrdersTableState
{
public int ShownItemsCount { get; set; }
}
public class OptimizedOrdersTable : IDynamicComponent<OrdersTableState>
{
private ICollection<OrderItem> Items { get; }
public OrdersTableState State { get; set; }
public OptimizedOrdersTable(ICollection<OrderItem> items)
{
Items = items;
State = new OrdersTableState
{
ShownItemsCount = 0
};
}
public DynamicComponentComposeResult Compose(DynamicContext context)
{
var header = ComposeHeader(context);
var sampleFooter = ComposeFooter(context, Enumerable.Empty<OrderItem>());
var decorationHeight = header.Size.Height + sampleFooter.Size.Height;
var rows = GetItemsForPage(context, decorationHeight).ToList();
var footer = ComposeFooter(context, rows.Select(x => x.Item));
var content = context.CreateElement(container =>
{
container.MinimalBox().Decoration(decoration =>
{
decoration.Header().Element(header);
decoration.Content().Box().Stack(stack =>
{
foreach (var row in rows)
stack.Item().Element(row.Element);
});
decoration.Footer().Element(footer);
});
});
State = new OrdersTableState
{
ShownItemsCount = State.ShownItemsCount + rows.Count
};
return new DynamicComponentComposeResult
{
Content = content,
HasMoreContent = State.ShownItemsCount < Items.Count
};
}
private IDynamicElement ComposeHeader(DynamicContext context)
{
return context.CreateElement(element =>
{
element
.Width(context.AvailableSize.Width)
.BorderBottom(1)
.BorderColor(Colors.Grey.Darken2)
.Padding(5)
.Row(row =>
{
var textStyle = TextStyle.Default.SemiBold();
row.ConstantItem(30).Text("#", textStyle);
row.RelativeItem().Text("Item name", textStyle);
row.ConstantItem(50).AlignRight().Text("Count", textStyle);
row.ConstantItem(50).AlignRight().Text("Price", textStyle);
row.ConstantItem(50).AlignRight().Text("Total", textStyle);
});
});
}
private IDynamicElement ComposeFooter(DynamicContext context, IEnumerable<OrderItem> items)
{
var total = items.Sum(x => x.Count * x.Price);
return context.CreateElement(element =>
{
element
.Width(context.AvailableSize.Width)
.Padding(5)
.AlignRight()
.Text($"Subtotal: {total}$", TextStyle.Default.Size(14).SemiBold());
});
}
private IEnumerable<(OrderItem Item, IDynamicElement Element)> GetItemsForPage(DynamicContext context, float decorationHeight)
{
var totalHeight = decorationHeight;
foreach (var index in Enumerable.Range(State.ShownItemsCount, Items.Count - State.ShownItemsCount))
{
var item = Items.ElementAt(index);
var element = context.CreateElement(content =>
{
content
.Width(context.AvailableSize.Width)
.BorderBottom(1)
.BorderColor(Colors.Grey.Lighten2)
.Padding(5)
.Row(row =>
{
row.ConstantItem(30).Text(index + 1);
row.RelativeItem().Text(item.ItemName);
row.ConstantItem(50).AlignRight().Text(item.Count);
row.ConstantItem(50).AlignRight().Text($"{item.Price}$");
row.ConstantItem(50).AlignRight().Text($"{item.Count*item.Price}$");
});
});
var elementHeight = element.Size.Height;
if (totalHeight + elementHeight > context.AvailableSize.Height)
break;
totalHeight += elementHeight;
yield return (item, element);
}
}
}
public static class DynamicOptimizedExamples
{
[Test]
public static void Dynamic()
{
RenderingTest
.Create()
.PageSize(PageSizes.A5)
.ShowResults()
.Render(container =>
{
var items = Enumerable.Range(0, 25).Select(x => new OrderItem()).ToList();
container
.Background(Colors.White)
.Padding(25)
.Decoration(decoration =>
{
decoration
.Header()
.PaddingBottom(5)
.Text(text =>
{
text.DefaultTextStyle(TextStyle.Default.SemiBold().FontColor(Colors.Blue.Darken2).FontSize(16));
text.Span("Page ");
text.CurrentPageNumber();
text.Span(" of ");
text.TotalPages();
});
decoration
.Content()
.Dynamic(new OptimizedOrdersTable(items));
});
});
}
}
}
@@ -1,69 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using QuestPDF.Elements;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class FooterWithAlternatingAlignment : IDynamicComponent<int>
{
public int State { get; set; }
public DynamicComponentComposeResult Compose(DynamicContext context)
{
var content = context.CreateElement(element =>
{
element
.Element(x => context.PageNumber % 2 == 0 ? x.AlignLeft() : x.AlignRight())
.Text(x =>
{
x.CurrentPageNumber();
x.Span(" / ");
x.TotalPages();
});
});
return new DynamicComponentComposeResult()
{
Content = content,
HasMoreContent = false
};
}
}
public static class DynamicPageNumberLeftRightExamples
{
[Test]
public static void Dynamic()
{
RenderingTest
.Create()
.PageSize(PageSizes.A5)
.MaxPages(100)
.ShowResults()
.ProduceImages()
.RenderDocument(container =>
{
container.Page(page =>
{
page.Size(PageSizes.A6);
page.PageColor(Colors.White);
page.Margin(1, Unit.Centimetre);
page.DefaultTextStyle(x => x.FontSize(18));
page.Content().Column(column =>
{
foreach (var i in Enumerable.Range(0, 50))
column.Item().PaddingTop(25).Background(Colors.Grey.Lighten2).Height(50);
});
page.Footer().Dynamic(new FooterWithAlternatingAlignment());
});
});
}
}
}
@@ -1,76 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using QuestPDF.Elements;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class ProgressHeader : IDynamicComponent<int>
{
public int State { get; set; }
public DynamicComponentComposeResult Compose(DynamicContext context)
{
var content = context.CreateElement(container =>
{
var width = context.AvailableSize.Width * context.PageNumber / context.TotalPages;
container
.Background(Colors.Blue.Lighten2)
.Height(25)
.Width(width)
.Background(Colors.Blue.Darken1);
});
return new DynamicComponentComposeResult
{
Content = content,
HasMoreContent = false
};
}
}
public static class DynamicProgressHeader
{
[Test]
public static void Dynamic()
{
RenderingTest
.Create()
.ShowResults()
.MaxPages(100)
.ProduceImages()
.RenderDocument(container =>
{
container.Page(page =>
{
page.Size(PageSizes.A6);
page.Margin(1, Unit.Centimetre);
page.PageColor(Colors.White);
page.DefaultTextStyle(x => x.FontSize(20));
page.Header().Dynamic(new ProgressHeader());
page.Content().Column(column =>
{
foreach (var i in Enumerable.Range(0, 100))
column.Item().PaddingTop(25).Background(Colors.Grey.Lighten2).Height(50);
});
page.Footer().AlignCenter().Text(text =>
{
text.DefaultTextStyle(x => x.FontSize(20));
text.CurrentPageNumber();
text.Span(" / ");
text.TotalPages();
});
});
});
}
}
}
@@ -1,157 +0,0 @@
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using NUnit.Framework;
using QuestPDF.Elements;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class OrdersTable : IDynamicComponent<OrdersTableState>
{
private IList<OrderItem> Items { get; }
public OrdersTableState State { get; set; }
public OrdersTable(IList<OrderItem> items)
{
Items = items;
State = new OrdersTableState
{
ShownItemsCount = 0
};
}
public DynamicComponentComposeResult Compose(DynamicContext context)
{
var possibleItems = Enumerable
.Range(1, Items.Count - State.ShownItemsCount)
.Select(itemsToDisplay => ComposeContent(context, itemsToDisplay))
.TakeWhile(x => x.Size.Height <= context.AvailableSize.Height)
.ToList();
State = new OrdersTableState
{
ShownItemsCount = State.ShownItemsCount + possibleItems.Count
};
return new DynamicComponentComposeResult
{
Content = possibleItems.Last(),
HasMoreContent = State.ShownItemsCount < Items.Count
};
}
private IDynamicElement ComposeContent(DynamicContext context, int itemsToDisplay)
{
var total = Items.Skip(State.ShownItemsCount).Take(itemsToDisplay).Sum(x => x.Count * x.Price);
return context.CreateElement(container =>
{
container
.MinimalBox()
.Width(context.AvailableSize.Width)
.Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.ConstantColumn(30);
columns.RelativeColumn();
columns.ConstantColumn(50);
columns.ConstantColumn(50);
columns.ConstantColumn(50);
});
table.Header(header =>
{
header.Cell().Element(Style).Text("#");
header.Cell().Element(Style).Text("Item name");
header.Cell().Element(Style).AlignRight().Text("Count");
header.Cell().Element(Style).AlignRight().Text("Price");
header.Cell().Element(Style).AlignRight().Text("Total");
IContainer Style(IContainer container)
{
return container
.DefaultTextStyle(x => x.SemiBold())
.BorderBottom(1)
.BorderColor(Colors.Grey.Darken2)
.Padding(5);
}
});
table.Footer(footer =>
{
footer
.Cell().ColumnSpan(5)
.AlignRight()
.PaddingTop(10)
.Text($"Subtotal: {total}$", TextStyle.Default.Bold());
});
foreach (var index in Enumerable.Range(State.ShownItemsCount, itemsToDisplay))
{
var item = Items[index];
table.Cell().Element(Style).Text(index + 1);
table.Cell().Element(Style).Text(item.ItemName);
table.Cell().Element(Style).AlignRight().Text(item.Count);
table.Cell().Element(Style).AlignRight().Text($"{item.Price}$");
table.Cell().Element(Style).AlignRight().Text($"{item.Count*item.Price}$");
IContainer Style(IContainer container)
{
return container
.BorderBottom(1)
.BorderColor(Colors.Grey.Lighten2)
.Padding(5);
}
}
});
});
}
}
public static class DynamicSimpleTableExample
{
[Test]
public static void Dynamic()
{
RenderingTest
.Create()
.PageSize(PageSizes.A5)
.ShowResults()
.ProduceImages()
.Render(container =>
{
var items = Enumerable.Range(0, 15).Select(x => new OrderItem()).ToList();
container
.Background(Colors.White)
.Padding(25)
.DefaultTextStyle(x => x.FontSize(16))
.Decoration(decoration =>
{
decoration
.Header()
.PaddingBottom(5)
.Text(text =>
{
text.DefaultTextStyle(TextStyle.Default.SemiBold().FontColor(Colors.Blue.Darken2));
text.Span("Page ");
text.CurrentPageNumber();
text.Span(" / ");
text.TotalPages();
});
decoration
.Content()
.Dynamic(new OrdersTable(items));
});
});
}
}
}
+58 -70
View File
@@ -40,12 +40,10 @@ namespace QuestPDF.Examples
.Decoration(decoration =>
{
decoration
.Before()
.Header()
.Background(Colors.Grey.Medium)
.Padding(10)
.Text("Notes")
.FontSize(16)
.FontColor("#FFF");
.Text("Notes", TextStyle.Default.Size(16).Color("#FFF"));
decoration
.Content()
@@ -68,27 +66,27 @@ namespace QuestPDF.Examples
container
.Background("#FFF")
.Padding(20)
.Column(column =>
.Stack(stack =>
{
column.Item()
stack.Item()
.PaddingBottom(10)
.AlignCenter()
.Text("This Row element is 700pt wide");
column.Item().Row(row =>
stack.Item().Row(row =>
{
row.ConstantItem(100)
row.ConstantColumn(100)
.Background(Colors.Grey.Lighten1)
.Padding(10)
.ExtendVertical()
.Text("This column is 100 pt wide");
row.RelativeItem()
row.RelativeColumn()
.Background(Colors.Grey.Lighten2)
.Padding(10)
.Text("This column takes 1/3 of the available space (200pt)");
row.RelativeItem(2)
row.RelativeColumn(2)
.Background(Colors.Grey.Lighten3)
.Padding(10)
.Text("This column takes 2/3 of the available space (400pt)");
@@ -111,15 +109,15 @@ namespace QuestPDF.Examples
.Row(row =>
{
row.Spacing(20);
row.RelativeItem(2).Border(1).Background(Colors.Grey.Lighten1);
row.RelativeItem(3).Border(1).Background(Colors.Grey.Lighten2);
row.RelativeItem(4).Border(1).Background(Colors.Grey.Lighten3);
row.RelativeColumn(2).Border(1).Background(Colors.Grey.Lighten1);
row.RelativeColumn(3).Border(1).Background(Colors.Grey.Lighten2);
row.RelativeColumn(4).Border(1).Background(Colors.Grey.Lighten3);
});
});
}
[Test]
public void column()
public void Stack()
{
RenderingTest
.Create()
@@ -129,13 +127,13 @@ namespace QuestPDF.Examples
container
.Background("#FFF")
.Padding(15)
.Column(column =>
.Stack(stack =>
{
column.Spacing(15);
stack.Spacing(15);
column.Item().Background(Colors.Grey.Medium).Height(50);
column.Item().Background(Colors.Grey.Lighten1).Height(100);
column.Item().Background(Colors.Grey.Lighten2).Height(150);
stack.Item().Background(Colors.Grey.Medium).Height(50);
stack.Item().Background(Colors.Grey.Lighten1).Height(100);
stack.Item().Background(Colors.Grey.Lighten2).Height(150);
});
});
}
@@ -269,12 +267,12 @@ namespace QuestPDF.Examples
layers
.PrimaryLayer()
.Padding(25)
.Column(column =>
.Stack(stack =>
{
column.Spacing(5);
stack.Spacing(5);
foreach (var _ in Enumerable.Range(0, 7))
column.Item().Text(Placeholders.Sentence());
stack.Item().Text(Placeholders.Sentence());
});
// layer above the main content
@@ -282,15 +280,12 @@ namespace QuestPDF.Examples
.Layer()
.AlignCenter()
.AlignMiddle()
.Text("Watermark")
.FontSize(48)
.Bold()
.FontColor(Colors.Green.Lighten3);
.Text("Watermark", TextStyle.Default.Size(48).Bold().Color(Colors.Green.Lighten3));
layers
.Layer()
.AlignBottom()
.Text(text => text.CurrentPageNumber().FontSize(16).FontColor(Colors.Green.Medium));
.Text(text => text.CurrentPageNumber(TextStyle.Default.Size(16).Color(Colors.Green.Medium)));
});
});
}
@@ -309,16 +304,16 @@ namespace QuestPDF.Examples
// {
// page.Header().PageNumber("Page {pdf:currentPage}");
//
// page.Content().Height(300).column(content =>
// page.Content().Height(300).Stack(content =>
// {
// content.Item().Height(200).Background(Colors.Grey.Lighten2);
//
// content.Item().EnsureSpace(100).column(column =>
// content.Item().EnsureSpace(100).Stack(stack =>
// {
// column.Spacing(10);
// stack.Spacing(10);
//
// foreach (var _ in Enumerable.Range(0, 4))
// column.Item().Height(50).Background(Colors.Green.Lighten1);
// stack.Item().Height(50).Background(Colors.Green.Lighten1);
// });
// });
// });
@@ -383,7 +378,7 @@ namespace QuestPDF.Examples
.Row(row =>
{
foreach (var color in colors)
row.RelativeItem().Background(color);
row.RelativeColumn().Background(color);
});
});
}
@@ -421,9 +416,7 @@ namespace QuestPDF.Examples
.Border(1)
.BorderColor(Colors.Grey.Medium)
.Padding(10)
.Text(font)
.FontFamily(font)
.FontSize(16);
.Text(font, TextStyle.Default.FontType(font).Size(16));
}
});
});
@@ -444,10 +437,10 @@ namespace QuestPDF.Examples
{
layers.Layer().Text("Something else");
layers.PrimaryLayer().Column(column =>
layers.PrimaryLayer().Stack(stack =>
{
column.Item().PaddingTop(20).Text("Text 1");
column.Item().PaddingTop(40).Text("Text 2");
stack.Item().PaddingTop(20).Text("Text 1");
stack.Item().PaddingTop(40).Text("Text 2");
});
layers.Layer().Canvas((canvas, size) =>
@@ -463,7 +456,7 @@ namespace QuestPDF.Examples
});
layers.Layer().Background("#8F00").Extend();
layers.Layer().PaddingTop(40).Text("It works!").FontSize(24);
layers.Layer().PaddingTop(40).Text("It works!", TextStyle.Default.Size(24));
});
});
}
@@ -484,7 +477,7 @@ namespace QuestPDF.Examples
//.MinimalBox()
.Background(Colors.Grey.Lighten2)
.Padding(15)
.Text("Test of the \n box element").FontSize(20);
.Text("Test of the \n box element", TextStyle.Default.Size(20));
});
}
@@ -508,14 +501,13 @@ namespace QuestPDF.Examples
.SemiBold();
decoration
.Before()
.Header()
.PaddingBottom(10)
.Text("Example: scale component")
.Style(headerFontStyle);
.Text("Example: scale component", headerFontStyle);
decoration
.Content()
.Column(column =>
.Stack(stack =>
{
var scales = new[] { 0.8f, 0.9f, 1.1f, 1.2f };
@@ -527,14 +519,13 @@ namespace QuestPDF.Examples
var fontStyle = TextStyle.Default.Size(16);
column
stack
.Item()
.Border(1)
.Background(fontColor)
.Scale(scale)
.Padding(5)
.Text($"Content with {scale} scale.")
.Style(fontStyle);
.Text($"Content with {scale} scale.", fontStyle);
}
});
});
@@ -564,8 +555,7 @@ namespace QuestPDF.Examples
.BorderColor(Colors.Green.Darken1)
.Padding(50)
.Text("Moved text")
.FontSize(25);
.Text("Moved text", TextStyle.Default.Size(25));
});
}
@@ -601,8 +591,7 @@ namespace QuestPDF.Examples
.MinimalBox()
.Background(Colors.White)
.Padding(10)
.Text($"Rotated {turns * 90}°")
.FontSize(16);
.Text($"Rotated {turns * 90}°", TextStyle.Default.Size(16));
}
});
});
@@ -700,8 +689,7 @@ namespace QuestPDF.Examples
.MinimalBox()
.Background(Colors.White)
.Padding(10)
.Text($"Flipped {turns}")
.FontSize(16);
.Text($"Flipped {turns}", TextStyle.Default.Size(16));
}
});
});
@@ -720,14 +708,14 @@ namespace QuestPDF.Examples
.Border(2)
.Row(row =>
{
row.ConstantItem(25)
row.ConstantColumn(25)
.Border(1)
.RotateLeft()
.AlignCenter()
.AlignMiddle()
.Text("Sample text");
row.RelativeItem().Border(1).Padding(5).Text(Placeholders.Paragraph());
row.RelativeColumn().Border(1).Padding(5).Text(Placeholders.Paragraph());
});
});
}
@@ -742,12 +730,12 @@ namespace QuestPDF.Examples
{
container
.Padding(25)
.PaddingLeft(50)
.Column(column =>
.PaddingLeft(75)
.Stack(stack =>
{
column.Item().Width(300).Height(150).Background(Colors.Blue.Lighten4);
stack.Item().Width(300).Height(150).Background(Colors.Blue.Lighten4);
column
stack
.Item()
// creates an infinite space for its child
@@ -763,7 +751,7 @@ namespace QuestPDF.Examples
.Background(Colors.Blue.Darken1);
column.Item().Width(300).Height(150).Background(Colors.Blue.Lighten3);
stack.Item().Width(300).Height(150).Background(Colors.Blue.Lighten3);
});
});
}
@@ -778,13 +766,13 @@ namespace QuestPDF.Examples
{
container
.Padding(25)
.Column(column =>
.Stack(stack =>
{
column.Item().Row(row =>
stack.Item().Row(row =>
{
row.RelativeItem().LabelCell("Label 1");
row.RelativeColumn().LabelCell("Label 1");
row.RelativeItem(3).Grid(grid =>
row.RelativeColumn(3).Grid(grid =>
{
grid.Columns(3);
@@ -796,11 +784,11 @@ namespace QuestPDF.Examples
});
});
column.Item().Row(row =>
stack.Item().Row(row =>
{
row.RelativeItem().ValueCell().Text("Value 1");
row.RelativeColumn().ValueCell().Text("Value 1");
row.RelativeItem(3).Grid(grid =>
row.RelativeColumn(3).Grid(grid =>
{
grid.Columns(3);
@@ -812,10 +800,10 @@ namespace QuestPDF.Examples
});
});
column.Item().Row(row =>
stack.Item().Row(row =>
{
row.RelativeItem().LabelCell("Label 6");
row.RelativeItem().ValueCell().Text("Value 6");
row.RelativeColumn().LabelCell("Label 6");
row.RelativeColumn().ValueCell().Text("Value 6");
});
});
});
+2 -4
View File
@@ -23,8 +23,6 @@ namespace QuestPDF.Examples.Engine
private bool ApplyCaching { get; set; }
private bool ApplyDebugging { get; set; }
private RenderingTestResult ResultType { get; set; } = RenderingTestResult.Images;
private bool ShowingResultsEnabled = true;
private RenderingTest()
{
@@ -116,7 +114,7 @@ namespace QuestPDF.Examples.Engine
Func<int, string> fileNameSchema = i => $"{FileNamePrefix}-${i}.png";
document.GenerateImages(fileNameSchema);
if (ShowResult && ShowingResultsEnabled)
if (ShowResult)
Process.Start("explorer", fileNameSchema(0));
}
@@ -125,7 +123,7 @@ namespace QuestPDF.Examples.Engine
var fileName = $"{FileNamePrefix}.pdf";
document.GeneratePdf(fileName);
if (ShowResult && ShowingResultsEnabled)
if (ShowResult)
Process.Start("explorer", fileName);
}
}
+5 -5
View File
@@ -21,19 +21,19 @@ namespace QuestPDF.Examples
{
page.Margin(20);
page.Size(PageSizes.A7.Landscape());
page.PageColor(Colors.White);
page.Background(Colors.White);
page.Header().Text("With ensure space").SemiBold();
page.Header().Text("With ensure space", TextStyle.Default.SemiBold());
page.Content().Column(column =>
page.Content().Stack(stack =>
{
column
stack
.Item()
.ExtendHorizontal()
.Height(75)
.Background(Colors.Grey.Lighten2);
column
stack
.Item()
.EnsureSpace(100)
.Text(Placeholders.LoremIpsum());
@@ -1,76 +0,0 @@
using NUnit.Framework;
using QuestPDF.Drawing.Exceptions;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class ExecutionOrderExamples
{
[Test]
public void Example()
{
RenderingTest
.Create()
.PageSize(400, 300)
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.DefaultTextStyle(TextStyle.Default.Size(18))
.Padding(25)
.Row(row =>
{
row.Spacing(25);
row.RelativeItem()
.Border(1)
.Padding(15)
.Background(Colors.Grey.Lighten2)
.Text("Lorem ipsum");
row.RelativeItem()
.Border(1)
.Background(Colors.Grey.Lighten2)
.Padding(15)
.Text("dolor sit amet");
});
});
}
[Test]
public void Example2()
{
RenderingTest
.Create()
.PageSize(200, 200)
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.Padding(25)
.Border(2)
.Width(150)
.Height(150)
.Background(Colors.Blue.Lighten2)
.PaddingTop(50)
.Background(Colors.Green.Lighten2)
.PaddingRight(50)
.Background(Colors.Red.Lighten2)
.PaddingBottom(50)
.Background(Colors.Amber.Lighten2)
.PaddingLeft(50)
.Background(Colors.Grey.Lighten2);
});
}
}
}
+6 -6
View File
@@ -17,9 +17,9 @@ namespace QuestPDF.Examples
.Padding(5);
}
public static void LabelCell(this IContainer container, string text) => container.Cell(true).Text(text).SemiBold();
public static void LabelCell(this IContainer container, string text) => container.Cell(true).Text(text, TextStyle.Default.SemiBold());
public static IContainer ValueCell(this IContainer container) => container.Cell(false);
public static void ValueCell(this IContainer container, string text) => container.ValueCell().Text(text);
public static void ValueCell(this IContainer container, string text) => container.ValueCell().Text(text, TextStyle.Default);
}
public class FrameExample
@@ -36,14 +36,14 @@ namespace QuestPDF.Examples
container
.Background("#FFF")
.Padding(25)
.Column(column =>
.Stack(stack =>
{
for(var i = 1; i <= 4; i++)
{
column.Item().Row(row =>
stack.Item().Row(row =>
{
row.RelativeItem(2).LabelCell(Placeholders.Label());
row.RelativeItem(3).ValueCell().Text(Placeholders.Paragraph());
row.RelativeColumn(2).LabelCell(Placeholders.Label());
row.RelativeColumn(3).ValueCell().Text(Placeholders.Paragraph());
});
}
});
-146
View File
@@ -1,146 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework;
using QuestPDF.Elements;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
namespace QuestPDF.Examples
{
public class ProcessRunningTime
{
public TimeSpan FluentTime { get; set; }
public TimeSpan GenerationTime { get; set; }
public float Size { get; set; }
}
public class GenerationBenchmark
{
public const int TestSize = 4096;
[Test]
public void BenchmarkAsync()
{
RunTest(() => Enumerable
.Range(0, TestSize)
.AsParallel() // difference
.Select(GenerateAndCollect)
.ToList());
}
[Test]
public void BenchmarkSync()
{
RunTest(() => Enumerable
.Range(0, TestSize)
.Select(GenerateAndCollect)
.ToList());
}
public void RunTest(Func<IEnumerable<ProcessRunningTime>> handler)
{
var totalFluentTime = TimeSpan.Zero;
var totalGenerationTime = TimeSpan.Zero;
var stopWatch = new Stopwatch();
stopWatch.Start();
var results = handler();
stopWatch.Stop();
foreach (var result in results)
{
totalFluentTime += result.FluentTime;
totalGenerationTime += result.GenerationTime;
}
Console.WriteLine($"Fluent: {totalFluentTime:g}");
Console.WriteLine($"Generation: {totalGenerationTime:g}");
Console.WriteLine($"Total: {stopWatch.Elapsed:g}");
}
static ProcessRunningTime GenerateAndCollect(int attemptNumber)
{
var stopwatch = new Stopwatch();
stopwatch.Start();
var container = new Container();
container
.Padding(10)
.MinimalBox()
.Border(1)
.Column(column =>
{
column.Item().Text($"Attempts {attemptNumber}");
const int numberOfRows = 100;
const int numberOfColumns = 10;
for (var y = 0; y < numberOfRows; y++)
{
column.Item().Row(row =>
{
for (var x = 0; x < numberOfColumns; x++)
{
row.RelativeItem()
.Background(Colors.Red.Lighten5)
.Padding(3)
.Background(Colors.Red.Lighten4)
.Padding(3)
.Background(Colors.Red.Lighten3)
.Padding(3)
.Background(Colors.Red.Lighten2)
.Padding(3)
.Background(Colors.Red.Lighten1)
.Padding(3)
.Background(Colors.Red.Medium)
.Padding(3)
.Background(Colors.Red.Darken1)
.Padding(3)
.Background(Colors.Red.Darken2)
.Padding(3)
.Background(Colors.Red.Darken3)
.Padding(3)
.Background(Colors.Red.Darken4)
.Height(3);
}
});
}
});
var fluentTime = stopwatch.Elapsed;
stopwatch.Reset();
stopwatch.Start();
var size = Document
.Create(x => x.Page(page => page.Content().Element(container)))
.GeneratePdf()
.Length;
var generationTime = stopwatch.Elapsed;
return new ProcessRunningTime
{
FluentTime = fluentTime,
GenerationTime = generationTime,
Size = size
};
}
}
}
-42
View File
@@ -1,42 +0,0 @@
using System.Linq;
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
namespace QuestPDF.Examples
{
public class GridBenchmark
{
[Test]
public void Benchmark()
{
RenderingTest
.Create()
.ProducePdf()
.PageSize(PageSizes.A4)
.ShowResults()
.MaxPages(10_000)
.EnableCaching(true)
.EnableDebugging(false)
.Render(container =>
{
container
.Padding(10)
.MinimalBox()
.Border(1)
.Grid(grid =>
{
const int numberOfRows = 100_000;
const int numberOfColumns = 10;
grid.Columns(numberOfColumns);
foreach (var row in Enumerable.Range(0, numberOfRows))
foreach (var column in Enumerable.Range(0, numberOfColumns))
grid.Item().Background(Placeholders.BackgroundColor()).Padding(5).Text($"{row}_{column}");
});
});
}
}
}
+6 -60
View File
@@ -1,5 +1,4 @@
using System;
using System.IO;
using System.IO;
using NUnit.Framework;
using QuestPDF.Drawing.Exceptions;
using QuestPDF.Examples.Engine;
@@ -20,36 +19,21 @@ namespace QuestPDF.Examples
.ShowResults()
.Render(page =>
{
page.Padding(25).Column(column =>
page.Padding(25).Stack(stack =>
{
column.Spacing(25);
stack.Spacing(25);
column.Item().Image("logo.png");
stack.Item().Image("logo.png");
var binaryData = File.ReadAllBytes("logo.png");
column.Item().Image(binaryData);
stack.Item().Image(binaryData);
using var stream = new FileStream("logo.png", FileMode.Open);
column.Item().Image(stream);
stack.Item().Image(stream);
});
});
}
[Test]
public void DynamicImage()
{
RenderingTest
.Create()
.PageSize(450, 350)
.ProducePdf()
.ShowResults()
.Render(page =>
{
page.Padding(25)
.Image(Placeholders.Image);
});
}
[Test]
public void Exception()
{
@@ -63,43 +47,5 @@ namespace QuestPDF.Examples
.Render(page => page.Image("non_existent.png"));
});
}
[Test]
public void ReusingTheSameImageFileShouldBePossible()
{
var fileName = Path.GetTempFileName() + ".jpg";
try
{
var image = Placeholders.Image(300, 100);
using var file = File.Create(fileName);
file.Write(image);
file.Dispose();
RenderingTest
.Create()
.ProducePdf()
.PageSize(PageSizes.A4)
.ShowResults()
.Render(container =>
{
container
.Padding(20)
.Column(column =>
{
column.Spacing(20);
column.Item().Image(fileName);
column.Item().Image(fileName);
column.Item().Image(fileName);
});
});
}
finally
{
File.Delete(fileName);
}
}
}
}
+2 -3
View File
@@ -25,7 +25,7 @@ namespace QuestPDF.Examples
.Padding(25)
.Decoration(decoration =>
{
decoration.Before().Text(text =>
decoration.Header().Text(text =>
{
text.DefaultTextStyle(TextStyle.Default.Size(20));
@@ -74,8 +74,7 @@ namespace QuestPDF.Examples
.PrimaryLayer()
.AlignCenter()
.AlignMiddle()
.Text(sizeText)
.FontSize(15);
.Text(sizeText, TextStyle.Default.Size(15));
});
}
});
-57
View File
@@ -1,57 +0,0 @@
using System.IO;
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class LineExamples
{
[Test]
public void LineHorizontal()
{
RenderingTest
.Create()
.PageSize(PageSizes.A5)
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.Padding(15)
.MinimalBox()
.DefaultTextStyle(TextStyle.Default.Size(16))
.Column(column =>
{
column.Item().Text("Above text");
column.Item().PaddingVertical(5).LineHorizontal(1).LineColor(Colors.Grey.Medium);
column.Item().Text("Below text");
});
});
}
[Test]
public void LineVertical()
{
RenderingTest
.Create()
.PageSize(PageSizes.A5)
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.Padding(15)
.DefaultTextStyle(TextStyle.Default.Size(16))
.Row(row =>
{
row.AutoItem().Text("Left text");
row.AutoItem().PaddingHorizontal(10).LineVertical(1).LineColor(Colors.Grey.Medium);
row.AutoItem().Text("Right text");
});
});
}
}
}
+1 -1
View File
@@ -43,7 +43,7 @@ namespace QuestPDF.Examples
container
.Background("#FFF")
.Padding(25)
.Column(column =>
.Stack(column =>
{
column.Spacing(10);
-53
View File
@@ -1,53 +0,0 @@
using System.Diagnostics;
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class MinimalApiExamples
{
[Test]
public void MinimalApi()
{
Document
.Create(container =>
{
container.Page(page =>
{
page.Size(PageSizes.A4);
page.Margin(2, Unit.Centimetre);
page.PageColor(Colors.White);
page.DefaultTextStyle(x => x.FontSize(20));
page.Header()
.Text("Hello PDF!")
.SemiBold().FontSize(36).FontColor(Colors.Blue.Medium);
page.Content()
.PaddingVertical(1, Unit.Centimetre)
.Column(x =>
{
x.Spacing(20);
x.Item().Text(Placeholders.LoremIpsum());
x.Item().Image(Placeholders.Image(200, 100));
});
page.Footer()
.AlignCenter()
.Text(x =>
{
x.Span("Page ");
x.CurrentPageNumber();
});
});
})
.GeneratePdf("hello.pdf");
Process.Start("explorer.exe", "hello.pdf");
}
}
}
+7 -8
View File
@@ -50,8 +50,7 @@ namespace QuestPDF.Examples
.Background("FFF")
.Padding(5)
.AlignCenter()
.Text("Sample text")
.FontFamily("Segoe UI emoji");
.Text("Sample text", TextStyle.Default.FontType("Segoe UI emoji"));
});
}
@@ -64,7 +63,7 @@ namespace QuestPDF.Examples
.Render(container =>
{
container
.Column(column =>
.Stack(column =>
{
column
.Item()
@@ -114,14 +113,14 @@ namespace QuestPDF.Examples
.Render(container =>
{
container
.Column(column =>
.Stack(column =>
{
column
.Item()
.Height(150)
.Row(row =>
{
row.RelativeItem()
row.RelativeColumn()
.Extend()
.Background("FFF")
@@ -129,7 +128,7 @@ namespace QuestPDF.Examples
.Width(50)
.Background("444");
row.RelativeItem()
row.RelativeColumn()
.Extend()
.Background("BBB")
@@ -143,7 +142,7 @@ namespace QuestPDF.Examples
.Height(150)
.Row(row =>
{
row.RelativeItem()
row.RelativeColumn()
.Extend()
.Background("BBB")
@@ -151,7 +150,7 @@ namespace QuestPDF.Examples
.Width(50)
.Background("444");
row.RelativeItem()
row.RelativeColumn()
.Extend()
.Background("BBB")
@@ -1,57 +0,0 @@
using System;
using System.Linq;
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class PageBackgroundForegroundExample
{
[Test]
public void PageBackgroundForeground()
{
RenderingTest
.Create()
.ProduceImages()
.MaxPages(100)
.ShowResults()
.RenderDocument(document =>
{
document.Page(page =>
{
page.Size(PageSizes.A4);
page.Margin(1, Unit.Inch);
page.DefaultTextStyle(TextStyle.Default.FontSize(16));
page.PageColor(Colors.White);
const string transparentBlue = "#662196f3";
page.Background()
.AlignTop()
.ExtendHorizontal()
.Height(200)
.Background(transparentBlue);
page.Foreground()
.AlignBottom()
.ExtendHorizontal()
.Height(250)
.Background(transparentBlue);
page.Header().Text("Background and foreground").Bold().FontColor(Colors.Blue.Darken2).FontSize(36);
page.Content().PaddingVertical(25).Column(column =>
{
column.Spacing(25);
foreach (var i in Enumerable.Range(0, 100))
column.Item().Background(Colors.Grey.Lighten2).Height(75);
});
});
});
}
}
}
+6 -10
View File
@@ -1,18 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.2" />
<PackageReference Include="BenchmarkDotNet" Version="0.13.1" />
<PackageReference Include="microcharts" Version="0.9.5.9" />
<PackageReference Include="nunit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.3.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.0" />
<PackageReference Include="SkiaSharp" Version="2.88.3" />
<PackageReference Include="Svg.Skia" Version="0.5.18" />
<PackageReference Include="nunit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
<PackageReference Include="SkiaSharp" Version="2.80.3" />
</ItemGroup>
<ItemGroup>
@@ -29,9 +28,6 @@
<None Update="logo.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="pdf-icon.svg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
+21 -72
View File
@@ -9,7 +9,7 @@ namespace QuestPDF.Examples
public class RowExamples
{
[Test]
public void ItemTypes()
public void ColumnTypes()
{
RenderingTest
.Create()
@@ -22,24 +22,25 @@ namespace QuestPDF.Examples
.Padding(25)
.MinimalBox()
.Border(1)
.Column(column =>
.Stack(stack =>
{
column.Item().LabelCell("Total width: 600px");
stack.Item().LabelCell("Total width: 600px");
column.Item().Row(row =>
stack.Item().Row(row =>
{
row.ConstantItem(150).ValueCell("150px");
row.ConstantItem(100).ValueCell("100px");
row.RelativeItem(4).ValueCell("200px");
row.RelativeItem(3).ValueCell("150px");
row.ConstantColumn(150).ValueCell("150px");
row.ConstantColumn(100).ValueCell("100px");
row.RelativeColumn(4).ValueCell("200px");
row.RelativeColumn(3).ValueCell("150px");
});
column.Item().Row(row =>
stack.Item().Row(row =>
{
row.ConstantItem(100).ValueCell("100px");
row.ConstantItem(50).ValueCell("50px");
row.RelativeItem(2).ValueCell("100px");
row.RelativeItem(1).ValueCell("50px");
row.ConstantColumn(100).ValueCell("100px");
row.ConstantColumn(50).ValueCell("50px");
row.Column(constantWidth: 100, relativeWidth: 4).ValueCell("350px");
row.RelativeColumn(2).ValueCell("100px");
row.RelativeColumn(1).ValueCell("50px");
});
});
});
@@ -62,17 +63,17 @@ namespace QuestPDF.Examples
.Padding(25)
.Row(row =>
{
row.RelativeItem().Column(column =>
row.RelativeColumn().Stack(stack =>
{
column.Item().ShowOnce().Element(CreateBox).Text("X");
column.Item().Element(CreateBox).Text("1");
column.Item().Element(CreateBox).Text("2");
stack.Item().ShowOnce().Element(CreateBox).Text("X");
stack.Item().Element(CreateBox).Text("1");
stack.Item().Element(CreateBox).Text("2");
});
row.RelativeItem().Column(column =>
row.RelativeColumn().Stack(stack =>
{
column.Item().Element(CreateBox).Text("1");
column.Item().Element(CreateBox).Text("2");
stack.Item().Element(CreateBox).Text("1");
stack.Item().Element(CreateBox).Text("2");
});
});
});
@@ -89,57 +90,5 @@ namespace QuestPDF.Examples
.ShowOnce();
}
}
[Test]
public void Stability_NoItems()
{
RenderingTest
.Create()
.ProducePdf()
.MaxPages(100)
.PageSize(250, 150)
.Render(container =>
{
container
.Padding(25)
.Row(row => { });
});
}
[Test]
public void RowElementForRelativeHeightDivision()
{
RenderingTest
.Create()
.ProduceImages()
.ShowResults()
.MaxPages(100)
.PageSize(250, 400)
.Render(container =>
{
container
.Padding(25)
.AlignLeft()
.RotateRight()
.Row(row =>
{
row.Spacing(20);
row.RelativeItem(1).Element(Content);
row.RelativeItem(2).Element(Content);
row.RelativeItem(3).Element(Content);
void Content(IContainer container)
{
container
.RotateLeft()
.Border(1)
.Background(Placeholders.BackgroundColor())
.Padding(5)
.Text(Placeholders.Label());
}
});
});
}
}
}
-42
View File
@@ -1,42 +0,0 @@
using System.Linq;
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class ScaleToFitExamples
{
[Test]
public void ScaleToFit()
{
RenderingTest
.Create()
.PageSize(PageSizes.A4)
.ProduceImages()
.ShowResults()
.Render(container =>
{
container.Padding(25).Column(column =>
{
var text = Placeholders.Paragraph();
foreach (var i in Enumerable.Range(2, 5))
{
column
.Item()
.MinimalBox()
.Border(1)
.Padding(5)
.Width(i * 40)
.Height(i * 20)
.ScaleToFit()
.Text(text);
}
});
});
}
}
}
+4 -4
View File
@@ -21,20 +21,20 @@ namespace QuestPDF.Examples
{
page.Margin(20);
page.Size(PageSizes.A7.Landscape());
page.PageColor(Colors.White);
page.Background(Colors.White);
page.Header().Text("With show once").SemiBold();
page.Header().Text("With show once", TextStyle.Default.SemiBold());
page.Content().PaddingVertical(5).Row(row =>
{
row.RelativeItem()
row.RelativeColumn()
.Background(Colors.Grey.Lighten2)
.Border(1)
.Padding(5)
.ShowOnce()
.Text(Placeholders.Label());
row.RelativeItem(2)
row.RelativeColumn(2)
.Border(1)
.Padding(5)
.Text(Placeholders.Paragraph());
+5 -6
View File
@@ -21,18 +21,17 @@ namespace QuestPDF.Examples
{
page.Margin(20);
page.Size(PageSizes.A7.Landscape());
page.PageColor(Colors.White);
page.Background(Colors.White);
page.Header().Column(column =>
page.Header().Stack(stack =>
{
column.Item().ShowOnce().Text("This header is visible on the first page.");
column.Item().SkipOnce().Text("This header is visible on the second page and all following.");
stack.Item().ShowOnce().Text("This header is visible on the first page.");
stack.Item().SkipOnce().Text("This header is visible on the second page and all following.");
});
page.Content()
.PaddingVertical(10)
.Text(Placeholders.Paragraphs())
.FontColor(Colors.Grey.Medium);
.Text(Placeholders.Paragraphs(), TextStyle.Default.Color(Colors.Grey.Medium));
page.Footer().Text(text =>
{
-48
View File
@@ -1,48 +0,0 @@
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class StopPaging
{
[Test]
public void Example()
{
RenderingTest
.Create()
.PageSize(300, 250)
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.Padding(25)
.DefaultTextStyle(TextStyle.Default.Size(14))
.Decoration(decoration =>
{
decoration
.Before()
.Text(text =>
{
text.DefaultTextStyle(TextStyle.Default.SemiBold().Color(Colors.Blue.Medium));
text.Span("Page ");
text.CurrentPageNumber();
});
decoration
.Content()
.Column(column =>
{
column.Spacing(25);
column.Item().StopPaging().Text(Placeholders.LoremIpsum());
column.Item().ExtendHorizontal().Height(75).Background(Colors.Grey.Lighten2);
});
});
});
}
}
}
-36
View File
@@ -1,36 +0,0 @@
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);
});
});
}
}
}
-46
View File
@@ -1,46 +0,0 @@
using System.Linq;
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
namespace QuestPDF.Examples
{
public class TableBenchmark
{
[Test]
public void Benchmark()
{
RenderingTest
.Create()
.ProducePdf()
.PageSize(PageSizes.A4)
.ShowResults()
.MaxPages(10_000)
.EnableCaching(true)
.EnableDebugging(false)
.Render(container =>
{
container
.Padding(10)
.MinimalBox()
.Border(1)
.Table(table =>
{
const int numberOfRows = 100_000;
const int numberOfColumns = 10;
table.ColumnsDefinition(columns =>
{
foreach (var _ in Enumerable.Range(0, numberOfColumns))
columns.RelativeColumn();
});
foreach (var row in Enumerable.Range(0, numberOfRows))
foreach (var column in Enumerable.Range(0, numberOfColumns))
table.Cell().Background(Placeholders.BackgroundColor()).Padding(5).Text($"{row}_{column}");
});
});
}
}
}
+2 -37
View File
@@ -49,39 +49,6 @@ 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()
{
@@ -250,7 +217,7 @@ namespace QuestPDF.Examples
RenderingTest
.Create()
.ProduceImages()
.PageSize(220, 170)
.PageSize(220, 220)
.ShowResults()
.Render(container =>
{
@@ -413,7 +380,7 @@ namespace QuestPDF.Examples
.EnableCaching()
.EnableDebugging(false)
.ShowResults()
.Render(container => GeneratePerformanceStructure(container, 250));
.Render(container => GeneratePerformanceStructure(container, 1000));
}
public static void GeneratePerformanceStructure(IContainer container, int repeats)
@@ -489,10 +456,8 @@ namespace QuestPDF.Examples
return container
.Border(1)
.Background(Colors.Grey.Lighten3)
.ShowOnce()
.MinWidth(50)
.MinHeight(50)
.Padding(10)
.AlignCenter()
.AlignMiddle();
}
+49 -42
View File
@@ -23,7 +23,7 @@ namespace QuestPDF.Examples
.PageSize(PageSizes.A4)
.ProducePdf()
.ShowResults()
.RenderDocument(x => ComposeBook(x, chapters));
.Render(x => ComposeBook(x, chapters));
}
[Test]
@@ -31,7 +31,7 @@ namespace QuestPDF.Examples
{
var chapters = GetBookChapters().ToList();
var results = PerformTest(128).ToList();
var results = PerformTest(16).ToList();
Console.WriteLine($"Min: {results.Min():F}");
Console.WriteLine($"Max: {results.Max():F}");
@@ -42,8 +42,8 @@ namespace QuestPDF.Examples
RenderingTest
.Create()
.PageSize(PageSizes.A4)
.ProducePdf()
.RenderDocument(x => ComposeBook(x, chapters));
.ProducePdf()
.Render(x => ComposeBook(x, chapters));
}
IEnumerable<float> PerformTest(int attempts)
@@ -102,105 +102,112 @@ namespace QuestPDF.Examples
}
}
private void ComposeBook(IDocumentContainer container, ICollection<BookChapter> chapters)
private void ComposeBook(IContainer container, ICollection<BookChapter> chapters)
{
var subtitleStyle = TextStyle.Default.Size(24).SemiBold().Color(Colors.Blue.Medium);
var normalStyle = TextStyle.Default.Size(14);
ComposePage(container);
container.Page(page =>
void ComposePage(IContainer container)
{
page.Margin(50);
page.Content().PaddingVertical(10).Column(column =>
{
column.Item().Element(Title);
column.Item().PageBreak();
column.Item().Element(TableOfContents);
column.Item().PageBreak();
container
.Padding(50)
.Decoration(decoration =>
{
decoration
.Content()
.Stack(stack =>
{
stack.Item().Element(Title);
stack.Item().PageBreak();
stack.Item().Element(TableOfContents);
stack.Item().PageBreak();
Chapters(column);
Chapters(stack);
column.Item().Element(Acknowledgements);
});
page.Footer().Element(Footer);
});
stack.Item().Element(Acknowledgements);
});
decoration.Footer().Element(Footer);
});
}
void Title(IContainer container)
{
container
.Extend()
.PaddingBottom(200)
.AlignBottom()
.Column(column =>
.Stack(stack =>
{
column.Item().Text("Quo Vadis").FontSize(72).Bold().FontColor(Colors.Blue.Darken2);
column.Item().Text("Henryk Sienkiewicz").FontSize(24).FontColor(Colors.Grey.Darken2);
stack.Item().Text("Quo Vadis", TextStyle.Default.Size(72).Bold().Color(Colors.Blue.Darken2));
stack.Item().Text("Henryk Sienkiewicz", TextStyle.Default.Size(24).Color(Colors.Grey.Darken2));
});
}
void TableOfContents(IContainer container)
{
container.Column(column =>
container.Stack(stack =>
{
SectionTitle(column, "Spis treści");
SectionTitle(stack, "Spis treści");
foreach (var chapter in chapters)
{
column.Item().InternalLink(chapter.Title).Row(row =>
stack.Item().InternalLink(chapter.Title).Row(row =>
{
row.RelativeItem().Text(chapter.Title).Style(normalStyle);
row.ConstantItem(100).AlignRight().Text(text => text.BeginPageNumberOfSection(chapter.Title).Style(normalStyle));
row.RelativeColumn().Text(chapter.Title, normalStyle);
row.ConstantColumn(100).AlignRight().Text(text => text.PageNumberOfLocation(chapter.Title, normalStyle));
});
}
});
}
void Chapters(ColumnDescriptor column)
void Chapters(StackDescriptor stack)
{
foreach (var chapter in chapters)
{
column.Item().Element(container => Chapter(container, chapter.Title, chapter.Content));
stack.Item().Element(container => Chapter(container, chapter.Title, chapter.Content));
}
}
void Chapter(IContainer container, string title, string content)
{
container.Column(column =>
container.Stack(stack =>
{
SectionTitle(column, title);
SectionTitle(stack, title);
column.Item().Text(text =>
stack.Item().Text(text =>
{
text.ParagraphSpacing(5);
text.Span(content).Style(normalStyle);
text.Span(content, normalStyle);
});
column.Item().PageBreak();
stack.Item().PageBreak();
});
}
void Acknowledgements(IContainer container)
{
container.Column(column =>
container.Stack(stack =>
{
SectionTitle(column, "Podziękowania");
SectionTitle(stack, "Podziękowania");
column.Item().Text(text =>
stack.Item().Text(text =>
{
text.DefaultTextStyle(normalStyle);
text.Span("Ten dokument został wygenerowany na podstawie książki w formacie TXT opublikowanej w serwisie ");
text.Hyperlink("wolnelektury.pl", "https://wolnelektury.pl/").FontColor(Colors.Blue.Medium).Underline();
text.ExternalLocation("wolnelektury.pl", "https://wolnelektury.pl/", normalStyle.Color(Colors.Blue.Medium).Underline());
text.Span(". Dziękuję za wspieranie polskiego czytelnictwa!");
});
});
}
void SectionTitle(ColumnDescriptor column, string text)
void SectionTitle(StackDescriptor stack, string text)
{
column.Item().Location(text).Text(text).Style(subtitleStyle);
column.Item().PaddingTop(10).PaddingBottom(50).BorderBottom(1).BorderColor(Colors.Grey.Lighten2).ExtendHorizontal();
stack.Item().Location(text).Text(text, subtitleStyle);
stack.Item().PaddingTop(10).PaddingBottom(50).BorderBottom(1).BorderColor(Colors.Grey.Lighten2).ExtendHorizontal();
}
void Footer(IContainer container)
+27 -546
View File
@@ -1,8 +1,7 @@
using System;
using System;
using System.Linq;
using System.Text;
using NUnit.Framework;
using QuestPDF.Elements.Text;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
@@ -12,32 +11,12 @@ namespace QuestPDF.Examples
{
public class TextExamples
{
[Test]
public void SimpleText()
{
RenderingTest
.Create()
.PageSize(500, 100)
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.Padding(5)
.MinimalBox()
.Border(1)
.Padding(10)
.Text(Placeholders.Paragraph());
});
}
[Test]
public void SimpleTextBlock()
{
RenderingTest
.Create()
.PageSize(600, 300)
.PageSize(500, 300)
.ProduceImages()
.ShowResults()
@@ -47,173 +26,22 @@ namespace QuestPDF.Examples
.Padding(5)
.MinimalBox()
.Border(1)
.MaxWidth(300)
.Padding(10)
.Text(text =>
{
text.DefaultTextStyle(TextStyle.Default.FontSize(20));
text.DefaultTextStyle(TextStyle.Default.Size(20));
text.Span("This is a normal text, followed by an ");
text.Span("underlined red text").FontColor(Colors.Red.Medium).Underline();
text.Span(".");
});
});
}
[Test]
public void TextWeight()
{
RenderingTest
.Create()
.PageSize(500, 500)
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.Padding(20)
.MinimalBox()
.Border(1)
.Padding(20)
.Text(text =>
{
text.DefaultTextStyle(x => x.FontFamily(Fonts.Calibri).FontSize(20));
text.Line("Thin").Thin();
text.Line("ExtraLight").ExtraLight();
text.Line("Light").Light();
text.Line("NormalWeight").NormalWeight();
text.Line("Medium").Medium();
text.Line("SemiBold").SemiBold();
text.Line("Bold").Bold();
text.Line("ExtraBold").ExtraBold();
text.Line("Black").Black();
text.Line("ExtraBlack").ExtraBlack();
text.Span("underlined red text.", TextStyle.Default.Size(20).Color(Colors.Red.Medium).Underline());
});
});
}
[Test]
public void LineHeight()
{
RenderingTest
.Create()
.PageSize(500, 700)
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.Padding(20)
.Column(column =>
{
var lineHeights = new[] { 0.8f, 1f, 1.5f };
var paragraph = Placeholders.Paragraph();
foreach (var lineHeight in lineHeights)
{
column
.Item()
.Border(1)
.Padding(10)
.Text(paragraph)
.FontSize(16)
.LineHeight(lineHeight);
}
});
});
}
[Test]
public void SuperscriptSubscript_Simple()
{
RenderingTest
.Create()
.PageSize(500, 500)
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.Padding(20)
.MinimalBox()
.Border(1)
.Padding(20)
.Text(text =>
{
text.DefaultTextStyle(x => x.FontSize(20));
text.ParagraphSpacing(10);
var highlight = TextStyle.Default.BackgroundColor(Colors.Green.Lighten3);
text.Span("E=mc").Style(highlight);
text.Span("2").Superscript().Style(highlight);
text.Span(" is the equation of massenergy equivalence.");
text.EmptyLine();
text.Span("H").Style(highlight);
text.Span("2").Subscript().Style(highlight);
text.Span("O").Style(highlight);
text.Span(" is the chemical formula for water.");
});
});
}
[Test]
public void SuperscriptSubscript_Effects()
{
RenderingTest
.Create()
.PageSize(800, 400)
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.Padding(25)
.DefaultTextStyle(x => x.FontSize(30))
.Column(column =>
{
column.Spacing(25);
column.Item().Text(text =>
{
text.DefaultTextStyle(x => x.Underline());
text.Span("Underline of the superscript (E = mc");
text.Span("2").Superscript();
text.Span(") should be at the same height as for normal text.");
});
column.Item().Text(text =>
{
text.DefaultTextStyle(x => x.Underline());
text.Span("Underline of the subscript(H");
text.Span("2").Subscript();
text.Span("O) should be slightly lower than a normal text.");
});
column.Item().Text(text =>
{
text.DefaultTextStyle(x => x.Strikethrough());
text.Span("Strikethrough of both superscript (E=mc");
text.Span("2").Superscript();
text.Span(") and subscript(H");
text.Span("2").Subscript();
text.Span("O) should be visible in the middle of the text.");
});
});
});
}
[Test]
public void ParagraphSpacing()
{
RenderingTest
.Create()
.PageSize(500, 500)
.PageSize(500, 300)
.ProduceImages()
.ShowResults()
.Render(container =>
@@ -225,7 +53,13 @@ namespace QuestPDF.Examples
.Padding(10)
.Text(text =>
{
text.Line(Placeholders.Paragraph());
text.ParagraphSpacing(10);
foreach (var i in Enumerable.Range(1, 3))
{
text.Span($"Paragraph {i}: ", TextStyle.Default.SemiBold());
text.Line(Placeholders.Paragraph());
}
});
});
}
@@ -247,7 +81,7 @@ namespace QuestPDF.Examples
.Padding(10)
.Text(text =>
{
text.DefaultTextStyle(TextStyle.Default.FontSize(20));
text.DefaultTextStyle(TextStyle.Default.Size(20));
text.Span("This is a random image aligned to the baseline: ");
text.Element()
@@ -286,13 +120,13 @@ namespace QuestPDF.Examples
text.Line(Placeholders.LoremIpsum());
text.Span($"This is target text that should show up. {DateTime.UtcNow:T} > This is a short sentence that will be wrapped into second line hopefully, right? <").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? <", TextStyle.Default.Underline());
});
});
}
[Test]
public void Textcolumn()
public void TextStack()
{
RenderingTest
.Create()
@@ -339,8 +173,6 @@ namespace QuestPDF.Examples
.Padding(10)
.Text(text =>
{
text.DefaultTextStyle(x => x.Bold());
text.DefaultTextStyle(TextStyle.Default);
text.AlignLeft();
text.ParagraphSpacing(10);
@@ -350,14 +182,14 @@ namespace QuestPDF.Examples
text.EmptyLine();
text.Span("This text is a normal text, ");
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("this is a bold text, ", TextStyle.Default.Bold());
text.Span("this is a red and underlined text, ", TextStyle.Default.Color(Colors.Red.Medium).Underline());
text.Span("and this is slightly bigger text.", TextStyle.Default.Size(16));
text.EmptyLine();
text.Span("The new text element also supports injecting custom content between words: ");
text.Element().PaddingBottom(-4).Height(16).Width(32).Image(Placeholders.Image);
text.Element().PaddingBottom(-10).Height(16).Width(32).Image(Placeholders.Image);
text.Span(".");
text.EmptyLine();
@@ -369,7 +201,7 @@ namespace QuestPDF.Examples
text.EmptyLine();
text.Hyperlink("Please visit QuestPDF website", "https://www.questpdf.com");
text.ExternalLocation("Please visit QuestPDF website", "https://www.questpdf.com");
text.EmptyLine();
@@ -378,7 +210,7 @@ namespace QuestPDF.Examples
text.EmptyLine();
text.Span(Placeholders.Paragraphs()).Italic();
text.Span(Placeholders.Paragraphs(), TextStyle.Default.Italic());
text.Line("This is target text that does not show up. " + Placeholders.Paragraph());
});
@@ -404,17 +236,17 @@ namespace QuestPDF.Examples
.Padding(10)
.Text(text =>
{
text.DefaultTextStyle(TextStyle.Default.FontSize(20).BackgroundColor(Colors.Red.Lighten4));
text.DefaultTextStyle(TextStyle.Default);
text.AlignLeft();
text.ParagraphSpacing(10);
text.Span("This text is a normal text, ");
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("this is a bold text, ", TextStyle.Default.Bold());
text.Span("this is a red and underlined text, ", TextStyle.Default.Color(Colors.Red.Medium).Underline());
text.Span("and this is slightly bigger text.", TextStyle.Default.Size(16));
text.Span("The new text element also supports injecting custom content between words: ");
text.Element().PaddingBottom(-4).Height(16).Width(32).Image(Placeholders.Image);
text.Element().PaddingBottom(-10).Height(16).Width(32).Image(Placeholders.Image);
text.Span(".");
text.EmptyLine();
@@ -423,7 +255,7 @@ namespace QuestPDF.Examples
{
text.Line($"{i}: {Placeholders.Paragraph()}");
text.Hyperlink("Please visit QuestPDF website. ", "https://www.questpdf.com");
text.ExternalLocation("Please visit QuestPDF website", "https://www.questpdf.com");
text.Span("This is page number ");
text.CurrentPageNumber();
@@ -435,356 +267,5 @@ namespace QuestPDF.Examples
});
});
}
[Test]
public void MeasureIssueWhenSpaceAtLineEnd()
{
// issue 135
RenderingTest
.Create()
.ProduceImages()
.ShowResults()
.RenderDocument(container =>
{
container.Page(page =>
{
page.Margin(50);
page.PageColor(Colors.White);
page.Size(PageSizes.A4);
page.Content().Text("This is a specially crafted sentence with a specially chosen length for demonstration of the bug that occurs ;;;;;. ").FontSize(11).BackgroundColor(Colors.Red.Lighten3);
});
});
}
[Test]
public void EmptyText()
{
// issue 135
RenderingTest
.Create()
.ProduceImages()
.ShowResults()
.RenderDocument(container =>
{
container.Page(page =>
{
page.Margin(50);
page.PageColor(Colors.White);
page.Size(PageSizes.A4);
page.Content().Text(" ").FontSize(11).BackgroundColor(Colors.Red.Lighten3);
});
});
}
[Test]
public void Whitespaces()
{
// issue 135
RenderingTest
.Create()
.ProduceImages()
.ShowResults()
.RenderDocument(container =>
{
container.Page(page =>
{
page.Margin(50);
page.PageColor(Colors.White);
page.Size(PageSizes.A4);
page.Content().Text(" x ").FontSize(11).BackgroundColor(Colors.Red.Lighten3);
});
});
}
[Test]
public void DrawingNullTextShouldNotThrowException()
{
RenderingTest
.Create()
.ProduceImages()
.ShowResults()
.RenderDocument(container =>
{
container.Page(page =>
{
page.Margin(50);
page.PageColor(Colors.White);
page.Size(PageSizes.A4);
page.Content().Column(column =>
{
column.Item().Text(null);
column.Item().Text(text =>
{
text.Span(null);
text.Line(null);
text.Hyperlink(null, "http://www.questpdf.com");
text.TotalPages().Format(x => null);
});
});
});
});
}
[Test]
public void BreakingLongWord()
{
RenderingTest
.Create()
.ProduceImages()
.ShowResults()
.RenderDocument(container =>
{
container.Page(page =>
{
page.Margin(50);
page.PageColor(Colors.White);
page.Size(PageSizes.A4);
page.Content().Column(column =>
{
column.Item().Text(null);
column.Item().Text(text =>
{
text.DefaultTextStyle(x => x.BackgroundColor(Colors.Red.Lighten3).FontSize(24));
text.Span(" " + Placeholders.LoremIpsum());
text.Span(" 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 ").WrapAnywhere();
});
});
});
});
}
[Test]
public void TextShaping_Unicode()
{
RenderingTest
.Create()
.PageSize(600, 100)
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.Padding(35)
.MinimalBox()
.Background(Colors.Grey.Lighten2)
.Text(text =>
{
text.DefaultTextStyle(TextStyle.Default.FontSize(20));
text.Span("Complex Unicode structure: ");
text.Span("T̶̖̔͆͆̽̔ḩ̷̼̫̐̈́̀͜͝͝ì̶͇̤͓̱̣͇͓͉̎s̵̡̟̹͍̜͉̗̾͛̈̐́͋͂͝͠ͅ ̴̨͙͍͇̭̒͗̀́͝ì̷̡̺͉̼̏̏̉̌͝s̷͍͙̗̰̖͙̈̑̂̔͑͊̌̓̊̇͜ ̶̛̼͚͊̅͘ṭ̷̨̘̣̙̖͉͌̏̂̅͑̄̽̕͝ȅ̶̲̲̙̭͈̬̣͔̝͔̈́͝s̸̢̯̪̫͓̭̮̓̀͆͜ț̸̢͉̞̥̤̏̌̓͝").FontFamily(Fonts.Calibri).FontColor(Colors.Red.Medium);
text.Span(".");
});
});
}
[Test]
public void TextShaping_Arabic()
{
RenderingTest
.Create()
.PageSize(250, 100)
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.Padding(25)
.MinimalBox()
.Background(Colors.Grey.Lighten2)
.Text("خوارزمية ترتيب")
.FontFamily(Fonts.Calibri)
.FontSize(30);
});
}
[Test]
public void FontFallback()
{
RenderingTest
.Create()
.ProduceImages()
.ShowResults()
.RenderDocument(container =>
{
container.Page(page =>
{
page.Margin(50);
page.PageColor(Colors.White);
page.DefaultTextStyle(x => x
.Fallback(y => y.FontFamily("Segoe UI Emoji")
.Fallback(y => y.FontFamily("Microsoft YaHei"))));
page.Size(PageSizes.A4);
page.Content().Text(t =>
{
t.Line("This is normal text.");
t.EmptyLine();
t.Line("Following line should use font fallback:");
t.Line("中文文本");
t.EmptyLine();
t.Line("The following line contains a mix of known and unknown characters.");
t.Line("Mixed line: This 中文 is 文文 a mixed 本 本 line 本 中文文本!");
t.EmptyLine();
t.Line("Emojis work out of the box because of font fallback: 😊😅🥳👍❤😍👌");
});
});
});
}
[Test]
public void WordWrappingStability()
{
// instruction: check if any characters repeat when performing the word-wrapping algorithm
RenderingTest
.Create()
.PageSize(PageSizes.A4)
.ProducePdf()
.ShowResults()
.Render(container =>
{
var text = "Lorem ipsum dolor sit amet consectetuer";
container
.Padding(20)
.Column(column =>
{
column.Spacing(10);
foreach (var width in Enumerable.Range(25, 200))
{
column
.Item()
.MaxWidth(width)
.Background(Colors.Grey.Lighten3)
.Text(text);
}
});
});
}
[Test]
public void AdvancedLanguagesSupport()
{
RenderingTest
.Create()
.PageSize(new PageSize(400, 400))
.ProduceImages()
.ShowResults()
.Render(container =>
{
var text = "في المعلوماتية أو الرياضيات، خوارزمية الترتيب هي خوارزمية تمكن من تنظيم مجموعة عناصر حسب ترتيب محدد.";
container
.Padding(20)
.ContentFromRightToLeft()
.Text(text)
.FontFamily(Fonts.Calibri)
.FontSize(22);
});
}
[Test]
public void WordWrappingWhenRightToLeft()
{
RenderingTest
.Create()
.PageSize(new PageSize(1000, 500))
.ProducePdf()
.ShowResults()
.Render(container =>
{
var text = "في المعلوماتية أو الرياضيات، خوارزمية الترتيب هي خوارزمية تمكن من تنظيم مجموعة عناصر حسب ترتيب محدد.";
container
.Padding(25)
.ContentFromRightToLeft()
.Column(column =>
{
column.Spacing(20);
foreach (var size in new[] { 36, 34, 32, 30, 15 })
{
column
.Item()
.ShowEntire()
.MaxWidth(size * 25)
.Background(Colors.Grey.Lighten3)
.MinimalBox()
.Background(Colors.Grey.Lighten2)
.Text(text)
.FontSize(20)
.FontFamily("Segoe UI");
}
});
});
}
[Test]
public void ForcingTextDirection()
{
RenderingTest
.Create()
.PageSize(new PageSize(1000, 500))
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.Padding(10)
.DefaultTextStyle(x => x.FontSize(24).FontFamily("Calibri"))
.Column(column =>
{
column.Spacing(10);
var word = "الجوريتم";
var definition = "algorithm in Arabic";
var text = $"{word} - {definition}";
// text direction is automatically detected using the first word
column.Item().Text(text);
// it is possible to force specific content direction
column.Item().Text(text).DirectionFromLeftToRight();
column.Item().Text(text).DirectionFromRightToLeft();
// to combine text in various content directions, split it into segments
column.Item().Text(text =>
{
text.Span(word);
text.Span(" - ");
text.Span(definition);
});
});
});
}
}
}
-149
View File
@@ -1,149 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="75.320129mm"
height="92.604164mm"
viewBox="0 0 75.320129 92.604164"
version="1.1"
id="svg8"
inkscape:version="0.92.3 (2405546, 2018-03-11)"
sodipodi:docname="PDF file icon.svg">
<title
id="title2682">PDF file icon</title>
<defs
id="defs2">
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath839">
<rect
style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke"
id="rect841"
width="68.688263"
height="67.886459"
x="-74.033661"
y="141.44913" />
</clipPath>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.98994949"
inkscape:cx="122.12727"
inkscape:cy="167.02823"
inkscape:document-units="mm"
inkscape:current-layer="g899"
showgrid="false"
inkscape:object-nodes="false"
inkscape:window-width="1366"
inkscape:window-height="705"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:snap-page="false"
inkscape:snap-others="false"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title>PDF file icon</dc:title>
<dc:date>08/10/2018</dc:date>
<dc:creator>
<cc:Agent>
<dc:title>Adobe Systems</dc:title>
</cc:Agent>
</dc:creator>
<dc:publisher>
<cc:Agent>
<dc:title>CMetalCore</dc:title>
</cc:Agent>
</dc:publisher>
<dc:description>Fuente del texto &quot;PDF&quot;:
Franklin Gothic Medium Cond</dc:description>
<cc:license
rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/" />
</cc:Work>
<cc:License
rdf:about="http://creativecommons.org/licenses/by-sa/4.0/">
<cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Notice" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Attribution" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#ShareAlike" />
</cc:License>
</rdf:RDF>
</metadata>
<g
inkscape:label="Capa 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(36.076172,-93.731774)">
<g
id="g899"
transform="matrix(1.4843054,0,0,1.4843054,17.471885,-90.243502)">
<g
id="g876">
<path
inkscape:connector-curvature="0"
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ff2116;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.11666656;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m -29.632812,123.94727 c -3.551967,0 -6.44336,2.89347 -6.44336,6.44531 v 49.49804 c 0,3.55185 2.891393,6.44532 6.44336,6.44532 H 8.2167969 c 3.5519661,0 6.4433591,-2.89335 6.4433591,-6.44532 v -40.70117 c 0,0 0.101353,-1.19181 -0.416015,-2.35156 -0.484969,-1.08711 -1.275391,-1.84375 -1.275391,-1.84375 a 1.0584391,1.0584391 0 0 0 -0.0059,-0.008 L 3.5722246,125.7752 a 1.0584391,1.0584391 0 0 0 -0.015625,-0.0156 c 0,0 -0.8017392,-0.76344 -1.9902344,-1.27344 -1.39939552,-0.6005 -2.8417968,-0.53711 -2.8417968,-0.53711 l 0.021484,-0.002 z"
id="path890" />
<path
inkscape:connector-curvature="0"
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#f5f5f5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.11666656;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m -29.632812,126.06445 h 28.3789058 a 1.0584391,1.0584391 0 0 0 0.021484,0 c 0,0 1.13480448,0.011 1.96484378,0.36719 0.79889772,0.34282 1.36536982,0.86176 1.36914062,0.86524 1.25e-5,1e-5 0.00391,0.004 0.00391,0.004 l 9.3671868,9.18945 c 0,0 0.564354,0.59582 0.837891,1.20899 0.220779,0.49491 0.234375,1.40039 0.234375,1.40039 a 1.0584391,1.0584391 0 0 0 -0.002,0.0449 v 40.74609 c 0,2.41592 -1.910258,4.32813 -4.3261717,4.32813 H -29.632812 c -2.415914,0 -4.326172,-1.91209 -4.326172,-4.32813 v -49.49804 c 0,-2.41603 1.910258,-4.32813 4.326172,-4.32813 z"
id="rect2684" />
<path
sodipodi:nodetypes="scccscccscccccssscacccsccccccccccccccccccccaccccccccc"
inkscape:connector-curvature="0"
d="m -23.40766,161.09299 c -1.45669,-1.45669 0.11934,-3.45839 4.39648,-5.58397 l 2.69124,-1.33743 1.04845,-2.29399 c 0.57665,-1.26169 1.43729,-3.32036 1.91254,-4.5748 l 0.8641,-2.28082 -0.59546,-1.68793 c -0.73217,-2.07547 -0.99326,-5.19438 -0.52872,-6.31588 0.62923,-1.51909 2.69029,-1.36323 3.50626,0.26515 0.63727,1.27176 0.57212,3.57488 -0.18329,6.47946 l -0.6193,2.38125 0.5455,0.92604 c 0.30003,0.50932 1.1764,1.71867 1.9475,2.68743 l 1.44924,1.80272 1.8033728,-0.23533 c 5.72900399,-0.74758 7.6912472,0.523 7.6912472,2.34476 0,2.29921 -4.4984914,2.48899 -8.2760865,-0.16423 -0.8499666,-0.59698 -1.4336605,-1.19001 -1.4336605,-1.19001 0,0 -2.3665326,0.48178 -3.531704,0.79583 -1.202707,0.32417 -1.80274,0.52719 -3.564509,1.12186 0,0 -0.61814,0.89767 -1.02094,1.55026 -1.49858,2.4279 -3.24833,4.43998 -4.49793,5.1723 -1.3991,0.81993 -2.86584,0.87582 -3.60433,0.13733 z m 2.28605,-0.81668 c 0.81883,-0.50607 2.47616,-2.46625 3.62341,-4.28553 l 0.46449,-0.73658 -2.11497,1.06339 c -3.26655,1.64239 -4.76093,3.19033 -3.98386,4.12664 0.43653,0.52598 0.95874,0.48237 2.01093,-0.16792 z m 21.21809,-5.95578 c 0.80089,-0.56097 0.68463,-1.69142 -0.22082,-2.1472 -0.70466,-0.35471 -1.2726074,-0.42759 -3.1031574,-0.40057 -1.1249,0.0767 -2.9337647,0.3034 -3.2403347,0.37237 0,0 0.993716,0.68678 1.434896,0.93922 0.58731,0.33544 2.0145161,0.95811 3.0565161,1.27706 1.02785,0.31461 1.6224,0.28144 2.0729,-0.0409 z m -8.53152,-3.54594 c -0.4847,-0.50952 -1.30889,-1.57296 -1.83152,-2.3632 -0.68353,-0.89643 -1.02629,-1.52887 -1.02629,-1.52887 0,0 -0.4996,1.60694 -0.90948,2.57394 l -1.27876,3.16076 -0.37075,0.71695 c 0,0 1.971043,-0.64627 2.97389,-0.90822 1.0621668,-0.27744 3.21787,-0.70134 3.21787,-0.70134 z m -2.74938,-11.02573 c 0.12363,-1.0375 0.1761,-2.07346 -0.15724,-2.59587 -0.9246,-1.01077 -2.04057,-0.16787 -1.85154,2.23517 0.0636,0.8084 0.26443,2.19033 0.53292,3.04209 l 0.48817,1.54863 0.34358,-1.16638 c 0.18897,-0.64151 0.47882,-2.02015 0.64411,-3.06364 z"
style="fill:#ff2116;fill-opacity:1;stroke-width:0.26458335"
id="path2697" />
<g
id="g858">
<path
inkscape:connector-curvature="0"
id="path845"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:125%;font-family:'Franklin Gothic Medium Cond';-inkscape-font-specification:'Franklin Gothic Medium Cond';letter-spacing:0px;word-spacing:4.26000023px;fill:#2c2c2c;fill-opacity:1;stroke:none;stroke-width:0.35824656px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m -20.930423,167.83862 h 2.364986 q 1.133514,0 1.840213,0.2169 0.706698,0.20991 1.189489,0.9446 0.482795,0.72769 0.482795,1.75625 0,0.94459 -0.391832,1.6233 -0.391833,0.67871 -1.056548,0.97958 -0.65772,0.30087 -2.02913,0.30087 h -0.818651 v 3.72941 h -1.581322 z m 1.581322,1.22447 v 3.33058 h 0.783664 q 1.049552,0 1.44838,-0.39184 0.405826,-0.39183 0.405826,-1.27345 0,-0.65772 -0.265887,-1.06355 -0.265884,-0.41282 -0.587747,-0.50378 -0.314866,-0.098 -1.000572,-0.098 z" />
<path
inkscape:connector-curvature="0"
id="path847"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:125%;font-family:'Franklin Gothic Medium Cond';-inkscape-font-specification:'Franklin Gothic Medium Cond';letter-spacing:0px;word-spacing:4.26000023px;fill:#2c2c2c;fill-opacity:1;stroke:none;stroke-width:0.35824656px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m -13.842461,167.83862 h 2.148082 q 1.560333,0 2.4909318,0.55276 0.9375993,0.55276 1.4133973,1.6443 0.482791,1.09153 0.482791,2.42096 0,1.3994 -0.4338151,2.49793 -0.4268149,1.09153 -1.3154348,1.76324 -0.8816233,0.67172 -2.5189212,0.67172 h -2.267031 z m 1.581326,1.26645 v 7.018 h 0.657715 q 1.378411,0 2.001144,-0.9516 0.6227329,-0.95858 0.6227329,-2.5539 0,-3.5125 -2.6238769,-3.5125 z" />
<path
inkscape:connector-curvature="0"
id="path849"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:125%;font-family:'Franklin Gothic Medium Cond';-inkscape-font-specification:'Franklin Gothic Medium Cond';letter-spacing:0px;word-spacing:4.26000023px;fill:#2c2c2c;fill-opacity:1;stroke:none;stroke-width:0.35824656px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m -5.7889096,167.83862 h 5.30372941 v 1.26645 H -4.2075842 v 2.85478 h 2.9807225 v 1.26646 h -2.9807225 v 4.16322 h -1.5813254 z" />
</g>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 12 KiB

-28
View File
@@ -1,5 +1,4 @@
using System;
using System.Collections.Concurrent;
using System.IO;
using SkiaSharp;
@@ -25,32 +24,5 @@ namespace QuestPDF.ReportSample
Latitude = Helpers.Random.NextDouble() * 180f - 90f
};
}
private static readonly ConcurrentDictionary<int, string> RomanNumeralCache = new ConcurrentDictionary<int, string>();
public static string FormatAsRomanNumeral(this int number)
{
if (number < 0 || number > 3999)
throw new ArgumentOutOfRangeException("Number should be in range from 1 to 3999");
return RomanNumeralCache.GetOrAdd(number, x =>
{
if (x >= 1000) return "M" + FormatAsRomanNumeral(x - 1000);
if (x >= 900) return "CM" + FormatAsRomanNumeral(x - 900);
if (x >= 500) return "D" + FormatAsRomanNumeral(x - 500);
if (x >= 400) return "CD" + FormatAsRomanNumeral(x - 400);
if (x >= 100) return "C" + FormatAsRomanNumeral(x - 100);
if (x >= 90) return "XC" + FormatAsRomanNumeral(x - 90);
if (x >= 50) return "L" + FormatAsRomanNumeral(x - 50);
if (x >= 40) return "XL" + FormatAsRomanNumeral(x - 40);
if (x >= 10) return "X" + FormatAsRomanNumeral(x - 10);
if (x >= 9) return "IX" + FormatAsRomanNumeral(x - 9);
if (x >= 5) return "V" + FormatAsRomanNumeral(x - 5);
if (x >= 4) return "IV" + FormatAsRomanNumeral(x - 4);
if (x >= 1) return "I" + FormatAsRomanNumeral(x - 1);
return string.Empty;
});
}
}
}
@@ -26,58 +26,58 @@ namespace QuestPDF.ReportSample.Layouts
private void ComposeHeader(IContainer container)
{
container.Background(Colors.Grey.Lighten3).Border(1).Column(column =>
container.Background(Colors.Grey.Lighten3).Border(1).Stack(stack =>
{
column.Item().ShowOnce().Padding(5).AlignMiddle().Row(row =>
stack.Item().ShowOnce().Padding(5).AlignMiddle().Row(row =>
{
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);
row.RelativeColumn(2).AlignMiddle().Text("PRIMARY HEADER", TextStyle.Default.Color(Colors.Grey.Darken3).Size(30).Bold());
row.RelativeColumn(1).AlignRight().MinimalBox().AlignMiddle().Background(Colors.Blue.Darken2).Padding(30);
});
column.Item().SkipOnce().Padding(5).Row(row =>
stack.Item().SkipOnce().Padding(5).Row(row =>
{
row.RelativeItem(2).Text("SECONDARY HEADER").FontColor(Colors.Grey.Darken3).FontSize(30).Bold();
row.RelativeItem(1).AlignRight().MinimalBox().Background(Colors.Blue.Lighten4).Padding(15);
row.RelativeColumn(2).Text("SECONDARY HEADER", TextStyle.Default.Color(Colors.Grey.Darken3).Size(30).Bold());
row.RelativeColumn(1).AlignRight().MinimalBox().Background(Colors.Blue.Lighten4).Padding(15);
});
});
}
private void ComposeContent(IContainer container)
{
container.Column(column =>
container.Stack(stack =>
{
column.Item().PaddingVertical(80).Text("First");
column.Item().PageBreak();
column.Item().PaddingVertical(80).Text("Second");
column.Item().PageBreak();
column.Item().PaddingVertical(80).Text("Third");
column.Item().PageBreak();
stack.Item().PaddingVertical(80).Text("First");
stack.Item().PageBreak();
stack.Item().PaddingVertical(80).Text("Second");
stack.Item().PageBreak();
stack.Item().PaddingVertical(80).Text("Third");
stack.Item().PageBreak();
});
}
private void ComposeFooter(IContainer container)
{
container.Background(Colors.Grey.Lighten3).Column(column =>
container.Background(Colors.Grey.Lighten3).Stack(stack =>
{
column.Item().ShowOnce().Background(Colors.Grey.Lighten3).Row(row =>
stack.Item().ShowOnce().Background(Colors.Grey.Lighten3).Row(row =>
{
row.RelativeItem().Text(x =>
row.RelativeColumn().Text(x =>
{
x.CurrentPageNumber();
x.Span(" / ");
x.TotalPages();
});
row.RelativeItem().AlignRight().Text("Footer for header");
row.RelativeColumn().AlignRight().Text("Footer for header");
});
column.Item().SkipOnce().Background(Colors.Grey.Lighten3).Row(row =>
stack.Item().SkipOnce().Background(Colors.Grey.Lighten3).Row(row =>
{
row.RelativeItem().Text(x =>
row.RelativeColumn().Text(x =>
{
x.CurrentPageNumber();
x.Span(" / ");
x.TotalPages();
});
row.RelativeItem().AlignRight().Text("Footer for every page except header");
row.RelativeColumn().AlignRight().Text("Footer for every page except header");
});
});
}
+10 -11
View File
@@ -1,4 +1,3 @@
using System;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
@@ -18,11 +17,11 @@ namespace QuestPDF.ReportSample.Layouts
{
container
.ShowEntire()
.Column(column =>
.Stack(stack =>
{
column.Spacing(5);
column.Item().Element(PhotoWithMaps);
column.Item().Element(PhotoDetails);
stack.Spacing(5);
stack.Item().Element(PhotoWithMaps);
stack.Item().Element(PhotoDetails);
});
}
@@ -31,14 +30,14 @@ namespace QuestPDF.ReportSample.Layouts
container
.Row(row =>
{
row.RelativeItem(2).AspectRatio(4 / 3f).Component<ImagePlaceholder>();
row.RelativeColumn(2).AspectRatio(4 / 3f).Component<ImagePlaceholder>();
row.RelativeItem().PaddingLeft(5).Column(column =>
row.RelativeColumn().PaddingLeft(5).Stack(stack =>
{
column.Spacing(7f);
stack.Spacing(7f);
column.Item().AspectRatio(4 / 3f).Component<ImagePlaceholder>();
column.Item().AspectRatio(4 / 3f).Component<ImagePlaceholder>();
stack.Item().AspectRatio(4 / 3f).Component<ImagePlaceholder>();
stack.Item().AspectRatio(4 / 3f).Component<ImagePlaceholder>();
});
});
}
@@ -48,7 +47,7 @@ namespace QuestPDF.ReportSample.Layouts
container.Border(0.75f).BorderColor(Colors.Grey.Medium).Grid(grid =>
{
grid.Columns(6);
grid.Item().LabelCell().Text("Date");
grid.Item(2).ValueCell().Text(Model.Date?.ToString("g") ?? string.Empty);
grid.Item().LabelCell().Text("Location");
@@ -21,26 +21,25 @@ namespace QuestPDF.ReportSample.Layouts
.Decoration(decoration =>
{
decoration
.Before()
.Header()
.PaddingBottom(5)
.Text(Model.Title)
.Style(Typography.Headline);
.Text(Model.Title, Typography.Headline);
decoration.Content().Border(0.75f).BorderColor(Colors.Grey.Medium).Column(column =>
decoration.Content().Border(0.75f).BorderColor(Colors.Grey.Medium).Stack(stack =>
{
foreach (var part in Model.Parts)
{
column.Item().EnsureSpace(25).Row(row =>
stack.Item().EnsureSpace(25).Row(row =>
{
row.ConstantItem(150).LabelCell().Text(part.Label);
var frame = row.RelativeItem().ValueCell();
row.ConstantColumn(150).LabelCell().Text(part.Label);
var frame = row.RelativeColumn().ValueCell();
if (part is ReportSectionText text)
frame.ShowEntire().Text(text.Text);
if (part is ReportSectionMap map)
frame.Element(x => MapElement(x, map));
if (part is ReportSectionPhotos photos)
frame.Element(x => PhotosElement(x, photos));
});
@@ -57,12 +56,12 @@ namespace QuestPDF.ReportSample.Layouts
return;
}
container.ShowEntire().Column(column =>
container.ShowEntire().Stack(stack =>
{
column.Spacing(5);
stack.Spacing(5);
column.Item().MaxWidth(250).AspectRatio(4 / 3f).Component<ImagePlaceholder>();
column.Item().Text(model.Location.Format());
stack.Item().MaxWidth(250).AspectRatio(4 / 3f).Component<ImagePlaceholder>();
stack.Item().Text(model.Location.Format());
});
}
@@ -70,7 +69,7 @@ namespace QuestPDF.ReportSample.Layouts
{
if (model.PhotoCount == 0)
{
container.Text("No photos").Style(Typography.Normal);
container.Text("No photos", Typography.Normal);
return;
}
@@ -83,8 +82,6 @@ namespace QuestPDF.ReportSample.Layouts
.Range(0, model.PhotoCount)
.ToList()
.ForEach(x => grid.Item().AspectRatio(4 / 3f).Component<ImagePlaceholder>());
//grid.Item().AspectRatio(4 / 3f).Width(100000).Component<ImagePlaceholder>();
});
}
}
+20 -20
View File
@@ -39,28 +39,28 @@ namespace QuestPDF.ReportSample.Layouts
page.Footer().AlignCenter().Text(text =>
{
text.CurrentPageNumber().Format(x => x?.FormatAsRomanNumeral() ?? "-----");
text.CurrentPageNumber();
text.Span(" / ");
text.TotalPages().Format(x => x?.FormatAsRomanNumeral() ?? "-----");
text.TotalPages();
});
});
}
private void ComposeHeader(IContainer container)
{
container.Column(column =>
container.Stack(stack =>
{
column.Item().Row(row =>
stack.Item().Row(row =>
{
row.Spacing(50);
row.RelativeItem().PaddingTop(-10).Text(Model.Title).Style(Typography.Title);
row.ConstantItem(90).Hyperlink("https://www.questpdf.com").MaxHeight(30).Component<ImagePlaceholder>();
row.RelativeColumn().PaddingTop(-10).Text(Model.Title, Typography.Title);
row.ConstantColumn(90).ExternalLink("https://www.questpdf.com").MaxHeight(30).Component<ImagePlaceholder>();
});
column.Item().ShowOnce().PaddingVertical(15).Border(1f).BorderColor(Colors.Grey.Lighten1).ExtendHorizontal();
stack.Item().ShowOnce().PaddingVertical(15).Border(1f).BorderColor(Colors.Grey.Lighten1).ExtendHorizontal();
column.Item().ShowOnce().Grid(grid =>
stack.Item().ShowOnce().Grid(grid =>
{
grid.Columns(2);
grid.Spacing(5);
@@ -69,7 +69,7 @@ namespace QuestPDF.ReportSample.Layouts
{
grid.Item().Text(text =>
{
text.Span($"{field.Label}: ").SemiBold();
text.Span($"{field.Label}: ", TextStyle.Default.SemiBold());
text.Span(field.Value);
});
}
@@ -79,23 +79,23 @@ namespace QuestPDF.ReportSample.Layouts
void ComposeContent(IContainer container)
{
container.PaddingVertical(20).Column(column =>
container.PaddingVertical(20).Stack(stack =>
{
column.Spacing(20);
stack.Spacing(20);
stack.Item().Component(new TableOfContentsTemplate(Model.Sections));
column.Item().Component(new TableOfContentsTemplate(Model.Sections));
column.Item().PageBreak();
stack.Item().PageBreak();
foreach (var section in Model.Sections)
column.Item().Section(section.Title).Component(new SectionTemplate(section));
column.Item().PageBreak();
column.Item().Section("Photos");
stack.Item().Location(section.Title).Component(new SectionTemplate(section));
stack.Item().PageBreak();
stack.Item().Location("Photos");
foreach (var photo in Model.Photos)
column.Item().Component(new PhotoTemplate(photo));
stack.Item().Component(new PhotoTemplate(photo));
});
}
}
}
}
@@ -1,9 +1,6 @@
using System.Collections.Generic;
using System.Drawing;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using SkiaSharp;
namespace QuestPDF.ReportSample.Layouts
{
@@ -22,19 +19,18 @@ namespace QuestPDF.ReportSample.Layouts
.Decoration(decoration =>
{
decoration
.Before()
.Header()
.PaddingBottom(5)
.Text("Table of contents")
.Style(Typography.Headline);
.Text("Table of contents", Typography.Headline);
decoration.Content().Column(column =>
decoration.Content().Stack(stack =>
{
column.Spacing(5);
stack.Spacing(5);
for (var i = 0; i < Sections.Count; i++)
column.Item().Element(c => DrawLink(c, i+1, Sections[i].Title));
stack.Item().Element(c => DrawLink(c, i+1, Sections[i].Title));
column.Item().Element(c => DrawLink(c, Sections.Count+1, "Photos"));
stack.Item().Element(c => DrawLink(c, Sections.Count+1, "Photos"));
});
});
}
@@ -42,39 +38,13 @@ namespace QuestPDF.ReportSample.Layouts
private void DrawLink(IContainer container, int number, string locationName)
{
container
.SectionLink(locationName)
.InternalLink(locationName)
.Row(row =>
{
row.ConstantItem(20).Text($"{number}.");
row.AutoItem().Text(locationName);
row.RelativeItem().PaddingHorizontal(2).AlignBottom().TranslateY(-3).Height(1).Canvas((canvas, space) =>
{
// best to statically cache
using var paint = new SKPaint
{
StrokeWidth = space.Height,
PathEffect = SKPathEffect.CreateDash(new float[] { 1, 3 }, 0)
};
canvas.DrawLine(0, 0, space.Width, 0, paint);
});
row.AutoItem().Text(text =>
{
text.BeginPageNumberOfSection(locationName);
text.Span(" - ");
text.EndPageNumberOfSection(locationName);
var lengthStyle = TextStyle.Default.FontColor(Colors.Grey.Medium);
text.TotalPagesWithinSection(locationName).Style(lengthStyle).Format(x =>
{
var formatted = x == 1 ? "1 page long" : $"{x} pages long";
return $" ({formatted})";
});
});
row.ConstantColumn(25).Text($"{number}.");
row.RelativeColumn().Text(locationName);
row.ConstantColumn(150).AlignRight().Text(text => text.PageNumberOfLocation(locationName));
});
}
}
}
}
+2 -2
View File
@@ -51,7 +51,7 @@ namespace QuestPDF.ReportSample
Content = documentContainer.Compose();
PageContext = new PageContext();
DocumentGenerator.RenderPass(PageContext, new FreeCanvas(), Content);
DocumentGenerator.RenderPass(PageContext, new FreeCanvas(), Content, Metadata, null);
var sw = new Stopwatch();
sw.Start();
@@ -69,7 +69,7 @@ namespace QuestPDF.ReportSample
[Benchmark]
public void GenerationTest()
{
DocumentGenerator.RenderPass(PageContext, new FreeCanvas(), Content);
DocumentGenerator.RenderPass(PageContext, new FreeCanvas(), Content, Metadata, null);
}
}
}
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
<IsPackable>false</IsPackable>
<LangVersion>8</LangVersion>
<RootNamespace>QuestPDF.ReportSample</RootNamespace>
@@ -9,10 +9,10 @@
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.1" />
<PackageReference Include="nunit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.3.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.0" />
<PackageReference Include="SkiaSharp" Version="2.88.3" />
<PackageReference Include="nunit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="SkiaSharp" Version="2.80.3" />
</ItemGroup>
<ItemGroup>

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

+8 -10
View File
@@ -6,7 +6,6 @@ using NUnit.Framework;
using QuestPDF.Drawing;
using QuestPDF.Fluent;
using QuestPDF.Infrastructure;
using QuestPDF.Previewer;
using QuestPDF.ReportSample.Layouts;
namespace QuestPDF.ReportSample
@@ -25,13 +24,11 @@ namespace QuestPDF.ReportSample
[Test]
public void GenerateAndShowPdf()
{
Report.ShowInPreviewer();
//ImagePlaceholder.Solid = true;
//
// var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"test_result.pdf");
// Report.GeneratePdf(path);
// Process.Start("explorer.exe", path);
ImagePlaceholder.Solid = true;
var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"test_result.pdf");
Report.GeneratePdf(path);
Process.Start("explorer.exe", path);
}
[Test]
@@ -51,10 +48,11 @@ namespace QuestPDF.ReportSample
Report.Compose(container);
var content = container.Compose();
var metadata = Report.GetMetadata();
var pageContext = new PageContext();
DocumentGenerator.RenderPass(pageContext, new FreeCanvas(), content);
DocumentGenerator.RenderPass(pageContext, new FreeCanvas(), content);
DocumentGenerator.RenderPass(pageContext, new FreeCanvas(), content, metadata, null);
DocumentGenerator.RenderPass(pageContext, new FreeCanvas(), content, metadata, null);
}
}
}
+4 -4
View File
@@ -6,8 +6,8 @@ namespace QuestPDF.ReportSample
{
public static class Typography
{
public static TextStyle Title => TextStyle.Default.FontFamily(Fonts.Lato).FontColor(Colors.Blue.Darken3).FontSize(26).Black();
public static TextStyle Headline => TextStyle.Default.FontFamily(Fonts.Lato).FontColor(Colors.Blue.Medium).FontSize(16).SemiBold();
public static TextStyle Normal => TextStyle.Default.FontFamily(Fonts.Lato).FontColor(Colors.Black).FontSize(10).LineHeight(1.2f);
public static TextStyle Title => TextStyle.Default.FontType(Fonts.Calibri).Color(Colors.Blue.Darken3).Size(26).Black();
public static TextStyle Headline => TextStyle.Default.FontType(Fonts.Calibri).Color(Colors.Blue.Medium).Size(16).SemiBold();
public static TextStyle Normal => TextStyle.Default.FontType(Fonts.Verdana).Color(Colors.Black).Size(10).LineHeight(1.2f);
}
}
}
-40
View File
@@ -153,9 +153,7 @@ namespace QuestPDF.UnitTests
Ratio = 2f
})
.DrawElement(new Size(500, 200))
.ExpectCanvasTranslate(0, 0)
.ExpectChildDraw(new Size(400, 200))
.ExpectCanvasTranslate(0, 0)
.CheckDrawResult();
}
@@ -170,45 +168,7 @@ namespace QuestPDF.UnitTests
Ratio = 2f
})
.DrawElement(new Size(400, 300))
.ExpectCanvasTranslate(0, 0)
.ExpectChildDraw(new Size(400, 200))
.ExpectCanvasTranslate(0, 0)
.CheckDrawResult();
}
[Test]
public void DrawChild_PerWidth_RightToLeft()
{
TestPlan
.For(x => new AspectRatio
{
Child = x.CreateChild(),
Option = AspectRatioOption.FitArea,
Ratio = 2f,
ContentDirection = ContentDirection.RightToLeft
})
.DrawElement(new Size(500, 200))
.ExpectCanvasTranslate(100, 0)
.ExpectChildDraw(new Size(400, 200))
.ExpectCanvasTranslate(-100, 0)
.CheckDrawResult();
}
[Test]
public void DrawChild_PerHeight_RightToLeft()
{
TestPlan
.For(x => new AspectRatio
{
Child = x.CreateChild(),
Option = AspectRatioOption.FitArea,
Ratio = 2f,
ContentDirection = ContentDirection.RightToLeft
})
.DrawElement(new Size(400, 300))
.ExpectCanvasTranslate(0, 0)
.ExpectChildDraw(new Size(400, 200))
.ExpectCanvasTranslate(0, 0)
.CheckDrawResult();
}
}
-38
View File
@@ -35,9 +35,7 @@ namespace QuestPDF.UnitTests
})
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure(expectedInput: new Size(400, 300), returns: SpacePlan.PartialRender(200, 100))
.ExpectCanvasTranslate(0, 0)
.ExpectChildDraw(new Size(200, 100))
.ExpectCanvasTranslate(0, 0)
.CheckDrawResult();
}
@@ -51,43 +49,7 @@ namespace QuestPDF.UnitTests
})
.MeasureElement(new Size(500, 400))
.ExpectChildMeasure(expectedInput: new Size(500, 400), returns: SpacePlan.FullRender(300, 200))
.ExpectCanvasTranslate(0, 0)
.ExpectChildDraw(new Size(300, 200))
.ExpectCanvasTranslate(0, 0)
.CheckDrawResult();
}
[Test]
public void Measure_PartialRender_RightToLeft()
{
TestPlan
.For(x => new MinimalBox
{
Child = x.CreateChild(),
ContentDirection = ContentDirection.RightToLeft
})
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure(expectedInput: new Size(400, 300), returns: SpacePlan.PartialRender(200, 100))
.ExpectCanvasTranslate(200, 0)
.ExpectChildDraw(new Size(200, 100))
.ExpectCanvasTranslate(-200, 0)
.CheckDrawResult();
}
[Test]
public void Measure_FullRender_RightToLeft()
{
TestPlan
.For(x => new MinimalBox
{
Child = x.CreateChild(),
ContentDirection = ContentDirection.RightToLeft
})
.MeasureElement(new Size(500, 400))
.ExpectChildMeasure(expectedInput: new Size(500, 400), returns: SpacePlan.FullRender(350, 200))
.ExpectCanvasTranslate(150, 0)
.ExpectChildDraw(new Size(350, 200))
.ExpectCanvasTranslate(-150, 0)
.CheckDrawResult();
}
}
+80 -83
View File
@@ -9,27 +9,35 @@ namespace QuestPDF.UnitTests
[TestFixture]
public class DecorationTests
{
private Decoration CreateDecoration(TestPlan testPlan)
{
return new Decoration
{
Before = testPlan.CreateChild("before"),
Content = testPlan.CreateChild("content"),
After = testPlan.CreateChild("after"),
};
}
#region Measure
[Test]
public void Measure_ReturnsWrap_WhenBeforeReturnsWrap()
public void Measure_ReturnsWrap_WhenDecorationReturnsWrap()
{
TestPlan
.For(CreateDecoration)
.For(x => new BinaryDecoration
{
Type = DecorationType.Append,
DecorationElement = x.CreateChild("decoration"),
ContentElement = x.CreateChild("content")
})
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("before", new Size(400, 300), SpacePlan.Wrap())
.ExpectChildMeasure("after", new Size(400, 300), SpacePlan.FullRender(100, 50))
.ExpectChildMeasure("content", new Size(400, 250), SpacePlan.FullRender(100, 100))
.ExpectChildMeasure("decoration", new Size(400, 300), SpacePlan.Wrap())
.CheckMeasureResult(SpacePlan.Wrap());
}
[Test]
public void Measure_ReturnsWrap_WhenDecorationReturnsPartialRender()
{
TestPlan
.For(x => new BinaryDecoration
{
Type = DecorationType.Append,
DecorationElement = x.CreateChild("decoration"),
ContentElement = x.CreateChild("content")
})
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("decoration", new Size(400, 300), SpacePlan.PartialRender(300, 200))
.CheckMeasureResult(SpacePlan.Wrap());
}
@@ -37,100 +45,89 @@ namespace QuestPDF.UnitTests
public void Measure_ReturnsWrap_WhenContentReturnsWrap()
{
TestPlan
.For(CreateDecoration)
.For(x => new BinaryDecoration
{
Type = DecorationType.Append,
DecorationElement = x.CreateChild("decoration"),
ContentElement = x.CreateChild("content")
})
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("before", new Size(400, 300), SpacePlan.FullRender(100, 50))
.ExpectChildMeasure("after", new Size(400, 300), SpacePlan.FullRender(100, 50))
.ExpectChildMeasure("decoration", new Size(400, 300), SpacePlan.FullRender(300, 100))
.ExpectChildMeasure("content", new Size(400, 200), SpacePlan.Wrap())
.CheckMeasureResult(SpacePlan.Wrap());
}
[Test]
public void Measure_ReturnsWrap_WhenAfterReturnsWrap()
public void Measure_ReturnsPartialRender_WhenContentReturnsPartialRender()
{
TestPlan
.For(CreateDecoration)
.For(x => new BinaryDecoration
{
Type = DecorationType.Append,
DecorationElement = x.CreateChild("decoration"),
ContentElement = x.CreateChild("content")
})
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("before", new Size(400, 300), SpacePlan.FullRender(100, 50))
.ExpectChildMeasure("after", new Size(400, 300), SpacePlan.Wrap())
.ExpectChildMeasure("content", new Size(400, 250), SpacePlan.FullRender(100, 100))
.CheckMeasureResult(SpacePlan.Wrap());
.ExpectChildMeasure("decoration", new Size(400, 300), SpacePlan.FullRender(300, 100))
.ExpectChildMeasure("content", new Size(400, 200), SpacePlan.PartialRender(200, 150))
.CheckMeasureResult(SpacePlan.PartialRender(400, 250));
}
[Test]
public void Measure_ReturnsWrap_WhenBeforeReturnsPartialRender()
public void Measure_ReturnsFullRender_WhenContentReturnsFullRender()
{
TestPlan
.For(CreateDecoration)
.For(x => new BinaryDecoration
{
Type = DecorationType.Append,
DecorationElement = x.CreateChild("decoration"),
ContentElement = x.CreateChild("content")
})
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("before", new Size(400, 300), SpacePlan.PartialRender(100, 50))
.ExpectChildMeasure("after", new Size(400, 300), SpacePlan.FullRender(100, 50))
.ExpectChildMeasure("content", new Size(400, 250), SpacePlan.FullRender(100, 100))
.CheckMeasureResult(SpacePlan.Wrap());
.ExpectChildMeasure("decoration", new Size(400, 300), SpacePlan.FullRender(300, 100))
.ExpectChildMeasure("content", new Size(400, 200), SpacePlan.FullRender(200, 150))
.CheckMeasureResult(SpacePlan.FullRender(400, 250));
}
[Test]
public void Measure_ReturnsWrap_WhenAfterReturnsPartialRender()
{
TestPlan
.For(CreateDecoration)
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("before", new Size(400, 300), SpacePlan.FullRender(100, 50))
.ExpectChildMeasure("after", new Size(400, 300), SpacePlan.PartialRender(100, 50))
.ExpectChildMeasure("content", new Size(400, 250), SpacePlan.FullRender(100, 100))
.CheckMeasureResult(SpacePlan.Wrap());
}
[Test]
public void Measure_ReturnsWrap_WhenContentReturnsPartialRender()
{
TestPlan
.For(CreateDecoration)
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("before", new Size(400, 300), SpacePlan.FullRender(100, 50))
.ExpectChildMeasure("after", new Size(400, 300), SpacePlan.FullRender(100, 50))
.ExpectChildMeasure("content", new Size(400, 200), SpacePlan.PartialRender(150, 100))
.CheckMeasureResult(SpacePlan.PartialRender(150, 200));
}
[Test]
public void Measure_ReturnsWrap_WhenContentReturnsFullRender()
{
TestPlan
.For(CreateDecoration)
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("before", new Size(400, 300), SpacePlan.FullRender(100, 50))
.ExpectChildMeasure("after", new Size(400, 300), SpacePlan.FullRender(100, 50))
.ExpectChildMeasure("content", new Size(400, 200), SpacePlan.FullRender(150, 100))
.CheckMeasureResult(SpacePlan.FullRender(150, 200));
}
#endregion
#region Draw
[Test]
public void Draw_Prepend()
{
TestPlan
.For(x => new BinaryDecoration
{
Type = DecorationType.Prepend,
DecorationElement = x.CreateChild("decoration"),
ContentElement = x.CreateChild("content")
})
.DrawElement(new Size(400, 300))
.ExpectChildMeasure("decoration", new Size(400, 300), SpacePlan.FullRender(300, 100))
.ExpectChildDraw("decoration", new Size(400, 100))
.ExpectCanvasTranslate(0, 100)
.ExpectChildDraw("content", new Size(400, 200))
.ExpectCanvasTranslate(0, -100)
.CheckDrawResult();
}
[Test]
public void Draw_Append()
{
TestPlan
.For(CreateDecoration)
.For(x => new BinaryDecoration
{
Type = DecorationType.Append,
DecorationElement = x.CreateChild("decoration"),
ContentElement = x.CreateChild("content")
})
.DrawElement(new Size(400, 300))
.ExpectChildMeasure("before", new Size(400, 300), SpacePlan.FullRender(200, 40))
.ExpectChildMeasure("after", new Size(400, 300), SpacePlan.FullRender(200, 60))
.ExpectChildMeasure("content", new Size(400, 200), SpacePlan.FullRender(300, 100))
.ExpectCanvasTranslate(0, 0)
.ExpectChildDraw("before", new Size(300, 40))
.ExpectCanvasTranslate(0, 0)
.ExpectCanvasTranslate(0, 40)
.ExpectChildDraw("content", new Size(300, 100))
.ExpectCanvasTranslate(0, -40)
.ExpectCanvasTranslate(0, 140)
.ExpectChildDraw("after", new Size(300, 60))
.ExpectCanvasTranslate(0, -140)
.ExpectChildMeasure("decoration", new Size(400, 300), SpacePlan.FullRender(300, 100))
.ExpectChildDraw("content", new Size(400, 200))
.ExpectCanvasTranslate(0, 200)
.ExpectChildDraw("decoration", new Size(400, 100))
.ExpectCanvasTranslate(0, -200)
.CheckDrawResult();
}
+1 -1
View File
@@ -8,7 +8,7 @@ namespace QuestPDF.UnitTests
public class ExternalLinkTests
{
[Test]
public void Measure() => SimpleContainerTests.Measure<Hyperlink>();
public void Measure() => SimpleContainerTests.Measure<ExternalLink>();
// TODO: consider tests for the Draw method
}
-146
View File
@@ -1,146 +0,0 @@
using FluentAssertions;
using NUnit.Framework;
using QuestPDF.Drawing;
using SkiaSharp;
using static SkiaSharp.SKFontStyleSlant;
namespace QuestPDF.UnitTests
{
[TestFixture]
public class FontStyleSetTests
{
private void ExpectComparisonOrder(SKFontStyle target, SKFontStyle[] styles)
{
for (var i = 0; i < styles.Length - 1; i++)
{
var currentStyle = styles[i];
var nextStyle = styles[i + 1];
FontStyleSet.IsBetterMatch(target, currentStyle, nextStyle).Should().BeTrue();
FontStyleSet.IsBetterMatch(target, nextStyle, currentStyle).Should().BeFalse();
}
}
[Test]
public void FontStyleSet_IsBetterMatch_CondensedWidth()
{
var styles = new[]
{
new SKFontStyle(500, 5, Upright),
new SKFontStyle(500, 4, Upright),
new SKFontStyle(500, 3, Upright),
new SKFontStyle(500, 6, Upright)
};
ExpectComparisonOrder(new SKFontStyle(500, 5, Upright), styles);
}
[Test]
public void FontStyleSet_IsBetterMatch_ExpandedWidth()
{
var styles = new[]
{
new SKFontStyle(500, 6, Upright),
new SKFontStyle(500, 7, Upright),
new SKFontStyle(500, 8, Upright),
new SKFontStyle(500, 5, Upright)
};
ExpectComparisonOrder(new SKFontStyle(500, 6, Upright), styles);
}
[Test]
public void FontStyleSet_IsBetterMatch_ItalicSlant()
{
var styles = new[]
{
new SKFontStyle(500, 5, Italic),
new SKFontStyle(500, 5, Oblique),
new SKFontStyle(500, 5, Upright)
};
ExpectComparisonOrder(new SKFontStyle(500, 5, Italic), styles);
}
[Test]
public void FontStyleSet_IsBetterMatch_ObliqueSlant()
{
var styles = new[]
{
new SKFontStyle(500, 5, Oblique),
new SKFontStyle(500, 5, Italic),
new SKFontStyle(500, 5, Upright)
};
ExpectComparisonOrder(new SKFontStyle(500, 5, Oblique), styles);
}
[Test]
public void FontStyleSet_IsBetterMatch_UprightSlant()
{
var styles = new[]
{
new SKFontStyle(500, 5, Upright),
new SKFontStyle(500, 5, Oblique),
new SKFontStyle(500, 5, Italic)
};
ExpectComparisonOrder(new SKFontStyle(500, 5, Upright), styles);
}
[Test]
public void FontStyleSet_IsBetterMatch_ThinWeight()
{
var styles = new[]
{
new SKFontStyle(300, 5, Upright),
new SKFontStyle(200, 5, Upright),
new SKFontStyle(100, 5, Upright),
new SKFontStyle(400, 5, Upright)
};
ExpectComparisonOrder(new SKFontStyle(300, 5, Upright), styles);
}
[Test]
public void FontStyleSet_IsBetterMatch_RegularWeight()
{
var styles = new[]
{
new SKFontStyle(500, 5, Upright),
new SKFontStyle(300, 5, Upright),
new SKFontStyle(100, 5, Upright),
new SKFontStyle(600, 5, Upright)
};
ExpectComparisonOrder(new SKFontStyle(400, 5, Upright), styles);
}
[Test]
public void FontStyleSet_IsBetterMatch_BoldWeight()
{
var styles = new[]
{
new SKFontStyle(600, 5, Upright),
new SKFontStyle(700, 5, Upright),
new SKFontStyle(800, 5, Upright),
new SKFontStyle(500, 5, Upright)
};
ExpectComparisonOrder(new SKFontStyle(600, 5, Upright), styles);
}
[Test]
public void FontStyleSet_RespectsPriority()
{
var styles = new[]
{
new SKFontStyle(600, 5, Italic),
new SKFontStyle(600, 6, Upright),
new SKFontStyle(500, 6, Italic)
};
ExpectComparisonOrder(new SKFontStyle(500, 5, Upright), styles);
}
}
}
+54 -54
View File
@@ -39,26 +39,26 @@ namespace QuestPDF.UnitTests
// assert
var expected = new Container();
expected.Column(column =>
expected.Stack(stack =>
{
column.Item().Row(row =>
stack.Item().Row(row =>
{
row.RelativeItem(6).Element(childA);
row.RelativeItem(4).Element(childB);
row.RelativeItem(2);
row.RelativeColumn(6).Element(childA);
row.RelativeColumn(4).Element(childB);
row.RelativeColumn(2);
});
column.Item().Row(row =>
stack.Item().Row(row =>
{
row.RelativeItem(4).Element(childC);
row.RelativeItem(2).Element(childD);
row.RelativeItem(6);
row.RelativeColumn(4).Element(childC);
row.RelativeColumn(2).Element(childD);
row.RelativeColumn(6);
});
column.Item().Row(row =>
stack.Item().Row(row =>
{
row.RelativeItem(8).Element(childE);
row.RelativeItem(4);
row.RelativeColumn(8).Element(childE);
row.RelativeColumn(4);
});
});
@@ -93,29 +93,29 @@ namespace QuestPDF.UnitTests
// assert
var expected = new Container();
expected.Column(column =>
expected.Stack(stack =>
{
column.Item().Row(row =>
stack.Item().Row(row =>
{
row.RelativeItem(1);
row.RelativeItem(6).Element(childA);
row.RelativeItem(4).Element(childB);
row.RelativeItem(1);
row.RelativeColumn(1);
row.RelativeColumn(6).Element(childA);
row.RelativeColumn(4).Element(childB);
row.RelativeColumn(1);
});
column.Item().Row(row =>
stack.Item().Row(row =>
{
row.RelativeItem(3);
row.RelativeItem(4).Element(childC);
row.RelativeItem(2).Element(childD);
row.RelativeItem(3);
row.RelativeColumn(3);
row.RelativeColumn(4).Element(childC);
row.RelativeColumn(2).Element(childD);
row.RelativeColumn(3);
});
column.Item().Row(row =>
stack.Item().Row(row =>
{
row.RelativeItem(2);
row.RelativeItem(8).Element(childE);
row.RelativeItem(2);
row.RelativeColumn(2);
row.RelativeColumn(8).Element(childE);
row.RelativeColumn(2);
});
});
@@ -150,26 +150,26 @@ namespace QuestPDF.UnitTests
// assert
var expected = new Container();
expected.Column(column =>
expected.Stack(stack =>
{
column.Item().Row(row =>
stack.Item().Row(row =>
{
row.RelativeItem(2);
row.RelativeItem(6).Element(childA);
row.RelativeItem(4).Element(childB);
row.RelativeColumn(2);
row.RelativeColumn(6).Element(childA);
row.RelativeColumn(4).Element(childB);
});
column.Item().Row(row =>
stack.Item().Row(row =>
{
row.RelativeItem(6);
row.RelativeItem(4).Element(childC);
row.RelativeItem(2).Element(childD);
row.RelativeColumn(6);
row.RelativeColumn(4).Element(childC);
row.RelativeColumn(2).Element(childD);
});
column.Item().Row(row =>
stack.Item().Row(row =>
{
row.RelativeItem(4);
row.RelativeItem(8).Element(childE);
row.RelativeColumn(4);
row.RelativeColumn(8).Element(childE);
});
});
@@ -210,36 +210,36 @@ namespace QuestPDF.UnitTests
// assert
var expected = new Container();
expected.Column(column =>
expected.Stack(stack =>
{
column.Spacing(20);
stack.Spacing(20);
column.Item().Row(row =>
stack.Item().Row(row =>
{
row.Spacing(30);
row.RelativeItem(3);
row.RelativeItem(5).Element(childA);
row.RelativeItem(5).Element(childB);
row.RelativeItem(3);
row.RelativeColumn(3);
row.RelativeColumn(5).Element(childA);
row.RelativeColumn(5).Element(childB);
row.RelativeColumn(3);
});
column.Item().Row(row =>
stack.Item().Row(row =>
{
row.Spacing(30);
row.RelativeItem(3);
row.RelativeItem(10).Element(childC);
row.RelativeItem(3);
row.RelativeColumn(3);
row.RelativeColumn(10).Element(childC);
row.RelativeColumn(3);
});
column.Item().Row(row =>
stack.Item().Row(row =>
{
row.Spacing(30);
row.RelativeItem(2);
row.RelativeItem(12).Element(childD);
row.RelativeItem(2);
row.RelativeColumn(2);
row.RelativeColumn(12).Element(childD);
row.RelativeColumn(2);
});
});
+1 -1
View File
@@ -8,7 +8,7 @@ namespace QuestPDF.UnitTests
public class InternalLinkTests
{
[Test]
public void Measure() => SimpleContainerTests.Measure<SectionLink>();
public void Measure() => SimpleContainerTests.Measure<InternalLink>();
// TODO: consider tests for the Draw method
}
+1 -1
View File
@@ -8,7 +8,7 @@ namespace QuestPDF.UnitTests
public class InternalLocationTests
{
[Test]
public void Measure() => SimpleContainerTests.Measure<SectionLink>();
public void Measure() => SimpleContainerTests.Measure<InternalLink>();
// TODO: consider tests for the Draw method
}
-54
View File
@@ -1,54 +0,0 @@
using System;
using NUnit.Framework;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using QuestPDF.Previewer;
namespace QuestPDF.UnitTests
{
public class PreviewerTests
{
[Test]
public void SendError()
{
Document
.Create(container =>
{
container.Page(page =>
{
page.Size(PageSizes.A4);
page.Margin(2, Unit.Centimetre);
page.PageColor(Colors.White);
page.DefaultTextStyle(x => x.FontSize(20));
page.Header()
.Text("Hello PDF!")
.SemiBold().FontSize(36).FontColor(Colors.Blue.Medium);
page.Content()
.PaddingVertical(1, Unit.Centimetre)
.Column(x =>
{
x.Spacing(20);
x.Item().Text(Placeholders.LoremIpsum());
x.Item().Image(Placeholders.Image(200, 100));
});
page.Footer()
.AlignCenter()
.Text(x =>
{
//throw new Exception("Dupa!");
x.Span("Page ");
x.CurrentPageNumber();
});
});
})
.ShowInPreviewer();
}
}
}
+6 -6
View File
@@ -1,16 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.8.0" />
<PackageReference Include="nunit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.3.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.0" />
<PackageReference Include="SkiaSharp" Version="2.88.3" />
<PackageReference Include="FluentAssertions" Version="6.1.0" />
<PackageReference Include="nunit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.0" />
<PackageReference Include="SkiaSharp" Version="2.80.3" />
</ItemGroup>
<ItemGroup>
+267
View File
@@ -0,0 +1,267 @@
using NUnit.Framework;
using QuestPDF.Drawing;
using QuestPDF.Elements;
using QuestPDF.Fluent;
using QuestPDF.Infrastructure;
using QuestPDF.UnitTests.TestEngine;
namespace QuestPDF.UnitTests
{
[TestFixture]
public class RowTests
{
#region Measure
[Test]
public void Measure_ReturnsWrap_WhenLeftChildReturnsWrap()
{
TestPlan
.For(x => new BinaryRow
{
Left = x.CreateChild("left"),
Right = x.CreateChild("right")
})
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("left", new Size(400, 300), SpacePlan.Wrap())
.CheckMeasureResult(SpacePlan.Wrap());
}
[Test]
public void Measure_ReturnsWrap_WhenRightChildReturnsWrap()
{
TestPlan
.For(x => new BinaryRow
{
Left = x.CreateChild("left"),
Right = x.CreateChild("right")
})
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("left", new Size(400, 300), SpacePlan.FullRender(250, 150))
.ExpectChildMeasure("right", new Size(150, 300), SpacePlan.Wrap())
.CheckMeasureResult(SpacePlan.Wrap());
}
[Test]
public void Measure_ReturnsPartialRender_WhenLeftChildReturnsPartialRender()
{
TestPlan
.For(x => new BinaryRow
{
Left = x.CreateChild("left"),
Right = x.CreateChild("right")
})
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("left", new Size(400, 300), SpacePlan.PartialRender(250, 150))
.ExpectChildMeasure("right", new Size(150, 300), SpacePlan.FullRender(100, 100))
.CheckMeasureResult(SpacePlan.PartialRender(350, 150));
}
[Test]
public void Measure_ReturnsPartialRender_WhenRightChildReturnsPartialRender()
{
TestPlan
.For(x => new BinaryRow
{
Left = x.CreateChild("left"),
Right = x.CreateChild("right")
})
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("left", new Size(400, 300), SpacePlan.FullRender(250, 150))
.ExpectChildMeasure("right", new Size(150, 300), SpacePlan.PartialRender(100, 100))
.CheckMeasureResult(SpacePlan.PartialRender(350, 150));
}
[Test]
public void Measure_ReturnsFullRender_WhenBothChildrenReturnFullRender()
{
TestPlan
.For(x => new BinaryRow
{
Left = x.CreateChild("left"),
Right = x.CreateChild("right")
})
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("left", new Size(400, 300), SpacePlan.FullRender(200, 150))
.ExpectChildMeasure("right", new Size(200, 300), SpacePlan.FullRender(100, 100))
.CheckMeasureResult(SpacePlan.FullRender(300, 150));
}
#endregion
#region Draw
[Test]
public void Draw()
{
TestPlan
.For(x => new BinaryRow
{
Left = x.CreateChild("left"),
Right = x.CreateChild("right")
})
.DrawElement(new Size(400, 300))
.ExpectChildMeasure("left", new Size(400, 300), SpacePlan.FullRender(250, 150))
.ExpectChildDraw("left", new Size(250, 300))
.ExpectCanvasTranslate(250, 0)
.ExpectChildDraw("right", new Size(150, 300))
.ExpectCanvasTranslate(-250, 0)
.CheckDrawResult();
}
#endregion
#region Structure
[Test]
public void Structure_RelativeColumnsHandling()
{
// arrange
var childA = TestPlan.CreateUniqueElement();
var childB = TestPlan.CreateUniqueElement();
var childC = TestPlan.CreateUniqueElement();
var childD = TestPlan.CreateUniqueElement();
var childE = TestPlan.CreateUniqueElement();
const int spacing = 25;
var availableSpace = new Size(1100, 400);
// act
var value = new Container();
value.Row(row =>
{
row.Spacing(spacing);
row.ConstantColumn(150).Element(childA);
row.ConstantColumn(250).Element(childB);
row.RelativeColumn(1).Element(childC);
row.RelativeColumn(2).Element(childD);
row.RelativeColumn(3).Element(childE);
});
// assert
var expected = new Container();
expected.Row(row =>
{
row.Spacing(spacing);
row.ConstantColumn(150).Element(childA);
row.ConstantColumn(250).Element(childB);
row.ConstantColumn(100).Element(childC);
row.ConstantColumn(200).Element(childD);
row.ConstantColumn(300).Element(childE);
});
TestPlan.CompareOperations(value, expected, availableSpace);
}
[Test]
public void Structure_Tree()
{
// arrange
var childA = TestPlan.CreateUniqueElement();
var childB = TestPlan.CreateUniqueElement();
var childC = TestPlan.CreateUniqueElement();
var childD = TestPlan.CreateUniqueElement();
var childE = TestPlan.CreateUniqueElement();
const int spacing = 25;
var availableSpace = new Size(1200, 400);
// act
var value = new Container();
value.Row(row =>
{
row.Spacing(spacing);
row.ConstantColumn(150).Element(childA);
row.ConstantColumn(200).Element(childB);
row.ConstantColumn(250).Element(childC);
row.RelativeColumn(2).Element(childD);
row.RelativeColumn(3).Element(childE);
});
// assert
var expected = new BinaryRow
{
Left = new BinaryRow
{
Left = new BinaryRow
{
Left = new Constrained
{
MinWidth = 150,
MaxWidth = 150,
Child = childA
},
Right = new Constrained
{
MinWidth = 25,
MaxWidth = 25
}
},
Right = new BinaryRow
{
Left = new Constrained
{
MinWidth = 200,
MaxWidth = 200,
Child = childB
},
Right = new Constrained
{
MinWidth = 25,
MaxWidth = 25
}
}
},
Right = new BinaryRow
{
Left = new BinaryRow
{
Left = new Constrained
{
MinWidth = 250,
MaxWidth = 250,
Child = childC
},
Right = new Constrained
{
MinWidth = 25,
MaxWidth = 25
}
},
Right = new BinaryRow
{
Left = new Constrained
{
MinWidth = 200,
MaxWidth = 200,
Child = childD
},
Right = new BinaryRow
{
Left = new Constrained
{
MinWidth = 25,
MaxWidth = 25
},
Right = new Constrained
{
MinWidth = 300,
MaxWidth = 300,
Child = childE
}
}
}
}
};
TestPlan.CompareOperations(value, expected, availableSpace);
}
#endregion
}
}
@@ -1,5 +1,4 @@
using System.Linq;
using NUnit.Framework;
using NUnit.Framework;
using QuestPDF.Drawing;
using QuestPDF.Elements;
using QuestPDF.Fluent;
@@ -9,40 +8,19 @@ using QuestPDF.UnitTests.TestEngine;
namespace QuestPDF.UnitTests
{
[TestFixture]
public class ColumnTests
public class StackTests
{
private Column CreateColumnWithTwoItems(TestPlan testPlan)
{
return new Column
{
Items =
{
new ColumnItem
{
Child = testPlan.CreateChild("first")
},
new ColumnItem
{
Child = testPlan.CreateChild("second")
}
}
};
}
private Column CreateColumnWithTwoItemsWhereFirstIsFullyRendered(TestPlan testPlan)
{
var column = CreateColumnWithTwoItems(testPlan);
column.Items.First().IsRendered = true;
return column;
}
#region Measure
[Test]
public void Measure_ReturnsWrap_WhenFirstChildWraps()
{
TestPlan
.For(CreateColumnWithTwoItems)
.For(x => new BinaryStack
{
First = x.CreateChild("first"),
Second = x.CreateChild("second")
})
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("first", new Size(400, 300), SpacePlan.Wrap())
.CheckMeasureResult(SpacePlan.Wrap());
@@ -52,7 +30,11 @@ namespace QuestPDF.UnitTests
public void Measure_ReturnsPartialRender_WhenFirstChildReturnsPartialRender()
{
TestPlan
.For(CreateColumnWithTwoItems)
.For(x => new BinaryStack
{
First = x.CreateChild("first"),
Second = x.CreateChild("second")
})
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("first", new Size(400, 300), SpacePlan.PartialRender(300, 200))
.CheckMeasureResult(SpacePlan.PartialRender(300, 200));
@@ -62,7 +44,11 @@ namespace QuestPDF.UnitTests
public void Measure_ReturnsPartialRender_WhenSecondChildWraps()
{
TestPlan
.For(CreateColumnWithTwoItems)
.For(x => new BinaryStack
{
First = x.CreateChild("first"),
Second = x.CreateChild("second")
})
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("first", new Size(400, 300), SpacePlan.FullRender(200, 100))
.ExpectChildMeasure("second", new Size(400, 200), SpacePlan.Wrap())
@@ -73,7 +59,11 @@ namespace QuestPDF.UnitTests
public void Measure_ReturnsPartialRender_WhenSecondChildReturnsPartialRender()
{
TestPlan
.For(CreateColumnWithTwoItems)
.For(x => new BinaryStack
{
First = x.CreateChild("first"),
Second = x.CreateChild("second")
})
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("first", new Size(400, 300), SpacePlan.FullRender(200, 100))
.ExpectChildMeasure("second", new Size(400, 200), SpacePlan.PartialRender(300, 150))
@@ -84,13 +74,33 @@ namespace QuestPDF.UnitTests
public void Measure_ReturnsFullRender_WhenSecondChildReturnsFullRender()
{
TestPlan
.For(CreateColumnWithTwoItems)
.For(x => new BinaryStack
{
First = x.CreateChild("first"),
Second = x.CreateChild("second")
})
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("first", new Size(400, 300), SpacePlan.FullRender(200, 100))
.ExpectChildMeasure("second", new Size(400, 200), SpacePlan.FullRender(100, 50))
.CheckMeasureResult(SpacePlan.FullRender(200, 150));
}
[Test]
public void Measure_UsesEmpty_WhenFirstChildIsRendered()
{
TestPlan
.For(x => new BinaryStack
{
First = x.CreateChild("first"),
Second = x.CreateChild("second"),
IsFirstRendered = true
})
.MeasureElement(new Size(400, 300))
.ExpectChildMeasure("second", new Size(400, 300), SpacePlan.FullRender(200, 300))
.CheckMeasureResult(SpacePlan.FullRender(200, 300));
}
#endregion
#region Draw
@@ -99,7 +109,11 @@ namespace QuestPDF.UnitTests
public void Draw_WhenFirstChildWraps()
{
TestPlan
.For(CreateColumnWithTwoItems)
.For(x => new BinaryStack
{
First = x.CreateChild("first"),
Second = x.CreateChild("second")
})
.DrawElement(new Size(400, 300))
.ExpectChildMeasure("first", new Size(400, 300), SpacePlan.Wrap())
.CheckDrawResult();
@@ -109,12 +123,14 @@ namespace QuestPDF.UnitTests
public void Draw_WhenFirstChildPartiallyRenders()
{
TestPlan
.For(CreateColumnWithTwoItems)
.For(x => new BinaryStack
{
First = x.CreateChild("first"),
Second = x.CreateChild("second")
})
.DrawElement(new Size(400, 300))
.ExpectChildMeasure("first", new Size(400, 300), SpacePlan.PartialRender(200, 100))
.ExpectCanvasTranslate(0, 0)
.ExpectChildDraw("first", new Size(400, 100))
.ExpectCanvasTranslate(0, 0)
.CheckDrawResult();
}
@@ -122,13 +138,15 @@ namespace QuestPDF.UnitTests
public void Draw_WhenFirstChildFullyRenders_AndSecondChildWraps()
{
TestPlan
.For(CreateColumnWithTwoItems)
.For(x => new BinaryStack
{
First = x.CreateChild("first"),
Second = x.CreateChild("second")
})
.DrawElement(new Size(400, 300))
.ExpectChildMeasure("first", new Size(400, 300), SpacePlan.FullRender(200, 100))
.ExpectChildMeasure("second", new Size(400, 200), SpacePlan.Wrap())
.ExpectCanvasTranslate(0, 0)
.ExpectChildDraw("first", new Size(400, 100))
.ExpectCanvasTranslate(0, 0)
.ExpectChildMeasure("second", new Size(400, 200), SpacePlan.Wrap())
.CheckDrawResult();
}
@@ -136,13 +154,15 @@ namespace QuestPDF.UnitTests
public void Draw_WhenFirstChildFullyRenders_AndSecondChildPartiallyRenders()
{
TestPlan
.For(CreateColumnWithTwoItems)
.For(x => new BinaryStack
{
First = x.CreateChild("first"),
Second = x.CreateChild("second")
})
.DrawElement(new Size(400, 300))
.ExpectChildMeasure("first", new Size(400, 300), SpacePlan.FullRender(200, 100))
.ExpectChildMeasure("second", new Size(400, 200), SpacePlan.PartialRender(250, 150))
.ExpectCanvasTranslate(0, 0)
.ExpectChildDraw("first", new Size(400, 100))
.ExpectCanvasTranslate(0, 0)
.ExpectChildMeasure("second", new Size(400, 200), SpacePlan.PartialRender(250, 150))
.ExpectCanvasTranslate(0, 100)
.ExpectChildDraw("second", new Size(400, 150))
.ExpectCanvasTranslate(0, -100)
@@ -153,13 +173,15 @@ namespace QuestPDF.UnitTests
public void Draw_WhenFirstChildFullyRenders_AndSecondChildFullyRenders()
{
TestPlan
.For(CreateColumnWithTwoItems)
.For(x => new BinaryStack
{
First = x.CreateChild("first"),
Second = x.CreateChild("second")
})
.DrawElement(new Size(400, 300))
.ExpectChildMeasure("first", new Size(400, 300), SpacePlan.FullRender(200, 100))
.ExpectChildMeasure("second", new Size(400, 200), SpacePlan.FullRender(250, 150))
.ExpectCanvasTranslate(0, 0)
.ExpectChildDraw("first", new Size(400, 100))
.ExpectCanvasTranslate(0, 0)
.ExpectChildMeasure("second", new Size(400, 200), SpacePlan.FullRender(250, 150))
.ExpectCanvasTranslate(0, 100)
.ExpectChildDraw("second", new Size(400, 150))
.ExpectCanvasTranslate(0, -100)
@@ -170,13 +192,19 @@ namespace QuestPDF.UnitTests
public void Draw_UsesEmpty_WhenFirstChildIsRendered()
{
TestPlan
.For(CreateColumnWithTwoItemsWhereFirstIsFullyRendered)
.For(x => new BinaryStack
{
First = x.CreateChild("first"),
Second = x.CreateChild("second"),
IsFirstRendered = true
})
.DrawElement(new Size(400, 300))
.ExpectChildMeasure("second", new Size(400, 300), SpacePlan.PartialRender(200, 300))
.ExpectCanvasTranslate(0, 0)
.ExpectChildDraw("second", new Size(400, 300))
.ExpectCanvasTranslate(0, 0)
.CheckState<Column>(x => x.Items.First().IsRendered)
.CheckState<BinaryStack>(x => x.IsFirstRendered)
.CheckDrawResult();
}
@@ -184,14 +212,97 @@ namespace QuestPDF.UnitTests
public void Draw_TogglesFirstRenderedFlag_WhenSecondFullyRenders()
{
TestPlan
.For(CreateColumnWithTwoItemsWhereFirstIsFullyRendered)
.For(x => new BinaryStack
{
First = x.CreateChild("first"),
Second = x.CreateChild("second"),
IsFirstRendered = true
})
.DrawElement(new Size(400, 300))
.ExpectChildMeasure("second", new Size(400, 300), SpacePlan.FullRender(200, 300))
.ExpectCanvasTranslate(0, 0)
.ExpectChildDraw("second", new Size(400, 300))
.ExpectCanvasTranslate(0, 0)
.CheckDrawResult()
.CheckState<Column>(x => !x.Items.First().IsRendered);
.CheckState<BinaryStack>(x => !x.IsFirstRendered);
}
#endregion
#region Structure
[Test]
public void Structure_Simple()
{
// arrange
var childA = TestPlan.CreateUniqueElement();
var childB = TestPlan.CreateUniqueElement();
var childC = TestPlan.CreateUniqueElement();
var childD = TestPlan.CreateUniqueElement();
var childE = TestPlan.CreateUniqueElement();
const int spacing = 20;
// act
var structure = new Container();
structure.Stack(stack =>
{
stack.Spacing(spacing);
stack.Item().Element(childA);
stack.Item().Element(childB);
stack.Item().Element(childC);
stack.Item().Element(childD);
stack.Item().Element(childE);
});
// assert
var expected = new Padding
{
Bottom = -spacing,
Child = new BinaryStack
{
First = new BinaryStack
{
First = new Padding
{
Bottom = spacing,
Child = childA
},
Second = new Padding
{
Bottom = spacing,
Child = childB
}
},
Second = new BinaryStack
{
First = new Padding
{
Bottom = spacing,
Child = childC
},
Second = new BinaryStack
{
First = new Padding
{
Bottom = spacing,
Child = childD
},
Second = new Padding
{
Bottom = spacing,
Child = childE
}
}
}
}
};
TestPlan.CompareOperations(structure, expected);
}
#endregion
+6 -6
View File
@@ -1,5 +1,4 @@
using System;
using QuestPDF.Drawing;
using QuestPDF.Infrastructure;
using SkiaSharp;
@@ -11,6 +10,7 @@ namespace QuestPDF.UnitTests.TestEngine
public Action<float> RotateFunc { get; set; }
public Action<float, float> ScaleFunc { get; set; }
public Action<SKImage, Position, Size> DrawImageFunc { get; set; }
public Action<string, Position, TextStyle> DrawTextFunc { get; set; }
public Action<Position, Size, string> DrawRectFunc { get; set; }
public void Translate(Position vector) => TranslateFunc(vector);
@@ -18,11 +18,11 @@ namespace QuestPDF.UnitTests.TestEngine
public void Scale(float scaleX, float scaleY) => ScaleFunc(scaleX, scaleY);
public void DrawRectangle(Position vector, Size size, string color) => DrawRectFunc(vector, size, color);
public void DrawText(SKTextBlob skTextBlob, Position position, TextStyle style) => throw new NotImplementedException();
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 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();
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();
}
}
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using QuestPDF.Drawing;
using QuestPDF.Infrastructure;
using QuestPDF.UnitTests.TestEngine.Operations;
using SkiaSharp;
@@ -16,11 +15,11 @@ namespace QuestPDF.UnitTests.TestEngine
public void Scale(float scaleX, float scaleY) => Operations.Add(new CanvasScaleOperation(scaleX, scaleY));
public void DrawRectangle(Position vector, Size size, string color) => Operations.Add(new CanvasDrawRectangleOperation(vector, size, color));
public void DrawText(SKTextBlob skTextBlob, Position position, TextStyle style) => throw new NotImplementedException();
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 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();
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();
}
}
+24 -6
View File
@@ -82,6 +82,19 @@ namespace QuestPDF.UnitTests.TestEngine
Assert.AreEqual(expected.Color, color, "Draw rectangle: color");
},
DrawTextFunc = (text, position, style) =>
{
var expected = GetExpected<CanvasDrawTextOperation>();
Assert.AreEqual(expected.Text, text);
Assert.AreEqual(expected.Position.X, position.X, "Draw text: X");
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.Size, style.Size, "Draw text: size");
},
DrawImageFunc = (image, position, size) =>
{
var expected = GetExpected<CanvasDrawImageOperation>();
@@ -188,6 +201,11 @@ namespace QuestPDF.UnitTests.TestEngine
return AddOperation(new CanvasDrawRectangleOperation(position, size, color));
}
public TestPlan ExpectCanvasDrawText(string text, Position position, TextStyle style)
{
return AddOperation(new CanvasDrawTextOperation(text, position, style));
}
public TestPlan ExpectCanvasDrawImage(Position position, Size size)
{
return AddOperation(new CanvasDrawImageOperation(position, size));
@@ -195,7 +213,7 @@ namespace QuestPDF.UnitTests.TestEngine
public TestPlan CheckMeasureResult(SpacePlan expected)
{
Element.InjectDependencies(null, Canvas);
Element.VisitChildren(x => x?.Initialize(null, Canvas));
var actual = Element.Measure(OperationInput);
@@ -210,7 +228,7 @@ namespace QuestPDF.UnitTests.TestEngine
public TestPlan CheckDrawResult()
{
Element.InjectDependencies(null, Canvas);
Element.VisitChildren(x => x?.Initialize(null, Canvas));
Element.Draw(OperationInput);
return this;
}
@@ -253,10 +271,10 @@ namespace QuestPDF.UnitTests.TestEngine
availableSpace ??= new Size(400, 300);
var canvas = new FreeCanvas();
value.InjectDependencies(null, canvas);
value.VisitChildren(x => x.Initialize(null, canvas));
var valueMeasure = value.Measure(availableSpace.Value);
expected.InjectDependencies(null, canvas);
expected.VisitChildren(x => x.Initialize(null, canvas));
var expectedMeasure = expected.Measure(availableSpace.Value);
valueMeasure.Should().BeEquivalentTo(expectedMeasure);
@@ -267,11 +285,11 @@ namespace QuestPDF.UnitTests.TestEngine
availableSpace ??= new Size(400, 300);
var valueCanvas = new OperationRecordingCanvas();
value.InjectDependencies(null, valueCanvas);
value.VisitChildren(x => x.Initialize(null, valueCanvas));
value.Draw(availableSpace.Value);
var expectedCanvas = new OperationRecordingCanvas();
expected.InjectDependencies(null, expectedCanvas);
expected.VisitChildren(x => x.Initialize(null, expectedCanvas));
expected.Draw(availableSpace.Value);
valueCanvas.Operations.Should().BeEquivalentTo(expectedCanvas.Operations);
-38
View File
@@ -77,9 +77,7 @@ namespace QuestPDF.UnitTests
})
.DrawElement(new Size(900, 800))
.ExpectChildMeasure(Size.Max, SpacePlan.PartialRender(1200, 1600))
.ExpectCanvasTranslate(0, 0)
.ExpectChildDraw(new Size(1200, 1600))
.ExpectCanvasTranslate(0, 0)
.CheckDrawResult();
}
@@ -93,43 +91,7 @@ namespace QuestPDF.UnitTests
})
.DrawElement(new Size(900, 800))
.ExpectChildMeasure(Size.Max, SpacePlan.FullRender(1600, 1000))
.ExpectCanvasTranslate(0, 0)
.ExpectChildDraw(new Size(1600, 1000))
.ExpectCanvasTranslate(0, 0)
.CheckDrawResult();
}
[Test]
public void Draw_WhenChildPartiallyRenders_RightToLeft()
{
TestPlan
.For(x => new Unconstrained
{
Child = x.CreateChild(),
ContentDirection = ContentDirection.RightToLeft
})
.DrawElement(new Size(900, 800))
.ExpectChildMeasure(Size.Max, SpacePlan.PartialRender(1200, 1600))
.ExpectCanvasTranslate(-1200, 0)
.ExpectChildDraw(new Size(1200, 1600))
.ExpectCanvasTranslate(1200, 0)
.CheckDrawResult();
}
[Test]
public void Draw_WhenChildFullyRenders_RightToLeft()
{
TestPlan
.For(x => new Unconstrained
{
Child = x.CreateChild(),
ContentDirection = ContentDirection.RightToLeft
})
.DrawElement(new Size(900, 800))
.ExpectChildMeasure(Size.Max, SpacePlan.FullRender(1600, 1000))
.ExpectCanvasTranslate(-1600, 0)
.ExpectChildDraw(new Size(1600, 1000))
.ExpectCanvasTranslate(1600, 0)
.CheckDrawResult();
}
-11
View File
@@ -8,13 +8,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuestPDF.UnitTests", "Quest
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuestPDF.Examples", "QuestPDF.Examples\QuestPDF.Examples.csproj", "{8BD0A2B4-2DC1-47BA-9724-C158320D9CAE}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Configuration", "Configuration", "{73123649-216A-47B4-BFB2-DADF13FBE75D}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimpleApp", "SimpleApp\SimpleApp.csproj", "{B361D452-80A8-493A-BEA6-AB268E99153E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -37,9 +30,5 @@ Global
{8BD0A2B4-2DC1-47BA-9724-C158320D9CAE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8BD0A2B4-2DC1-47BA-9724-C158320D9CAE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8BD0A2B4-2DC1-47BA-9724-C158320D9CAE}.Release|Any CPU.Build.0 = Release|Any CPU
{B361D452-80A8-493A-BEA6-AB268E99153E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B361D452-80A8-493A-BEA6-AB268E99153E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B361D452-80A8-493A-BEA6-AB268E99153E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B361D452-80A8-493A-BEA6-AB268E99153E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
+6
View File
@@ -0,0 +1,6 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
[assembly: InternalsVisibleTo("QuestPDF.UnitTests")]
[assembly: InternalsVisibleTo("QuestPDF.Examples")]
[assembly: InternalsVisibleTo("QuestPDF.ReportSample")]
+3 -4
View File
@@ -16,14 +16,13 @@ namespace QuestPDF.Drawing
var container = new Container();
container
.DebugPointer("Document")
.Column(column =>
.Stack(stack =>
{
Pages
.SelectMany(x => new List<Action>()
{
() => column.Item().PageBreak(),
() => column.Item().DebugPointer("Page").Component(x)
() => stack.Item().PageBreak(),
() => stack.Item().Component(x)
})
.Skip(1)
.ToList()
+33 -92
View File
@@ -10,7 +10,6 @@ using QuestPDF.Elements.Text.Items;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using QuestPDF.Previewer.Inspection;
namespace QuestPDF.Drawing
{
@@ -18,8 +17,6 @@ namespace QuestPDF.Drawing
{
internal static void GeneratePdf(Stream stream, IDocument document)
{
CheckIfStreamIsCompatible(stream);
var metadata = document.GetMetadata();
var canvas = new PdfCanvas(stream, metadata);
RenderDocument(canvas, document);
@@ -27,21 +24,10 @@ namespace QuestPDF.Drawing
internal static void GenerateXps(Stream stream, IDocument document)
{
CheckIfStreamIsCompatible(stream);
var metadata = document.GetMetadata();
var canvas = new XpsCanvas(stream, metadata);
RenderDocument(canvas, document);
}
private static void CheckIfStreamIsCompatible(Stream stream)
{
if (!stream.CanWrite)
throw new ArgumentException("The library requires a Stream object with the 'write' capability available (the CanWrite flag). Please consider using the MemoryStream class.");
if (!stream.CanSeek)
throw new ArgumentException("The library requires a Stream object with the 'seek' capability available (the CanSeek flag). Please consider using the MemoryStream class.");
}
internal static ICollection<byte[]> GenerateImages(IDocument document)
{
@@ -52,48 +38,30 @@ namespace QuestPDF.Drawing
return canvas.Images;
}
internal static DocumentPreviewResult GeneratePreviewerPictures(IDocument document)
{
var canvas = new SkiaPictureCanvas();
InspectionElement documentHierarchy = null;
RenderDocument(canvas, document, true, x => documentHierarchy = x);
return new DocumentPreviewResult
{
PageSnapshots = canvas.Pictures.ToList(),
DocumentHierarchy = documentHierarchy
};
}
internal static void RenderDocument<TCanvas>(TCanvas canvas, IDocument document, bool applyInspection = false, Action<InspectionElement> inspectionResultHandler = null)
private static void RenderDocument<TCanvas>(TCanvas canvas, IDocument document)
where TCanvas : ICanvas, IRenderingCanvas
{
var container = new DocumentContainer();
document.Compose(container);
var content = container.Compose();
ApplyDefaultTextStyle(content, TextStyle.LibraryDefault);
ApplyContentDirection(content, ContentDirection.LeftToRight);
if (Settings.EnableCaching)
var metadata = document.GetMetadata();
var pageContext = new PageContext();
var debuggingState = metadata.ApplyDebugging ? ApplyDebugging(content) : null;
if (metadata.ApplyCaching)
ApplyCaching(content);
var pageContext = new PageContext();
RenderPass(pageContext, new FreeCanvas(), content);
if (applyInspection)
ApplyInspection(content);
RenderPass(pageContext, canvas, content);
if (applyInspection)
inspectionResultHandler(content.ExtractDocumentHierarchy());
RenderPass(pageContext, new FreeCanvas(), content, metadata, debuggingState);
RenderPass(pageContext, canvas, content, metadata, debuggingState);
}
internal static void RenderPass<TCanvas>(PageContext pageContext, TCanvas canvas, Container content)
internal static void RenderPass<TCanvas>(PageContext pageContext, TCanvas canvas, Container content, DocumentMetadata documentMetadata, DebuggingState? debuggingState)
where TCanvas : ICanvas, IRenderingCanvas
{
InjectDependencies(content, pageContext, canvas);
content.VisitChildren(x => x?.Initialize(pageContext, canvas));
content.VisitChildren(x => (x as IStateResettable)?.ResetState());
canvas.BeginDocument();
@@ -103,6 +71,7 @@ namespace QuestPDF.Drawing
while(true)
{
pageContext.SetPageNumber(currentPage);
debuggingState?.Reset();
var spacePlan = content.Measure(Size.Max);
@@ -125,7 +94,7 @@ namespace QuestPDF.Drawing
canvas.EndPage();
if (currentPage >= Settings.DocumentLayoutExceptionThreshold)
if (currentPage >= documentMetadata.DocumentLayoutExceptionThreshold)
{
canvas.EndDocument();
ThrowLayoutException();
@@ -142,34 +111,18 @@ namespace QuestPDF.Drawing
void ThrowLayoutException()
{
var message = $"Composed layout generates infinite document. This may happen in two cases. " +
$"1) Your document and its layout configuration is correct but the content takes more than {Settings.DocumentLayoutExceptionThreshold} pages. " +
$"In this case, please increase the value {nameof(QuestPDF)}.{nameof(Settings)}.{nameof(Settings.DocumentLayoutExceptionThreshold)} static property. " +
$"1) Your document and its layout configuration is correct but the content takes more than {documentMetadata.DocumentLayoutExceptionThreshold} pages. " +
$"In this case, please increase the value {nameof(DocumentMetadata)}.{nameof(DocumentMetadata.DocumentLayoutExceptionThreshold)} property configured in the {nameof(IDocument.GetMetadata)} method. " +
$"2) The layout configuration of your document is invalid. Some of the elements require more space than is provided." +
$"Please analyze your documents structure to detect this element and fix its size constraints.";
content.RemoveExistingProxies();
content.RemoveExistingContainers();
content.ApplyLayoutDebugging();
content.Measure(Size.Max);
var layoutTrace = content.CollectLayoutErrorTrace();
throw new DocumentLayoutException(message, layoutTrace);
throw new DocumentLayoutException(message)
{
ElementTrace = debuggingState?.BuildTrace() ?? "Debug trace is available only in the DEBUG mode."
};
}
}
internal static void InjectDependencies(this Element content, IPageContext pageContext, ICanvas canvas)
{
content.VisitChildren(x =>
{
if (x == null)
return;
x.PageContext = pageContext;
x.Canvas = canvas;
});
}
private static void ApplyCaching(Container content)
{
content.VisitChildren(x =>
@@ -179,33 +132,19 @@ namespace QuestPDF.Drawing
});
}
private static void ApplyInspection(Container content)
private static DebuggingState ApplyDebugging(Container content)
{
var debuggingState = new DebuggingState();
content.VisitChildren(x =>
{
x.CreateProxy(y => y is ElementProxy or Container ? y : new InspectionProxy(y));
x.CreateProxy(y => new DebuggingProxy(debuggingState, y));
});
}
internal static void ApplyContentDirection(this Element? content, ContentDirection direction)
{
if (content == null)
return;
if (content is ContentDirectionSetter contentDirectionSetter)
{
ApplyContentDirection(contentDirectionSetter.Child, contentDirectionSetter.ContentDirection);
return;
}
if (content is IContentDirectionAware contentDirectionAware)
contentDirectionAware.ContentDirection = direction;
foreach (var child in content.GetChildren())
ApplyContentDirection(child, direction);
return debuggingState;
}
internal static void ApplyDefaultTextStyle(this Element? content, TextStyle documentDefaultTextStyle)
private static void ApplyDefaultTextStyle(this Element? content, TextStyle documentDefaultTextStyle)
{
if (content == null)
return;
@@ -216,7 +155,7 @@ namespace QuestPDF.Drawing
{
if (textBlockItem is TextBlockSpan textSpan)
{
textSpan.Style = textSpan.Style.ApplyGlobalStyle(documentDefaultTextStyle);
textSpan.Style.ApplyGlobalStyle(documentDefaultTextStyle);
}
else if (textBlockItem is TextBlockElement textElement)
{
@@ -227,14 +166,16 @@ namespace QuestPDF.Drawing
return;
}
if (content is DynamicHost dynamicHost)
dynamicHost.TextStyle = dynamicHost.TextStyle.ApplyGlobalStyle(documentDefaultTextStyle);
var targetTextStyle = documentDefaultTextStyle;
if (content is DefaultTextStyle defaultTextStyleElement)
documentDefaultTextStyle = defaultTextStyleElement.TextStyle.ApplyGlobalStyle(documentDefaultTextStyle);
{
defaultTextStyleElement.TextStyle.ApplyParentStyle(documentDefaultTextStyle);
targetTextStyle = defaultTextStyleElement.TextStyle;
}
foreach (var child in content.GetChildren())
ApplyDefaultTextStyle(child, documentDefaultTextStyle);
ApplyDefaultTextStyle(child, targetTextStyle);
}
}
}
+7 -20
View File
@@ -1,5 +1,4 @@
using System;
using QuestPDF.Infrastructure;
namespace QuestPDF.Drawing
{
@@ -19,26 +18,14 @@ namespace QuestPDF.Drawing
public DateTime CreationDate { get; set; } = DateTime.Now;
public DateTime ModifiedDate { get; set; } = DateTime.Now;
[Obsolete("This API has been moved since version 2022.9. Please use the QuestPDF.Settings.DocumentLayoutExceptionThreshold static property.")]
public int DocumentLayoutExceptionThreshold
{
get => Settings.DocumentLayoutExceptionThreshold;
set => Settings.DocumentLayoutExceptionThreshold = value;
}
/// <summary>
/// If the number of generated pages exceeds this threshold
/// (likely due to infinite layout), the exception is thrown.
/// </summary>
public int DocumentLayoutExceptionThreshold { get; set; } = 250;
[Obsolete("This API has been moved since version 2022.9. Please use the QuestPDF.Settings.EnableCaching static property.")]
public bool ApplyCaching
{
get => Settings.EnableCaching;
set => Settings.EnableCaching = value;
}
[Obsolete("The new implementation for debugging layout issues does not introduce additional performance overhead. Therefore, this setting is no longer used since the 2023.1 release. Please remove this setter from your code.")]
public bool ApplyDebugging
{
get => Settings.EnableDebugging;
set => Settings.EnableDebugging = value;
}
public bool ApplyCaching { get; set; } = !System.Diagnostics.Debugger.IsAttached;
public bool ApplyDebugging { get; set; } = System.Diagnostics.Debugger.IsAttached;
public static DocumentMetadata Default => new DocumentMetadata();
}
@@ -4,7 +4,17 @@ namespace QuestPDF.Drawing.Exceptions
{
public class DocumentComposeException : Exception
{
internal DocumentComposeException(string message) : base(message)
public DocumentComposeException()
{
}
public DocumentComposeException(string message) : base(message)
{
}
public DocumentComposeException(string message, Exception inner) : base(message, inner)
{
}
@@ -4,12 +4,17 @@ namespace QuestPDF.Drawing.Exceptions
{
public class DocumentDrawingException : Exception
{
internal DocumentDrawingException(string message) : base(message)
public DocumentDrawingException()
{
}
internal DocumentDrawingException(string message, Exception inner) : base(message, inner)
public DocumentDrawingException(string message) : base(message)
{
}
public DocumentDrawingException(string message, Exception inner) : base(message, inner)
{
}
@@ -1,15 +1,24 @@
using System;
using QuestPDF.Previewer;
namespace QuestPDF.Drawing.Exceptions
{
public class DocumentLayoutException : Exception
{
internal LayoutErrorTrace? ElementTrace { get; }
internal DocumentLayoutException(string message, LayoutErrorTrace? elementTrace = null) : base(message)
public string ElementTrace { get; set; }
public DocumentLayoutException()
{
ElementTrace = elementTrace;
}
public DocumentLayoutException(string message) : base(message)
{
}
public DocumentLayoutException(string message, Exception inner) : base(message, inner)
{
}
}
}
@@ -1,36 +0,0 @@
using System;
namespace QuestPDF.Drawing.Exceptions
{
public class InitializationException : Exception
{
internal InitializationException(string documentType, Exception innerException) : base(CreateMessage(documentType, innerException.Message), innerException)
{
}
private static string CreateMessage(string documentType, string innerExceptionMessage)
{
var (libraryName, nugetConvention) = GetLibraryName();
return $"Cannot create the {documentType} document using the {libraryName} library. " +
$"This exception usually means that, on your operating system where you run the application, {libraryName} requires installing additional dependencies. " +
$"Such dependencies are available as additional nuget packages, for example {nugetConvention}.Linux.NoDependencies. " +
$"Some operating systems may require installing multiple nugets, e.g. MacOS may need both {nugetConvention}.macOS.NoDependencies and {nugetConvention}.Linux.NoDependencies." +
$"Please refer to the {libraryName} documentation for more details. " +
$"Also, please consult the inner exception that has been originally thrown by the dependency library.";
(string GetLibraryName, string nugetConvention) GetLibraryName()
{
if (innerExceptionMessage.Contains("libSkiaSharp"))
return ("SkiaSharp", "SkiaSharp.NativeAssets");
if (innerExceptionMessage.Contains("libHarfBuzzSharp"))
return ("HarfBuzzSharp", "HarfBuzzSharp.NativeAssets");
// default
return ("SkiaSharp-related", "*.NativeAssets");
}
}
}
}
+18 -163
View File
@@ -1,211 +1,66 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using HarfBuzzSharp;
using QuestPDF.Fluent;
using QuestPDF.Infrastructure;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace QuestPDF.Drawing
{
public static class FontManager
{
private static readonly ConcurrentDictionary<string, FontStyleSet> StyleSets = new();
private static readonly ConcurrentDictionary<TextStyle, SKFontMetrics> FontMetrics = new();
private static readonly ConcurrentDictionary<TextStyle, SKPaint> FontPaints = new();
private static readonly ConcurrentDictionary<string, SKPaint> ColorPaints = new();
private static readonly ConcurrentDictionary<TextStyle, Font> ShaperFonts = new();
private static readonly ConcurrentDictionary<TextStyle, SKFont> Fonts = new();
private static readonly ConcurrentDictionary<TextStyle, TextShaper> TextShapers = new();
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>();
static FontManager()
{
RegisterLibraryDefaultFonts();
}
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.8 this method has been renamed. Please use the RegisterFontWithCustomName method.")]
public static void RegisterFontType(string fontName, Stream stream)
{
RegisterFontWithCustomName(fontName, stream);
Typefaces.TryAdd(fontName, SKTypeface.FromStream(stream));
}
public static void RegisterFontWithCustomName(string fontName, Stream 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);
}
public static void RegisterFontFromEmbeddedResource(string pathName)
{
using var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(pathName);
RegisterFont(stream);
}
private static void RegisterLibraryDefaultFonts()
{
var fontFileNames = new[]
{
"Lato-Black.ttf",
"Lato-BlackItalic.ttf",
"Lato-Bold.ttf",
"Lato-BoldItalic.ttf",
"Lato-Regular.ttf",
"Lato-Italic.ttf",
"Lato-Light.ttf",
"Lato-LightItalic.ttf",
"Lato-Thin.ttf",
"Lato-ThinItalic.ttf"
};
fontFileNames
.Select(x => $"QuestPDF.Resources.DefaultFont.{x}")
.ToList()
.ForEach(RegisterFontFromEmbeddedResource);
}
internal static SKPaint ColorToPaint(this string color)
{
return ColorPaints.GetOrAdd(color, Convert);
return ColorPaint.GetOrAdd(color, Convert);
static SKPaint Convert(string color)
{
return new SKPaint
{
Color = SKColor.Parse(color),
IsAntialias = true
Color = SKColor.Parse(color)
};
}
}
internal static SKPaint ToPaint(this TextStyle style)
{
return FontPaints.GetOrAdd(style, Convert);
return Paints.GetOrAdd(style.Key, key => Convert(style));
static SKPaint Convert(TextStyle style)
{
return new SKPaint
{
Color = SKColor.Parse(style.Color),
Typeface = GetTypeface(style),
TextSize = (style.Size ?? 12) * GetTextScale(style),
IsAntialias = true,
TextSize = (style.Size ?? 12),
TextEncoding = SKTextEncoding.Utf32
};
}
static SKTypeface GetTypeface(TextStyle style)
{
var weight = (SKFontStyleWeight)(style.FontWeight ?? FontWeight.Normal);
// superscript and subscript use slightly bolder font to match visually line thickness
if (style.FontPosition is FontPosition.Superscript or FontPosition.Subscript)
{
var weightValue = (int)weight;
weightValue = Math.Min(weightValue + 100, 1000);
weight = (SKFontStyleWeight) (weightValue);
}
if (Typefaces.TryGetValue(style.FontType, out var result))
return result;
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);
if (fontFromDefaultSource != null)
return fontFromDefaultSource;
var availableFontNames = string.Join(", ", SKFontManager.Default.GetFontFamilies());
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. " +
$"Available font family names: [{availableFontNames}]");
}
static float GetTextScale(TextStyle style)
{
return style.FontPosition switch
{
FontPosition.Normal => 1f,
FontPosition.Subscript => 0.625f,
FontPosition.Superscript => 0.625f,
_ => throw new ArgumentOutOfRangeException()
};
return SKTypeface.FromFamilyName(style.FontType, (int)(style.FontWeight ?? FontWeight.Normal), (int)SKFontStyleWidth.Normal, slant)
?? throw new ArgumentException($"The typeface {style.FontType} could not be found.");
}
}
internal static SKFontMetrics ToFontMetrics(this TextStyle style)
{
return FontMetrics.GetOrAdd(style, key => key.NormalPosition().ToPaint().FontMetrics);
}
internal static Font ToShaperFont(this TextStyle style)
{
return ShaperFonts.GetOrAdd(style, key =>
{
var typeface = key.ToPaint().Typeface;
using var harfBuzzBlob = typeface.OpenStream(out var ttcIndex).ToHarfBuzzBlob();
using var face = new Face(harfBuzzBlob, ttcIndex)
{
Index = ttcIndex,
UnitsPerEm = typeface.UnitsPerEm,
GlyphCount = typeface.GlyphCount
};
var font = new Font(face);
font.SetScale(TextShaper.FontShapingScale, TextShaper.FontShapingScale);
font.SetFunctionsOpenType();
return font;
});
}
internal static TextShaper ToTextShaper(this TextStyle style)
{
return TextShapers.GetOrAdd(style, key => new TextShaper(key));
}
internal static SKFont ToFont(this TextStyle style)
{
return Fonts.GetOrAdd(style, key => key.ToPaint().ToFont());
return FontMetrics.GetOrAdd(style.Key, key => style.ToPaint().FontMetrics);
}
}
}
-132
View File
@@ -1,132 +0,0 @@
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;
}
}
}
+4 -4
View File
@@ -41,7 +41,7 @@ namespace QuestPDF.Drawing
}
public void DrawText(SKTextBlob skTextBlob, Position position, TextStyle style)
public void DrawText(string text, Position position, TextStyle style)
{
}
@@ -51,17 +51,17 @@ namespace QuestPDF.Drawing
}
public void DrawHyperlink(string url, Size size)
public void DrawExternalLink(string url, Size size)
{
}
public void DrawSectionLink(string sectionName, Size size)
public void DrawLocationLink(string locationName, Size size)
{
}
public void DrawSection(string sectionName)
public void DrawLocation(string locationName)
{
}
+4 -18
View File
@@ -1,7 +1,5 @@
using System;
using System.IO;
using QuestPDF.Drawing.Exceptions;
using QuestPDF.Helpers;
using System.IO;
using QuestPDF.Infrastructure;
using SkiaSharp;
namespace QuestPDF.Drawing
@@ -9,23 +7,11 @@ namespace QuestPDF.Drawing
internal class PdfCanvas : SkiaDocumentCanvasBase
{
public PdfCanvas(Stream stream, DocumentMetadata documentMetadata)
: base(CreatePdf(stream, documentMetadata))
: base(SKDocument.CreatePdf(stream, MapMetadata(documentMetadata)))
{
}
private static SKDocument CreatePdf(Stream stream, DocumentMetadata documentMetadata)
{
try
{
return SKDocument.CreatePdf(stream, MapMetadata(documentMetadata));
}
catch (TypeInitializationException exception)
{
throw new InitializationException("PDF", exception);
}
}
private static SKDocumentPdfMetadata MapMetadata(DocumentMetadata metadata)
{
return new SKDocumentPdfMetadata
+1 -1
View File
@@ -37,7 +37,7 @@ namespace QuestPDF.Drawing.Proxy
base.Draw(availableSpace);
}
private static bool IsClose(float x, float y)
private bool IsClose(float x, float y)
{
return Math.Abs(x - y) < Size.Epsilon;
}
+14
View File
@@ -0,0 +1,14 @@
using System.Collections.Generic;
using QuestPDF.Infrastructure;
namespace QuestPDF.Drawing.Proxy
{
public class DebugStackItem
{
public IElement Element { get; internal set; }
public Size AvailableSpace { get; internal set; }
public SpacePlan SpacePlan { get; internal set; }
public ICollection<DebugStackItem> Stack { get; internal set; } = new List<DebugStackItem>();
}
}
+8 -18
View File
@@ -1,34 +1,24 @@
using System.Collections.Generic;
using QuestPDF.Infrastructure;
using QuestPDF.Infrastructure;
namespace QuestPDF.Drawing.Proxy
{
internal class DebuggingProxy : ElementProxy
{
internal List<MeasureDetails> Measurements { get; } = new();
private DebuggingState DebuggingState { get; }
public DebuggingProxy(Element child)
public DebuggingProxy(DebuggingState debuggingState, Element child)
{
DebuggingState = debuggingState;
Child = child;
}
internal override SpacePlan Measure(Size availableSpace)
{
var spacePlan = Child.Measure(availableSpace);
Measurements.Add(new MeasureDetails(availableSpace, spacePlan));
DebuggingState.RegisterMeasure(Child, availableSpace);
var spacePlan = base.Measure(availableSpace);
DebuggingState.RegisterMeasureResult(Child, spacePlan);
return spacePlan;
}
}
internal struct MeasureDetails
{
public Size AvailableSpace { get; }
public SpacePlan SpacePlan { get;}
public MeasureDetails(Size availableSpace, SpacePlan spacePlan)
{
AvailableSpace = availableSpace;
SpacePlan = spacePlan;
}
}
}
+130
View File
@@ -0,0 +1,130 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using QuestPDF.Elements;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Drawing.Proxy
{
internal class DebuggingState
{
private DebugStackItem Root { get; set; }
private Stack<DebugStackItem> Stack { get; set; }
public DebuggingState()
{
Reset();
}
public void Reset()
{
Root = null;
Stack = new Stack<DebugStackItem>();
}
public void RegisterMeasure(IElement element, Size availableSpace)
{
if (element.GetType() == typeof(Container))
return;
var item = new DebugStackItem
{
Element = element,
AvailableSpace = availableSpace
};
Root ??= item;
if (Stack.Any())
Stack.Peek().Stack.Add(item);
Stack.Push(item);
}
public void RegisterMeasureResult(IElement element, SpacePlan spacePlan)
{
if (element.GetType() == typeof(Container))
return;
var item = Stack.Pop();
if (item.Element != element)
throw new Exception();
item.SpacePlan = spacePlan;
}
public string BuildTrace()
{
var builder = new StringBuilder();
var nestingLevel = 0;
Traverse(Root);
return builder.ToString();
void Traverse(DebugStackItem item)
{
var indent = new string(' ', nestingLevel * 4);
var title = item.Element.GetType().Name;
if (item.Element is DebugPointer debugPointer)
{
title = debugPointer.Target;
title += debugPointer.Highlight ? " 🌟" : string.Empty;
}
if (item.SpacePlan.Type != SpacePlanType.FullRender)
title = "🔥 " + title;
builder.AppendLine(indent + title);
builder.AppendLine(indent + new string('-', title.Length));
builder.AppendLine(indent + "Available space: " + item.AvailableSpace);
builder.AppendLine(indent + "Requested space: " + item.SpacePlan);
foreach (var configuration in GetElementConfiguration(item.Element))
builder.AppendLine(indent + configuration);
builder.AppendLine();
nestingLevel++;
item.Stack.ToList().ForEach(Traverse);
nestingLevel--;
}
static IEnumerable<string> GetElementConfiguration(IElement element)
{
if (element is DebugPointer)
return Enumerable.Empty<string>();
return element
.GetType()
.GetProperties()
.Select(x => new
{
Property = x.Name.PrettifyName(),
Value = x.GetValue(element)
})
.Where(x => !(x.Value is IElement))
.Where(x => x.Value is string || !(x.Value is IEnumerable))
.Where(x => !(x.Value is TextStyle))
.Select(x => $"{x.Property}: {FormatValue(x.Value)}");
string FormatValue(object value)
{
const int maxLength = 100;
var text = value?.ToString() ?? "-";
if (text.Length < maxLength)
return text;
return text.AsSpan(0, maxLength).ToString() + "...";
}
}
}
}
}
-45
View File
@@ -1,45 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using QuestPDF.Previewer;
namespace QuestPDF.Drawing.Proxy;
internal static class Helpers
{
public static IReadOnlyCollection<DocumentElementProperty> GetElementConfiguration(this IElement element)
{
return element
.GetType()
.GetProperties()
.Select(x => new
{
Property = x.Name.PrettifyName(),
Value = x.GetValue(element)
})
.Where(x => !(x.Value is IElement))
.Where(x => x.Value is string || !(x.Value is IEnumerable))
.Where(x => !(x.Value is TextStyle))
.Select(x => new DocumentElementProperty
{
Label = x.Property,
Value = FormatValue(x.Value)
})
.ToList();
string FormatValue(object value)
{
const int maxLength = 100;
var text = value?.ToString() ?? "-";
if (text.Length < maxLength)
return text;
return text.AsSpan(0, maxLength).ToString() + "...";
}
}
}
-36
View File
@@ -1,36 +0,0 @@
using System.Collections;
using System.Collections.Generic;
using QuestPDF.Infrastructure;
using QuestPDF.Previewer.Inspection;
namespace QuestPDF.Drawing.Proxy
{
internal class InspectionProxy : ElementProxy
{
public Dictionary<int, InspectionStateItem> Statistics { get; set; } = new();
public InspectionProxy(Element child)
{
Child = child;
}
internal override void Draw(Size availableSpace)
{
if (Canvas is SkiaCanvasBase canvas)
{
var matrix = canvas.Canvas.TotalMatrix;
var inspectionItem = new InspectionStateItem
{
Element = Child,
Position = new Position(matrix.TransX, matrix.TransY),
Size = availableSpace
};
Statistics[PageContext.CurrentPage] = inspectionItem;
}
base.Draw(availableSpace);
}
}
}
@@ -1,74 +0,0 @@
using System.Linq;
using QuestPDF.Elements;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using QuestPDF.Previewer;
namespace QuestPDF.Drawing.Proxy
{
internal static class LayoutErrorAnalyzer
{
public static void ApplyLayoutDebugging(this Container content)
{
content.VisitChildren(x =>
{
x.CreateProxy(y => y is ElementProxy ? y : new DebuggingProxy(y));
});
}
public static void RemoveExistingProxies(this Container content)
{
content.VisitChildren(x =>
{
x.CreateProxy(y => y is ElementProxy proxy ? proxy.Child : y);
});
}
public static void RemoveExistingContainers(this Container content)
{
content.VisitChildren(x =>
{
x.CreateProxy(y => y is Container container ? container.Child : y);
});
}
public static LayoutErrorTrace CollectLayoutErrorTrace(this Container content)
{
var proxies = content.ExtractProxyOfType<DebuggingProxy>();
return Map(proxies);
LayoutErrorTrace Map(TreeNode<DebuggingProxy> proxy)
{
var element = proxy.Value.Child;
return new LayoutErrorTrace
{
ElementType = element.GetType().Name,
IsSingleChildContainer = element is ContainerElement,
ElementProperties = element.GetElementConfiguration().ToList(),
Measurements = proxy
.Value
.Measurements
.Select(x => new LayoutErrorMeasurement
{
AvailableSpace = new QuestPDF.Previewer.Size()
{
Width = x.AvailableSpace.Width,
Height = x.AvailableSpace.Height
},
SpacePlan = new QuestPDF.Previewer.SpacePlan()
{
Width = x.SpacePlan.Width,
Height = x.SpacePlan.Height,
Type = x.SpacePlan.Type
}
})
.ToList(),
Children = proxy.Children.Select(Map).ToList()
};
}
}
}
}
+7 -8
View File
@@ -1,6 +1,5 @@
using QuestPDF.Infrastructure;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
namespace QuestPDF.Drawing
{
@@ -28,9 +27,9 @@ namespace QuestPDF.Drawing
Canvas.DrawRect(vector.X, vector.Y, size.Width, size.Height, paint);
}
public void DrawText(SKTextBlob skTextBlob, Position position, TextStyle style)
public void DrawText(string text, Position vector, TextStyle style)
{
Canvas.DrawText(skTextBlob, position.X, position.Y, style.ToPaint());
Canvas.DrawText(text, vector.X, vector.Y, style.ToPaint());
}
public void DrawImage(SKImage image, Position vector, Size size)
@@ -38,19 +37,19 @@ namespace QuestPDF.Drawing
Canvas.DrawImage(image, new SKRect(vector.X, vector.Y, size.Width, size.Height));
}
public void DrawHyperlink(string url, Size size)
public void DrawExternalLink(string url, Size size)
{
Canvas.DrawUrlAnnotation(new SKRect(0, 0, size.Width, size.Height), url);
}
public void DrawSectionLink(string sectionName, Size size)
public void DrawLocationLink(string locationName, Size size)
{
Canvas.DrawLinkDestinationAnnotation(new SKRect(0, 0, size.Width, size.Height), sectionName);
Canvas.DrawLinkDestinationAnnotation(new SKRect(0, 0, size.Width, size.Height), locationName);
}
public void DrawSection(string sectionName)
public void DrawLocation(string locationName)
{
Canvas.DrawNamedDestinationAnnotation(new SKPoint(0, 0), sectionName);
Canvas.DrawNamedDestinationAnnotation(new SKPoint(0, 0), locationName);
}
public void Rotate(float angle)
+4 -4
View File
@@ -6,16 +6,16 @@ namespace QuestPDF.Drawing
{
internal class SkiaDocumentCanvasBase : SkiaCanvasBase
{
private SKDocument? Document { get; }
private SKDocument Document { get; }
protected SkiaDocumentCanvasBase(SKDocument document)
public SkiaDocumentCanvasBase(SKDocument document)
{
Document = document;
}
~SkiaDocumentCanvasBase()
{
Document?.Dispose();
Document.Dispose();
}
public override void BeginDocument()
@@ -25,7 +25,7 @@ namespace QuestPDF.Drawing
public override void EndDocument()
{
Canvas?.Dispose();
Canvas.Dispose();
Document.Close();
Document.Dispose();
-55
View File
@@ -1,55 +0,0 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using SkiaSharp;
namespace QuestPDF.Drawing
{
public class PreviewerPicture
{
public SKPicture Picture { get; set; }
public Size Size { get; set; }
public PreviewerPicture(SKPicture picture, Size size)
{
Picture = picture;
Size = size;
}
}
internal class SkiaPictureCanvas : SkiaCanvasBase
{
private SKPictureRecorder? PictureRecorder { get; set; }
private Size? CurrentPageSize { get; set; }
public ICollection<PreviewerPicture> Pictures { get; } = new List<PreviewerPicture>();
public override void BeginDocument()
{
Pictures.Clear();
}
public override void BeginPage(Size size)
{
CurrentPageSize = size;
PictureRecorder = new SKPictureRecorder();
Canvas = PictureRecorder.BeginRecording(new SKRect(0, 0, size.Width, size.Height));
}
public override void EndPage()
{
var picture = PictureRecorder?.EndRecording();
if (picture != null && CurrentPageSize.HasValue)
Pictures.Add(new PreviewerPicture(picture, CurrentPageSize.Value));
PictureRecorder?.Dispose();
PictureRecorder = null;
}
public override void EndDocument() { }
}
}
+8
View File
@@ -0,0 +1,8 @@
namespace QuestPDF.Drawing
{
internal struct TextMeasurement
{
public int LineIndex { get; set; }
public float FragmentWidth { get; set; }
}
}
-212
View File
@@ -1,212 +0,0 @@
using System;
using System.Linq;
using HarfBuzzSharp;
using QuestPDF.Infrastructure;
using SkiaSharp;
using Buffer = HarfBuzzSharp.Buffer;
namespace QuestPDF.Drawing
{
internal class TextShaper
{
public const int FontShapingScale = 512;
private TextStyle TextStyle { get; }
private SKFont Font => TextStyle.ToFont();
private Font ShaperFont => TextStyle.ToShaperFont();
private SKPaint Paint => TextStyle.ToPaint();
public TextShaper(TextStyle textStyle)
{
TextStyle = textStyle;
}
public TextShapingResult Shape(string text)
{
using var buffer = new Buffer();
PopulateBufferWithText(buffer, text);
buffer.GuessSegmentProperties();
if (TextStyle.Direction == TextDirection.LeftToRight)
buffer.Direction = Direction.LeftToRight;
if (TextStyle.Direction == TextDirection.RightToLeft)
buffer.Direction = Direction.RightToLeft;
ShaperFont.Shape(buffer);
var length = buffer.Length;
var glyphInfos = buffer.GlyphInfos;
var glyphPositions = buffer.GlyphPositions;
var scaleY = Paint.TextSize / FontShapingScale;
var scaleX = scaleY * Paint.TextScaleX;
var xOffset = 0f;
var yOffset = 0f;
var glyphs = new ShapedGlyph[length];
for (var i = 0; i < length; i++)
{
glyphs[i] = new ShapedGlyph
{
Codepoint = (ushort)glyphInfos[i].Codepoint,
Position = new SKPoint(xOffset + glyphPositions[i].XOffset * scaleX, yOffset - glyphPositions[i].YOffset * scaleY),
Width = glyphPositions[i].XAdvance * scaleX
};
xOffset += glyphPositions[i].XAdvance * scaleX;
yOffset += glyphPositions[i].YAdvance * scaleY;
}
return new TextShapingResult(buffer.Direction, glyphs);
}
void PopulateBufferWithText(Buffer buffer, string text)
{
var encoding = Paint.TextEncoding;
if (encoding == SKTextEncoding.Utf8)
buffer.AddUtf8(text);
else if (encoding == SKTextEncoding.Utf16)
buffer.AddUtf16(text);
else if (encoding == SKTextEncoding.Utf32)
buffer.AddUtf32(text);
else
throw new NotSupportedException("TextEncoding of type GlyphId is not supported.");
}
}
internal struct ShapedGlyph
{
public ushort Codepoint;
public SKPoint Position;
public float Width;
}
internal struct DrawTextCommand
{
public SKTextBlob SkTextBlob;
public float TextOffsetX;
}
internal class TextShapingResult
{
private Direction Direction { get; }
private ShapedGlyph[] Glyphs { get; }
public int Length => Glyphs.Length;
public ShapedGlyph this[int index] =>
Direction == Direction.LeftToRight
? Glyphs[index]
: Glyphs[Glyphs.Length - 1 - index];
public TextShapingResult(Direction direction, ShapedGlyph[] glyphs)
{
Direction = direction;
Glyphs = glyphs;
}
public int BreakText(int startIndex, float maxWidth)
{
return Direction switch
{
Direction.LeftToRight => BreakTextLeftToRight(),
Direction.RightToLeft => BreakTextRightToLeft(),
_ => throw new ArgumentOutOfRangeException()
};
int BreakTextLeftToRight()
{
var index = startIndex;
maxWidth += Glyphs[startIndex].Position.X;
while (index < Glyphs.Length)
{
var glyph = Glyphs[index];
if (glyph.Position.X + glyph.Width > maxWidth + Size.Epsilon)
break;
index++;
}
return index - 1;
}
int BreakTextRightToLeft()
{
var index = startIndex;
var startOffset = this[startIndex].Position.X + this[startIndex].Width;
while (index < Glyphs.Length)
{
if (startOffset - this[index].Position.X > maxWidth + Size.Epsilon)
break;
index++;
}
return index - 1;
}
}
public float MeasureWidth(int startIndex, int endIndex)
{
if (Glyphs.Length == 0)
return 0;
var start = this[startIndex];
var end = this[endIndex];
return Direction switch
{
Direction.LeftToRight => end.Position.X - start.Position.X + end.Width,
Direction.RightToLeft => start.Position.X - end.Position.X + start.Width,
_ => throw new NotSupportedException()
};
}
public DrawTextCommand? PositionText(int startIndex, int endIndex, TextStyle textStyle)
{
if (Glyphs.Length == 0)
return null;
if (startIndex > endIndex)
return null;
using var skTextBlobBuilder = new SKTextBlobBuilder();
var positionedRunBuffer = skTextBlobBuilder.AllocatePositionedRun(textStyle.ToFont(), endIndex - startIndex + 1);
var glyphSpan = positionedRunBuffer.GetGlyphSpan();
var positionSpan = positionedRunBuffer.GetPositionSpan();
for (var sourceIndex = startIndex; sourceIndex <= endIndex; sourceIndex++)
{
var runIndex = sourceIndex - startIndex;
glyphSpan[runIndex] = this[sourceIndex].Codepoint;
positionSpan[runIndex] = this[sourceIndex].Position;
}
var firstVisualCharacterIndex = Direction == Direction.LeftToRight
? startIndex
: endIndex;
return new DrawTextCommand
{
SkTextBlob = skTextBlobBuilder.Build(),
TextOffsetX = -this[firstVisualCharacterIndex].Position.X
};
}
}
}
+3 -17
View File
@@ -1,7 +1,5 @@
using System;
using System.IO;
using QuestPDF.Drawing.Exceptions;
using QuestPDF.Helpers;
using System.IO;
using QuestPDF.Infrastructure;
using SkiaSharp;
namespace QuestPDF.Drawing
@@ -9,21 +7,9 @@ namespace QuestPDF.Drawing
internal class XpsCanvas : SkiaDocumentCanvasBase
{
public XpsCanvas(Stream stream, DocumentMetadata documentMetadata)
: base(CreateXps(stream, documentMetadata))
: base(SKDocument.CreateXps(stream, documentMetadata.RasterDpi))
{
}
private static SKDocument CreateXps(Stream stream, DocumentMetadata documentMetadata)
{
try
{
return SKDocument.CreateXps(stream, documentMetadata.RasterDpi);
}
catch (TypeInitializationException exception)
{
throw new InitializationException("XPS", exception);
}
}
}
}
+1 -10
View File
@@ -4,10 +4,8 @@ using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal class AspectRatio : ContainerElement, ICacheable, IContentDirectionAware
internal class AspectRatio : ContainerElement, ICacheable
{
public ContentDirection ContentDirection { get; set; }
public float Ratio { get; set; } = 1;
public AspectRatioOption Option { get; set; } = AspectRatioOption.FitWidth;
@@ -44,14 +42,7 @@ namespace QuestPDF.Elements
return;
var size = GetTargetSize(availableSpace);
var offset = ContentDirection == ContentDirection.LeftToRight
? Position.Zero
: new Position(availableSpace.Width - size.Width, 0);
Canvas.Translate(offset);
base.Draw(size);
Canvas.Translate(offset.Reverse());
}
private Size GetTargetSize(Size availableSpace)
+1 -4
View File
@@ -1,5 +1,4 @@
using QuestPDF.Drawing;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using SkiaSharp;
@@ -13,9 +12,7 @@ namespace QuestPDF.Elements
internal override SpacePlan Measure(Size availableSpace)
{
return availableSpace.IsNegative()
? SpacePlan.Wrap()
: SpacePlan.FullRender(availableSpace);
return SpacePlan.FullRender(availableSpace);
}
internal override void Draw(Size availableSpace)

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