File: SVG.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 (235 lines) | stat: -rw-r--r-- 8,543 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
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
# Scalable Vector Graphics (SVG) #

`fpdf2` supports basic conversion of SVG paths into PDF paths, which can be
inserted into an existing PDF document or used as the contents of a new PDF
document.

Not all SVGs will convert correctly. Please see
[the list of unsupported features](#currently-unsupported-notable-svg-features)
for more information about what to look out for.

## Basic usage ##

SVG files can be directly inserted inside a PDF file using the [image()](https://py-pdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.image) method:

```python
from fpdf import FPDF

pdf = FPDF()
pdf.add_page()
pdf.image("vector.svg")
pdf.output("doc-with-svg.pdf")
```

Either the embedded `.svg` file must includes `width` and/or `height` attributes (absolute or relative),
or some dimensions must be provided to `.image()` through its `w=` and/or `h=` parameters.

## Detailed example ##

The following script will create a PDF that consists only of the graphics
contents of the provided SVG file, filling the whole page:

```python
import fpdf

svg = fpdf.svg.SVGObject.from_file("my_file.svg")

pdf = fpdf.FPDF(unit="pt", format=(svg.width, svg.height))
pdf.add_page()
svg.draw_to_page(pdf)

pdf.output("my_file.pdf")
```

Because this takes the PDF document size from the source SVG, it does assume
that the width/height of the SVG are specified in absolute units rather than
relative ones (i.e. the top-level `<svg>` tag has something like `width="5cm"`
and not `width=50%`). In this case, if the values are percentages, they will be
interpreted as their literal numeric value (i.e. `100%` would be treated as `100 pt`).
The next example uses `transform_to_page_viewport`, which will scale
an SVG with a percentage based `width` to the pre-defined PDF page size.

The converted SVG object can be returned as an fpdf.drawing.GraphicsContext
collection of drawing directives for more control over how it is rendered:

```python
import fpdf

svg = fpdf.svg.SVGObject.from_file("my_file.svg")

pdf = FPDF(unit="in", format=(8.5, 11))
pdf.add_page()

# We pass align_viewbox=False because we want to perform positioning manually
# after the size transform has been computed.
width, height, paths = svg.transform_to_page_viewport(pdf, align_viewbox=False)
# note: transformation order is important! This centers the svg drawing at the
# origin, rotates it 90 degrees clockwise, and then repositions it to the
# middle of the output page.
paths.transform = paths.transform @ fpdf.drawing.Transform.translation(
    -width / 2, -height / 2
).rotate_d(90).translate(pdf.w / 2, pdf.h / 2)

pdf.draw_path(paths)

pdf.output("my_file.pdf")
```

## Converting vector graphics to raster graphics ##
Usually, embedding SVG as vector graphics in PDF documents is the best approach,
as it is both lightweight and will allow for better details / precision of the images inserted.

But sometimes, SVG images cannot be directly embedded as vector graphics (SVG),
and a conversion to raster graphics (PNG, JPG) must be performed.

The following sections demonstrate how to perform such conversion, using [Pygal charts](Maths.md#using-pygal) as examples:

### Using cairosvg ###
A faster and efficient approach for embedding `Pygal` SVG charts into a PDF file is to use the `cairosvg` library to convert the vector graphics generated into a `BytesIO` instance, so that we can keep these data in an in-memory buffer:

```python
import pygal
from fpdf import FPDF
from io import BytesIO
import cairosvg

# Create a Pygal bar chart
bar_chart = pygal.Bar()
bar_chart.title = 'Browser usage evolution (in %)'
bar_chart.x_labels = map(str, range(2002, 2013))
bar_chart.add('Firefox', [None, None, 0, 16.6,   25,   31, 36.4, 45.5, 46.3, 42.8, 37.1])
bar_chart.add('Chrome',  [None, None, None, None, None, None,    0,  3.9, 10.8, 23.8, 35.3])
bar_chart.add('IE',      [85.8, 84.6, 84.7, 74.5,   66, 58.6, 54.7, 44.8, 36.2, 26.6, 20.1])
bar_chart.add('Others',  [14.2, 15.4, 15.3,  8.9,    9, 10.4,  8.9,  5.8,  6.7,  6.8,  7.5])
svg_img = bar_chart.render()

# Convert the SVG chart to a PNG image in a BytesIO object
img_bytesio = BytesIO()
cairosvg.svg2png(svg_img, write_to=img_bytesio, dpi=96)

# Set the position and size of the image in the PDF
x = 50
y = 50
w = 100
h = 70

# Build the PDF
pdf = FPDF()
pdf.add_page()
pdf.image(img_bytesio, x=x, y=y, w=w, h=h)
pdf.output('browser-usage-bar-chart.pdf')
```
The above code generates a PDF with the following graph:
![](pygal_chart_cairo.png)

**!! Troubleshooting advice !!**

You may encounter `GTK` (Gnome Toolkit) errors while executing the above example in windows. Error could be like following -
```
OSError: no library called "cairo-2" was found
no library called "cairo" was found
no library called "libcairo-2" was found
cannot load library 'libcairo.so.2': error 0x7e
cannot load library 'libcairo.2.dylib': error 0x7e
cannot load library 'libcairo-2.dll': error 0x7e
```
In this case install install `GTK` from [GTK-for-Windows-Runtime-Environment-Installer](https://github.com/tschoonj/GTK-for-Windows-Runtime-Environment-Installer/releases). Restart your editor. And you are all done.

### Using svglib and reportlab ###
An alternative, purely pythonic but slightly slower solution is to use `reportlab` and `svglib`:

```python
import io
import pygal
from reportlab.graphics import renderPM
from svglib.svglib import SvgRenderer
from fpdf import FPDF
from lxml import etree

# Create a Pygal bar chart
bar_chart = pygal.Bar()
bar_chart.title = 'Sales by Year'
bar_chart.x_labels = ['2016', '2017', '2018', '2019', '2020']
bar_chart.add('Product A', [500, 750, 1000, 1250, 1500])
bar_chart.add('Product B', [750, 1000, 1250, 1500, 1750])
svg_img = bar_chart.render()

# Convert the SVG chart to a JPEG image in a BytesIO object
drawing = SvgRenderer('').render(etree.fromstring(svg_img))
jpg_img_bytes = renderPM.drawToString(drawing, fmt='JPG', dpi=72)
img_bytesio = io.BytesIO(jpg_img_bytes)

# Set the position and size of the image in the PDF
x = 50
y = 50
w = 100
h = 70

# Build the PDF
pdf = FPDF()
pdf.add_page()
pdf.image(img_bytesio, x=x, y=y, w=w, h=h)
pdf.output('sales-by-year-bar-chart.pdf')
```

The above code generates the following output:
![](pygal_chart.png)

**Performance considerations**

Regarding performance, `cairosvg` is generally faster than `svglib` when it comes to rendering SVG files to other formats. This is because `cairosvg` is built on top of a fast C-based rendering engine, while `svglib` is written entirely in Python, and hence a bit slower.
Additionally, `cairosvg` offers various options for optimizing the rendering performance, such as disabling certain features, like fonts or filters.


## Warning logs ##

The `fpdf.svg` module produces `WARNING` log messages for some **unsupported** SVG tags & attributes.
If need be, you can suppress those logs:

```python
logging.getLogger("fpdf.svg").propagate = False
```

## Supported SVG Features ##

- groups (`<g>`)
- paths (`<path>`)
- basic shapes (`<rect>`, `<circle>`, `<ellipse>`, `<line>`, `<polyline>`, `<polygon>`)
- basic `<image>` elements
- basic cross-references, with `defs` tags anywhere in the SVG code
- stroke & fill coloring and opacity
- basic stroke styling
- inline CSS styling via `style="..."` attributes
- clipping paths


## Currently Unsupported Notable SVG Features ##

Everything not listed as supported is unsupported, which is a lot. SVG is a
very complex format that has become increasingly complex as it absorbs
more of the entire browser rendering stack into its specification.

However, there are some pretty commonly used features that are unsupported
and may cause unexpected results, up to and including a normal-looking SVG
rendering as a completely blank PDF.

There are some common SVG features that are currently **unsupported**, but that `fpdf2` could end up supporting with the help of contributors :

- `<tspan>` / `<textPath>` / `<text>` (-> there is a starting [draft PR](https://github.com/py-pdf/fpdf2/pull/1029))
- `<symbol>`
- `<marker>`
- `<pattern>`
- gradients: `<linearGradient>` & `<radialGradient>`
- embedded non-image content (including nested SVGs)
- many standard attributes
- CSS styling via `<style>` tags or external *.css files.

{==

Contributions would be very welcome to add support for more SVG features! 👍

If you are interested in contributing to `fpdf2` regarding this,
drop a comment on GitHub issue [#537](https://github.com/py-pdf/fpdf2/issues/537)
and a maintainer will give some pointers to start poking with the code 😊

==}