Compare commits

...

132 Commits

Author SHA1 Message Date
Marcin Ziąbek fc33056434 API improvements 2021-10-22 01:38:08 +02:00
Marcin Ziąbek d3e8386418 Dynamic element prototype 2021-10-19 01:40:56 +02:00
Marcin Ziąbek 68f050a154 Adjusted tests for better benchmarking 2021-10-16 22:34:54 +02:00
Marcin Ziąbek 88e58500a8 Improved caching 2021-10-07 21:26:14 +02:00
Marcin Ziąbek 7980befb77 Synced with 2021.10 2021-10-01 02:32:35 +02:00
Marcin Ziąbek f3b75b43ac Merge branch 'main' of https://github.com/QuestPDF/library into element-proxy
 Conflicts:
	QuestPDF.ReportSample/QuestPDF.ReportSample.csproj
	QuestPDF.ReportSample/Tests.cs
	QuestPDF.UnitTests/ShowOnceTest.cs
	QuestPDF.UnitTests/StackTests.cs
	QuestPDF.UnitTests/TestEngine/Operations/ChildMeasureOperation.cs
	QuestPDF.UnitTests/TestEngine/SingleChildTests.cs
	QuestPDF.UnitTests/TestEngine/TestPlan.cs
	QuestPDF/Drawing/DocumentGenerator.cs
	QuestPDF/Drawing/TextRender.cs
	QuestPDF/Elements/PageBreak.cs
	QuestPDF/Elements/PageNumber.cs
	QuestPDF/Elements/Rotate.cs
	QuestPDF/Elements/Row.cs
	QuestPDF/Elements/Scale.cs
	QuestPDF/Elements/SimpleRotate.cs
	QuestPDF/Elements/Text.cs
	QuestPDF/Elements/Unconstrained.cs
	QuestPDF/Infrastructure/Element.cs
2021-10-01 02:20:21 +02:00
Marcin Ziąbek 17e2727833 Added missing option to style a line 2021-10-01 01:44:53 +02:00
Marcin Ziąbek 1b9ea70f6e Fixed code highlighting 2021-09-30 23:50:34 +02:00
Marcin Ziąbek 87f4c9774a Update readme.md 2021-09-30 23:48:35 +02:00
Marcin Ziąbek 3bdd03031b Updated csproj 2021-09-30 23:08:47 +02:00
Marcin Ziąbek 26335faa97 Improved and fixed old tests 2021-09-30 22:48:39 +02:00
Marcin Ziąbek 7b81e61840 Merge pull request #26 from QuestPDF/richtext
2021.10 Richtext
2021-09-30 22:05:45 +02:00
Marcin Ziąbek 391c5a6092 Merge branch 'main' into richtext 2021-09-30 22:04:47 +02:00
Marcin Ziąbek d951e0a6b2 Removed not needed nuget dependencies 2021-09-19 01:42:52 +02:00
Marcin Ziąbek 188fbc22a7 Added structural tests for: Row, Stack. 2021-09-18 23:40:21 +02:00
Marcin Ziąbek cc6d1c81c8 Updated tests to use FluentAssertions 6 2021-09-18 20:59:21 +02:00
Marcin Ziąbek 6d333da84c Added tests to check simple, container-like constraints for some elements 2021-09-18 01:21:48 +02:00
Marcin Ziąbek e411554a29 Added tests for elements: Box, Layers, Rotate, Scale, SimpleRotate, Translate, Unconstrained 2021-09-18 01:03:58 +02:00
Marcin Ziąbek 9fddea84ba Cleaned tests suite 2021-09-17 22:05:07 +02:00
Marcin Ziąbek 84e896e13b 2021.10.0-beta.2 Improved API naming 2021-09-14 12:16:49 +02:00
Marcin Ziąbek 569864d48a Fixed rendering text when put in the Box element 2021-09-14 02:13:15 +02:00
Marcin Ziąbek 2c11491d4e Library update 2021-09-14 02:02:45 +02:00
Marcin Ziąbek acdd8164be Optimized the TextLine object 2021-09-14 00:16:16 +02:00
Marcin Ziąbek 094b25a9fb Merge remote-tracking branch 'origin/richtext' into richtext
# Conflicts:
#	QuestPDF.Examples/QuestPDF.Examples.csproj
#	QuestPDF/Drawing/DocumentGenerator.cs
#	QuestPDF/Drawing/FontManager.cs
#	QuestPDF/Elements/Text/Items/TextBlockSpan.cs
2021-09-13 20:38:50 +02:00
Marcin Ziąbek 122f35d0a7 2021.10-beta + Code rebase 2021-09-13 20:36:29 +02:00
Marcin Ziąbek f4c9f8b3c2 Added text rendering tests 2021-09-13 20:30:18 +02:00
Marcin Ziąbek ae7873680d Fixed layout rendering 2021-09-13 20:30:16 +02:00
Marcin Ziąbek 175db288f6 Improved text measuring performance 2021-09-13 20:29:53 +02:00
Marcin Ziąbek 22401be791 Fixed rendering spaces, fixed text alignment 2021-09-13 20:29:53 +02:00
Marcin Ziąbek e2774ef347 Improved book layout in benchmark 2021-09-13 20:29:53 +02:00
Marcin Ziąbek 99f004dd85 Improved text rendering (space corner cases) 2021-09-13 20:29:53 +02:00
Marcin Ziąbek 8ada092531 Fixed text rendering (spacing corner case) 2021-09-13 20:29:53 +02:00
Marcin Ziąbek c334390af0 Better new line handling 2021-09-13 20:29:53 +02:00
Marcin Ziąbek deed219a0a Code structure improvements. Text rendering: support for words longer than available space 2021-09-13 20:29:53 +02:00
Marcin Ziąbek bb8e590fc6 Code structure improvements 2021-09-13 20:29:53 +02:00
Marcin Ziąbek a08a7b85c9 Example report fixes 2021-09-13 20:29:52 +02:00
Marcin Ziąbek 755447e1fd Text: rendering custom elements inside text 2021-09-13 20:29:52 +02:00
Marcin Ziąbek b66a53a189 Text component: internal and external links 2021-09-13 20:29:52 +02:00
Marcin Ziąbek 6d41ac9b9a Fix: the Stack may not show last an element in certain scenarios 2021-09-13 20:29:52 +02:00
Marcin Ziąbek a124d23862 Code cleanup 2021-09-13 20:29:50 +02:00
Marcin Ziąbek 5f65b2daed Text Rendering Optimization: Caching Measurement Results 2021-09-13 20:29:44 +02:00
Marcin Ziąbek 07938a1510 Text rendering optimization: style key, font metrics caching 2021-09-13 20:29:34 +02:00
Marcin Ziąbek f17860f519 Implemented text rendering benchmark 2021-09-13 20:29:24 +02:00
Marcin Ziąbek 6023b4de34 Improved text rendering capabilities 2021-09-13 20:29:17 +02:00
Marcin Ziąbek 748b8f65f5 Initial text rendering improvements 2021-09-13 20:29:17 +02:00
Marcin Ziąbek feab280a27 Fixed detecting infinite layout exception in specific cases 2021-09-13 01:09:45 +02:00
Marcin Ziąbek ae32511172 Added text rendering tests 2021-09-13 01:06:06 +02:00
Marcin Ziąbek 6c4da7285a Fixed layout rendering 2021-09-13 00:55:21 +02:00
Marcin Ziąbek 070b6a20bd Improved text measuring performance 2021-09-12 21:37:46 +02:00
Marcin Ziąbek f7cfdb763f Fixed rendering spaces, fixed text alignment 2021-09-12 21:01:10 +02:00
Marcin Ziąbek 5975b19445 Improved book layout in benchmark 2021-09-07 13:34:04 +02:00
Marcin Ziąbek fb7743e828 Improved text rendering (space corner cases) 2021-09-07 13:16:34 +02:00
Marcin Ziąbek eca93b7e66 Added debug pointers to page 2021-09-07 11:17:49 +02:00
Marcin Ziąbek 6a3bfd855e Improved infinite layout debugging experience 2021-09-07 00:32:51 +02:00
Marcin Ziąbek 5e7f2a3adf Simple layout debugging when overflow 2021-09-06 21:43:31 +02:00
Marcin Ziąbek 2f1dd4b383 Improved tests with Benchmark.net 2021-09-06 21:43:12 +02:00
Marcin Ziąbek adec37007b Refactorization: space planning algorithm 2021-09-02 21:48:20 +02:00
Marcin Ziąbek 814d325376 Implemented measurement cache proxy 2021-09-02 13:30:39 +02:00
Marcin Ziąbek 4152a0f701 Improved row performance and layout structure stability 2021-09-02 00:53:50 +02:00
Marcin Ziąbek 6c80c59996 Fixed misspelling in release notes 2021-09-01 19:19:16 +02:00
Marcin Ziąbek 08a37666fe 2021.9.1 2021-09-01 19:14:52 +02:00
Marcin Ziąbek c2fb68562d 2021.9.0 2021-08-30 23:37:44 +02:00
Marcin Ziąbek 683ab362d8 Added support for registering font types based on the suggestion of brendonparker 2021-08-29 20:15:12 +02:00
Marcin Ziąbek 3bb46ecc42 Fixed text rendering (spacing corner case) 2021-08-29 19:18:58 +02:00
Marcin Ziąbek 3112ee10cf Better new line handling 2021-08-27 20:25:20 +02:00
Marcin Ziąbek 899c138842 Code structure improvements. Text rendering: support for words longer than available space 2021-08-27 19:51:28 +02:00
Marcin Ziąbek 5177dcfd2b Code structure improvements 2021-08-27 12:40:06 +02:00
Marcin Ziąbek 4e6415aee2 Example report fixes 2021-08-27 11:40:33 +02:00
Marcin Ziąbek d9b3f016e8 Text: rendering custom elements inside text 2021-08-27 01:55:20 +02:00
Marcin Ziąbek b9cfaf3846 Text component: internal and external links 2021-08-27 01:38:26 +02:00
Marcin Ziąbek e3e7a22af8 Fix: the Stack may not show last an element in certain scenarios 2021-08-27 01:38:10 +02:00
Marcin Ziąbek c938ea32b0 Code cleanup 2021-08-27 01:10:30 +02:00
Marcin Ziąbek 69419c8440 Text Rendering Optimization: Caching Measurement Results 2021-08-27 00:29:04 +02:00
Marcin Ziąbek 23e8aa3491 Text rendering optimization: style key, font metrics caching 2021-08-26 23:36:43 +02:00
Marcin Ziąbek fd5d51e764 Implemented text rendering benchmark 2021-08-26 22:25:33 +02:00
Marcin Ziąbek dbc479db07 Improved text rendering capabilities 2021-08-25 03:40:16 +02:00
Marcin Ziąbek 04af33fc31 Included examples for DSL and complex layouts 2021-08-23 23:51:42 +02:00
Marcin Ziąbek edc595d199 Initial text rendering improvements 2021-08-09 22:35:39 +02:00
Marcin Ziąbek 7f94ae589a Updated release notes and code example 2021-08-03 01:43:48 +02:00
Marcin Ziąbek adfce6204e Improved testing environment by adding proper result files names, Added more code examples 2021-08-03 01:35:46 +02:00
Marcin Ziąbek 6d3c8d0708 Added elements: Rotate, Unconstrained 2021-08-01 21:09:16 +02:00
Marcin Ziąbek 3b8c300126 Merge pull request #17 from QuestPDF/2021.6
Merge 2021.6 for QuestPdf 2021.8
2021-08-01 16:58:44 +02:00
MarcinZiabek 239a042615 Fixed rendering for the "Rotate" element 2021-07-28 21:36:51 +02:00
Marcin Ziąbek 49fb6dadb3 Improved null safety. Fixed tests 2021-07-26 12:26:31 +02:00
Marcin Ziąbek c79a4a0c08 Fixed rendering images 2021-07-26 11:59:48 +02:00
Marcin Ziąbek 31191efd88 Multipass rendering. Total pages / location position support. Continuous pages. 2021-07-26 11:35:52 +02:00
Marcin Ziąbek 20fe49dd01 Update SECURITY.md 2021-07-18 18:10:55 +02:00
Marcin Ziąbek 7cdc1fcee2 Create SECURITY.md 2021-07-18 18:10:20 +02:00
Marcin Ziąbek 14f11232be New elements: rotate, scale, translate 2021-06-09 23:34:25 +02:00
Marcin Ziąbek c05c5e1597 Update readme.md
Updated code, added the support section
2021-05-15 16:09:01 +02:00
Marcin Ziąbek 07f1eb99b9 Fixed: stack should not apply spacing for empty items 2021-05-05 13:26:46 +02:00
Marcin Ziąbek 44d7538578 Examples to the documentation 2021-05-05 00:10:48 +02:00
Marcin Ziąbek b894262f7e Improved developer experience via injecting Element() and Component() content in separate containers 2021-05-03 21:04:23 +02:00
Marcin Ziąbek 4423774648 Fixed Grid spacing. Adjusted performance test 2021-05-03 13:32:46 +02:00
Marcin Ziąbek 5d4f1f530a Merge pull request #11 from QuestPDF/tree_stack
Improved Stack and Row
2021-05-03 11:40:57 +02:00
Marcin Ziąbek 3ba8185871 Merge pull request #10 from QuestPDF/2021.05
2021.05 Update
2021-05-03 11:39:59 +02:00
Marcin Ziąbek b6e0bd505c Merge branch 'main' into 2021.05 2021-05-03 11:39:17 +02:00
Marcin Ziąbek 78e5cbd098 Minor refactorization 2021-05-03 00:38:48 +02:00
Marcin Ziąbek f792f6c749 Added tests for stack and row, project cleanup 2021-05-02 21:11:08 +02:00
Marcin Ziąbek 0d86039c0c Implemented experimental, tree-based simple stack and row 2021-05-02 14:40:08 +02:00
Marcin Ziąbek 779245b260 Minor Fluent API changes 2021-05-01 22:46:37 +02:00
Marcin Ziąbek 1698212669 Improved behaviour of the EnsureSpace element 2021-04-29 11:23:54 +02:00
Marcin Ziąbek 388adc5ad1 Update ElementExamples.cs 2021-04-21 23:44:15 +02:00
Marcin Ziąbek 81a9599b99 Updated version: 2021.5-alpha2 2021-04-21 14:12:22 +02:00
MarcinZiabek 48f41db98b Improved sample report styling 2021-04-20 12:55:07 +02:00
MarcinZiabek 3dd8513e42 Code cleanup 2021-04-20 12:54:38 +02:00
MarcinZiabek c3d805e5e5 Removed not needed images (photos and map). Replaced by image placeholder 2021-04-20 12:15:16 +02:00
Marcin Ziąbek f77d8baa63 Added Box element, Redesigned Debug element, Added image placeholder 2021-04-18 23:11:29 +02:00
MarcinZiabek 950d971119 Consolidated classes for Row and Grid elements 2021-04-17 21:55:19 +02:00
MarcinZiabek e4203838cb Cleaned up using statements 2021-04-17 21:52:33 +02:00
MarcinZiabek 25b690fcca Bug fix: maintain origin matrix after drawing the Canvas element 2021-04-17 21:51:17 +02:00
MarcinZiabek 4e3ba1d878 Fixed wrong import in QuestPDF.Examples.csproj 2021-04-16 10:22:41 +02:00
Marcin Ziąbek 3f0b500b1b New elements: Grid, Canvas, EnsureSpace, Layers. Predefined colors and fonts. 2021-04-16 01:19:34 +02:00
Marcin Ziąbek fb3aec4bf8 PageSize names refactorization 2021-04-09 19:17:36 +02:00
Marcin Ziąbek 8c2250ecde Code refactorization 2021-04-09 19:11:31 +02:00
Marcin Ziąbek bb2850b2d8 Added material design colors 2021-04-09 19:11:17 +02:00
Marcin Ziąbek 842b856f7a Added tests for several elements 2021-04-09 19:10:55 +02:00
Marcin Ziąbek 733dbc8746 Stack element: updated tests 2021-04-09 16:32:32 +02:00
Marcin Ziąbek 0b7b05d36c Decoration element: added tests 2021-04-09 16:32:22 +02:00
Marcin Ziąbek a83ebe2fed Simplified implementation for Section and Page elements 2021-04-03 00:13:14 +02:00
Marcin Ziąbek 6f0c6513da Bugfix: exception when providing null content into the Text element 2021-03-31 13:00:29 +02:00
Marcin Ziąbek 500cea1fbf Simplified the layouting algorithm in the Stack element; Added a Spacing property to the Row element; Implemented new Grid element; Updated examples 2021-03-28 17:49:19 +02:00
Laurence Mingle 3369da42ed Additional page sizes (#6)
* Update PageSizes.cs

Added/Updated page sizes using details found in the Adobe PostScript Printer Description File Format Specification version 4.3.

* Update PageSizes.cs

Rounded values

* Update PageSizes.cs

Using float

* Update PageSizes.cs

Formatting

* Update PageSizes.cs

Fewer PageSizes

* Update PageSizes.cs

Addition of PageSize class, dimensions using point values from papersizes.io and extension methods for portrait & landscape orientation.
2021-03-27 11:08:00 +01:00
MarcinZiabek c5cb588857 Fixed: AspectRatio behaviour does not take into account child measurement. Added tests for AspectRatio, Image and DynamicImage elements. Small TestPlan framework improvements. 2021-03-23 18:05:39 +01:00
Marcin Ziąbek 08126b2fa4 simplified AspectRatio code, better exception handling for QuestPdf.Examples, fixed Placeholder image 2021-03-23 14:43:03 +01:00
Marcin Ziąbek 5e9ff7ec1e Added scaling options for AspectRatio and Image component. Added Debug component to the fluent API 2021-03-22 11:49:27 +01:00
Marcin Ziąbek 9c05c182b8 Fix: GeneratePdf closes provided stream 2021-03-21 17:43:55 +01:00
Marcin Ziąbek dee97b22e4 Improved readme 2021-03-10 19:55:58 +01:00
Marcin Ziąbek 1d1d969b5a 2021.3.1 2021-03-01 17:06:37 +01:00
Marcin Ziąbek 190b1e7dee Update issue templates 2021-03-01 16:49:27 +01:00
Marcin Ziąbek 98be1abe74 Merge pull request #1 from QuestPDF/add-code-of-conduct-1
Create CODE_OF_CONDUCT.md
2021-03-01 16:41:17 +01:00
Marcin Ziąbek 6b6d2d7fa3 Create CODE_OF_CONDUCT.md 2021-03-01 16:40:03 +01:00
529 changed files with 18016 additions and 2490 deletions
+23
View File
@@ -0,0 +1,23 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Please provide an example, minimalistic code that shows the problem.
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Additional context**
Add any other context about the problem here.
+20
View File
@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is.
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
+76
View File
@@ -0,0 +1,76 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at marcin@ziabek.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq
+33
View File
@@ -0,0 +1,33 @@
using System.IO;
using NUnit.Framework;
using QuestPDF.Drawing;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class BarCode
{
[Test]
public void Example()
{
FontManager.RegisterFontType("LibreBarcode39", File.OpenRead("LibreBarcode39-Regular.ttf"));
RenderingTest
.Create()
.PageSize(400, 100)
.FileName()
.ShowResults()
.Render(container =>
{
container
.Background(Colors.White)
.AlignCenter()
.AlignMiddle()
.Text("*QuestPDF*", TextStyle.Default.FontType("LibreBarcode39").Size(64));
});
}
}
}
+32
View File
@@ -0,0 +1,32 @@
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
[TestFixture]
public class BarcodeExamples
{
[Test]
public void Barcode()
{
RenderingTest
.Create()
.PageSize(300, 300)
.FileName()
.Render(container =>
{
container
.Background("#FFF")
.Padding(25)
.Stack(stack =>
{
stack.Item().Border(1).Background(Colors.Grey.Lighten3).Padding(5).Text("Barcode Example");
stack.Item().Border(1).Padding(5).AlignCenter().Text("*123456789*", TextStyle.Default.FontType("CarolinaBar-Demo-25E2").Size(20));
});
});
}
}
}
+46
View File
@@ -0,0 +1,46 @@
using System.Diagnostics;
using System.Linq;
using NUnit.Framework;
using QuestPDF.Drawing;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class ContinuousPageDocument : IDocument
{
public DocumentMetadata GetMetadata() => DocumentMetadata.Default;
public void Compose(IDocumentContainer container)
{
container.Page(page =>
{
page.Margin(20);
page.ContinuousSize(150);
page.Header().Text("Header");
page.Content().PaddingVertical(10).Border(1).Padding(10).Stack(stack =>
{
foreach (var index in Enumerable.Range(1, 100))
stack.Item().Text($"Line {index}", TextStyle.Default.Color(Placeholders.Color()));
});
page.Footer().Text("Footer");
});
}
}
public class ContinuousPageExamples
{
[Test]
public void ContinuousPage()
{
var path = "example.pdf";
new ContinuousPageDocument().GeneratePdf(path);
Process.Start("explorer", path);
}
}
}
+33
View File
@@ -0,0 +1,33 @@
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
namespace QuestPDF.Examples
{
public class DebuggingTesting
{
[Test]
public void Stack()
{
RenderingTest
.Create()
.PageSize(500, 360)
.FileName()
.Render(container =>
{
container
.Background(Colors.White)
.Padding(15)
.Grid(grid =>
{
grid.Spacing(15);
grid.Item().Background(Colors.Grey.Medium).Text(Placeholders.LoremIpsum());
grid.Item().DebugPointer("Test").Background(Colors.Grey.Lighten1).Height(1000); // <-- problem
grid.Item().Background(Colors.Grey.Lighten2).Height(150);
});
});
}
}
}
+170
View File
@@ -0,0 +1,170 @@
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 class OrdersTable : IDynamicComponent
{
private ICollection<OrderItem> Items { get; }
private ICollection<OrderItem> ItemsLeft { get; set; }
public OrdersTable(ICollection<OrderItem> items)
{
Items = items;
}
public void Compose(DynamicContext context, IDynamicContainer container)
{
if (context.Operation == DynamicLayoutOperation.Reset)
{
ItemsLeft = new List<OrderItem>(Items);
return;
}
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));
if (ItemsLeft.Count > rows.Count)
container.HasMoreContent();
if (context.Operation == DynamicLayoutOperation.Draw)
ItemsLeft = ItemsLeft.Skip(rows.Count).ToList();
container.Box().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);
});
}
private IDynamicElement ComposeHeader(DynamicContext context)
{
return context.CreateElement(element =>
{
element
.BorderBottom(1)
.BorderColor(Colors.Grey.Darken2)
.Padding(5)
.Row(row =>
{
var textStyle = TextStyle.Default.SemiBold();
row.ConstantColumn(30).Text("#", textStyle);
row.RelativeColumn().Text("Item name", textStyle);
row.ConstantColumn(50).AlignRight().Text("Count", textStyle);
row.ConstantColumn(50).AlignRight().Text("Price", textStyle);
row.ConstantColumn(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
.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;
var counter = Items.Count - ItemsLeft.Count + 1;
foreach (var orderItem in ItemsLeft)
{
var element = context.CreateElement(content =>
{
content
.BorderBottom(1)
.BorderColor(Colors.Grey.Lighten2)
.Padding(5)
.Row(row =>
{
row.ConstantColumn(30).Text(counter++);
row.RelativeColumn().Text(orderItem.ItemName);
row.ConstantColumn(50).AlignRight().Text(orderItem.Count);
row.ConstantColumn(50).AlignRight().Text($"{orderItem.Price}$");
row.ConstantColumn(50).AlignRight().Text($"{orderItem.Count*orderItem.Price}$");
});
});
var elementHeight = element.Size.Height;
if (totalHeight + elementHeight > context.AvailableSize.Height)
break;
totalHeight += elementHeight;
yield return (orderItem, element);
}
}
}
public static class DynamicExamples
{
[Test]
public static void Dynamic()
{
RenderingTest
.Create()
.PageSize(PageSizes.A5)
.FileName()
.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().Color(Colors.Blue.Darken2).Size(16));
text.Span("Page ");
text.CurrentPageNumber();
text.Span(" of ");
text.TotalPages();
});
decoration
.Content()
.Dynamic(new OrdersTable(items));
});
});
}
}
}
+824 -102
View File
@@ -1,138 +1,860 @@
using System.Linq;
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using SkiaSharp;
namespace QuestPDF.Examples
{
public class ElementExamples : ExampleTestBase
[TestFixture]
public class ElementExamples
{
//[ShowResult]
[ImageSize(200, 150)]
public void Placeholder(IContainer container)
[Test]
public void Placeholder()
{
container
.Background("#FFF")
.Padding(25)
.Placeholder();
}
//[ShowResult]
[ImageSize(300, 300)]
public void Section(IContainer container)
{
container
.Background("#FFF")
.Padding(25)
.Section(section =>
RenderingTest
.Create()
.PageSize(200, 150)
.FileName()
.Render(container =>
{
section
.Header()
.Background("#888")
.Padding(10)
.Text("Notes", TextStyle.Default.Size(16).Color("#FFF"));
section
.Content()
.Background("#DDD")
.Padding(10)
.ExtendVertical()
.Text(TextPlaceholder.LoremIpsum());
container
.Background("#FFF")
.Padding(25)
.Placeholder();
});
}
//[ShowResult]
[ImageSize(298, 421)]
public void Page(IContainer container)
[Test]
public void Decoration()
{
container
.Background("#FFF")
.Padding(15)
.Page(page =>
RenderingTest
.Create()
.PageSize(300, 300)
.FileName()
.Render(container =>
{
page.Header()
.Height(60)
.Background("#BBB")
.AlignCenter()
.AlignMiddle()
.Text("Header");
container
.Background("#FFF")
.Padding(25)
.Decoration(decoration =>
{
decoration
.Header()
.Background(Colors.Grey.Medium)
.Padding(10)
.Text("Notes", TextStyle.Default.Size(16).Color("#FFF"));
page.Content()
.Background("#DDD")
.AlignCenter()
.AlignMiddle()
.Text("Content");
decoration
.Content()
.Background(Colors.Grey.Lighten3)
.Padding(10)
.ExtendVertical()
.Text(Helpers.Placeholders.LoremIpsum());
});
});
}
[Test]
public void Row()
{
RenderingTest
.Create()
.PageSize(740, 200)
.FileName()
.Render(container =>
{
container
.Background("#FFF")
.Padding(20)
.Stack(stack =>
{
stack.Item()
.PaddingBottom(10)
.AlignCenter()
.Text("This Row element is 700pt wide");
stack.Item().Row(row =>
{
row.ConstantColumn(100)
.Background(Colors.Grey.Lighten1)
.Padding(10)
.ExtendVertical()
.Text("This column is 100 pt wide");
row.RelativeColumn()
.Background(Colors.Grey.Lighten2)
.Padding(10)
.Text("This column takes 1/3 of the available space (200pt)");
row.RelativeColumn(2)
.Background(Colors.Grey.Lighten3)
.Padding(10)
.Text("This column takes 2/3 of the available space (400pt)");
});
});
});
}
[Test]
public void RowSpacing()
{
RenderingTest
.Create()
.PageSize(740, 200)
.FileName()
.Render(container =>
{
container
.Background("#FFF")
.Padding(20)
.Row(row =>
{
row.Spacing(20);
row.RelativeColumn(2).Border(1).Background(Colors.Grey.Lighten1);
row.RelativeColumn(3).Border(1).Background(Colors.Grey.Lighten2);
row.RelativeColumn(4).Border(1).Background(Colors.Grey.Lighten3);
});
});
}
[Test]
public void Stack()
{
RenderingTest
.Create()
.PageSize(500, 360)
.FileName()
.Render(container =>
{
container
.Background("#FFF")
.Padding(15)
.Stack(stack =>
{
stack.Spacing(15);
stack.Item().Background(Colors.Grey.Medium).Height(50);
stack.Item().Background(Colors.Grey.Lighten1).Height(100);
stack.Item().Background(Colors.Grey.Lighten2).Height(150);
});
});
}
[Test]
public void Debug()
{
RenderingTest
.Create()
.PageSize(210, 210)
.FileName()
.Render(container =>
{
container
.Padding(25)
.DebugArea("Grid example", Colors.Blue.Medium)
.Grid(grid =>
{
grid.Columns(3);
grid.Spacing(5);
foreach (var _ in Enumerable.Range(0, 8))
grid.Item().Height(50).Placeholder();
});
});
}
[Test]
public void ElementEnd()
{
RenderingTest
.Create()
.PageSize(300, 200)
.FileName()
.Render(container =>
{
var text = "";
container
.Padding(10)
.Element(x =>
{
if (string.IsNullOrWhiteSpace(text))
x.Height(10).Width(50).Background("#DDD");
else
x.Text(text);
});
});
}
[Test]
public void GridExample()
{
RenderingTest
.Create()
.PageSize(400, 230)
.FileName()
.Render(container =>
{
var textStyle = TextStyle.Default.Size(14);
container
.Padding(15)
.AlignRight()
.Grid(grid =>
{
grid.VerticalSpacing(10);
grid.HorizontalSpacing(10);
grid.AlignCenter();
grid.Columns(10); // 12 by default
grid.Item(6).Background(Colors.Blue.Lighten1).Height(50);
grid.Item(4).Background(Colors.Blue.Lighten3).Height(50);
grid.Item(2).Background(Colors.Teal.Lighten1).Height(70);
grid.Item(3).Background(Colors.Teal.Lighten2).Height(70);
grid.Item(5).Background(Colors.Teal.Lighten3).Height(70);
grid.Item(2).Background(Colors.Green.Lighten1).Height(50);
grid.Item(2).Background(Colors.Green.Lighten2).Height(50);
grid.Item(2).Background(Colors.Green.Lighten3).Height(50);
});
});
}
[Test]
public void Canvas()
{
RenderingTest
.Create()
.PageSize(300, 200)
.FileName()
.Render(container =>
{
container
.Background("#FFF")
.Padding(25)
.Canvas((canvas, size) =>
{
using var paint = new SKPaint
{
Color = SKColors.Red,
StrokeWidth = 10,
IsStroke = true
};
// move origin to the center of the available space
canvas.Translate(size.Width / 2, size.Height / 2);
// draw a circle
canvas.DrawCircle(0, 0, 50, paint);
});
});
}
[Test]
public void LayersExample()
{
RenderingTest
.Create()
.PageSize(400, 250)
.FileName()
.Render(container =>
{
container
.Padding(25)
.Layers(layers =>
{
// layer below main content
layers
.Layer()
.Height(100)
.Width(100)
.Background(Colors.Grey.Lighten3);
layers
.PrimaryLayer()
.Padding(25)
.Stack(stack =>
{
stack.Spacing(5);
foreach (var _ in Enumerable.Range(0, 7))
stack.Item().Text(Placeholders.Sentence());
});
// layer above the main content
layers
.Layer()
.AlignCenter()
.AlignMiddle()
.Text("Watermark", TextStyle.Default.Size(48).Bold().Color(Colors.Green.Lighten3));
layers
.Layer()
.AlignBottom()
.Text(text => text.CurrentPageNumber(TextStyle.Default.Size(16).Color(Colors.Green.Medium)));
});
});
}
// [Test]
// public void EnsureSpace()
// {
// RenderingTest
// .Create()
// .PageSize(300, 400)
// .Render(container =>
// {
// container
// .Padding(50)
// .Page(page =>
// {
// page.Header().PageNumber("Page {pdf:currentPage}");
//
// page.Content().Height(300).Stack(content =>
// {
// content.Item().Height(200).Background(Colors.Grey.Lighten2);
//
// content.Item().EnsureSpace(100).Stack(stack =>
// {
// stack.Spacing(10);
//
// foreach (var _ in Enumerable.Range(0, 4))
// stack.Item().Height(50).Background(Colors.Green.Lighten1);
// });
// });
// });
// });
// }
[Test]
public void RandomColorMatrix()
{
RenderingTest
.Create()
.PageSize(300, 300)
.FileName()
.Render(container =>
{
container
.Padding(25)
.Grid(grid =>
{
grid.Columns(5);
Enumerable
.Range(0, 25)
.Select(x => Placeholders.BackgroundColor())
.ToList()
.ForEach(x => grid.Item().Height(50).Background(x));
});
});
}
[Test]
public void DefinedColors()
{
var colors = new[]
{
Colors.Green.Darken4,
Colors.Green.Darken3,
Colors.Green.Darken2,
Colors.Green.Darken1,
Colors.Green.Medium,
Colors.Green.Lighten1,
Colors.Green.Lighten2,
Colors.Green.Lighten3,
Colors.Green.Lighten4,
Colors.Green.Lighten5,
Colors.Green.Accent1,
Colors.Green.Accent2,
Colors.Green.Accent3,
Colors.Green.Accent4,
};
RenderingTest
.Create()
.PageSize(450, 150)
.FileName()
.Render(container =>
{
container
.Padding(25)
.Height(100)
.Row(row =>
{
foreach (var color in colors)
row.RelativeColumn().Background(color);
});
});
}
[Test]
public void DefinedFonts()
{
var fonts = new[]
{
Fonts.Calibri,
Fonts.Candara,
Fonts.Arial,
Fonts.TimesNewRoman,
Fonts.Consolas,
Fonts.Tahoma,
Fonts.Impact,
Fonts.Trebuchet,
Fonts.ComicSans
};
RenderingTest
.Create()
.PageSize(500, 175)
.FileName()
.Render(container =>
{
container
.Padding(25)
.Grid(grid =>
{
grid.Columns(3);
foreach (var font in fonts)
{
grid.Item()
.Border(1)
.BorderColor(Colors.Grey.Medium)
.Padding(10)
.Text(font, TextStyle.Default.FontType(font).Size(16));
}
});
});
}
[Test]
public void Layers()
{
RenderingTest
.Create()
.PageSize(300, 300)
.FileName()
.Render(container =>
{
container
.Background("#FFF")
.Padding(25)
.Layers(layers =>
{
layers.Layer().Text("Something else");
layers.PrimaryLayer().Stack(stack =>
{
stack.Item().PaddingTop(20).Text("Text 1");
stack.Item().PaddingTop(40).Text("Text 2");
});
layers.Layer().Canvas((canvas, size) =>
{
using var paint = new SKPaint
{
Color = SKColors.Red,
StrokeWidth = 5
};
canvas.Translate(size.Width / 2, size.Height / 2);
canvas.DrawCircle(0, 0, 50, paint);
});
layers.Layer().Background("#8F00").Extend();
layers.Layer().PaddingTop(40).Text("It works!", TextStyle.Default.Size(24));
});
});
}
[Test]
public void Box()
{
RenderingTest
.Create()
.PageSize(300, 150)
.FileName()
.Render(container =>
{
container
.Background("#FFF")
.Padding(15)
.Border(4)
.BorderColor(Colors.Blue.Medium)
//.Box()
.Background(Colors.Grey.Lighten2)
.Padding(15)
.Text("Test of the \n box element", TextStyle.Default.Size(20));
});
}
[Test]
public void Scale()
{
RenderingTest
.Create()
.PageSize(300, 175)
.FileName()
.Render(container =>
{
container
.Background(Colors.White)
.Padding(10)
.Decoration(decoration =>
{
var headerFontStyle = TextStyle
.Default
.Size(20)
.Color(Colors.Blue.Darken2)
.SemiBold();
decoration
.Header()
.PaddingBottom(10)
.Text("Example: scale component", headerFontStyle);
decoration
.Content()
.Stack(stack =>
{
var scales = new[] { 0.8f, 0.9f, 1.1f, 1.2f };
foreach (var scale in scales)
{
var fontColor = scale <= 1f
? Colors.Red.Lighten4
: Colors.Green.Lighten4;
var fontStyle = TextStyle.Default.Size(16);
stack
.Item()
.Border(1)
.Background(fontColor)
.Scale(scale)
.Padding(5)
.Text($"Content with {scale} scale.", fontStyle);
}
});
});
});
}
[Test]
public void Translate()
{
RenderingTest
.Create()
.PageSize(300, 200)
.FileName()
.Render(container =>
{
container
.Background("#FFF")
.Box()
.Padding(25)
.Background(Colors.Green.Lighten3)
.TranslateX(15)
.TranslateY(15)
.Border(2)
.BorderColor(Colors.Green.Darken1)
.Padding(50)
.Text("Moved text", TextStyle.Default.Size(25));
});
}
[Test]
public void ConstrainedRotate()
{
RenderingTest
.Create()
.PageSize(650, 450)
.FileName()
.Render(container =>
{
container
.Padding(20)
.Grid(grid =>
{
grid.Columns(2);
grid.Spacing(10);
foreach (var turns in Enumerable.Range(0, 4))
{
grid.Item()
.Width(300)
.Height(200)
.Background(Colors.Grey.Lighten2)
.Padding(10)
.Element(element =>
{
foreach (var x in Enumerable.Range(0, turns))
element = element.RotateRight();
return element;
})
.Box()
.Background(Colors.White)
.Padding(10)
.Text($"Rotated {turns * 90}°", TextStyle.Default.Size(16));
}
});
});
}
[Test]
public void FreeRotate()
{
RenderingTest
.Create()
.PageSize(300, 300)
.FileName()
.Render(container =>
{
container
.Padding(25)
.Background(Colors.Grey.Lighten2)
page.Footer()
.Height(30)
.Background("#BBB")
.AlignCenter()
.AlignMiddle()
.Text("Footer");
.Background(Colors.White)
.Rotate(30)
.Width(100)
.Height(100)
.Background(Colors.Blue.Medium);
});
}
//[ShowResult]
[ImageSize(740, 200)]
public void Row(IContainer container)
[Test]
public void FreeRotateCenter()
{
container
.Background("#FFF")
.Padding(20)
.Stack(stack =>
RenderingTest
.Create()
.PageSize(300, 300)
.FileName()
.Render(container =>
{
stack.Element()
.PaddingBottom(10)
container
.Padding(25)
.Background(Colors.Grey.Lighten2)
.AlignCenter()
.Text("This Row element is 700pt wide");
.AlignMiddle()
.Background(Colors.White)
.TranslateX(50)
.TranslateY(50)
.Rotate(30)
stack.Element().Row(row =>
{
row.ConstantColumn(100)
.Background("#DDD")
.Padding(10)
.ExtendVertical()
.Text("This column is 100 pt wide");
.TranslateX(-50)
.TranslateY(-50)
.Width(100)
.Height(100)
.Background(Colors.Blue.Medium);
});
}
row.RelativeColumn()
.Background("#BBB")
.Padding(10)
.Text("This column takes 1/3 of the available space (200pt)");
[Test]
public void Flip()
{
RenderingTest
.Create()
.PageSize(350, 350)
.FileName()
.Render(container =>
{
container
.Padding(20)
.Grid(grid =>
{
grid.Columns(2);
grid.Spacing(10);
foreach (var turns in Enumerable.Range(0, 4))
{
grid.Item()
.Width(150)
.Height(150)
.Background(Colors.Grey.Lighten3)
.Padding(10)
.Element(element =>
{
if (turns == 1 || turns == 2)
element = element.FlipHorizontal();
row.RelativeColumn(2)
.Background("#DDD")
.Padding(10)
.Text("This column takes 2/3 of the available space (400pt)");
});
if (turns == 2 || turns == 3)
element = element.FlipVertical();
return element;
})
.Box()
.Background(Colors.White)
.Padding(10)
.Text($"Flipped {turns}", TextStyle.Default.Size(16));
}
});
});
}
//[ShowResult]
[ImageSize(500, 350)]
public void Column(IContainer container)
[Test]
public void RotateInTable()
{
container
.Background("#FFF")
.Padding(15)
.Stack(column =>
RenderingTest
.Create()
.PageSize(200, 200)
.FileName()
.Render(container =>
{
column.Spacing(10);
column
.Element()
.Background("#999")
.Height(50);
column
.Element()
.Background("#BBB")
.Height(100);
column
.Element()
.Background("#DDD")
.Height(150);
container
.Padding(10)
.Border(2)
.Row(row =>
{
row.ConstantColumn(25)
.Border(1)
.RotateLeft()
.AlignCenter()
.AlignMiddle()
.Text("Sample text");
row.RelativeColumn().Border(1).Padding(5).Text(Placeholders.Paragraph());
});
});
}
[Test]
public void Unconstrained()
{
RenderingTest
.Create()
.PageSize(400, 350)
.FileName()
.Render(container =>
{
container
.Padding(25)
.PaddingLeft(75)
.Stack(stack =>
{
stack.Item().Width(300).Height(150).Background(Colors.Blue.Lighten4);
stack
.Item()
// creates an infinite space for its child
.Unconstrained()
// moves the child up and left
.TranslateX(-50)
.TranslateY(-50)
// limits the space for the child
.Width(100)
.Height(100)
.Background(Colors.Blue.Darken1);
stack.Item().Width(300).Height(150).Background(Colors.Blue.Lighten3);
});
});
}
[Test]
public void ComplexLayout()
{
RenderingTest
.Create()
.PageSize(500, 225)
.FileName()
.Render(container =>
{
container
.Padding(25)
.Stack(stack =>
{
stack.Item().Row(row =>
{
row.RelativeColumn().LabelCell("Label 1");
row.RelativeColumn(3).Grid(grid =>
{
grid.Columns(3);
grid.Item(2).LabelCell("Label 2");
grid.Item().LabelCell("Label 3");
grid.Item(2).ValueCell().Text("Value 2");
grid.Item().ValueCell().Text("Value 3");
});
});
stack.Item().Row(row =>
{
row.RelativeColumn().ValueCell().Text("Value 1");
row.RelativeColumn(3).Grid(grid =>
{
grid.Columns(3);
grid.Item().LabelCell("Label 4");
grid.Item(2).LabelCell("Label 5");
grid.Item().ValueCell().Text("Value 4");
grid.Item(2).ValueCell().Text("Value 5");
});
});
stack.Item().Row(row =>
{
row.RelativeColumn().LabelCell("Label 6");
row.RelativeColumn().ValueCell().Text("Value 6");
});
});
});
}
[Test]
public void DomainSpecificLanguage()
{
RenderingTest
.Create()
.PageSize(600, 310)
.FileName()
.Render(container =>
{
container
.Padding(25)
.Grid(grid =>
{
grid.Columns(10);
for(var i=1; i<=4; i++)
{
grid.Item(2).LabelCell(Placeholders.Label());
grid.Item(3).ValueCell().Image(Placeholders.Image(200, 150));
}
});
});
}
}
}
}
@@ -1,89 +0,0 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using NUnit.Framework;
using QuestPDF.Drawing;
using QuestPDF.Elements;
using QuestPDF.Fluent;
using QuestPDF.Infrastructure;
using SkiaSharp;
namespace QuestPDF.Examples.Engine
{
[TestFixture]
public class ExampleTestBase
{
private readonly Size DefaultImageSize = new Size(400, 300);
private const string ResultPath = "Result";
[SetUp]
public void Setup()
{
if (Directory.Exists(ResultPath))
Directory.Delete(ResultPath, true);
Directory.CreateDirectory(ResultPath);
}
[Test]
public void RunTest()
{
GetType()
.GetMethods()
.Where(IsExampleMethod)
.ToList()
.ForEach(HandleExample);
}
private bool IsExampleMethod(MethodInfo method)
{
var parameters = method.GetParameters();
return parameters.Length == 1 && parameters.First().ParameterType == typeof(IContainer);
}
private T GetAttribute<T>(MethodInfo methodInfo) where T : Attribute
{
return methodInfo.GetCustomAttributes().FirstOrDefault(y => y is T) as T;
}
private void HandleExample(MethodInfo methodInfo)
{
var size = GetAttribute<ImageSizeAttribute>(methodInfo)?.Size ?? DefaultImageSize;
var fileName = GetAttribute<FileNameAttribute>(methodInfo)?.FileName ?? methodInfo.Name;
var showResult = GetAttribute<ShowResultAttribute>(methodInfo) != null;
var shouldIgnore = GetAttribute<IgnoreAttribute>(methodInfo) != null;
if (shouldIgnore)
return;
var container = new Container();
methodInfo.Invoke(this, new object[] {container});
Func<int, string> fileNameSchema = i => $"{fileName.ToLower()}-${i}.png";
var document = new SimpleDocument(container, size);
document.GenerateImages(fileNameSchema);
if (showResult)
Process.Start("explorer", fileNameSchema(0));
}
private byte[] RenderPage(Element element, Size size)
{
// scale the result so it is more readable
const float scalingFactor = 2;
var imageInfo = new SKImageInfo((int)(size.Width * scalingFactor), (int)(size.Height * scalingFactor));
using var surface = SKSurface.Create(imageInfo);
surface.Canvas.Scale(scalingFactor);
var canvas = new Canvas(surface.Canvas);
element?.Draw(canvas, size);
surface.Canvas.Save();
return surface.Snapshot().Encode(SKEncodedImageFormat.Png, 100).ToArray();
}
}
}
@@ -1,14 +0,0 @@
using System;
namespace QuestPDF.Examples.Engine
{
public class FileNameAttribute : Attribute
{
public string FileName { get; }
public FileNameAttribute(string fileName)
{
FileName = fileName;
}
}
}
@@ -1,9 +0,0 @@
using System;
namespace QuestPDF.Examples.Engine
{
public class IgnoreAttribute : Attribute
{
}
}
@@ -1,19 +0,0 @@
using System;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples.Engine
{
public class ImageSizeAttribute : Attribute
{
private int Width { get; }
private int Height { get; }
public Size Size => new Size(Width, Height);
public ImageSizeAttribute(int width, int height)
{
Width = width;
Height = height;
}
}
}
+95
View File
@@ -0,0 +1,95 @@
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using QuestPDF.Elements;
using QuestPDF.Fluent;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples.Engine
{
public enum RenderingTestResult
{
Pdf,
Images
}
public class RenderingTest
{
private string FileNamePrefix = "test";
private Size Size { get; set; }
private bool ShowResult { get; set; }
private RenderingTestResult ResultType { get; set; } = RenderingTestResult.Images;
private RenderingTest()
{
}
public static RenderingTest Create()
{
return new RenderingTest();
}
public RenderingTest FileName([CallerMemberName] string fileName = "test")
{
FileNamePrefix = fileName;
return this;
}
public RenderingTest PageSize(Size size)
{
Size = size;
return this;
}
public RenderingTest PageSize(int width, int height)
{
return PageSize(new Size(width, height));
}
public RenderingTest ProducePdf()
{
ResultType = RenderingTestResult.Pdf;
return this;
}
public RenderingTest ProduceImages()
{
ResultType = RenderingTestResult.Images;
return this;
}
public RenderingTest ShowResults()
{
ShowResult = true;
return this;
}
public void Render(Action<IContainer> content)
{
var container = new Container();
content(container);
var maxPages = ResultType == RenderingTestResult.Pdf ? 1000 : 10;
var document = new SimpleDocument(container, Size, maxPages);
if (ResultType == RenderingTestResult.Images)
{
Func<int, string> fileNameSchema = i => $"{FileNamePrefix}-${i}.png";
document.GenerateImages(fileNameSchema);
if (ShowResult)
Process.Start("explorer", fileNameSchema(0));
}
if (ResultType == RenderingTestResult.Pdf)
{
var fileName = $"{FileNamePrefix}.pdf";
document.GeneratePdf(fileName);
if (ShowResult)
Process.Start("explorer", fileName);
}
}
}
}
@@ -1,9 +0,0 @@
using System;
namespace QuestPDF.Examples.Engine
{
public class ShowResultAttribute : Attribute
{
}
}
+15 -6
View File
@@ -1,4 +1,5 @@
using QuestPDF.Drawing;
using QuestPDF.Elements;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
@@ -7,27 +8,35 @@ namespace QuestPDF.Examples.Engine
{
public class SimpleDocument : IDocument
{
public const int ImageScalingFactor = 2;
private IContainer Container { get; }
private Size Size { get; }
private int MaxPages { get; }
public SimpleDocument(IContainer container, Size size)
public SimpleDocument(IContainer container, Size size, int maxPages)
{
Container = container;
Size = size;
MaxPages = maxPages;
}
public DocumentMetadata GetMetadata()
{
return new DocumentMetadata()
{
RasterDpi = PageSizes.PointsPerInch * 2,
Size = Size
RasterDpi = PageSizes.PointsPerInch * ImageScalingFactor,
DocumentLayoutExceptionThreshold = MaxPages
};
}
public void Compose(IContainer container)
public void Compose(IDocumentContainer container)
{
container.Element(Container.Child);
container.Page(page =>
{
page.Size(new PageSize(Size.Width, Size.Height));
page.Content().Container().Background(Colors.White).Element(Container as Container);
});
}
}
}
+24 -17
View File
@@ -1,4 +1,4 @@
using System.Linq;
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
@@ -12,33 +12,40 @@ namespace QuestPDF.Examples
{
return container
.Border(1)
.Background(dark ? "#EEE" : "#FFF")
.Background(dark ? Colors.Grey.Lighten2 : Colors.White)
.Padding(10);
}
public static IContainer LabelCell(this IContainer container) => container.Cell(true);
public static void LabelCell(this IContainer container, string text) => container.Cell(true).Text(text, TextStyle.Default.Medium());
public static IContainer ValueCell(this IContainer container) => container.Cell(false);
}
public class FrameExample: ExampleTestBase
public class FrameExample
{
[ImageSize(550, 400)]
[ShowResult]
public void Frame(IContainer container)
[Test]
public void Frame()
{
container
.Background("#FFF")
.Padding(25)
.Stack(stack =>
RenderingTest
.Create()
.PageSize(550, 400)
.FileName()
.ShowResults()
.Render(container =>
{
for(var i=1; i<=4; i++)
{
stack.Element().Row(row =>
container
.Background("#FFF")
.Padding(25)
.Stack(stack =>
{
row.RelativeColumn(2).LabelCell().Text(TextPlaceholder.Label());
row.RelativeColumn(3).ValueCell().Text(TextPlaceholder.Paragraph());
for(var i=1; i<=4; i++)
{
stack.Item().Row(row =>
{
row.RelativeColumn(2).LabelCell(Placeholders.Label());
row.RelativeColumn(3).ValueCell().Text(Placeholders.Paragraph());
});
}
});
}
});
}
}
Binary file not shown.
+23 -16
View File
@@ -1,4 +1,5 @@
using System.Net;
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Infrastructure;
@@ -27,27 +28,33 @@ namespace QuestPDF.Examples
}
}
public class LoremPicsumExample : ExampleTestBase
public class LoremPicsumExample
{
[ShowResult]
[ImageSize(350, 280)]
public void LoremPicsum(IContainer container)
[Test]
public void LoremPicsum()
{
container
.Background("#FFF")
.Padding(25)
.Stack(column =>
RenderingTest
.Create()
.PageSize(350, 280)
.FileName()
.Render(container =>
{
column.Spacing(10);
container
.Background("#FFF")
.Padding(25)
.Stack(column =>
{
column.Spacing(10);
column
.Element()
.Component(new LoremPicsum(true));
column
.Item()
.Component(new LoremPicsum(true));
column
.Element()
.AlignRight()
.Text("From Lorem Picsum");
column
.Item()
.AlignRight()
.Text("From Lorem Picsum");
});
});
}
}
+141 -106
View File
@@ -1,132 +1,167 @@
using QuestPDF.Examples.Engine;
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class Examples : ExampleTestBase
public class Examples
{
public void Padding(IContainer container)
[Test]
public void Padding()
{
container
.Background("#FDD")
.Padding(50)
.Background("#AFA")
.PaddingVertical(50)
.Background("#77F")
.PaddingHorizontal(50)
.Background("#444");
}
public void Border(IContainer container)
{
container
.Background("#EEE")
.Padding(25)
.AlignBottom()
.AlignCenter()
.BorderBottom(2)
.BorderColor("#000")
.Background("FFF")
.Padding(5)
.Text("Sample text", TextStyle.Default.FontType("Segoe UI emoji").Alignment(HorizontalAlignment.Center));
}
public void Alignment(IContainer container)
{
container
.Stack(column =>
RenderingTest
.Create()
.PageSize(300, 300)
.FileName()
.ShowResults()
.Render(container =>
{
column
.Element()
.Height(100)
.Background("#FFF")
.AlignLeft()
.AlignMiddle()
container
.Background("#FDD")
.Padding(50)
.Background("#AFA")
.PaddingVertical(50)
.Background("#77F")
.PaddingHorizontal(50)
.Width(50)
.Height(50)
.Background("#444");
column
.Element()
.Height(100)
.Background("#DDD")
.AlignCenter()
.AlignMiddle()
.Width(50)
.Height(50)
.Background("#222");
column
.Element()
.Height(100)
.Background("#BBB")
.AlignRight()
.AlignMiddle()
.Width(50)
.Height(50)
.Background("#000");
});
}
public void Expand(IContainer container)
[Test]
public void Border()
{
container
.Stack(column =>
RenderingTest
.Create()
.PageSize(200, 150)
.FileName()
.Render(container =>
{
column
.Element()
.Height(150)
.Row(row =>
container
.Background("#EEE")
.Padding(25)
.AlignBottom()
.AlignCenter()
.BorderBottom(2)
.BorderColor("#000")
.Background("FFF")
.Padding(5)
.AlignCenter()
.Text("Sample text", TextStyle.Default.FontType("Segoe UI emoji"));
});
}
[Test]
public void Alignment()
{
RenderingTest
.Create()
.PageSize(200, 150)
.FileName()
.Render(container =>
{
container
.Stack(column =>
{
row.RelativeColumn()
.Extend()
.Background("FFF")
column
.Item()
.Height(100)
.Background("#FFF")
.AlignLeft()
.AlignMiddle()
.Height(50)
.Width(50)
.Background("444");
row.RelativeColumn()
.Extend()
.Background("BBB")
.Height(50)
.ExtendHorizontal()
.Background("444");
});
.Background("#444");
column
.Element()
.Height(150)
.Row(row =>
{
row.RelativeColumn()
.Extend()
.Background("BBB")
column
.Item()
.Height(100)
.Background("#DDD")
.AlignCenter()
.AlignMiddle()
.ExtendVertical()
.Width(50)
.Background("444");
row.RelativeColumn()
.Extend()
.Background("BBB")
.Height(50)
.Background("#222");
column
.Item()
.Height(100)
.Background("#BBB")
.AlignRight()
.AlignMiddle()
.ExtendVertical()
.ExtendHorizontal()
.Background("444");
.Width(50)
.Height(50)
.Background("#000");
});
});
}
[Test]
public void Expand()
{
RenderingTest
.Create()
.PageSize(200, 150)
.FileName()
.Render(container =>
{
container
.Stack(column =>
{
column
.Item()
.Height(150)
.Row(row =>
{
row.RelativeColumn()
.Extend()
.Background("FFF")
.Height(50)
.Width(50)
.Background("444");
row.RelativeColumn()
.Extend()
.Background("BBB")
.Height(50)
.ExtendHorizontal()
.Background("444");
});
column
.Item()
.Height(150)
.Row(row =>
{
row.RelativeColumn()
.Extend()
.Background("BBB")
.ExtendVertical()
.Width(50)
.Background("444");
row.RelativeColumn()
.Extend()
.Background("BBB")
.ExtendVertical()
.ExtendHorizontal()
.Background("444");
});
});
});
}
+12 -4
View File
@@ -6,15 +6,23 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="nunit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.15.1" />
<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.2" />
<PackageReference Include="SkiaSharp" Version="2.80.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\QuestPDF.InvoiceSample\QuestPDF.InvoiceSample.csproj" />
<ProjectReference Include="..\QuestPDF\QuestPDF.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="quo-vadis.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="LibreBarcode39-Regular.ttf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
+228
View File
@@ -0,0 +1,228 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class TextBenchmark
{
[Test]
public void Generate()
{
var chapters = GetBookChapters().ToList();
RenderingTest
.Create()
.PageSize(PageSizes.A4)
.FileName()
.ProducePdf()
.ShowResults()
.Render(x => ComposeBook(x, chapters));
}
[Test]
public void Benchmark()
{
var chapters = GetBookChapters().ToList();
var results = PerformTest(16).ToList();
Console.WriteLine($"Min: {results.Min():F}");
Console.WriteLine($"Max: {results.Max():F}");
Console.WriteLine($"Avg: {results.Average():F}");
void GenerateDocument()
{
RenderingTest
.Create()
.PageSize(PageSizes.A4)
.FileName()
.ProducePdf()
.Render(x => ComposeBook(x, chapters));
}
IEnumerable<float> PerformTest(int attempts)
{
foreach (var i in Enumerable.Range(0, attempts))
{
var timer = new Stopwatch();
timer.Start();
GenerateDocument();
timer.Stop();
Console.WriteLine($"Attempt {i}: {timer.ElapsedMilliseconds:F}");
yield return timer.ElapsedMilliseconds;
}
}
}
class BookChapter
{
public string Title { get; set; }
public string Content { get; set; }
}
private static IEnumerable<BookChapter> GetBookChapters()
{
var book = File.ReadAllLines("quo-vadis.txt");
var chapterPointers = book
.Select((line, index) => new
{
LineNumber = index,
Text = line
})
.Where(x => x.Text.Length < 50 && x.Text.Contains("Rozdział") || x.Text.Contains("-----"))
.Select(x => x.LineNumber)
.ToList();
foreach (var index in Enumerable.Range(0, chapterPointers.Count - 1))
{
var chapter = chapterPointers[index];
var title = book[chapter];
var lineFrom = chapterPointers[index];
var lineTo = chapterPointers[index + 1] - 1;
var lines = book.Skip(lineFrom + 1).Take(lineTo - lineFrom).Where(x => !string.IsNullOrWhiteSpace(x));
var content = string.Join(Environment.NewLine, lines);
yield return new BookChapter
{
Title = title,
Content = content
};
}
}
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);
void ComposePage(IContainer container)
{
container
.Padding(50)
.Decoration(decoration =>
{
decoration
.Content()
.Stack(stack =>
{
stack.Item().Element(Title);
stack.Item().PageBreak();
stack.Item().Element(TableOfContents);
stack.Item().PageBreak();
Chapters(stack);
stack.Item().Element(Acknowledgements);
});
decoration.Footer().Element(Footer);
});
}
void Title(IContainer container)
{
container
.Extend()
.PaddingBottom(200)
.AlignBottom()
.Stack(stack =>
{
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.Stack(stack =>
{
SectionTitle(stack, "Spis treści");
foreach (var chapter in chapters)
{
stack.Item().InternalLink(chapter.Title).Row(row =>
{
row.RelativeColumn().Text(chapter.Title, normalStyle);
row.ConstantColumn(100).AlignRight().Text(text => text.PageNumberOfLocation(chapter.Title, normalStyle));
});
}
});
}
void Chapters(StackDescriptor stack)
{
foreach (var chapter in chapters)
{
stack.Item().Element(container => Chapter(container, chapter.Title, chapter.Content));
}
}
void Chapter(IContainer container, string title, string content)
{
container.Stack(stack =>
{
SectionTitle(stack, title);
stack.Item().Text(text =>
{
text.ParagraphSpacing(5);
text.Span(content, normalStyle);
});
stack.Item().PageBreak();
});
}
void Acknowledgements(IContainer container)
{
container.Stack(stack =>
{
SectionTitle(stack, "Podziękowania");
stack.Item().Text(text =>
{
text.DefaultTextStyle(normalStyle);
text.Span("Ten dokument został wygenerowany na podstawie książki w formacie TXT opublikowanej w serwisie ");
text.ExternalLocation("wolnelektury.pl", "https://wolnelektury.pl/", normalStyle.Color(Colors.Blue.Medium).Underline());
text.Span(". Dziękuję za wspieranie polskiego czytelnictwa!");
});
});
}
void SectionTitle(StackDescriptor stack, string text)
{
stack.Item().Location(text).Text(text, subtitleStyle);
stack.Item().PaddingTop(10).PaddingBottom(50).BorderBottom(1).BorderColor(Colors.Grey.Lighten2).ExtendHorizontal();
}
void Footer(IContainer container)
{
container
.AlignCenter()
.Text(text =>
{
text.CurrentPageNumber();
text.Span(" / ");
text.TotalPages();
});
}
}
}
}
+277
View File
@@ -0,0 +1,277 @@
using System;
using System.Linq;
using System.Text;
using NUnit.Framework;
using QuestPDF.Examples.Engine;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples
{
public class TextExamples
{
[Test]
public void SimpleTextBlock()
{
RenderingTest
.Create()
.PageSize(500, 300)
.FileName()
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.Padding(5)
.Box()
.Border(1)
.Padding(10)
.Text(text =>
{
text.DefaultTextStyle(TextStyle.Default.Size(20));
text.Span("This is a normal text, followed by an ");
text.Span("underlined red text.", TextStyle.Default.Size(20).Color(Colors.Red.Medium).Underline());
});
});
}
[Test]
public void ParagraphSpacing()
{
RenderingTest
.Create()
.PageSize(500, 300)
.FileName()
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.Padding(5)
.Box()
.Border(1)
.Padding(10)
.Text(text =>
{
text.ParagraphSpacing(10);
foreach (var i in Enumerable.Range(1, 3))
{
text.Span($"Paragraph {i}: ", TextStyle.Default.SemiBold());
text.Line(Placeholders.Paragraph());
}
});
});
}
[Test]
public void CustomElement()
{
RenderingTest
.Create()
.PageSize(500, 200)
.FileName()
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.Padding(5)
.Box()
.Border(1)
.Padding(10)
.Text(text =>
{
text.DefaultTextStyle(TextStyle.Default.Size(20));
text.Span("This is a random image aligned to the baseline: ");
text.Element()
.PaddingBottom(-6)
.Height(24)
.Width(48)
.Image(Placeholders.Image);
text.Span(".");
});
});
}
[Test]
public void TextElements()
{
RenderingTest
.Create()
.PageSize(PageSizes.A4)
.FileName()
.ProducePdf()
.ShowResults()
.Render(container =>
{
container
.Padding(20)
.Padding(10)
.Box()
.Border(1)
.Padding(5)
.Padding(10)
.Text(text =>
{
text.DefaultTextStyle(TextStyle.Default);
text.AlignLeft();
text.ParagraphSpacing(10);
text.Line(Placeholders.LoremIpsum());
text.Span($"This is target text that should show up. {DateTime.UtcNow:T} > This is a short sentence that will be wrapped into second line hopefully, right? <", TextStyle.Default.Underline());
});
});
}
[Test]
public void TextStack()
{
RenderingTest
.Create()
.PageSize(PageSizes.A4)
.FileName()
.ProducePdf()
.ShowResults()
.Render(container =>
{
container
.Padding(20)
.Padding(10)
.Box()
.Border(1)
.Padding(5)
.Padding(10)
.Text(text =>
{
text.DefaultTextStyle(TextStyle.Default);
text.AlignLeft();
text.ParagraphSpacing(10);
foreach (var i in Enumerable.Range(1, 100))
text.Line($"{i}: {Placeholders.Paragraph()}");
});
});
}
[Test]
public void SpaceIssue()
{
RenderingTest
.Create()
.PageSize(PageSizes.A4)
.FileName()
.ProducePdf()
.ShowResults()
.Render(container =>
{
container
.Padding(20)
.Padding(10)
.Box()
.Border(1)
.Padding(5)
.Padding(10)
.Text(text =>
{
text.DefaultTextStyle(TextStyle.Default);
text.AlignLeft();
text.ParagraphSpacing(10);
text.Span(Placeholders.LoremIpsum());
text.EmptyLine();
text.Span("This text is a normal text, ");
text.Span("this is a bold text, ", TextStyle.Default.Bold());
text.Span("this is a red and underlined text, ", TextStyle.Default.Color(Colors.Red.Medium).Underline());
text.Span("and this is slightly bigger text.", TextStyle.Default.Size(16));
text.EmptyLine();
text.Span("The new text element also supports injecting custom content between words: ");
text.Element().PaddingBottom(-10).Height(16).Width(32).Image(Placeholders.Image);
text.Span(".");
text.EmptyLine();
text.Span("This is page number ");
text.CurrentPageNumber();
text.Span(" out of ");
text.TotalPages();
text.EmptyLine();
text.ExternalLocation("Please visit QuestPDF website", "https://www.questpdf.com");
text.EmptyLine();
text.Span(Placeholders.Paragraphs());
text.EmptyLine();
text.Span(Placeholders.Paragraphs(), TextStyle.Default.Italic());
text.Line("This is target text that does not show up. " + Placeholders.Paragraph());
});
});
}
[Test]
public void HugeList()
{
RenderingTest
.Create()
.PageSize(PageSizes.A4)
.FileName()
.ProducePdf()
.ShowResults()
.Render(container =>
{
container
.Padding(20)
.Padding(10)
.Box()
.Border(1)
.Padding(5)
.Padding(10)
.Text(text =>
{
text.DefaultTextStyle(TextStyle.Default);
text.AlignLeft();
text.ParagraphSpacing(10);
text.Span("This text is a normal text, ");
text.Span("this is a bold text, ", TextStyle.Default.Bold());
text.Span("this is a red and underlined text, ", TextStyle.Default.Color(Colors.Red.Medium).Underline());
text.Span("and this is slightly bigger text.", TextStyle.Default.Size(16));
text.Span("The new text element also supports injecting custom content between words: ");
text.Element().PaddingBottom(-10).Height(16).Width(32).Image(Placeholders.Image);
text.Span(".");
text.EmptyLine();
foreach (var i in Enumerable.Range(1, 100))
{
text.Line($"{i}: {Placeholders.Paragraph()}");
text.ExternalLocation("Please visit QuestPDF website", "https://www.questpdf.com");
text.Span("This is page number ");
text.CurrentPageNumber();
text.Span(" out of ");
text.TotalPages();
text.EmptyLine();
}
});
});
}
}
}
File diff suppressed because one or more lines are too long
+22 -24
View File
@@ -2,7 +2,6 @@ using System;
using System.Collections.Generic;
using System.Linq;
using QuestPDF.Helpers;
using QuestPDF.ReportSample.Layouts;
namespace QuestPDF.ReportSample
{
@@ -18,9 +17,9 @@ namespace QuestPDF.ReportSample
Title = "Sample Report Document",
HeaderFields = HeaderFields(),
LogoData = Helpers.GetImage("logo.png"),
Sections = Enumerable.Range(0, 10).Select(x => GenerateSection()).ToList(),
Photos = Enumerable.Range(0, 10).Select(x => GetReportPhotos()).ToList()
LogoData = Helpers.GetImage("Logo.png"),
Sections = Enumerable.Range(0, 40).Select(x => GenerateSection()).ToList(),
Photos = Enumerable.Range(0, 25).Select(x => GetReportPhotos()).ToList()
};
List<ReportHeaderField> HeaderFields()
@@ -41,6 +40,11 @@ namespace QuestPDF.ReportSample
{
Label = "Date",
Value = DateTime.Now.ToString("g")
},
new ReportHeaderField()
{
Label = "Status",
Value = "Completed, found 2 issues"
}
};
}
@@ -48,12 +52,12 @@ namespace QuestPDF.ReportSample
ReportSection GenerateSection()
{
var sectionLength = Helpers.Random.NextDouble() > 0.75
? Helpers.Random.Next(10, 20)
: Helpers.Random.Next(3, 11);
? Helpers.Random.Next(20, 40)
: Helpers.Random.Next(5, 10);
return new ReportSection
{
Title = TextPlaceholder.Label(),
Title = Placeholders.Label(),
Parts = Enumerable.Range(0, sectionLength).Select(x => GetRandomElement()).ToList()
};
}
@@ -62,10 +66,10 @@ namespace QuestPDF.ReportSample
{
var random = Helpers.Random.NextDouble();
if (random < 0.8f)
if (random < 0.9f)
return GetTextElement();
if (random < 0.9f)
if (random < 0.95f)
return GetMapElement();
return GetPhotosElement();
@@ -75,19 +79,17 @@ namespace QuestPDF.ReportSample
{
return new ReportSectionText
{
Label = TextPlaceholder.Label(),
Text = TextPlaceholder.Paragraph()
Label = Placeholders.Label(),
Text = Placeholders.Paragraph()
};
}
ReportSectionMap GetMapElement()
{
var rnd = Helpers.Random.Next(0, 64);
return new ReportSectionMap
{
Label = "Location",
ImageSource = x => Helpers.GetDocumentMap($"{rnd}.jpg"),
ImageSource = Placeholders.Image,
Location = Helpers.RandomLocation()
};
}
@@ -98,28 +100,24 @@ namespace QuestPDF.ReportSample
{
Label = "Photos",
Photos = Enumerable
.Range(0, Helpers.Random.Next(1, 10))
.Select(x => Helpers.Random.Next(0, 128))
.Select(x => Helpers.GetPhoto($"{x}.jpg"))
.Range(0, Helpers.Random.Next(1, 15))
.Select(x => Placeholders.Image(400, 300))
.ToList()
};
}
ReportPhoto GetReportPhotos()
{
var photoId = Helpers.Random.Next(0, 128);
var mapId = Helpers.Random.Next(0, 64);
return new ReportPhoto()
{
PhotoData = Helpers.GetPhoto($"{photoId}.jpg"),
PhotoData = Placeholders.Image(800, 600),
Comments = TextPlaceholder.Paragraph(),
Comments = Placeholders.Sentence(),
Date = DateTime.Now - TimeSpan.FromDays(Helpers.Random.NextDouble() * 100),
Location = Helpers.RandomLocation(),
MapContextSource = x => Helpers.GetContextMap($"{mapId}.jpg"),
MapDetailsSource = x => Helpers.GetDetailsMap($"{mapId}.jpg")
MapContextSource = x => Placeholders.Image(400, 300),
MapDetailsSource = x => Placeholders.Image(400, 300)
};
}
}
+2 -9
View File
@@ -1,22 +1,15 @@
using System;
using System.IO;
using QuestPDF.ReportSample.Layouts;
using SkiaSharp;
namespace QuestPDF.ReportSample
{
public static class Helpers
{
public static Random Random { get; } = new Random();
public static Random Random { get; } = new Random(1);
public static string GetTestItem(string path) => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Resources", path);
public static byte[] GetDocumentMap(string name) => GetImage(Path.Combine("Maps", "Document", name));
public static byte[] GetDetailsMap(string name) => GetImage(Path.Combine("Maps", "Details", name));
public static byte[] GetContextMap(string name) => GetImage(Path.Combine("Maps", "Context", name));
public static byte[] GetPhoto(string name) => GetImage(Path.Combine("Photos", name));
public static byte[] GetImage(string name)
{
var photoPath = GetTestItem(name);
+8 -6
View File
@@ -1,27 +1,29 @@
using System;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.ReportSample.Layouts
{
public static class Helpers
{
static IContainer Cell(this IContainer container, string color)
static IContainer Cell(this IContainer container, bool background)
{
return container
.Border(0.5f)
.Background(color)
.BorderColor(Colors.Grey.Lighten1)
.Background(background ? Colors.Grey.Lighten4 : Colors.White)
.Padding(5);
}
public static IContainer LightCell(this IContainer container)
public static IContainer ValueCell(this IContainer container)
{
return container.Cell("#0000");
return container.Cell(false);
}
public static IContainer DarkCell(this IContainer container)
public static IContainer LabelCell(this IContainer container)
{
return container.Cell("#1000");
return container.Cell(true);
}
public static string Format(this Location location)
+20 -21
View File
@@ -1,4 +1,5 @@
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.ReportSample.Layouts
@@ -15,47 +16,45 @@ namespace QuestPDF.ReportSample.Layouts
public void Compose(IContainer container)
{
container
.ShowEntire()
.Stack(stack =>
{
stack.Element(PhotoWithMaps);
stack.Element(PhotoDetails);
stack.Spacing(5);
stack.Item().Element(PhotoWithMaps);
stack.Item().Element(PhotoDetails);
});
}
void PhotoWithMaps(IContainer container)
{
container
.Padding(-3)
.PaddingBottom(3)
.Row(row =>
{
row.RelativeColumn(2).Padding(3).Component(new ImageTemplate(Model.PhotoData));
row.RelativeColumn(2).AspectRatio(4 / 3f).Background(Colors.Grey.Lighten3);
row.RelativeColumn().Stack(stack =>
row.RelativeColumn().PaddingLeft(5).Stack(stack =>
{
stack.Element().Padding(3).Component(new ImageTemplate(Model.MapDetailsSource));
stack.Element().Padding(3).Component(new ImageTemplate(Model.MapContextSource));
stack.Spacing(7f);
stack.Item().AspectRatio(4 / 3f).Background(Colors.Grey.Lighten3);
stack.Item().AspectRatio(4 / 3f).Background(Colors.Grey.Lighten3);
});
});
}
void PhotoDetails(IContainer container)
{
container.Stack(stack =>
container.Border(0.75f).BorderColor(Colors.Grey.Medium).Grid(grid =>
{
stack.Element().Row(row =>
{
row.RelativeColumn().DarkCell().Text("Date", Typography.Normal);
row.RelativeColumn(2).LightCell().Text(Model.Date?.ToString("g") ?? string.Empty, Typography.Normal);
row.RelativeColumn().DarkCell().Text("Location", Typography.Normal);
row.RelativeColumn(2).LightCell().Text(Model.Location.Format(), Typography.Normal);
});
grid.Columns(6);
stack.Element().Row(row =>
{
row.RelativeColumn().DarkCell().Text("Comments", Typography.Normal);
row.RelativeColumn(5).LightCell().Text(Model.Comments, Typography.Normal);
});
grid.Item().LabelCell().Text("Date", Typography.Normal);
grid.Item(2).ValueCell().Text(Model.Date?.ToString("g") ?? string.Empty, Typography.Normal);
grid.Item().LabelCell().Text("Location", Typography.Normal);
grid.Item(2).ValueCell().Text(Model.Location.Format(), Typography.Normal);
grid.Item().LabelCell().Text("Comments", Typography.Normal);
grid.Item(5).ValueCell().Text(Model.Comments, Typography.Normal);
});
}
}
@@ -1,6 +1,5 @@
using System;
using System.Linq;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.ReportSample.Layouts
@@ -17,31 +16,31 @@ namespace QuestPDF.ReportSample.Layouts
public void Compose(IContainer container)
{
container
.MinHeight(100)
.Section(section =>
.EnsureSpace()
.Decoration(decoration =>
{
section
decoration
.Header()
.PaddingBottom(5)
.Text(Model.Title, Typography.Headline);
section.Content().PageableStack(stack =>
decoration.Content().Border(0.75f).BorderColor(Colors.Grey.Medium).Stack(stack =>
{
foreach (var part in Model.Parts)
{
stack.Element().Row(row =>
stack.Item().EnsureSpace(25).Row(row =>
{
row.ConstantColumn(150).DarkCell().Text(part.Label, Typography.Normal);
var frame = row.RelativeColumn().LightCell();
row.ConstantColumn(150).LabelCell().Text(part.Label, Typography.Normal);
var frame = row.RelativeColumn().ValueCell();
if (part is ReportSectionText text)
frame.Text(text.Text, Typography.Normal);
frame.ShowEntire().Text(text.Text, Typography.Normal);
if (part is ReportSectionMap map)
frame.Element(container => MapElement(container, map));
frame.Element(x => MapElement(x, map));
if (part is ReportSectionPhotos photos)
frame.Element(container => PhotosElement(container, photos));
frame.Element(x => PhotosElement(x, photos));
});
}
});
@@ -56,38 +55,29 @@ namespace QuestPDF.ReportSample.Layouts
return;
}
container.Stack(stack =>
container.ShowEntire().Stack(stack =>
{
stack.Spacing(5);
stack.Element().Component(new ImageTemplate(model.ImageSource));
stack.Element().Text(model.Location.Format(), Typography.Normal);
stack.Item().MaxWidth(250).AspectRatio(4 / 3f).Background(Colors.Grey.Lighten3);
stack.Item().Text(model.Location.Format(), Typography.Normal);
});
}
void PhotosElement(IContainer container, ReportSectionPhotos model)
{
if (model.Photos.Count == 0)
if (model.Photos.Count == 0)
{
container.Text("No photos", Typography.Normal);
return;
}
var rowCount = (int) Math.Ceiling(model.Photos.Count / 3f);
container.Padding(-2).Stack(stack =>
container.DebugArea("Photos").Grid(grid =>
{
foreach (var rowId in Enumerable.Range(0, rowCount))
{
stack.Element().Row(row =>
{
foreach (var id in Enumerable.Range(0, 3))
{
var data = model.Photos.ElementAtOrDefault(rowId + id);
row.RelativeColumn().Padding(2).Component(new ImageTemplate(data));
}
});
}
grid.Spacing(5);
grid.Columns(3);
model.Photos.ForEach(x => grid.Item().AspectRatio(4 / 3f).Background(Colors.Grey.Lighten3));
});
}
}
+47 -43
View File
@@ -18,79 +18,83 @@ namespace QuestPDF.ReportSample.Layouts
{
return new DocumentMetadata()
{
Title = Model.Title,
Size = PageSizes.A4
Title = Model.Title
};
}
public void Compose(IContainer container)
public void Compose(IDocumentContainer container)
{
container
.PaddingVertical(40)
.PaddingHorizontal(50)
.Page(page =>
{
page.Header(ComposeHeader);
page.Content(ComposeContent);
page.Footer().AlignCenter().PageNumber("Page {number}");
page.MarginVertical(40);
page.MarginHorizontal(50);
page.Size(PageSizes.A4);
page.Header().Element(ComposeHeader);
page.Content().Element(ComposeContent);
page.Footer().AlignCenter().Text(text =>
{
text.DefaultTextStyle(Typography.Normal);
text.CurrentPageNumber();
text.Span(" / ");
text.TotalPages();
});
});
}
private void ComposeHeader(IContainer container)
{
container.Row(row =>
container.Stack(stack =>
{
row.RelativeColumn().MaxWidth(300).Stack(stack =>
stack.Item().Row(row =>
{
stack.Spacing(5);
row.Spacing(50);
stack
.Element()
.PaddingBottom(5)
.Text(Model.Title, Typography.Title);
stack.Element().ShowOnce().Stack(table =>
{
table.Spacing(5);
foreach (var field in Model.HeaderFields)
{
table.Element().Row(row =>
{
row.ConstantColumn(50)
.AlignLeft()
.Text($"{field.Label}:", Typography.Normal);
row.RelativeColumn()
.PaddingLeft(10)
.Text(field.Value, Typography.Normal);
});
}
});
row.RelativeColumn().PaddingTop(-10).Text(Model.Title, Typography.Title);
row.ConstantColumn(150).ExternalLink("https://www.questpdf.com").Background(Colors.Grey.Lighten3);
});
stack.Item().ShowOnce().PaddingVertical(15).Border(1f).BorderColor(Colors.Grey.Lighten1).ExtendHorizontal();
row.ConstantColumn(150).ExternalLink("https://www.questpdf.com").Image(Model.LogoData);
stack.Item().ShowOnce().Grid(grid =>
{
grid.Columns(2);
grid.Spacing(5);
foreach (var field in Model.HeaderFields)
{
grid.Item().Text(text =>
{
text.Span($"{field.Label}: ", Typography.Normal.SemiBold());
text.Span(field.Value, Typography.Normal);
});
}
});
});
}
void ComposeContent(IContainer container)
{
container.PaddingVertical(20).PageableStack(stack =>
container.PaddingVertical(20).Stack(stack =>
{
stack.Spacing(20);
stack.Element().Component(new TableOfContentsTemplate(Model.Sections));
stack.Item().Component(new TableOfContentsTemplate(Model.Sections));
stack.Item().PageBreak();
foreach (var section in Model.Sections)
stack.Element().Location(section.Title).Component(new SectionTemplate(section));
stack.Item().Location(section.Title).Component(new SectionTemplate(section));
stack.Element().PageBreak();
stack.Element().Location("Photos");
stack.Item().PageBreak();
stack.Item().Location("Photos");
foreach (var photo in Model.Photos)
stack.Element().Component(new PhotoTemplate(photo));
stack.Item().Component(new PhotoTemplate(photo));
});
}
}
@@ -1,6 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Collections.Generic;
using QuestPDF.Fluent;
using QuestPDF.Infrastructure;
@@ -18,21 +16,21 @@ namespace QuestPDF.ReportSample.Layouts
public void Compose(IContainer container)
{
container
.Section(section =>
.Decoration(decoration =>
{
section
decoration
.Header()
.PaddingBottom(5)
.Text("Table of contents", Typography.Headline);
section.Content().PageableStack(stack =>
decoration.Content().Stack(stack =>
{
stack.Spacing(5);
for (var i = 0; i < Sections.Count; i++)
stack.Element(c => DrawLink(c, i+1, Sections[i].Title));
stack.Item().Element(c => DrawLink(c, i+1, Sections[i].Title));
stack.Element(c => DrawLink(c, Sections.Count+1, "Photos"));
stack.Item().Element(c => DrawLink(c, Sections.Count+1, "Photos"));
});
});
}
@@ -45,6 +43,7 @@ namespace QuestPDF.ReportSample.Layouts
{
row.ConstantColumn(25).Text($"{number}.", Typography.Normal);
row.RelativeColumn().Text(locationName, Typography.Normal);
row.ConstantColumn(150).AlignRight().Text(text => text.PageNumberOfLocation(locationName, Typography.Normal));
});
}
}
+70
View File
@@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Engines;
using BenchmarkDotNet.Running;
using Microsoft.VisualStudio.TestPlatform.TestHost;
using NUnit.Framework;
using QuestPDF.Drawing;
using QuestPDF.Drawing.Proxy;
using QuestPDF.Elements;
using QuestPDF.Infrastructure;
using QuestPDF.ReportSample.Layouts;
namespace QuestPDF.ReportSample
{
[SimpleJob(RunStrategy.Monitoring, launchCount: 0, warmupCount: 1, targetCount: 100)]
[MinColumn, MaxColumn, MeanColumn, MedianColumn]
public class PerformanceTests
{
private PageContext PageContext { get; set; }
private DocumentMetadata Metadata { get; set; }
private Container Content { get; set; }
[Test]
public void Run()
{
var configuration = ManualConfig
.Create(DefaultConfig.Instance)
.WithOptions(ConfigOptions.DisableOptimizationsValidator);
BenchmarkRunner.Run<PerformanceTests>(configuration);
}
[IterationSetup]
public void GenerateReportData()
{
var model = DataSource.GetReport();
var report = new StandardReport(model);
Metadata = report.GetMetadata();
var documentContainer = new DocumentContainer();
report.Compose(documentContainer);
Content = documentContainer.Compose();
PageContext = new PageContext();
DocumentGenerator.RenderPass(PageContext, new FreeCanvas(), Content, Metadata, null);
var sw = new Stopwatch();
sw.Start();
Content.HandleVisitor(x =>
{
if (x is ICacheable)
x.CreateProxy(y => new CacheProxy(y));
});
sw.Stop();
Console.WriteLine($"Creating cache took: {sw.ElapsedMilliseconds}");
}
[Benchmark]
public void GenerationTest()
{
DocumentGenerator.RenderPass(PageContext, new FreeCanvas(), Content, Metadata, null);
}
}
}
@@ -8,19 +8,14 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="DeepCloner" Version="0.10.2" />
<PackageReference Include="nunit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
<PackageReference Include="SkiaSharp" Version="2.80.2" />
<PackageReference Include="BenchmarkDotNet" Version="0.13.1" />
<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>
<Folder Include="Resources" />
</ItemGroup>
<ItemGroup>
<Content Include="Resources\Photos\readme.txt" />
<Content Include="Resources\**\**">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -29,4 +24,8 @@
<ItemGroup>
<ProjectReference Include="..\QuestPDF\QuestPDF.csproj" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\Logo.png" />
</ItemGroup>
</Project>

Before

Width:  |  Height:  |  Size: 993 B

After

Width:  |  Height:  |  Size: 993 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

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