Compound containers

Complex containers play a crucial role in structuring and organizing content. By using a proper container, you can easily present text and images in a user-friendly way.

It is definitely possible to construct a document using only simple text and image containers. Though, there are requirements that are hard or impossible to implement with simple containers only. Compound containers help in such cases. Also, complex containers help achieve your goals with less code.

Compound containers

Use methods of the LayoutContainer class to add complex containers like the Row and the Column to document pages. Using these containers, you can implement other containers like grid and lists. There are also the Inlined and the Layers methods for less common but still important cases.

This article is part of a series about Layout API for PDF generation. If you are new to the API, read the Getting Started with Layout API part first.

Docotic.Pdf library 9.5.17548-dev Layout add-on 9.5.17548-dev
Regression tests 14,726 passed Total NuGet downloads 4,514,921

Row

Row containers provide room for items arranged horizontally in one line. Each item in a row is a container. This means that you can put content of different types in one row. You can specify the spacing between items by using the Spacing method.

All items in a row have the same height. The library uses the height of the tallest item as the height of the row. There are three ways to specify the width of an item. You must pick one when creating the item. A row can contain items created in different ways.

The Row.AutoItem method creates an item without an explicitly specified width. For such items, the library calculates the intrinsic size of their content. The calculated content width is the width of the item. Please note that items created with the AutoItem do not wrap long lines.

Use the ConstantItem method to create an item with width equal to an exact number of points.

The RelativeItem is handy when you don't know the exact widths of the items in the row and do not want to use intrinsic sizes. Instead, you can specify relative widths for items in the row. The method accepts the number of parts the item should occupy. The total number of parts is the sum of all numbers in all the RelativeItem calls in this row.

For example, if there is one RelativeItem call, then the number is not important. The item will occupy the all available width. For two or more items, the numbers define the proportion.

row.RelativeItem(2)
row.RelativeItem(3)
row.RelativeItem(1)

All items created with the above code take 6 parts (2 + 3 + 1 = 6). Individual items occupy 2 of 6, 3 of 6, and 1 of 6 parts, respectively.

Layout API uses the following formula to calculate the width of one part:

PartWidth = (RowWidth - AutoWidth - ConstantWidth) / TotalParts

where:
RowWidth = width of the row container
AutoWidth = width of all items created with the AutoItem method
ConstantWidth = width of all items created with the ConstantItem method

Here is an example that creates a row with items of all three kinds.

var monthNames = DateTimeFormatInfo.InvariantInfo.MonthNames;
var groups = new[]
{
    string.Join(", ", monthNames.Take(4)),
    string.Join(", ", monthNames.Skip(4).Take(4)),
    string.Join(", ", monthNames.Skip(8).Take(4)),
};

PdfDocumentBuilder.Create().Generate("compounds-row.pdf", doc => doc.Pages(page =>
{
    page.Content()
        .Padding(20)
        .MinimalBox()
        .Row(row =>
        {
            row.ConstantItem(100)
                .Background(new PdfRgbColor(187, 237, 237))
                .Text("100 points wide");

            for (int i = 0; i < groups.Length; i++)
            {
                row.AutoItem().LineVertical(0.1);

                var numberOfParts = groups.Length - i + 1;
                row.RelativeItem(numberOfParts)
                    .PaddingHorizontal(5)
                    .Text(t =>
                    {
                        t.Line($"{numberOfParts} parts wide");
                        t.Line();
                        t.Line(groups[i]);
                    });
            }
        });
}));

You can see the result of the code in compounds-row.pdf.

Column

To arrange items vertically, one after another, use a Column container. Each item in a column is a container. Because of this, you can put content of different types in one column.

Each item's width is equal to column width. The height of each item depends on the item's content and properties. Column containers support paging, so Layout add-on can render items of a column on more than one page.

By default, a Column container does not have header or footer content. Use the methods Header and Footer to access and setup the corresponding containers. When column items take more than one page, the library repeats both headers and footers on each page.

Use the Spacing method to add some vertical space between column items. Please note that the library does not apply spacing between the header and the first item. The library also does not add space before the footer.

PdfDocumentBuilder.Create().Generate("compounds-column.pdf", doc => doc.Pages(page =>
{
    page.Size(PdfPaperSize.A6);

    page.Content()
        .Padding(20)
        .Column(column =>
        {
            column.Header()
                .Background(new PdfRgbColor(187, 237, 237))
                .Padding(5)
                .Text("Month names");

            for (int i = 0; i < 12; i++)
            {
                column.Item().Background(
                    new PdfGrayColor(i % 2 == 0 ? 90 : 100))
                    .Padding(5)
                    .Text(DateTimeFormatInfo.InvariantInfo.MonthNames[i]);
            }

            column.Footer().LineHorizontal(1);
        });
}));

You can see the result of the code in compounds-column.pdf.

Grid

Grid layouts organize items into columns and rows. Grids are similar to tables in this aspect. Layout API does not provide a special container type for grids. You can implement a grid layout using the Column and the Row containers.

It helps to think of a grid as a column, with each of its items being a row. Both the Column and the Row containers provide the ability to set spacing between items. You can have a header and a footer, if you like.

Each row can have an independent layout. There can be a different number of items in each row. Items can have different width and height. It is possible to add extra space before, after, or between items in a row. Use items without content and decoration for this.

var blue = new PdfRgbColor(187, 237, 237);
var darkerBlue = blue.Darken(50);
PdfDocumentBuilder.Create().Generate("compounds-grid.pdf", doc => doc.Pages(page =>
{
    page.Size(300, 200);

    page.Content().Padding(15).Column(column =>
    {
        column.Spacing(10);

        column.Item().Row(row =>
        {
            row.Spacing(10);

            row.ConstantItem(100).Background(darkerBlue).Height(40);
            row.RelativeItem(4).Background(blue);
        });

        column.Item().Row(row =>
        {
            row.Spacing(10);

            row.RelativeItem(2).Background(blue).Height(60);
            row.RelativeItem(1);
            row.RelativeItem(2).Background(blue);
        });

        column.Item().Row(row =>
        {
            row.Spacing(10);

            row.RelativeItem(1).Background(blue).Height(50);
            row.RelativeItem(3).Background(blue);
            row.ConstantItem(50).Background(darkerBlue);
        });
    });
}));

You can see the result of the code in compounds-grid.pdf.

Lists

Lists enhance readability by breaking down information into concise points. List items can have numbers, bullet points, and other symbols next to the text in them. You can easily implement a list layout using the Column and the Row containers. Layout API does not provide a special container type for lists.

Check the example code that creates a list of months by seasons. Please note that the list has a header. If items contain text that can wrap onto the next line, use either the RelativeItem or the ConstantItem method for the text part of the item.

var monthNames = DateTimeFormatInfo.InvariantInfo.MonthNames.ToList();
monthNames.Insert(0, monthNames[11]);

PdfDocumentBuilder.Create().Generate("compounds-list.pdf", doc => doc.Pages(page =>
{
    page.Size(150, 200);

    page.Content().Padding(5).Column(column =>
    {
        column.Header()
            .Text("Months by seasons:")
            .Style(TextStyle.Parent.Underline());

        for (int i = 0; i < 4; i++)
        {
            var season = string.Join(", ", monthNames.Skip(i * 3).Take(3));
            column.Item().Row(row =>
            {
                row.Spacing(2);

                row.AutoItem().Text("•");
                row.RelativeItem().Text(season);
            });
        }
    });
}));

You can see the result of the code in compounds-list.pdf.

Table

Table layouts organize items into columns and rows. The Table container type provides an extensive set of features and can help you with the most sophisticated cases. Read about all the features in the Table container article.

InlineContainer

You can fill a container with a collection of other containers. Start by calling the LayoutContainer.Inlined method. Then call the Item method of the provided InlineContainer to add child containers.

Layout add-on puts containers in a row one after another. If there is no space to put an item, the library starts a new row. Use the Spacing/HorizontalSpacing/VerticalSpacing methods to add some space between items.

You can affect the position of items in the container by using alignment methods. The AlignTop/AlignMiddle/AlignBottom methods align items vertically. For the horizontal direction, use the AlignLeft/AlignCenter/AlignRight/AlignJustify methods.

There is a special case. The AlignSpaceAround method that aligns items horizontally. It also adds extra spacing before the first item and after the last item.

var orange = new PdfRgbColor(250, 123, 5);
var brown = orange.Darken(50);
var itemProps = new (int Width, PdfColor Color)[] {
    (20, orange), (30, orange), (50, brown), (50, orange), (50, orange),
    (30, orange), (20, brown), (30, orange), (50, brown), (10, brown)
};

PdfDocumentBuilder.Create().Generate("compounds-inlined.pdf", doc => doc.Pages(page =>
{
    page.Size(150, 120);

    page.Content().Inlined(c =>
    {
        c.Spacing(5);

        foreach (var (Width, Color) in itemProps)
            c.Item().Height(30).Width(Width).Background(Color);
    });
}));

You can see the result of the code in compounds-inlined.pdf.

LayerContainer

You might need to put some content below and/or above the main page content. The obvious use case is to add a watermark on top of PDF pages.

First, get a LayerContainer object by calling the LayoutContainer.Layers method. Given the object, you can start adding layers. Calling the Layer method adds a secondary layer. Call the PrimaryLayer method to add the main content layer. You must add exactly one primary layer.

Layout API will compose layers in the same order as you create them. Layers added before the primary layer will go to the background. All layers added after the primary layer will go above the main content. Container repeats the secondary layers on all the pages occupied by the main content.

Here is a sample code for how to add watermarks to PDF pages.

PdfDocumentBuilder.Create().Generate("compounds-layers.pdf", doc => doc.Pages(page =>
{
    var largeRedText = TextStyle.Parent.FontSize(48)
        .FontColor(new PdfRgbColor(235, 64, 52));

    page.Size(400, 250);

    page.Content()
        .Padding(25)
        .Layers(layers =>
        {
            layers.Layer()
                .AlignCenter()
                .Text(text => text.CurrentPageNumber().Style(largeRedText));

            layers.PrimaryLayer()
                .Background(new PdfGrayColor(85), 65)
                .Padding(25)
                .Text(new string('_', 790));

            layers.Layer()
                .AlignCenter()
                .AlignMiddle()
                .Text("Watermark")
                .Style(largeRedText);
        });
}));

You can see the result of the code in compounds-layers.pdf.

Watermarks

One of the common requirements is to add watermark to PDF. The requirement can exist for a variety of reasons. You can add watermark to PDF to identify ownership or to point out confidentiality or sensitivity of the information in the PDF.

One approach to watermarking PDFs is to use layers. See the example in the previous section. Let me show you another approach.

I will use page background and foreground containers to watermark PDFs. Layout add-on repeats these containers on next pages. Every page with primary document content will also contain background and foreground containers. This makes them suitable for the task.

I start by adding an image to the background container. You can add your logo to PDF in the same way. The image will appear behind page content. It does not matter how many pages of your document use the background image. API will add only one copy of the image bytes to the generated PDF.

The primary document content can be anything. For this sample code, I use the famous Lorem Ipsum text.

The text watermark goes into the foreground container. The text itself can be anything and you can use any color or font. I used rotated text drawn with semi-transparent red letters of a larger size.

The sample code asynchronously downloads both the image and the text from our repository of sample code. Of course, you can use a local image and/or read the text from a file.

var urlPrefix =
    "https://raw.githubusercontent.com/BitMiracle/Docotic.Pdf.Samples/master/Samples/Sample%20Data/";

using var client = new HttpClient();
using var imageResponse = await client.GetAsync(urlPrefix + "watermark-background.png");
using var imageStream = await imageResponse.Content.ReadAsStreamAsync().ConfigureAwait(false);

var loremIpsum = string.Empty;
using (var textResponse = await client.GetAsync(urlPrefix + "lorem-ipsum.txt"))
     loremIpsum = await textResponse.Content.ReadAsStringAsync().ConfigureAwait(false);

PdfDocumentBuilder.Create().Generate("compounds-watermarks.pdf", doc =>
{
    var image = doc.Image(imageStream);

    doc.Pages(page =>
    {
        page.Size(400, 250);

        page.Background()
            .AlignMiddle()
            .Image(image);

        page.Content()
            .Padding(25)
            .Text(loremIpsum);

        var largeRedText = TextStyle.Parent.FontSize(48)
            .FontColor(new PdfRgbColor(235, 64, 52), 50);
        page.Foreground()
            .Rotate(-30)
            .Translate(-50, 180)
            .Text("DO NOT COPY")
            .Style(largeRedText);
    });
});

You can see the result of the code in compounds-watermarks.pdf.

Sample code

We have some sample apps that cover the aforementioned features in greater detail. Please spend some time checking them out.