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:

**!! 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:

**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 😊
==}
|