1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327
|
# Templates
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`](https://py-pdf.github.io/fpdf2/fpdf/template.html#fpdf.template.Template) class, This class accepts one template definition, and can apply it to each page of a document. The usage pattern here is:
```python
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](https://py-pdf.github.io/fpdf2/fpdf/template.html#fpdf.template.Template).
You can also check [test/template/test_template.py](https://github.com/py-pdf/fpdf2/blob/master/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:
```python
Template["company_name"] = "Sample Company"
```
## Using FlexTemplate
When more flexibility is desired, then the [`FlexTemplate`](https://py-pdf.github.io/fpdf2/fpdf/template.html#fpdf.template.FlexTemplate) class comes into play.
In this case, you first need to create your own [`FPDF`](https://py-pdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.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.
```python
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.
```python
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](https://py-pdf.github.io/fpdf2/fpdf/template.html#fpdf.template.FlexTemplate).
You can also check [test/template/test_flextemplate.py](https://github.com/py-pdf/fpdf2/blob/master/test/template/test_flextemplate.py) for usage examples of `FlexTemplate`.
The dict syntax for setting text values is the same as above:
```python
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.
* __name__: placeholder identification (unique text string)
* _mandatory_
* __type__:
* '__T__': Text - places one or several lines of text on the page
* '__L__': Line - draws a line from x1/y1 to x2/y2
* '__I__': Image - positions and scales an image into the bounding box
* '__B__': Box - draws a rectangle around the bounding box
* '__E__': Ellipse - draws an ellipse inside the bounding box
* '__BC__': Barcode - inserts an "Interleaved 2 of 5" type barcode
* '__C39__': Code 39 - inserts a "Code 39" type barcode
* Incompatible change: A previous implementation of this type used the non-standard element keys "x", "y", "w", and "h", which are now deprecated (but still work for the moment).
* '__W__': "Write" - uses the `FPDF.write()` method to add text to the page
* _mandatory_
* __x1, y1, x2, y2__: top-left, bottom-right coordinates, defining a bounding box in most cases
* for multiline text, this is the bounding box of just the first line, not the complete box
* for the barcodes types, the height of the barcode is `y2 - y1`, x2 is ignored.
* _mandatory_ ("x2" _optional_ for the barcode types)
* __font__: the name of a font type for the text types
* _optional_
* default: "helvetica"
* __size__: the size property of the element (float value)
* for text, the font size (in points!)
* for line, box, and ellipse, the line width
* for the barcode types, the width of one bar
* _optional_
* default: 10 for text, 2 for 'BC', 1.5 for 'C39'
* __bold, italic, underline__: text style properties
* in dict/JSON, enabled with True/true or equivalent value
* in CSV, only int values, 0 as false, non-0 as true
* _optional_
* default: false
* __foreground, background__: text and fill colors (int value, commonly given in hex as 0xRRGGBB)
* in JSON, a decimal value or a HTML style "#RRGGBB" string (6 digits) can be given.
* _optional_
* default: foreground 0x000000 = black; background None/empty = transparent
* Incompatible change: Up to 2.4.5, the default background for text and box elements was solid white, with no way to make them transparent.
* __align__: text alignment, '__L__': left, '__R__': right, '__C__': center
* _optional_
* default: 'L'
* __text__: default string, can be replaced at runtime
* displayed text for 'T' and 'W'
* data to encode for barcode types
* _optional_ (if missing for text/write, the element is ignored)
* default: empty
* __priority__: Z-order (int value)
* _optional_
* default: 0
* __multiline__: configure text wrapping
* in dicts/JSON, None/null for single line, True/true for multicells (multiple lines), False/false trims to exactly fit the space defined
* in CSV, 0 for single line, >0 for multiple lines, <0 for exact fit
* _optional_
* default: single line
* __rotation__: rotate the element in degrees around the top left corner x1/y1 (float)
* _optional_
* default: 0.0 - no rotation
* __wrapmode__: optionally set wrapmode to "CHAR" to support multiline line wrapping on characters instead of words
* _optional_
* default: 'WORD'
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:
* By defining everything directly as a Python dictionary
* By reading the template definition from a JSON document with `.parse_json()`
* By reading the template definition from a CSV document with `.parse_csv()`
* By defining the template in a database (this applies to [Web2Py] (Web2Py.md) integration)
### Example - Python dict
```python
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 [:octicons-tag-24: 2.8.0](https://github.com/py-pdf/fpdf2/blob/master/CHANGELOG.md)_
The JSON file must consist of an array of objects.
Each object with its name/value pairs define a template element:
```json
[
{
"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:
```python
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:
```python
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")
```
|