C# PDF generation quick start guide

Read about how to generate PDF files like reports, invoices and receipts in your .NET projects. Using C# or VB.NET, create PDF documents easily by composing structural elements. The elements include headers, footers, containers, tables, paragraphs, images, and the like. The API automatically breaks content into pages.

Some other methods of generating PDF files described in the Create PDF documents in C# and VB.NET article.

Introducing PDF generation library

We offer the layout API as a free add-on for Docotic.Pdf library. Both the library and Layout add-on are available on NuGet and from our site. Get the library, the add-on, and a free time-limited license key on the Download C# .NET PDF library page.

Docotic.Pdf library 9.5.17615-dev Layout add-on 9.5.17615-dev
Regression tests 14,813 passed Total NuGet downloads 4,924,084

Installation

Install the BitMiracle.Docotic.Pdf.Layout package from NuGet. This is the easiest and most convenient way to install Layout add-on.

As an alternative, you can download the ZIP with the library binaries on our site. To use Layout API, add references to the following DLLs from the ZIP package:

  • BitMiracle.Docotic.Pdf.dll
  • Layout add-on/BitMiracle.Docotic.Pdf.Layout.dll

The approach

The entry point is the PdfDocumentBuilder class. To start generating a PDF, you call the Generate method of the class. The method requires a delegate of the Action<Document> type as one of its parameters. The library expects you to layout document contents in the delegate.

PdfDocumentBuilder.Create().Generate("output.pdf", (Document doc) =>
{
    // build document contents here
});

Given a Document object, the delegate should define layout. The complete layout consists of smaller building blocks. Pages, headers and footers, containers, text blocks are examples of such blocks.

Many method calls are chained together. The order of calls in a chain is important. Here is an example that shows how to set up pages content in a few chained calls.

PdfDocumentBuilder.Create()
    .Generate("output.pdf", doc => doc.Pages(pages =>
    {
        pages.Content().Padding(24).AlignCenter().Text("Some text");
    }));

Intellisense will help you with discovering the API. Try the API yourself. I hope you find it easy to use.

Hello, world!

Let's develop a simple application that shows common building blocks in action. The app will use the approach mentioned above.

I put the complete application code in our Docotic.Pdf sample code repository. The code for this step is in the HelloWorld class of the example app.

public void CreatePdf()
{
    PdfDocumentBuilder.Create().Generate("hello.pdf", doc => doc.Pages(page =>
    {
        page.Content().Text("Hello, world!");
    }));
}

Not much code, but what is happening here?

Long explanation for the short code

The code creates an instance of the document builder, asks the builder to generate hello.pdf. The builder delegates the job of document content layout to my code.

In the code of the delegate, I ask the document to build some pages. Now the document delegates the job of laying out the content of the pages to my code.

In the delegate for the pages, I access the layout container for the primary content of the pages. I do this by calling the Content method. The chained call to the Text adds the famous text span to the content of the pages.

Now it is time for the builder to generate the document with some pages according to the provided layout. The builder creates the exact number of pages required to contain the data I added into the layout container for the primary content. One page is enough in this case? Okay.

Prepare for future updates

I am going to add some more features to the code. To make it more convenient to develop the app further, I changed the code like this:

public void CreatePdf()
{
    PdfDocumentBuilder.Create().Generate("hello.pdf", BuildDocument);
}

static void BuildDocument(Document doc)
{
    doc.Pages(BuildPages);
}

static void BuildPages(PageLayout pages)
{
    pages.Content().Text("Hello, world!");
}

It might look unnecessary, but splitting the code into methods at least makes it more readable.

The sample code for this step is in the HelloWorld2 class of the example app.

Please check the following articles for more information about documents, pages and containers.

Fonts and colors

The default font is good, but I will show how to use another one. The main idea is to create a text style using a font. Then, if needed, apply some optional properties to the style and, finally, use the style on a text span.

You can use a font from the collection of fonts installed in the operating system or load a font from a file or a stream.

I recommend overriding the pre-defined styles by setting up typography for the document. But that is not required and you can use text styles directly.

The sample code for this step is in the HelloWorld3 class of the example app. Below are the parts that changed from the previous step.

static void BuildDocument(Document doc)
{
    doc.Typography(t =>
    {
        t.Document = doc.TextStyleWithFont(SystemFont.Family("Calibri"));
    });
    doc.Pages(BuildPages);
}

static void BuildPages(PageLayout pages)
{
    pages.Size(PdfPaperSize.A6);
    pages.Margin(10);

    BuildTextContent(pages.Content());
}

static void BuildTextContent(LayoutContainer content)
{
    var colorAccented = new PdfRgbColor(56, 194, 10);
    var styleAccented = TextStyle.Parent.FontColor(colorAccented);

    content.Text(t =>
    {
        t.Span("Hello, ").Style(styleAccented);
        t.Span("world!").Style(styleAccented.Underline());
    });
}

As you can see, I have updated the code for the BuildDocument and the BuildPages. I also added a new method named BuildTextContent.

In the BuildDocument, I create a text style from the system font named "Calibri". Then I set this text style as the default text style for the document.

The BuildPages method now contains code for setting size and margin for all pages. Also, the method calls the BuildTextContent, passing the container for the primary content of the pages as the parameter.

The way I construct the primary content is different now. I use two text spans and apply different text styles to each span. In effect, both spans use the accent color, but the second span also has an underline.

The code produces a PDF with the custom text font and color. If your result contains a trial message warning, please get a free time-limited license on the Docotic.Pdf library page.

Many real-world PDF documents like reports or invoices contain header and footer. Let me show how to add header and footer to a PDF.

The sample code for this step is in the HelloWorld4 class of the example app. Below are the parts that changed from the previous step.

static void BuildPages(PageLayout pages)
{
    pages.Size(PdfPaperSize.A6);
    pages.Margin(10);

    BuildPagesHeader(pages.Header());
    BuildPagesFooter(pages.Footer());

    BuildTextContent(pages.Content());
}

static void BuildPagesHeader(LayoutContainer header)
{
    header.TextStyle(TextStyle.Parent.FontSize(8))
        .AlignRight()
        .Text(t =>
        {
            t.Line($"Created by: {Environment.UserName}");
            t.Line($"Date: {DateTime.Now}");
        });
}

static void BuildPagesFooter(LayoutContainer footer)
{
    footer.Height(20)
        .Background(new PdfGrayColor(95))
        .AlignCenter()
        .Text(t => t.CurrentPageNumber());
}

I changed the BuildPages method to call the BuildPagesHeader with the header container as the parameter. The method also calls the BuildPagesFooter passing the footer container to it.

The resulting PDF with header and footer definitely looks more like a professional PDF document.

The BuildPagesHeader method sets a smaller font size for the text. I use two lines for the header contents: one with the name of the current user and the second with the current date. The text is right-aligned.

Please note that I do not specify any explicit size for the header. It will occupy the entire page width minus the left and the right margins. The height will depend on the text lines height.

The footer is similar, except that it has its height specified explicitly. And there is a background color applied to it. The interesting part is that to put the current page number in the PDF footer, I can just use the CurrentPageNumber method. All calculations happen inside the library.

Images

As they say, you can save a lot of words by using one picture. In a quote or receipt, you will probably use a logo of your company or some other important image. For this example code, I will use just a nice-looking one.

The file is on our site, for you to see how the resulting PDF with the beautiful image looks like.

To use an image, you would need to add it to the document first. The library can read the image from a file or a stream. I changed the BuildDocument method to show how to add an image to the document.

The sample code for this step is in the HelloWorld5 class of the example app. Below are the parts that changed from the previous step.

static void BuildDocument(Document doc)
{
    doc.Typography(t =>
    {
        t.Document = doc.TextStyleWithFont(SystemFont.Family("Calibri"));
    });

    var imageFile = new FileInfo("red-flowers-at-butterfly-world.jpg");
    var image = doc.Image(imageFile);

    doc.Pages(pages => {
        BuildPages(pages);

        pages.Content().Column(c =>
        {
            BuildTextContent(c.Item());
            BuildImageContent(c.Item(), image);
        });
    });
}

static void BuildPages(PageLayout pages)
{
    pages.Size(PdfPaperSize.A6);
    pages.Margin(10);

    BuildPagesHeader(pages.Header());
    BuildPagesFooter(pages.Footer());
}

static void BuildImageContent(LayoutContainer content, Image image)
{
    content.AlignCenter()
        .PaddingVertical(20)
        .Image(image);
}

As you can see, there are more changes in the BuildDocument method. Before, the code used the Text method to set some text as the primary content for pages. This still happens in the BuildTextContent method. But now I also want an image in the content.

To have both the text and the image in the primary content of the pages, I need a container. I use the Column container to add the text and the image vertically one after another. The Item method of the column container provides a sub-container. I use one call to the Item method to get a container for the text and another call to get a container for the image.

As you can see, I didn't change the BuildTextContent a bit. But, of course, I had to remove a call to the BuildTextContent from the BuildPages method.

The BuildImageContent does an important job in three lines. It adds the image with some padding at the top and at the bottom. And it also centers the image on the page.

We have more information about the Column container and images in the following articles.

Lists

What is a list? Let's think of it as a group of numbered items written one below the other. This idea suggests a way to implement a list.

The sample code for this step is in the HelloWorld6 class of the example app. Below are the parts that changed from the previous step.

static void BuildDocument(Document doc)
{
    ....

    doc.Pages(pages => {
        BuildPages(pages);

        pages.Content().Column(c =>
        {
            BuildTextContent(c.Item());
            BuildImageContent(c.Item(), image);
            BuildListContent(c.Item());
        });
    });
}

static void BuildListContent(LayoutContainer content)
{
    var dayNames = DateTimeFormatInfo.InvariantInfo.DayNames;
    var dayNamesSpain = DateTimeFormatInfo.GetInstance(new CultureInfo("es-ES")).DayNames;

    content.Column(column =>
    {
        for (int i = 0; i < dayNames.Length; i++)
        {
            column.Item().Row(row =>
            {
                row.Spacing(5);
                row.AutoItem().Text($"{i + 1}.");
                row.RelativeItem().Text(t => {
                    t.Line(dayNames[i]);
                    t.Line($"In Spain they call it {dayNamesSpain[i]}");
                });
            });
        }
    });
}

The only change in the BuildDocument is the new method call. I added the call to the BuildListContent after the BuildImageContent call.

The BuildListContent creates the list as a column of rows. In each row, there are two items. The first (left) one is for the item number. Another (right) one is for the item text. The code explicitly sets the spacing between the items in the row.

To arrange items, the Row container needs to know beforehand or calculate the size of each item. I use the AutoItem and the RelativeItem methods in this example. As the result, the row container will calculate the width required to contain the first item. Then the container will use the remaining available width for the second item.

After adding the list, the contents of the resulting PDF no longer fit one page. Layout API automatically adds the second page and puts the list on it. You can see that API repeated the header and footer on the new page in the PDF document with multiple pages.

We have more information about lists in the Compound containers article.

Tables

Many PDF documents contain tables. There is no surprise here because tables enhance clarity and organization of data. Let me show how to create a table in PDF using Layout API.

The sample code for this step is in the HelloWorld7 class of the example app. Below are the parts that changed from the previous step.

static void BuildDocument(Document doc)
{
    ....

    doc.Pages(pages => {
        BuildPages(pages);

        pages.Content().Column(c =>
        {
            BuildTextContent(c.Item());
            BuildImageContent(c.Item(), image);
            BuildListContent(c.Item());
            BuildTableContent(c.Item());
        });
    });
}

static void BuildTableContent(LayoutContainer content)
{
    var color = new PdfGrayColor(75);

    content.PaddingTop(20).Table(t =>
    {
        t.Columns(c =>
        {
            c.RelativeColumn(4);
            c.RelativeColumn(1);
            c.RelativeColumn(4);
        });

        t.Header(h =>
        {
            h.Cell().Background(color).Text("Month in 2024");
            h.Cell().Background(color).Text("Days");
            h.Cell().Background(color).Text("First Day");
        });

        for (int i = 0; i < 12; i++)
        {
            var stats = GetMonthStats(2024, i);

            t.Cell().Text(stats.Item1);
            t.Cell().Text(stats.Item2);
            t.Cell().Text(stats.Item3);
        }
    });
}

static (string, string, string) GetMonthStats(int year, int monthIndex)
{
    return (
        DateTimeFormatInfo.InvariantInfo.MonthNames[monthIndex],
        DateTime.DaysInMonth(year, monthIndex + 1).ToString(),
        new DateTime(year, monthIndex + 1, 1).DayOfWeek.ToString()
    );
}

The call to the BuildTableContent is the only change in the BuildDocument.

In the BuildTableContent, I create a simple table. The table displays trivial information about months in the year 2024. For a start, I define three columns with relative widths. The left- and the rightmost columns will be 4 times wider than the column in the middle.

The code also defines the header for the table. It does so by adding header cells and specifying text and background color for each cell. When the table does not fit onto one page, the header gets repeated on each page occupied by the table. You can see this in the resulting PDF document with table. In the document, the table starts on the second page and continues on the third one.

I am using the simple loop to make up rows. The code in the loop starts by retrieving the information about each month. Then it adds three cells making up a row of information.

The Table container article explains all the Table container features in greater detail.

Sample code

Above, I presented some of the more popular features of PDF generation in C#. I suggest you continue discovering the API by reading the following article. Also try the sample code from Docotic.Pdf.Samples repository on GitHub. The sample code for Layout API is in the Layout folder of the repository.

You can use the sample code as a code playground. This way you can try some ideas without starting from scratch each time.

The same sample code is in the Samples folder of the ZIP package. There are two solution files. The SamplesCSharp is for sample projects that use C# language. And the SamplesVB.NET is for the VB.NET versions.