2022.9
版本发布时间: 2022-09-19 18:07:38
QuestPDF/QuestPDF最新发布版本:2024.7.3(2024-08-27 22:56:00)
QuestPDF is an open-source .NET library for PDF documents generation.
About the library
It offers a layout engine designed with full paging support in mind. The document consists of many simple elements (e.g. border, background, image, text, padding, table, grid etc.) that are composed together to create more complex structures. This way, as a developer, you can understand the behavior of every element and use them with full confidence. Additionally, the document and all its elements support paging functionality. For example, an element can be moved to the next page (if there is not enough space) or even be split between pages like table's rows.
To learn how easy it is to design documents with the library, let's quickly analyse the code below:
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
// code in your main method
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");
And compare it to the produced PDF file:
Please help by giving a star
The QuestPDF nuget package has reached over 250 thousands of downloads! I was dreaming about helping the community for years, about giving something back and be a part of the ecosystem development. This is not simple: requires tons of time and proper planning. But numbers are not lying! My hard work is beneficial to many of you. Thank you for your support!
⭐ Please consider giving this repository a star. It takes seconds and help thousands of developers! ⭐
2022.9 Release notes 🎉 TLDR
🔣 Implemented font-fallback algorithm
⚙️ Introduced new Settings API,
🚀 Improved rendering performance by 50% (in text-heavy documents),
📉 Significantly reduced memory allocation cost for TextStyle objects,
🔎 Implemented optional checking if all font glyphs are available,
📉 Minor text-rendering optimizations.
TextStyle optimization
Rationale
- Text is one of the fundamental content blocks in all documents.
- For every Text element, new TextStyle object is created, leading to millions of instances over time.
- TextStyle objects are heavily mutated by parent styles and global styles. To avoid collisions, it often leads to double of TextStyle elements.
- TextStyle is heavy, constains multiple references and many primitivies.
- TextStyle is commonly used as dictionary key.
Observation
During document generation, thousands of similar/identical TextStyles are generated. This increases GC overhead.
Solution
- Make TextStyle readonly and possible to reuse in concurrent environment.
- TextStyle lifetime should be infinite, not connected to the document generation moment.
- Create mutation graph: when changing TextStyle object (by creating new immutable instance with applied change), cache both input TextStyle, mutation type, new property value and outcome.
- When applying parent / global TextStyles, cache entire operation for all properties to reduce dictionary usage.
Results
The cost of TextStyle objects became negligable:
- Number of created objects and their total size decreased by several orders of magnitude.
- They do not put any additional GC overhead anymore.
New Settings API
Rationale
There are several parameters used to alter document generation process. In the current implementation, such parameters are specified per document in the DocumentMetadata
object, like so:
public class DocumentMetadata
{
// actual metadata or rendering settings
// generation settings:
public int DocumentLayoutExceptionThreshold { get; set; } = 250;
public bool ApplyCaching { get; set; } // false when debugger is attached
public bool ApplyDebugging { get; set; } // true when debugger is attached
}
It becomes clear that with new library iterations, more and more parameters will be created. It is important to put them in the more meaningful place.
Solution
There are several parameters that alter the generation process. All of them are available under statically available Settings
class.
// settings definition with default settings
public static class Settings
{
public static int DocumentLayoutExceptionThreshold { get; set; } = 250;
public static bool EnableCaching { get; set; } = !System.Diagnostics.Debugger.IsAttached;
public static bool EnableDebugging { get; set; } = System.Diagnostics.Debugger.IsAttached;
public static bool CheckIfAllTextGlyphsAreAvailable { get; set; } = System.Diagnostics.Debugger.IsAttached;
}
// adjust properties wherever you want
// best in the startup code
QuestPDF.Settings.DocumentLayoutExceptionThreshold = 1000;
Maximum document length
This value represents the maximum length of the document that the library produces. This is useful when layout constraints are too strong, e.g. one element does not fit in another. In such cases, the library would produce document of infinite length, consuming all available resources. To break the algorithm and save the environment, the library breaks the rendering process after reaching specified length of document.
If your content requires generating longer documents, please assign the most reasonable value.
QuestPDF.Settings.DocumentLayoutExceptionThreshold = 250;
Caching
This flag generates additional document elements to cache layout calculation results. In the vast majority of cases, this significantly improves performance, while slightly increasing memory consumption.
By default, this flag is enabled only when the debugger is NOT attached.
QuestPDF.Settings.EnableCaching = true;
Debugging
This flag generates additional document elements to improve layout debugging experience. When the DocumentLayoutException is thrown, the library is able to provide additional execution context. It includes layout calculation results and path to the problematic area.
By default, this flag is enabled only when the debugger IS attached.
QuestPDF.Settings.EnableDebugging = false;
Checking font glyph availability
This flag enables checking the font glyph availability.
If your text contains glyphs that are not present in the specified font:
- when this flag is enabled: the DocumentDrawingException is thrown.
- when this flag is disabled: placeholder characters are visible in the produced PDF file.
Enabling this flag may slightly decrease document generation performance. However, it provides hints that used fonts are not sufficient to produce correct results.
By default, this flag is enabled only when the debugger IS attached.
QuestPDF.Settings.CheckIfAllTextGlyphsAreAvailable = false;
Font fallback
Acknowledgements
Implementation of this feature has been started by @Bebo-Maker in #187. Thank you for your work and preparing this fantastic foundation. You really saved me a lot of time!
Rationale
Each font file contains a well-specified set of glyphs. Sometimes, to reduce font file size, more advanced glyphs are not present. For example, English uses around a hundred of characters, whereas Chinesee requires thousands of glyphs. Therefore, it is possible that text in document may contain glyphs not available in the configured font. In such cases, an ugly character (usually square with question mark) is rendered.
Solution
The TextStyle object should allow to set a fallback TextStyle.
Algorithm:
- Text should be split into codepoints (where a codepoint corresponds to one character).
- Algorithm should check if the codepoint has appropriate glyph available in the configured font (defined in TextStyle). If not, the fallback TextStyle should be used.
- It should be possible to define nested fallbacks. The algorithm should use fallback closest to the root.
- Fallback style should inherit style properties from its parent, e.g. text size, color, but can also override properties.
API
It is possible to define font fallbacks like so:
TextStyle
.Default
.FontFamily(Fonts.Calibri)
.Fallback(x => x.FontFamily("Segoe UI Emoji")); // <- here
// or
TextStyle
.Default
.FontFamily(Fonts.Calibri)
.Fallback(TextStyle.Default.FontFamily("Segoe UI Emoji")); // <- and here
Results
Let's analyse more complex example by defining text style supporting emojis and Chinesee glyphs.
var textStyleWithFallback = TextStyle
.Default
.FontFamily(Fonts.Calibri)
.FontSize(18)
.Fallback(x => x
.FontFamily("Segoe UI Emoji")
.NormalWeight()
.Underline()
.Fallback(y => y
.FontFamily("Microsoft YaHei")
.SemiBold()
.Underline(false)
.BackgroundColor(Colors.Red.Lighten4)));
And now, we can use the newly created style:
.Text(text =>
{
text.DefaultTextStyle(textStyleWithFallback);
text.Line("This is normal text.");
text.EmptyLine();
text.Line("Following line should use font fallback:");
text.Line("中文文本");
text.EmptyLine();
text.Line("The following line contains a mix of known and unknown characters.");
text.Line("Mixed line: This 中文 is 文文 a mixed 本 本 line 本 中文文本!");
text.EmptyLine();
text.Span("Emojis work out of the box because of font fallback: 😊😅🥳👍❤😍👌");
});
Please notice that additional styles (e.g. red background color) are applied only to glyphs from the associated fallback configuration. This let's you fine tune text parameters, e.g. to match visual text size in various fonts.
Before | After |
---|---|
Optional checking if all glyphs are available
Rationale
The font-fallback implementation is opt-in. That means, it attempts to find best font based on the explicitly defined configuration. For stability and predictability reasons, it does not search through fonts available on the runtime environment.
Reason: quite often, development environment contains hundreds of fonts, whereas production environments contain very little to none. Relying on configuration makes sure that results produced on both environments are consistent.
Solution
When not all glyphs are present, even with configured fallbacks, the library should throw an exception. Additionally, the library can search through available fonts and propose list of fonts with desired glyph. Example exception:
QuestPDF.Drawing.Exceptions.DocumentDrawingException:
Could not find an appropriate font fallback for glyph: U-4E2D '中'.
Font families available on current environment that contain this glyph: Malgun Gothic, Microsoft JhengHei, Microsoft JhengHei UI, Microsoft YaHei, Microsoft YaHei UI, MS Gothic, MS UI Gothic, MS PGothic, SimSun, NSimSun, Yu Gothic, Yu Gothic UI, Droid Sans Fallback, MotoyaLCedar W3 mono.
Possible solutions:
1) Use one of the listed fonts as the primary font in your document.
2) Configure the fallback TextStyle using the 'TextStyle.Fallback' method with one of the listed fonts.
When this feature is disabled: do nothing, render placeholder glyphs (rectangle with question mark).
Feature availability
This feature depends on the current environment. The default configuration is as follows:
- It is enabled when debugger is attached (most likely development or test environment).
- It is disabled when debugger is NOT attached (most likely production).
This behaviour can be changed by using the new settings API:
QuestPDF.Settings.CheckIfAllTextGlyphsAreAvailable = false;
Text rendering optimization
Rationale
In a typical document:
- Most of the text instances use only one style - there is only one text run.
- Most of the text does not contain line breaks.
The Fluent API for text capability, produces additional hierarchy elements that are usually not needed:
- The
DefaultTextStyle
element to apply global style, even if not specified. - The
Column
to handle multiple paragraphs.
Solution:
Do not apply hierarchy elements if they are not needed.
1、 QuestPDF.2022.9.0.nupkg 2.28MB
2、 QuestPDF.2022.9.0.snupkg 191.34KB
3、 QuestPDF.Previewer.2022.9.1.nupkg 44.12MB