fpdf2 - Templates (original) (raw)

Templates are a fpdf2 feature that define predefined documents (like invoices, tax forms, etc.), or parts of such documents, where each element (text, lines, barcodes, etc.) has a fixed position (x1, y1, x2, y2), style (font, size, etc.) and a default text.

These elements can act as placeholders, so the program can change the default text "filling in" the document.

Besides being defined in code, the elements can also be defined in a CSV file, a JSON file, or in a database, so the user can easily adapt the form to his printing needs.

A template is used like a dict, setting its items' values.

There are two approaches to using templates:

Using Template

The traditional approach is to use the Template class, This class accepts one template definition, and can apply it to each page of a document. The usage pattern here is:

`tmpl = Template(elements=elements)

first page and content

tmpl.add_page() tmpl[item_key_01] = "Text 01" tmpl[item_key_02] = "Text 02" ...

second page and content

tmpl.add_page() tmpl[item_key_01] = "Text 11" tmpl[item_key_02] = "Text 12" ...

possibly more pages

...

finalize document and write to file

tmpl.render(outfile="example.pdf") `

The Template class will create and manage its own FPDF instance, so you don't need to worry about how it all works together. It also allows to set the page format, title of the document, measuring unit, and other metadata for the PDF file.

For the method signatures, see py-pdf.github.io: class Template.

You can also check test/template/test_template.py for usage examples of Template.

Setting text values for specific template items is done by treating the class as a dict, with the name of the item as the key:

Template["company_name"] = "Sample Company"

Using FlexTemplate

When more flexibility is desired, then the FlexTemplate class comes into play. In this case, you first need to create your own FPDF instance. You can then pass this to the constructor of one or several FlexTemplate instances, and have each of them load a template definition. For any page of the document, you can set text values on a template, and then render it on that page. After rendering, the template will be reset to its default values.

`from fpdf import FlexTemplate, FPDF

pdf = FPDF() pdf.add_page()

One template for the first page

fp_tmpl = FlexTemplate(pdf, elements=fp_elements) fp_tmpl["item_key_01"] = "Text 01" fp_tmpl["item_key_02"] = "Text 02" ... fp_tmpl.render() # add template items to first page

add some more non-template content to the first page

pdf.polyline(point_list, fill=False, polygon=False)

second page

pdf.add_page()

header for the second page

h_tmpl = FlexTemplate(pdf, elements=h_elements) h_tmpl["item_key_HA"] = "Text 2A" h_tmpl["item_key_HB"] = "Text 2B" ... h_tmpl.render() # add header items to second page

footer for the second page

f_tmpl = FlexTemplate(pdf, elements=f_elements) f_tmpl["item_key_FC"] = "Text 2C" f_tmpl["item_key_FD"] = "Text 2D" ... f_tmpl.render() # add footer items to second page

other content on the second page

pdf.set_dash_pattern(dash=1, gap=1) pdf.line(x1, y1, x2, y2): pdf.set_dash_pattern()

third page

pdf.add_page()

header for the third page, just reuse the same template instance after render()

h_tmpl["item_key_HA"] = "Text 3A" h_tmpl["item_key_HB"] = "Text 3B" ... h_tmpl.render() # add header items to third page

footer for the third page

f_tmpl["item_key_FC"] = "Text 3C" f_tmpl["item_key_FD"] = "Text 3D" ... f_tmpl.render() # add footer items to third page

other content on the third page

pdf.rect(x, y, w, h, style=None)

possibly more pages

pdf.add_page() ... ...

finally write everything to a file

pdf.output("example.pdf") `

Evidently, this can end up quite a bit more involved, but there are hardly any limits on how you can combine templated and non-templated content on each page. Just think of the different templates as of building blocks, like configurable rubber stamps, which you can apply in any combination on any page you like.

Of course, you can just as well use a set of full-page templates, possibly differentiating between cover page, table of contents, normal content pages, and an index page, or something along those lines.

And here's how you can use a template several times on one page (and by extension, several times on several pages). When rendering with an offsetx and/or offsety argument, the contents of the template will end up in a different place on the page. A rotate argument will change its orientation, rotated around the origin of the template. The pivot of the rotation is the offset location. And finally, a scale argument allows you to insert the template larger or smaller than it was defined.

`from fpdf import FlexTemplate, FPDF

pdf = FPDF() pdf.add_page() templ = FlexTemplate(pdf, [ {"name":"box", "type":"B", "x1":0, "y1":0, "x2":50, "y2":50,}, {"name":"d1", "type":"L", "x1":0, "y1":0, "x2":50, "y2":50,}, {"name":"d2", "type":"L", "x1":0, "y1":50, "x2":50, "y2":0,}, {"name":"label", "type":"T", "x1":0, "y1":52, "x2":50, "y2":57, "text":"Label",}, ]) templ["label"] = "Offset: 50 / 50 mm" templ.render(offsetx=50, offsety=50) templ["label"] = "Offset: 50 / 120 mm" templ.render(offsetx=50, offsety=120) templ["label"] = "Offset: 120 / 50 mm, Scale: 0.5" templ.render(offsetx=120, offsety=50, scale=0.5) templ["label"] = "Offset: 120 / 120 mm, Rotate: 30°, Scale=0.5" templ.render(offsetx=120, offsety=120, rotate=30.0, scale=0.5) pdf.output("example.pdf") `

For the method signatures, see py-pdf.github.io: class FlexTemplate.

You can also check test/template/test_flextemplate.py for usage examples of FlexTemplate.

The dict syntax for setting text values is the same as above:

FlexTemplate["company_name"] = "Sample Company"

Details - Template definition

A template definition consists of a number of elements, which have the following properties (columns in a CSV, items in a dict, name/value pairs in a JSON object, fields in a database). Dimensions (except font size, which always uses points) are given in user defined units (default: mm). Those are the units that can be specified when creating a Template or a FPDF instance.

Fields that are not relevant to a specific element type will be ignored there, but if not left empty, they must still adhere to the specified data type (in dicts, string fields may be None).

How to create a template

A template can be created in several ways:

Example - Python dict

`from fpdf import Template

#this will define the ELEMENTS that will compose the template. elements = [ { 'name': 'company_logo', 'type': 'I', 'x1': 20.0, 'y1': 17.0, 'x2': 78.0, 'y2': 30.0, 'font': None, 'size': 0.0, 'bold': 0, 'italic': 0, 'underline': 0, 'align': 'C', 'text': 'logo', 'priority': 2, 'multiline': False}, { 'name': 'company_name', 'type': 'T', 'x1': 17.0, 'y1': 32.5, 'x2': 115.0, 'y2': 37.5, 'font': 'helvetica', 'size': 12.0, 'bold': 1, 'italic': 0, 'underline': 0,'align': 'C', 'text': '', 'priority': 2, 'multiline': False}, { 'name': 'multiline_text', 'type': 'T', 'x1': 20, 'y1': 100, 'x2': 40, 'y2': 105, 'font': 'helvetica', 'size': 12, 'bold': 0, 'italic': 0, 'underline': 0, 'background': 0x88ff00, 'align': 'C', 'text': 'Lorem ipsum dolor sit amet, consectetur adipisici elit', 'priority': 2, 'multiline': True, 'wrapmode': 'WORD'}, { 'name': 'box', 'type': 'B', 'x1': 15.0, 'y1': 15.0, 'x2': 185.0, 'y2': 260.0, 'font': 'helvetica', 'size': 0.0, 'bold': 0, 'italic': 0, 'underline': 0, 'align': 'C', 'text': None, 'priority': 0, 'multiline': False}, { 'name': 'box_x', 'type': 'B', 'x1': 95.0, 'y1': 15.0, 'x2': 105.0, 'y2': 25.0, 'font': 'helvetica', 'size': 0.0, 'bold': 1, 'italic': 0, 'underline': 0, 'align': 'C', 'text': None, 'priority': 2, 'multiline': False}, { 'name': 'line1', 'type': 'L', 'x1': 100.0, 'y1': 25.0, 'x2': 100.0, 'y2': 57.0, 'font': 'helvetica', 'size': 0, 'bold': 0, 'italic': 0, 'underline': 0, 'align': 'C', 'text': None, 'priority': 3, 'multiline': False}, { 'name': 'barcode', 'type': 'BC', 'x1': 20.0, 'y1': 246.5, 'x2': 140.0, 'y2': 254.0, 'font': 'Interleaved 2of5 NT', 'size': 0.75, 'bold': 0, 'italic': 0, 'underline': 0, 'align': 'C', 'text': '200000000001000159053338016581200810081', 'priority': 3, 'multiline': False}, ]

#here we instantiate the template f = Template(format="A4", elements=elements, title="Sample Invoice") f.add_page()

#we FILL some of the fields of the template with the information we want #note we access the elements treating the template instance as a "dict" f["company_name"] = "Sample Company" f["company_logo"] = "docs/fpdf2-logo.png"

#and now we render the page f.render("./template.pdf") `

See template.py or [Web2Py] (Web2Py.md) for a complete example.

Example - Elements defined in JSON file

New in 2.8.0

The JSON file must consist of an array of objects. Each object with its name/value pairs define a template element:

[ { "name": "employee_name", "type": "T", "x1": 20, "y1": 75, "x2": 118, "y2": 90, "font": "helvetica", "size": 12, "bold": true, "underline": true, "text": "" } ]

Then you import and use that template as follows:

def test_template(): f = Template(format="A4", title="Template Demo") f.parse_json("myjsonfile.json") f.add_page() f["employee_name"] = "Joe Doe" return f.render("./template.pdf")

Example - Elements defined in CSV file

You can define template elements in a CSV file template_definition.csv. It can look like this:

line0;L;20.0;12.0;190.0;12.0;times;0.5;0;0;0;0;16777215;C;;0;0;0.0 line1;L;20.0;36.0;190.0;36.0;times;0.5;0;0;0;0;16777215;C;;0;0;0.0 name0;T;21.0;14.0;104.0;25.0;times;16.0;0;0;0;0;16777215;L;name;2;0;0.0 title0;T;21.0;26.0;104.0;30.0;times;10.0;0;0;0;0;16777215;L;title;2;0;0.0 multiline;T;21.0;50.0;28.0;54.0;times;10.5;0;0;0;0;0xffff00;L;multi line;0;1;0.0 numeric_text;T;21.0;80.0;100.0;84.0;times;10.5;0;0;0;0;;R;007;0;0;0.0 empty_fields;T;21.0;100.0;100.0;104.0 rotated;T;21.0;80.0;100.0;84.0;times;10.5;0;0;0;0;;R;ROTATED;0;0;30.0

Remember that each line represents an element and each field represents one of the properties of the element in the following order: ('name','type','x1','y1','x2','y2','font','size','bold','italic','underline','foreground','background','align','text','priority', 'multiline', 'rotate', 'wrapmode') As noted above, most fields may be left empty, so a line is valid with only 6 items. The "empty_fields" line of the example demonstrates all that can be left away. In addition, for the barcode types "x2" may be empty.

Then you can use the file like this:

def test_template(): f = Template(format="A4", title="Sample Invoice") f.parse_csv("template_definition.csv", delimiter=";") f.add_page() f["name0"] = "Joe Doe" return f.render("./template.pdf")