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.