Tutorial

In this tutorial we will create a PDF document using pdfme to showcase some of its functionalities.

We will use the preferred way to build a document in pdfme, that is using pdfme.document.build_pdf() function. This function receives as its first argument a nested dict structure with the contents and styling of the document, and most of this tutorial will be focused on building this dict.

In every step we will tell you the class or function definition where you can get more information.

Let’s start importing the library and creating the root dictionary where the definitions that affect the whole document will be stated: document.

from pdfme import build_pdf

document = {}

Now add the style key to this dictionary, with the styling that all of the sections will inherit.

document['style'] = {
    'margin_bottom': 15,
    'text_align': 'j'
}

In our example we define the margin_bottom property, that will be the default space below every element in the document, and text_align will be the default text alignment for all the paragraphs in the document. In this dict you can set the default value for the style properties that affect the paragraphs (text_align, line_height, indent, list_text, list_style, list_indent, b, i, s, f, u, c, bg, r), images (image_place), tables (cell_margin, cell_margin_left, cell_margin_top, cell_margin_right, cell_margin_bottom, cell_fill, border_width, border_color, border_style) and content boxes (margin_top, margin_left, margin_bottom, margin_right) inside the document. For information about paragraph properties see pdfme.text.PDFText, about table properties see pdfme.table.PDFTable, and about image and content properties see pdfme.content.PDFContent.

You can set page related properties in style too, like page_size, rotate_page, margin, page_numbering_offset and page_numbering_style (see pdfme.pdf.PDF definition).

You can also define named style instructions or formats (something like CSS classes) in the document dict like this:

document['formats'] = {
    'url': {'c': 'blue', 'u': 1},
    'title': {'b': 1, 's': 13}
}

Every key in formats dict will be the name of a format that you will be able to use anywhere in the document. In the example above we define a format for urls, the typical blue underlined style, and a format for titles with a bigger font size and bolded text. Given you can use this formats anywhere, the properties you can add to them are the same you can add to the document’s style we described before.

One more key you can add to document dict is running_sections. In here you can define named content boxes that when referenced in a section, will be added to every page of it. Let’s see how we can define a header and footer for our document using running sections:

document['running_sections'] = {
    'header': {
        'x': 'left', 'y': 20, 'height': 'top',
        'style': {'text_align': 'r'},
        'content': [{'.b': 'This is a header'}]
    },
    'footer': {
        'x': 'left', 'y': 800, 'height': 'bottom',
        'style': {'text_align': 'c'},
        'content': [{'.': ['Page ', {'var': '$page'}]}]
    }
}

Here we defined running sections header and footer, with their respective positions and styles. To know more about running sections see pdfme.document.PDFDocument definition. We will talk about text formatting later, but one important thing to note here is the use of $page variable inside footer’s content. This is the way you can include the number of the page inside a paragraph in pdfme.

Just defining these running sections won’t add them to every page of the document; you will have to reference them in the section you want to really use them, or add a per_page dictionary like this:

document['per_page'] = [
    {'pages': '1:1000:2', 'style': {'margin': [60, 100, 60, 60]}},
    {'pages': '0:1000:2', 'style': {'margin': [60, 60, 60, 100]}},
    {'pages': '0:4:2', 'running_sections': {'include': ['header']}},
]

This dictionary will style, include or exclude running sections from the pages you set in the property pages. This key is a string of comma separated ranges of pages, and in this particular case we will add header to pages 0 and 2, and will add more left margin in odd pages, and more right margin in even pages. To know more about per_page dict see pdfme.document.PDFDocument. Keep reading to see how we add header and footer per sections.

Finally we are going to talk about sections. These can have their own page layout, page numbering, running sections and style, and are the places where we define the contents of the document. It’s important to note that after every section there’s a page break.

Let’s create sections list to contain the documents sections, and add our first section section1.

document['sections'] = []
section1 = {}
document['sections'].append(section1)

A section is just a content box, a multi-column element where you can add paragraphs, images, tables and even content boxes themselves (see pdfme.content.PDFContent for more informarion about content boxes). pdfme will put every element from a section in the PDF document from top to bottom, and when the first page is full it will add a new page to keep adding elements to the document, and will keep adding pages until all of the elements are inside the document.

Like a regular content box you can add a style key to a section, where you can reference a format (from the formats dict we created before), or add a new style dict, and with this you can overwrite any of the default style properties of the document.

section1['style'] = {
    'page_numbering_style': 'roman'
}

Here we overwrite only page_numbering_style, a property that sets the style of the page numbers inside the section (see pdfme.pdf.PDF definition). Default value is arabic style, and here we change it to roman (at least for this section).

Now we are going to reference the running sections that we will use in this section.

section1['running_sections'] = ['footer']

In this first section we will only use the footer. pdfme will add all of the running_sections referenced in running_sections list, in the order they are in this list, to every page of this section.

And finally we will define the contents of this section, inside content1 list.

section1['content'] = content1 = []

We will first add a title for this section:

content1.append({
    '.': 'A Title', 'style': 'title', 'label': 'title1',
    'outline': {'level': 1, 'text': 'A different title 1'}
})

We added a paragraph dict, and it’s itself what we call a paragraph part. A paragraph part can have other nested paragraph parts, as it’s explained in pdfme.text.PDFText definition. This is like an HTML structure, where you can define a style in a root element and its style will be passed to all of its descendants.

The first key in this dictionary we added is what we call a dot key, and is where we place the contents of a paragraph part, and its descendants. We won’t extend much on the format for paragraphs, as it’s explained in pdfme.text.PDFText definition, so let’s talk about the other keys in this dict. First we have a style key, with the name of a format that we defined before in the document’s formats dict. This will apply all of the properties of that format into this paragraph part. We have a label key too, defining a position in the PDF document called title1. Thanks to this we will be able to navigate to this position from any place in the document, just by using a reference to this label (keep reading to see how we reference this title in the second section). Finally, we have an outline key with a dictionary defining a PDF outline, a position in the PDF document, to which we can navigate to from the outline panel of the pdf reader. More information about outlines in pdfme.text.PDFText.

Now we will add our first paragraph.

content1.append(
    ['This is a paragraph with a ', {'.b;c:green': 'bold green part'}, ', a ',
    {'.': 'link', 'style': 'url', 'uri': 'https://some.url.com'},
    ', a footnote', {'footnote': 'description of the footnote'},
    ' and a reference to ',
    {'.': 'Title 2.', 'style': 'url', 'ref': 'title2'}]
)

Note that this paragraph is not a dict, like the title we added before. Here we use a list of paragraph parts, a shortcut when you have a paragraph with different styles or with labels, references, urls, outlines or footnotes.

We give format to the second paragraph part by using its dot key. This way of giving format to a paragraph part is something like the inline styles in HTML elements, and in particular in this example we are making the text inside this part bold and green.

The rest of this list paragraph parts are examples of how to add a url, a footnote and a reference (clickable links to go to the location in the document of the label we reference) to the second title of this document ( located in the second section).

Next we will add an image to the document, located in the relative path path/to/some_image.jpg.

content1.append({
    'image': 'path/to/some_image.jpg',
    'style': {'margin_left': 100, 'margin_right': 100}
})

In style dict we set margin_left and margin_right to 100 to make our image narrower and center it in the page.

Next we will add a group element, containing an image and a paragraph with the image description. This guarantees that both the image and its description will be placed in the same page. To know more about group elements, and how to control the its height check pdfme.content.PDFContent.

content1.append({
    "style": {"margin_left": 80, "margin_right": 80},
    "group": [
        {"image": 'path/to/some_image.jpg'},
        {".": "Figure 1: Description of figure 1"}
    ]
})

Next we will add our first table to the document, a table with summary statistics from a database table.

table_def1 = {
    'widths': [1.5, 1, 1, 1],
    'style': {'border_width': 0, 'margin_left': 70, 'margin_right': 70},
    'fills': [{'pos': '1::2;:', 'color': 0.7}],
    'borders': [{'pos': 'h0,1,-1;:', 'width': 0.5}],
    'table': [
        ['', 'column 1', 'column 2', 'column 3'],
        ['count', '2000', '2000', '2000'],
        ['mean', '28.58', '2643.66', '539.41'],
        ['std', '12.58', '2179.94', '421.49'],
        ['min', '1.00', '2.00', '1.00'],
        ['25%', '18.00', '1462.00', '297.00'],
        ['50%', '29.00', '2127.00', '434.00'],
        ['75%', '37.00', '3151.25', '648.25'],
        ['max', '52.00', '37937.00', '6445.00']
    ]
}

content1.append(table_def1)

In widths list we defined the width for every column in the table. The numbers here are not percentages or fractions but proportions. For example, in our table the first column is 1.5 times larger than the second one, and the third and fourth one are the same length as the second one.

In style dict we set the border_width of the table to 0, thus hiding all of this table lines. We also set margin_left and margin_right to 70 to make our table narrower and center it in the page.

In fills we overwrite the default value of cell_fill, for some of the rows in the table. The format of this fills list is explained in pdfme.table.PDFTable definition, but in short, we are setting the fill color of the even rows to a gray color.

In borders we overwrite the default value of border_width (which we set to 0 in style) for some of the horizontal borders in the table. The format of this borders list is explained in pdfme.table.PDFTable definition too, but in short, we are setting the border width of the first, second and last horizontal borders to 0.5.

And finally we are adding the table contents in the table key. Each list, in this table list, represents a row of the table, and each element in a row list represents a cell.

Next we will add our second table to the document, a form table with some cells combined.

table_def2 = {
    'widths': [1.2, .8, 1, 1],
    'table': [
        [
            {
                'colspan': 4,
                'style': {
                    'cell_fill': [0.8, 0.53, 0.3],
                    'text_align': 'c'
                },
                '.b;c:1;s:12': 'Fake Form'
            },None, None, None
        ],
        [
            {'colspan': 2, '.': [{'.b': 'First Name\n'}, 'Fakechael']}, None,
            {'colspan': 2, '.': [{'.b': 'Last Name\n'}, 'Fakinson Faker']}, None
        ],
        [
            [{'.b': 'Email\n'}, 'fakeuser@fakemail.com'],
            [{'.b': 'Age\n'}, '35'],
            [{'.b': 'City of Residence\n'}, 'Fake City'],
            [{'.b': 'Cell Number\n'}, '33333333333'],
        ]
    ]
}

content1.append(table_def2)

In the first row we combined the 4 columns to show the title of the form; in the second row we combine the first 2 columns for the first name, and the other 2 columns for the last name; and in the last row we use the four cells to the rest of the information.

Notice that cells that are below or to the right of a merged cell must be equal to None, and that instead of using strings inside the cells, like we did in the first table, we used paragraph parts in the cells. And besides paragraphs you can add a content box, an image or even another table to a cell.

Now we will add a second section.

document['sections'].append({
    'style': {
        'page_numbering_reset': True, 'page_numbering_style': 'arabic'
    },
    'running_sections': ['header', 'footer'],
    'content': [

        {
            '.': 'Title 2', 'style': 'title', 'label': 'title2',
            'outline': {}
        },

        {
            'style': {'list_text': '1.  '},
            '.': ['This is a list paragraph with a reference to ',
            {'.': 'Title 1.', 'style': 'url', 'ref': 'title1'}]
        }
    ]
})

In this section we set the page numbering style back to the default value, arabic, and we reset the page count to 1 by including page_numbering_reset in the style dict.

We also added running section header, additional to the running section footer we used in the first section.

And we added the second title of the document, with its label and outline, and a list paragraph (a paragraph with text '1.  ' on the left of the paragraph) with a reference to the first title of the document.

Finally, we will generate the PDF document from the dict document we just built, by using build_pdf function.

with open('document.pdf', 'wb') as f:
    build_pdf(document, f)

Following these steps we will have a PDF document called document.pdf with all of the contents we added to document dict.