File: Drawing.md

package info (click to toggle)
fpdf2 2.8.4-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 53,828 kB
  • sloc: python: 39,486; sh: 133; makefile: 12
file content (200 lines) | stat: -rw-r--r-- 6,781 bytes parent folder | download | duplicates (2)
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
# Drawing #

The `fpdf.drawing` module provides an API for composing paths out of an
arbitrary sequence of straight lines and curves. This allows fairly low-level
control over the graphics primitives that PDF provides, giving the user the
ability to draw pretty much any vector shape on the page.

The drawing API makes use of features (notably transparency and blending modes)
that were introduced in PDF 1.4. Therefore, use of the features of this module
will automatically set the output version to 1.4 (fpdf normally defaults to
version 1.3. Because the PDF 1.4 specification was originally published in
2001, this version should be compatible with all viewers currently in general
use).

## Getting Started

The easiest way to add a drawing to the document is via `fpdf.FPDF.new_path`.
This is a context manager that takes care of serializing the path to the
document once the context is exited.

Drawings follow the fpdf convention that the origin (that is, coordinate(0, 0)),
is at the top-left corner of the page. The numbers specified to the various
path commands are interpreted in the document units.

```python
import fpdf

pdf = fpdf.FPDF(unit='mm', format=(10, 10))
pdf.add_page()

with pdf.new_path() as path:
    path.move_to(2, 2)
    path.line_to(8, 8)
    path.horizontal_line_relative(-6)
    path.line_relative(6, -6)
    path.close()

pdf.output("drawing-demo.pdf")
```
This example draws an hourglass shape centered on the page:

<p align="center"><img src="drawing/demo-1.webp"/></p>
<p align="center"><a href="drawing/demo-1.pdf">view as PDF</a></p>


## Adding Some Style

Drawings can be styled, changing how they look and blend with other drawings.
Styling can change the color, opacity, stroke shape, and other attributes of a
drawing.

Let's add some color to the above example:

```python
import fpdf

pdf = fpdf.FPDF(unit='mm', format=(10, 10))
pdf.add_page()

with pdf.new_path() as path:
    path.style.fill_color = '#A070D0'
    path.style.stroke_color = fpdf.drawing.gray8(210)
    path.style.stroke_width = 1
    path.style.stroke_opacity = 0.75
    path.style.stroke_join_style = 'round'

    path.move_to(2, 2)
    path.line_to(8, 8)
    path.horizontal_line_relative(-6)
    path.line_relative(6, -6)
    path.close()

pdf.output("drawing-demo.pdf")
```

If you make color choices like these, it's probably not a good idea to quit your
day job to become a graphic designer. Here's what the output should look like:

<p align="center"><img src="drawing/demo-2.webp"/></p>
<p align="center"><a href="drawing/demo-2.pdf">view as PDF</a></p>

## Transforms And You

Transforms provide the ability to manipulate the placement of points within a
path without having to do any pesky math yourself. Transforms are composable
using python's matrix multiplication operator (`@`), so, for example, a
transform that both rotates and scales an object can be create by matrix
multiplying a rotation transform with a scaling transform.

An important thing to note about transforms is that the result is order
dependent, which is to say that something like performing a rotation followed
by scaling will not, in the general case, result in the same output as
performing the same scaling followed by the same rotation.

Additionally, it's not generally possible to deconstruct a composed
transformation (representing an ordered sequence of translations, scaling,
rotations, shearing) back into the sequence of individual transformation
functions that produced it. That's okay, because this isn't important unless
you're trying to do something like animate transforms after they've been
composed, which you can't do in a PDF anyway.

All that said, let's take the example we've been working with for a spin (the
pun is intended, you see, because we're going to rotate the drawing).
Explaining the joke does make it better.

An easy way to apply a transform to a path is through the `path.transform`
property.

```python
import fpdf

pdf = fpdf.FPDF(unit="mm", format=(10, 10))
pdf.add_page()

with pdf.new_path() as path:
    path.style.fill_color = "#A070D0"
    path.style.stroke_color = fpdf.drawing.gray8(210)
    path.style.stroke_width = 1
    path.style.stroke_opacity = 0.75
    path.style.stroke_join_style = "round"
    path.transform = fpdf.drawing.Transform.rotation_d(45).scale(0.707).about(5, 5)

    path.move_to(2, 2)
    path.line_to(8, 8)
    path.horizontal_line_relative(-6)
    path.line_relative(6, -6)

    path.close()

pdf.output("drawing-demo.pdf")
```

<p align="center"><img src="drawing/demo-3.webp"/></p>
<p align="center"><a href="drawing/demo-3.pdf">view as PDF</a></p>

The transform in the above example rotates the path 45 degrees clockwise
and scales it by `1/sqrt(2)` around its center point. This transform could be
equivalently written as:

```python
import fpdf
T = fpdf.drawing.Transform

T.translation(-5, -5) @ T.rotation_d(45) @ T.scaling(0.707) @ T.translation(5, 5)
```

Because all transforms operate on points relative to the origin, if we had
rotated the path without first centering it on the origin, we would have
rotated it partway off of the page. Similarly, the size-reduction from the
scaling would have moved it closer to the origin. By bracketing the transforms
with the two translations, the placement of the drawing on the page is
preserved.

## Clipping Paths

The clipping path is used to define the region that the normal path is actually
painted. This can be used to create drawings that would otherwise be difficult
to produce.

```python
import fpdf

pdf = fpdf.FPDF(unit="mm", format=(10, 10))
pdf.add_page()

clipping_path = fpdf.drawing.ClippingPath()
clipping_path.rectangle(x=2.5, y=2.5, w=5, h=5, rx=1, ry=1)

with pdf.new_path() as path:
    path.style.fill_color = "#A070D0"
    path.style.stroke_color = fpdf.drawing.gray8(210)
    path.style.stroke_width = 1
    path.style.stroke_opacity = 0.75
    path.style.stroke_join_style = "round"

    path.clipping_path = clipping_path

    path.move_to(2, 2)
    path.line_to(8, 8)
    path.horizontal_line_relative(-6)
    path.line_relative(6, -6)

    path.close()

pdf.output("drawing-demo.pdf")
```
<p align="center"><img src="drawing/demo-4.webp"/></p>
<p align="center"><a href="drawing/demo-4.pdf">view as PDF</a></p>

## Next Steps

The presented API style is designed to make it simple to produce shapes
declaratively in your Python scripts. However, paths can just as easily be
created programmatically by creating instances of the
`fpdf.drawing.PaintedPath` for paths and `fpdf.drawing.GraphicsContext` for
groups of paths.

Storing paths in intermediate objects allows reusing them and can open up more
advanced use-cases. The [`fpdf.svg`](SVG.md) SVG converter, for example, is
implemented using the `fpdf.drawing` interface.