C#/.Net Stop Using Aspose and iTextSharp! QuestPDF Generates PDFs Faster and More Efficiently!

C#/.Net Stop Using Aspose and iTextSharp! QuestPDF Generates PDFs Faster and More Efficiently!

It provides a layout engine designed with full pagination support and flexibility requirements in mind! It is much better than the commonly used Aspose and iTextSharp on the market!

Last updated 4/23/2022 1:55 PM
黑哥聊dotNet
7 min read
Category
.NET
Tags
.NET C# PDF Generate PDF Aspose

1. About QuestPDF

QuestPDF is an open-source library for generating PDF documents in .NET or .NET Core.

It provides a layout engine designed with full page break support and flexibility in mind. It is much easier to use than common libraries like Aspose and iTextSharp!

2. Installation

Install-Package QuestPDF

3. Examples

3.1 Simple Example

Generating a PDF document consists of three parts: Header, Content, and Footer. Below is the example code:

Document.Create(container =>
{
    container.Page(page =>
    {
        page.Size(PageSizes.A4);
        page.Margin(2, Unit.Centimetre);
        page.Background(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");

The resulting PDF from the above code:

3.2 Template Generation

Using template generation involves three application layers:

  1. Document Model (a set of classes describing the PDF content)
  2. Data Source (the layer mapping domain entities to the document model)
  3. Template (the presentation layer describing how to visualize information and convert it into a PDF file)

For example, let's design a basic invoice with a shopping list, seller and buyer addresses, invoice number, etc. We'll design three model classes:

public class InvoiceModel
{
    public int InvoiceNumber { get; set; }
    public DateTime IssueDate { get; set; }
    public DateTime DueDate { get; set; }

    public Address SellerAddress { get; set; }
    public Address CustomerAddress { get; set; }

    public List<OrderItem> Items { get; set; }
    public string Comments { get; set; }
}

public class OrderItem
{
    public string Name { get; set; }
    public decimal Price { get; set; }
    public int Quantity { get; set; }
}

public class Address
{
    public string CompanyName { get; set; }
    public string Street { get; set; }
    public string City { get; set; }
    public string State { get; set; }
    public object Email { get; set; }
    public string Phone { get; set; }
}

After defining the models, we define some dummy data to fill the PDF:

public static class InvoiceDocumentDataSource
{
    private static Random Random = new Random();

    public static InvoiceModel GetInvoiceDetails()
    {
        var items = Enumerable
            .Range(1, 8)
            .Select(i => GenerateRandomOrderItem())
            .ToList();

        return new InvoiceModel
        {
            InvoiceNumber = Random.Next(1_000, 10_000),
            IssueDate = DateTime.Now,
            DueDate = DateTime.Now + TimeSpan.FromDays(14),

            SellerAddress = GenerateRandomAddress(),
            CustomerAddress = GenerateRandomAddress(),

            Items = items,
            Comments = "Test comments"
        };
    }

    private static OrderItem GenerateRandomOrderItem()
    {
        return new OrderItem
        {
            Name = "Product",
            Price = (decimal)Math.Round(Random.NextDouble() * 100, 2),
            Quantity = Random.Next(1, 10)
        };
    }

    private static Address GenerateRandomAddress()
    {
        return new Address
        {
            CompanyName = "Test Store",
            Street = "Test Street",
            City = "Test City",
            State = "Test State",
            Email = "test@example.com",
            Phone = "123-456-7890"
        };
    }
}

Then we build the template scaffolding. To use the template scaffolding, we start by defining a new class that implements the IDocument interface.

The interface contains two methods:

  1. DocumentMetadata GetMetadata();
  2. void Compose(IDocumentContainer container);

The first returns basic metadata for the template document, and the second defines the template container. Based on these principles, we design a template layer class:

public class InvoiceDocument : IDocument
{
    public InvoiceModel Model { get; }

    public InvoiceDocument(InvoiceModel model)
    {
        Model = model;
    }

    public DocumentMetadata GetMetadata() => DocumentMetadata.Default;

    public void Compose(IDocumentContainer container)
    {
        container
            .Page(page =>
            {
                page.PageColor(Colors.Red.Lighten1);
                page.Size(PageSizes.A4);
                page.Margin(10); // margin

                page.Header().Height(100).Background(Colors.LightBlue.Lighten1);
                page.Content().Background(Colors.Grey.Lighten3);
                page.Footer().Height(50).Background(Colors.Grey.Lighten1);
            });
    }
}

A PDF page always has three elements: header, footer, and content. Let's check the generated document:

So far, we have built a very simple page where each section has a different color or size.

Next, we fill in the header. After organizing the data source, we can call the Element method to compose:

public void Compose(IDocumentContainer container)
{
    container
        .Page(page =>
        {
            page.PageColor(Colors.Red.Lighten1);
            page.Size(PageSizes.A4);
            page.Margin(10); // margin

            page.Header().Height(100).Background(Colors.LightBlue.Lighten1).Element(ComposeHeader);
            page.Content().Background(Colors.Grey.Lighten3);
            page.Footer().Height(50).Background(Colors.Grey.Lighten1);
        });
}

void ComposeHeader(IContainer container)
{
    var titleStyle = TextStyle.Default.FontSize(20).SemiBold().FontColor(Colors.Blue.Medium);

    container.Row(row =>
    {
        row.RelativeItem().Column(column =>
        {
            column.Item().Text($"Invoice #{Model.InvoiceNumber}").FontFamily("simhei").Style(titleStyle);

            column.Item().Text(text =>
            {
                text.Span("Issue date: ").SemiBold().FontFamily("simhei");
                text.Span($"{Model.IssueDate:d}").FontFamily("simhei");
            });

            column.Item().Text(text =>
            {
                text.Span("Due date: ").FontFamily("simhei").SemiBold();
                text.Span($"{Model.DueDate:d}").FontFamily("simhei");
            });
        });
    });
}

Finally, we implement the content:

public void Compose(IDocumentContainer container)
{
    container
        .Page(page =>
        {
            page.PageColor(Colors.Red.Lighten1);
            page.Size(PageSizes.A4);
            page.Margin(10);

            page.Header().Height(100).Background(Colors.LightBlue.Lighten1).Element(ComposeHeader);
            page.Content().Background(Colors.Grey.Lighten3).Element(ComposeContent);
            page.Footer().Height(50).Background(Colors.Grey.Lighten1);
        });
}

void ComposeHeader(IContainer container)
{
    var titleStyle = TextStyle.Default.FontSize(20).SemiBold().FontColor(Colors.Blue.Medium);

    container.Row(row =>
    {
        row.RelativeItem().Column(column =>
        {
            column.Item().Text($"Invoice #{Model.InvoiceNumber}").FontFamily("simhei").Style(titleStyle);

            column.Item().Text(text =>
            {
                text.Span("Issue date: ").SemiBold().FontFamily("simhei");
                text.Span($"{Model.IssueDate:d}").FontFamily("simhei");
            });

            column.Item().Text(text =>
            {
                text.Span("Due date: ").FontFamily("simhei").SemiBold();
                text.Span($"{Model.DueDate:d}").FontFamily("simhei");
            });
        });
    });
}

void ComposeContent(IContainer container)
{
    container.Table(table =>
    {
        // step 1: define columns
        table.ColumnsDefinition(columns =>
        {
            columns.ConstantColumn(25);
            columns.RelativeColumn(3);
            columns.RelativeColumn();
            columns.RelativeColumn();
            columns.RelativeColumn();
        });

        // step 2: header
        table.Header(header =>
        {
            header.Cell().Text("#").FontFamily("simhei");
            header.Cell().Text("Product").FontFamily("simhei");
            header.Cell().AlignRight().Text("Price").FontFamily("simhei");
            header.Cell().AlignRight().Text("Quantity").FontFamily("simhei");
            header.Cell().AlignRight().Text("Total").FontFamily("simhei");

            header.Cell().ColumnSpan(5)
                .PaddingVertical(5).BorderBottom(1).BorderColor(Colors.Black);
        });

        // step 3: data rows
        foreach (var item in Model.Items)
        {
            table.Cell().Element(CellStyle).Text(Model.Items.IndexOf(item) + 1).FontFamily("simhei");
            table.Cell().Element(CellStyle).Text(item.Name).FontFamily("simhei");
            table.Cell().Element(CellStyle).AlignRight().Text($"{item.Price}$").FontFamily("simhei");
            table.Cell().Element(CellStyle).AlignRight().Text(item.Quantity).FontFamily("simhei");
            table.Cell().Element(CellStyle).AlignRight().Text($"{item.Price * item.Quantity}$").FontFamily("simhei");

            static IContainer CellStyle(IContainer container)
            {
                return container.BorderBottom(1).BorderColor(Colors.Grey.Lighten2).PaddingVertical(5);
            }
        }
    });
}

After all these preparations, we can generate the PDF document:

var filePath = "invoice.pdf";

var model = InvoiceDocumentDataSource.GetInvoiceDetails();
var document = new InvoiceDocument(model);
document.GeneratePdf(filePath);

4. Summary

Of course, there are many more interesting features. Today I just gave you an overview to get you familiar with this library. I will continue to share more functionalities of this library in future posts. If you are interested in this library, feel free to follow me! WeChat public account: [黑哥聊 dotNet]

Keep Exploring

Related Reading

More Articles
Same category / Same tag 7/16/2022

C# Convert PDF to Image

Today, a colleague told me that the PDF file you obtained doesn't quite fit our existing software workflow, so can you convert our PDF file to an image?

Continue Reading