QuestPDF

QuestPDF is a modern open-source .NET library for PDF document generation. Offering comprehensive layout engine powered by concise and discoverable C# Fluent API. Easily generate PDF reports, invoices, exports, etc.

OTHER License

Stars
11.7K
Committers
32

Bot releases are visible (Hide)

QuestPDF - 2023.10.1

Published by MarcinZiabek 12 months ago

Version 2023.10.1

  • Enhanced developer experience by refining the accuracy of the exception message thrown when native SkiaSharp dependencies are unavailable in the runtime environment.
  • Fixed link in the license-related message (thank @ruyut).
QuestPDF - 2023.10

Published by MarcinZiabek 12 months ago

  • Primary focus: Improved the developer experience by enhancing debugging for document layout issues. Now, the library visually marks the places where problems occur
  • Updated dependency SkiaSharp from 2.88.3 to 2.88.6
  • Updated QuestPDF Previewer dependencies: from Avalonia 0.10.X to 11.0.X
  • Improved code quality (thank you @Lehonti)

Let's analyse a simple example showing how the new layout issue debugging mechanism works in practice. In the following scenario, we are artifically limiting the available vertical space, which makes the text content overflow:

container
    .ShowEntire()
    .Background(Colors.Grey.Lighten3)
    .Padding(10)
    .MaxHeight(20) // <- here is the problematic element
    .Column(column => 
    {
        column.Spacing(5);
        column.Item().Text("Comments").FontSize(14).SemiBold();
        column.Item().Text(Model.Comments);
    });

This results in the following output when generating the document with the debugger attached. The green area represents available space, while the red area shows the overflow.

QuestPDF - 2023.9.1

Published by MarcinZiabek about 1 year ago

  • Text rendering enhancement: a fake underline is drawn when the font typeface lacks an underline effect configuration or it is unavailable on runtime operating system
  • Text rendering enhancement: a fake strikethrough is drawn when the font typeface lacks a strikethrough effect configuration or it is unavailable on runtime operating system
  • Text rendering enhancement: a fake skew operation is applied to mimic an italic effect when the provided font lacks an italic variant
  • Text rendering enhancement: a fake bold effect is applied when the font doesn't offer variants of higher weight
  • Code quality improvements (provided by @Lehonti, thank you!).
QuestPDF - 2023.9.0

Published by MarcinZiabek about 1 year ago

  1. Primary focus: in-code IntelliSense documentation for all public APIs
  2. Improvement: the Row element respects now available vertical space, which is closer to expected behavior
QuestPDF - 2023.6.3

Published by MarcinZiabek about 1 year ago

Fix: the GeneratePdfAndShow method does not always properly open the generated PDF file in a default application due to a timing issue.

QuestPDF - 2023.6.2

Published by MarcinZiabek about 1 year ago

  • Fix: improved Image.FitUnproportionally behavior to ensure it now respects minimal size constraints and doesn't just expand to use all the available space.
  • Adjustment: increased the default DPI setting from 144 to 288 to better match industry standards and provide visuals of expected quality. To counterbalance for potential output size increase, the image quality has been decreased from "very high" to "high" (JPEG 90 to 75).
QuestPDF - 2023.6

Published by MarcinZiabek over 1 year ago

Version 2023.6.0

  • Improvement: the library received a final version of the dual license, which has been enhanced to be more accommodating to both the business and community (read more)
  • Feature: generating merged PDF documents (@bennetbo has started this effort and completed a significant portion of it, thank you so much for your help!).
  • Improvement: image loading exception better describes the failure reason.
QuestPDF - 2023.5

Published by MarcinZiabek over 1 year ago

Version 2023.5.0

  • Simplified development loop by introducing cross-platform methods: GeneratePdfAndShow() and GenerateXpsAndShow()
  • New shared image API: the ability to define a single image resource that is used in multiple places in the document without increasing its size
  • New DocumentSettings API: target image raster DPI - now, the library automatically resizes all images to achieve desired DPI (dots-per-inch) resolution. It allows for minimizing output file size
  • New DocumentSettings API: target image compression quality - the ability to specify the balance between size and quality for images in the document. It allows for minimizing output file size
  • Refactoring: moved the PdfA setting from the DocumentMetadata class to the DocumentSettings class
  • Improved Image API by providing additional FluentA API methods
  • Improvement: the GenerateImage element now provides the expected image resolution, abstracting away the physical area size and target image DPI

Version 2023.5.1

  • Adjusted and simplified the Image and SharedImage APIs,
  • Improved image generation performance,
  • Provided the ability to configure the image generation process: image format, compression quality and raster DPI.
QuestPDF - 2023.4.2

Published by MarcinZiabek over 1 year ago

Version 2023.4.0

This release does not contain any features or quality improvements.
Its purpose is to mark the QuestPDF shift towards the dual-licensing model.
Most users are not affected by this change.

Please visit the https://www.questpdf.com/pricing.html webpage for more information.

Version 2023.4.1:

Added IntelliSense-powered documentation for certain API methods.

Version 2023.4.2:

Fix: fixed the rendering order of table cells in certain scenarios

QuestPDF - 2022.12.6

Published by MarcinZiabek over 1 year ago

Version 2022.12.0

Feature: implemented LetterSpacing property for the Text element
Improvement: the Text element API now accepts only string values; objects are not automatically converted anymore
Fix: the Alignment element incorrectly limits the size of its child when only one axis is set (horizontal or vertical)
Maintenance: Updated SkiaSharp dependency to 2.88.3

This release was possible thanks to the enormous help of AntonyCorbett. Thank you!

Version 2022.12.1:

Fixed: loading fonts from embedded resource via the FontManager.RegisterFontFromEmbeddedResource method
Fixed: better layout calculation stability for the Column element
Improvement: exposed missing API method for the Dynamic component, enabling applying more advanced optimizations
Improvement: better API documentation for the Settings.DocumentLayoutExceptionThreshold property

Version 2022.12.2

Performance improvements in various areas
Text rendering stability improvements
Fixed: the Settings.CheckIfAllTextGlyphsAreAvailable setting does not work correctly

Version 2022.12.3

Fix: inconsistent text height when using multiple lines with different TextStyles
Improvement: added validation for color arguments
Fix: the inlined element is shown only once in the header but should be repeated on each page

Version 2022.12.4

Fix: the TextStyle.Fallback property incorrectly inherits parent's and global properties
Improvement: updated the CreateNotMatchingFontException message to mention that the glyph checking operation can be disabled with the Settings.CheckIfAllTextGlyphsAreAvailable setting

Version 2022.12.4

Fixed release regression

Version 2022.12.6

Fix: fixed the rendering order of table cells in certain scenarios

QuestPDF - QuestPDF 2022.12 release notes

Published by MarcinZiabek almost 2 years ago

QuestPDF 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

⭐ Please consider giving a start to this repository. It takes seconds and helps thousands of developers! ⭐

What's new? 🎊

This QuestPDF release is special as it mainly consists of features proposed and developed by the community:

  1. Feature: implemented LetterSpacing property for the Text element (implemeneted by @Bebo-Maker in #398)

  2. Improvement: the Text element API accepts now only string values, objects are not automatically converted anymore (proposed by @rstm-sf in #362)

  3. Fix: the Alignment element incorrectly limits size of its child when only one axis is set: horizontal or vertical (found by @zlatanov in #401)

  4. Maintenance: Updated SkiaSharp dependency to 2.88.3. Updated Avalonia dependency in the QuestPDF.Previewer project to the latest version. (this was a long awaiting effort that should improve experience for many developers)

  5. Development: Implemented usage on GitHub Actions to trigger automated solution build after every commit. This build is also used for packing nuget artifacts. It is a great starting point for future improvements. (proposed by @rstm-sf in #366)

Let me thank you all for your help!

Letter spacing

Letter spacing allows to increase or decrease space between characters. This setting is useful when you want to make the text more compact (by decreasing letter spacing) or easier to read (by increasing letter spacing):

  • Value 0 corresponds to normal spacing defined by a font.
  • Positive values create additional space.
  • Negative values reduce space between characters.

This settings uses relative units. Example: let's assume your text has font size 20. If letter spacing is set to 0.1, an additional space of 2 points will be added between characters.

.Column(column =>
{
    var letterSpacing = new[] { -0.05f, 0f, 0.2f };
    var paragraph = Placeholders.Sentence();

    foreach (var spacing in letterSpacing)
    {
        column
            .Item()
            .Border(1)
            .Padding(10)
            .Column(nestedColumn =>
            {
                nestedColumn
                    .Item()
                    .Text(paragraph)
                    .FontSize(18)
                    .LetterSpacing(spacing); // <- here 😊

                nestedColumn
                    .Item()
                    .Text($"Letter spacing of {spacing} em")
                    .FontSize(14)
                    .Italic()
                    .FontColor(Colors.Blue.Medium);
            });
        
    }
});
QuestPDF - QuestPDF 2022.11 release notes

Published by MarcinZiabek almost 2 years ago

QuestPDF 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:


Popularity milestone

Two major releases ago, I was excited to mention that QuestPDF has over 250 thousand downloads. Very little did I know that the next milestone will come so quickly! Three months have passed and now we are in the middle of to-the-million journey. Maybe somewhere in 2023? Something that was a dream, now sounds plausible. Thank you all for helping me in this process!

Please help by giving a star

⭐ Please consider giving a start to this repository. It takes seconds and helps thousands of developers! ⭐

What is content direction and why it is hard?

Most languages (such as English, German, Polish, etc.) are using the left-to-right content direction. However, there are languages (e.g. Arabic) that use the right-to-left content direction.

The right-to-left content direction significantly changes how the layout is planned:

  1. Text position (aligned to the right).
  2. Text direction where text starts on the right side and ends on the left side.
  3. Text word-wrapping algorithm that needs to take into account direction of text when breaking a line.
  4. Order of elements in collections, e.g. the first item in a row should be placed most to the right (in RTL) or to the left (int LTR).
  5. Default content position (aligned to the right).

Right-to-left content direction support

The new content-direction API introduces the possibility to set this feature locally to entire child content, or even override this setting:

.ContentFromRightToLeft()
.Column(column => 
{
    // this content uses inherited right-to-left content direction
    column.Item() // ... content
        
    // this item overrides the content direction to right-to-left    
    column.Item().ContentFromLeftToRight() // ... content     
});

The RTL mode is supported in all available elements. Let's quickly analyse a couple of examples to learn on how this mode infers the rendering process. In the Row element, the elements are displayed in accordance to the content direction. For example, the first element is put most to the left (in LTR mode) or most to the right (int RTL) mode:

.ContentFromRightToLeft() // LTR or RTL mode
.Row(row =>
{
    row.Spacing(5);
    
    row.AutoItem().Height(50).Width(50).Background(Colors.Red.Lighten1);
    row.AutoItem().Height(50).Width(50).Background(Colors.Green.Lighten1);
    row.AutoItem().Height(50).Width(75).Background(Colors.Blue.Lighten1);
});

It is important to notice that content direction does NOT impact element alignment. This is nicely shown when working with more advanced elements such as Inlined:

Global content direction

It is also possible to set a global content direction for the entire document. Of course, it is still possible to override content direction locally using one of the above methods. Or even to apply conditional content direction.

Let's analyse a simple example showing an invoice generated with Arabic language.

document.Page(page =>
{
    page.Size(PageSizes.A5);
    page.Margin(20);
    page.PageColor(Colors.White);
    
    page.DefaultTextStyle(x => x.FontFamily("Calibri").FontSize(20));
    page.ContentFromRightToLeft(); // this flag changes content direction to RTL in entire document
    
    page.Content().Column(column =>
    {
        column.Spacing(20);

        column.Item()
            .Text("مثال على الفاتورة") // example invoice
            .FontSize(32).FontColor(Colors.Blue.Darken2).SemiBold();
        
        column.Item().Table(table =>
        {
            table.ColumnsDefinition(columns =>
            {
                columns.RelativeColumn();
                columns.ConstantColumn(75);
                columns.ConstantColumn(100);
            });

            table.Cell().Element(HeaderStyle).Text("وصف السلعة"); // item description
            table.Cell().Element(HeaderStyle).Text("كمية"); // quantity
            table.Cell().Element(HeaderStyle).Text("سعر"); // price

            var items = new[]
            {
                "دورة البرمجة", // programming course
                "دورة تصميم الرسومات", // graphics design course
                "تحليل وتصميم الخوارزميات", // analysis and design of algorithms
            };
            
            foreach (var item in items)
            {
                var price = Placeholders.Random.NextDouble() * 100;
                                    
                table.Cell().Text(item);
                table.Cell().Text(Placeholders.Random.Next(1, 10));
                table.Cell().Text($"USD${price:F2}");
            }

            static IContainer HeaderStyle(IContainer x) => x.BorderBottom(1).PaddingVertical(5);
        });
    });
});

Fixed word-wrapping algorithm for right-to-left languages

Previous versions of QuestPDF have introduced an advanced text shaping support. This allows to properly render text of more advanced languages, where characters in words are often combined together and displayed as single glyphs.

This release fixes the word-wrapping algorithm. This example renders the same text but the available width changes in each block:

var text = "في المعلوماتية أو الرياضيات، خوارزمية الترتيب هي خوارزمية تمكن من تنظيم مجموعة عناصر حسب ترتيب محدد.";
                    
container
    .Padding(25)
    .ContentFromRightToLeft()
    .Column(column =>
    {
        column.Spacing(20);
        
        foreach (var size in new[] { 36, 34, 32, 30, 15 })
        {
            column
                .Item()
                .ShowEntire()
                .MaxWidth(size * 25)
                .Background(Colors.Grey.Lighten3)
                .MinimalBox()
                .Background(Colors.Grey.Lighten2)
                .Text(text)
                .FontSize(20)
                .FontFamily("Segoe UI");
        }
    });

Forcing text direction

QuestPDF automatically detects text direction and applies proper text alignment. However, it is possible to override text direction.

TextStyle.Default.DirectionAuto() // default
TextStyle.Default.DirectionFromLeftToRight()
TextStyle.Default.DirectionFromRightToLeft()

This may be useful with more advanced corner cases:

.DefaultTextStyle(x => x.FontSize(24).FontFamily("Calibri"))
.Column(column =>
{
    var word = "الجوريتم";
    var definition = "algorithm in Arabic";

    var text = $"{word} - {definition}";
    
    // text direction is automatically detected using the first word
    column.Item().Text(text);
    
    // it is possible to force specific content direction
    column.Item().Text(text).DirectionFromLeftToRight();
    column.Item().Text(text).DirectionFromRightToLeft();

    // to combine text in various content directions, split it into segments
    column.Item().Text(text =>
    {
        text.Span(word);
        text.Span(" - ");
        text.Span(definition);
    });
});
QuestPDF - QuestPDF 2022.9 release notes

Published by MarcinZiabek about 2 years ago

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

  1. Text is one of the fundamental content blocks in all documents.
  2. For every Text element, new TextStyle object is created, leading to millions of instances over time.
  3. TextStyle objects are heavily mutated by parent styles and global styles. To avoid collisions, it often leads to double of TextStyle elements.
  4. TextStyle is heavy, constains multiple references and many primitivies.
  5. TextStyle is commonly used as dictionary key.

Observation

During document generation, thousands of similar/identical TextStyles are generated. This increases GC overhead.

Solution

  1. Make TextStyle readonly and possible to reuse in concurrent environment.
  2. TextStyle lifetime should be infinite, not connected to the document generation moment.
  3. 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.
  4. When applying parent / global TextStyles, cache entire operation for all properties to reduce dictionary usage.

Results

The cost of TextStyle objects became negligable:

  1. Number of created objects and their total size decreased by several orders of magnitude.
  2. 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:

  1. when this flag is enabled: the DocumentDrawingException is thrown.
  2. 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:

  1. Text should be split into codepoints (where a codepoint corresponds to one character).
  2. 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.
  3. It should be possible to define nested fallbacks. The algorithm should use fallback closest to the root.
  4. 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
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:

  1. It is enabled when debugger is attached (most likely development or test environment).
  2. 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:

  1. Most of the text instances use only one style - there is only one text run.
  2. Most of the text does not contain line breaks.

The Fluent API for text capability, produces additional hierarchy elements that are usually not needed:

  1. The DefaultTextStyle element to apply global style, even if not specified.
  2. The Column to handle multiple paragraphs.

Solution:

Do not apply hierarchy elements if they are not needed.


QuestPDF - 2022.8.1

Published by MarcinZiabek about 2 years ago

For all major changes introduced in the 2022.8 release, please read this article

  • Fixed: default text style does not always work
  • Fixed: page breaking rendering does not work in very specific corner cases
  • Stability improvements for text wrapping
  • Updated stability of rendering elements in negative space
  • Optimization for the Column element: do not measure child when available height is negative
QuestPDF - QuestPDF 2022.8 release notes

Published by MarcinZiabek about 2 years ago

QuestPDF is an open-source .NET library for PDF documents generation.

It offers a layouting engine designed with a 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:

Popularity milestone

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! ⭐

JetBrains help and performance optimization

The QuestPDF project received an enormous help from JetBrains over last months.

The library was shown during one of episodes of the OSS Power-Ups program hosted by Matthias Koch. This gave me a fresh energy to develop new great features.

Moreover, I got a change to cooperate with Maarten Balliauw, perform a detailed performance analysis and apply multiple fixes. To read more about this effort, please take a look at this fantastic article. Many of the improvements are already applied, making the library faster and more reliable for everyone. Some are inspiration for more fundamental changes that should significantly reduce resource utilization in the nearest future.

Dear JetBrains team, thank you for helping me making the library flourish, for promoting it across entire the .NET community, for your great patience and professionalism, and finally for making our industry a fantastic place to work.

Documentation update

The QuestPDF documentation is written manually as MarkDown files. Those files are then compiled together to create a webpage.

I decided to change the documentation engine from VuePress to VitePress. This makes the webpage much faster, improves its look and feel, as well as increases usability.

Also, the old documentation structure has been designed nearly two years ago. Since then, the documentation grew over three times in size. Therefore, I have also decided to update its hierarchy, so it should be easier to traverse and find what you need.

The next step is to rewrite all articles, make descriptions more accurate and provide more examples.

New default font (breaking change)

In this release, I decided to change the default font from Calibri to Lato. Lato is an open-source, free for commercial use font created by Polish author Łukasz Dziedzic.

The font is distributed with the library as embedded resource and part of the dll file / nuget package. This way, as long as you use the default font, you have it available on all environments. Also, the font is around 20x smaller, this should reduce substantially PDF file size (1.57 MB -> 74 KB) when using the default font.

Of course there is caveat, this font does not contain more advanced glyphs, e.g. for Arabic/Chinese/Japanese languages, or for advanced unicode formatting. For such cases, you still need to use a font with proper support.

This effort solves two issues:

  1. Makes sure that your code works on all environments (in terms of font availability). You don't get exceptions from a family of "Calibri is not available on Linux by default", etc.
  2. The average PDF output size will decrease drastically for most projects. In many cases, this will reduce the need of font subsetting. Of course, in the average case.

Others

Improved: if you use the font that is not available in the runtime environment, the exception thrown by QuestPDF provides all fonts available.

Fixed: a rare case when the Inlined element throws the layout overflow exception when generating PDF document.

Fixed: memory leak introduced in the 2022.6 release connected to the HarfBuzzSharp library usage.

Fixed: page breaking rendering does not work in very specific corner cases

QuestPDF - 2022.6

Published by MarcinZiabek over 2 years ago

Integrated the text-shaping algorithm. This change significantly improves the Unicode compatibility. Also, it extends support for more advanced languages (e.g. Arabic) that:

  1. Combine multiple text characters and display them as a single visual glyph.
  2. Are displayed in the right-to-left order.

Improved the exception message when SkiaSharp throws the TypeInitializationException. On some operating systems, SkiaSharp requires additional dependencies installed as nuget packages. This change should help developers determine how to choose and install them correctly.

Fixed: a rare case when the Row.AutoItem() does not correctly calculate the width of its content.

Fixed: the QuestPDF Previewer does not work with content-rich documents.

QuestPDF - 2022.5

Published by MarcinZiabek over 2 years ago

  • Implemented the DynamicComponent element (useful when you want to generate dynamic and conditional content that is page aware, e.g. per-page totals),
  • Extended text rendering capabilities by adding subscript and superscript effects (special thanks to Bennet Fenner),
  • Improved table rendering performance,
  • Previewer tool stability fixes.
QuestPDF - 2022.4.1

Published by MarcinZiabek over 2 years ago

QuestPDF: throw an exception when cannot connect to the previewer tool within 10 seconds after launching it.
QuestPDF Previewer: hide the "Generate PDF and show in default browser" button when no ducument is loaded.

QuestPDF - 2022.4

Published by MarcinZiabek over 2 years ago

🎊 Release theme:
Introduced the QuestPDF Previewer tool - a hot-reload powered, cross-platform program that visualizes your PDF document and updates its preview every time you make a code change. You don't need to recompile your code after every small adjustment. Save time and enjoy the design process! (available only for dotnet 6 and beyond)

Special thanks to Bennet Fenner who came up with the idea, implemented the prototype, actively discussed architectural concepts, and took a crucial role in the testing phase. People like him make open-source a joy!

Other changes:

  • Improved default word-wrapping algorithm to better handle words which do not fit on the available width,
  • Introduced new word-wrapping option 'WrapAnywhere' that wraps word at the last possible character instead of moving it into new line.
QuestPDF - 2022.3

Published by MarcinZiabek over 2 years ago

  • Added minimal API to help you quickly start with QuestPDF development and speed up prototyping,
  • Improved exception message when desired font type cannot be found (instead of loading default font on Windows and failing with wrong characters on Linux),
  • Improved support for custom font types: loading all type faces from a file, respecting true font family, using CSS-like algorithm to find best style match,
  • Added support for custom page number formats in the Text element, e.g. you can implement roman literal style if required,
  • Improved text API to make it more concise,
  • Extended support for the Section element (previously the Location element) by tracking: beginning page number, end page number, page length, page number within location,
  • Renaming: the ExternalLink element was renamed to the Hyperlink element,
  • Renaming: the Location element was renamed to the Section element,
  • Renaming: the InternalLink element was renamed to the SectionLink element,
  • Updated homepage and GitHub pages content.