Export a Map to SVG and PDF in PyQGIS
Raster export is easy and lossy; vector export is nearly as easy and full of small traps. A PDF that looks perfect on screen can arrive at the printer with one layer flattened to a bitmap, fonts substituted, or every letter converted to outlines so the client's designer cannot fix a typo. All three are settings you chose, usually without knowing.
This recipe belongs to Map Canvas & Image Export in PyQGIS. It covers exporting a layout to PDF and SVG from Python, the settings that decide whether output stays vector, what silently forces rasterisation, and how to produce a layered SVG that opens sensibly in a drawing program.
Prerequisites
- QGIS 3.34 LTR (bundled Python 3.12) or newer.
- A print layout in the project. Canvas-only export goes through
QgsMapSettingsinstead — see rendering a layer to an image without the GUI. - Fonts installed on the machine doing the export, not just on the machine where the layout was designed.
Export a PDF that stays vector
The exporter takes a settings object, and two of its fields decide almost everything.
from qgis.core import QgsProject, QgsLayoutExporter
project = QgsProject.instance()
layout = project.layoutManager().layoutByName("A3 landscape")
settings = QgsLayoutExporter.PdfExportSettings()
settings.dpi = 300
settings.forceVectorOutput = True
settings.rasterizeWholeImage = False
settings.textRenderFormat = QgsRenderContext.TextFormatAlwaysText
settings.appendGeoreference = True
exporter = QgsLayoutExporter(layout)
result = exporter.exportToPdf("/data/output/site_plan.pdf", settings)
if result != QgsLayoutExporter.Success:
raise RuntimeError(f"PDF export failed with code {result}")
Breakdown: forceVectorOutput = True asks QGIS to keep vectors wherever it can; it is not a guarantee, because a layer using a blend mode or a draw effect still rasterises. rasterizeWholeImage = True is the opposite instruction and flattens the entire page — occasionally the right answer when an effect must be reproduced exactly, and a disaster when a printer needs line work. textRenderFormat = TextFormatAlwaysText keeps text as text so it stays selectable, searchable and editable; TextFormatAlwaysOutlines converts every glyph to a path, which guarantees the appearance on a machine without the font at the cost of a much larger file that nobody can correct. dpi still matters in a vector PDF because any raster layer in the layout is resampled to it.
appendGeoreference = True writes the map's georeferencing into the PDF, producing a GeoPDF that opens in a GIS with coordinates intact. It costs nothing and is worth setting by default.
What silently forces rasterisation
Four things flatten a layer regardless of forceVectorOutput, and knowing them turns a mystery into a checklist.
A blend mode other than the default on a layer, symbol or symbol layer. A draw effect — outer glow, drop shadow, blur — anywhere in the symbology. Layer or symbol opacity below 1 in some combinations, particularly where it interacts with a blend mode. And an actual raster layer, which is pixels to begin with.
from qgis.PyQt.QtGui import QPainter
for layer in project.mapLayers().values():
if hasattr(layer, "blendMode") and layer.blendMode() != QPainter.CompositionMode_SourceOver:
print(f"{layer.name()} will rasterise: blend mode {layer.blendMode()}")
Breakdown: Running this check before an export names the layers that will flatten, which is much quicker than opening the PDF in a vector editor and hunting. The fix is either to accept it, or to bake the effect into the data — multiplying a hillshade into a thematic raster once with the raster calculator rather than at draw time, for example, as covered in opacity and blend modes.
Export a layered SVG
SVG export has one option the other formats do not: it can write a separate group per map layer.
svg_settings = QgsLayoutExporter.SvgExportSettings()
svg_settings.dpi = 300
svg_settings.forceVectorOutput = True
svg_settings.exportAsLayers = True
svg_settings.exportMetadata = True
svg_settings.textRenderFormat = QgsRenderContext.TextFormatAlwaysText
result = exporter.exportToSvg("/data/output/site_plan.svg", svg_settings)
if result != QgsLayoutExporter.Success:
raise RuntimeError(f"SVG export failed with code {result}")
Breakdown: exportAsLayers = True is the setting a designer actually wants — the result opens in Illustrator or Inkscape with each map layer in its own group, so a background can be recoloured or a layer hidden without unpicking a single flat drawing. It writes one file per page, and with multiple pages the filenames gain a numeric suffix automatically. exportMetadata embeds the layout's title and author into the SVG's metadata, which is small and occasionally saves an argument about provenance.
SVG files from dense vector data get very large — a parcel layer with fifty thousand polygons produces tens of megabytes and opens slowly. Simplifying the geometry for the export copy, as described in simplifying a geometry, is usually the difference between a file a designer can work with and one they cannot.
Fonts, and the machine that does the export
A layout designed on one machine and exported on another is where fonts go wrong. Qt substitutes a missing family silently, so a headless export can produce a perfectly valid PDF in the wrong typeface.
from qgis.PyQt.QtGui import QFontDatabase
REQUIRED = {"Source Sans 3", "Source Serif 4"}
available = set(QFontDatabase().families())
missing = REQUIRED - available
if missing:
raise RuntimeError(f"fonts missing on this machine: {sorted(missing)}")
Breakdown: Checking before exporting turns a silent substitution into a startup failure, which is exactly the right trade for a scheduled export. Where the fonts genuinely cannot be installed on the export machine, TextFormatAlwaysOutlines is the fallback: the glyphs are drawn from whatever is available and then frozen as paths, so at least the output is consistent — but the check above should still run, because outlining the wrong font just makes the wrong font permanent.
A hardened export function
Everything above collapses into one function worth keeping in a project's utility module, because the checks matter more than the export call.
from pathlib import Path
from qgis.core import QgsLayoutExporter, QgsRenderContext, QgsProject
from qgis.PyQt.QtGui import QPainter, QFontDatabase
def export_layout(layout_name, out_path, fonts=(), outlines=False):
project = QgsProject.instance()
missing = set(fonts) - set(QFontDatabase().families())
if missing:
raise RuntimeError(f"fonts missing: {sorted(missing)}")
flattening = [
layer.name() for layer in project.mapLayers().values()
if hasattr(layer, "blendMode")
and layer.blendMode() != QPainter.CompositionMode_SourceOver
]
if flattening:
print(f"note: these layers will rasterise — {flattening}")
layout = project.layoutManager().layoutByName(layout_name)
if layout is None:
raise LookupError(f"no layout named {layout_name!r}")
layout.refresh()
settings = QgsLayoutExporter.PdfExportSettings()
settings.dpi = 300
settings.forceVectorOutput = True
settings.appendGeoreference = True
settings.textRenderFormat = (
QgsRenderContext.TextFormatAlwaysOutlines if outlines
else QgsRenderContext.TextFormatAlwaysText
)
Path(out_path).parent.mkdir(parents=True, exist_ok=True)
result = QgsLayoutExporter(layout).exportToPdf(str(out_path), settings)
if result != QgsLayoutExporter.Success:
raise RuntimeError(f"export failed for {layout_name} with code {result}")
return out_path
Breakdown: Creating the output directory before exporting removes the most boring failure mode, which the exporter reports only as a status code. layout.refresh() before exporting is what keeps a themed or atlas-driven map item from writing a stale cached render — the same trap described in applying a map theme to a layout map. Reporting the flattening layers rather than raising on them is the right severity: rasterisation is often intended, and a printed note in the job log is enough to catch the time it was not.
QGIS version compatibility
The examples target QGIS 3.34 LTR (Python 3.12).
| QGIS version | Python | Notes |
|---|---|---|
| 3.16 LTR | 3.7 | QgsLayoutExporter with PDF, SVG and image settings present. |
| 3.22 LTR | 3.9 | simplifyGeometries added to PDF settings; appendGeoreference stable. |
| 3.28 LTR | 3.9 | textRenderFormat moved to QgsRenderContext enum values. |
| 3.34 LTR | 3.12 | Baseline for this page. |
| 3.40+ | 3.12 | PDF export can write layer groups and optional-content groups. |
Troubleshooting
- One layer in the PDF is fuzzy. It uses a blend mode or a draw effect and was rasterised. Check with the blend-mode scan above.
- Text is not selectable.
textRenderFormatis set to outlines. Switch toTextFormatAlwaysTextif the font is installed everywhere. - The wrong typeface appeared. Qt substituted a missing font. Verify with
QFontDatabase().families()before exporting. exportToPdfreturned but wrote nothing. The return code was not checked — compare againstQgsLayoutExporter.Success, since the call does not raise.- The SVG is hundreds of megabytes. Dense vector data exported at full detail. Simplify a copy of the layer for export.
- A multi-page layout produced one file. PDF writes all pages into one document; SVG writes one file per page with a numeric suffix. That is by design.
Conclusion
Set forceVectorOutput, keep text as text where the fonts are guaranteed, and check the return code because the exporter reports failure rather than raising. Scan for blend modes before exporting so rasterisation is a decision rather than a surprise, and use exportAsLayers whenever the file is going to somebody with a drawing program.
Frequently Asked Questions
Can I export the canvas directly to PDF?
Not through the exporter, which works on layouts. Build a minimal layout containing one map item at the canvas extent, or render through QgsMapSettings to an image — see exporting the map canvas to an image.
What DPI should a vector PDF use? It affects only the raster elements in the layout, so match it to the worst raster present — 300 for print, 150 if the only raster is a basemap that will never be examined closely.
Does a GeoPDF work in other software? The georeferencing QGIS writes follows the OGC best practice and is read by GDAL and by several desktop GIS products. Adobe's own GeoPDF extension is a different, proprietary thing and is not what this produces.
How do I export every layout in a project?
Iterate project.layoutManager().printLayouts() and export each in turn, which is exactly the pattern in exporting multiple layouts to PDF.