WeasyPrint SVG Images: Fix Missing Logos in PDFs
Fix missing SVG logos in WeasyPrint PDFs with a correct base URL and explicit CSS sizing, then check the saved PDF for vector paths, proportions, and artwork.
On this page
WeasyPrint can place an SVG in a PDF through an HTML image element. If the logo disappears when you pass HTML as a string, first check the image URL: a relative path needs a base URL. Once the file loads, set its printed size in CSS and inspect the saved PDF. The current WeasyPrint API reference documents this base_url requirement.
The example below reproduces that missing-image case with an original frame-shaped logo. It also checks that the repaired PDF contains vector drawing geometry at the requested size. If you already have an SVG, start with that file. Vectorization only enters this workflow when the source artwork exists as pixels.
Check loading before changing the artwork
A PDF can be created even when its logo is missing. Check the resource warning, resolve the image path, and rerender the same SVG before editing its shapes or converting it to another format.
Reproduce the missing SVG with a small document
We tested with Python 3.13.9 and WeasyPrint 70.0. Use a project environment and install the pinned version:
python -m pip install "weasyprint==70.0"
python -m weasyprint --infoWeasyPrint also needs native libraries, including Pango. Follow its operating-system installation instructions if the import or version command fails. A missing native library is a different problem from a missing logo.
Create this folder structure:
example/
render.py
assets/
logo.svgSave this original demonstration artwork as assets/logo.svg. The opening in the middle gives you something more useful to inspect than a solid rectangle. It is hand-authored geometry, not a PerfectVector conversion.
<svg xmlns="http://www.w3.org/2000/svg"
width="120" height="60" viewBox="0 0 120 60">
<path fill="#087f8c" fill-rule="evenodd"
d="M0 0H120V60H0Z M20 15V45H100V15Z"/>
</svg>Put the following in render.py:
from pathlib import Path
import logging
from weasyprint import HTML
logging.basicConfig(level=logging.WARNING)
root = Path(__file__).resolve().parent
html = """<!doctype html>
<html>
<head>
<meta charset="utf-8">
<style>
@page { size: A4; margin: 20mm; }
body { margin: 0; }
.logo { display: block; width: 60mm; height: auto; }
</style>
</head>
<body>
<img class="logo" src="assets/logo.svg" alt="Frame logo">
</body>
</html>
"""
HTML(string=html).write_pdf(root / "missing-base.pdf")
HTML(
string=html,
base_url=root.as_uri() + "/",
).write_pdf(root / "resolved.pdf")Run python render.py. In our first PDF, the frame was absent and the image's alternative text appeared. The log contained:
Relative URI reference without a base URI: assets/logo.svgThe second PDF showed the teal frame with its opening intact. Both calls produced a file, so the existence of a PDF was not a useful success check by itself.
Give relative paths a stable starting folder
assets/logo.svg identifies a file relative to something else. The base_url in the second call supplies that starting location. root.as_uri() creates an absolute file URI; the trailing slash makes it a directory base. The logo remains in the same assets folder and the SVG content does not change.
Using Path(__file__) also keeps the example tied to the script's folder when you launch it from another working directory. In a notebook, use an explicit project folder instead, since __file__ is normally unavailable.

There is another useful control test. Save the HTML as invoice.html beside render.py and load the file itself:
HTML(filename=str(root / "invoice.html")).write_pdf(
root / "from-file.pdf"
)WeasyPrint can derive a base from an input filename or URL. Our file-based test produced the same frame as the explicit-base case. A string of HTML has no corresponding filename for that inference. These input modes and their base-resolution rules are documented in the HTML constructor.
For an image hosted behind a login, a correct URL may still be insufficient. The URL fetcher documentation says the default HTTP client does not provide cookies or authentication. Use your application's approved resource-fetching arrangement and test it in the environment that generates the PDF. Do not assume a logo visible in your signed-in browser is accessible to the PDF process.
Set the image size separately from the PDF page size
The example uses @page for the A4 sheet and margins, while .logo sets the image to 60 mm wide. Its 2:1 source ratio makes it 30 mm high with height: auto. In our saved PDF, the frame measured 60 × 30 mm and began 20 mm from the page's top and left edges.
Here the drawing fills its entire viewBox. Your logo may have empty space inside that rectangle, so a 60 mm image box can contain a visibly narrower mark. Check the SVG canvas before compensating with an arbitrary larger CSS width. The SVG import-size guide explains this distinction in more detail.
Keep write_pdf() at its default zoom=1 when checking physical dimensions. WeasyPrint's PDF output reference warns that changing zoom scales physical CSS units and named page sizes too.
| What you see | First check | Next step |
|---|---|---|
| Alternative text replaces the logo | Image URL and resource log | Supply a valid base URL or reachable absolute URL |
| Logo appears at the wrong size | CSS width and source aspect ratio | Set a physical width and inspect the SVG canvas |
| Part of the artwork is missing | Source bounds and the specific SVG feature | Reduce to a small failing example and compare the exported PDF |
| Local rendering works, server rendering fails | Available files, fonts, and URL access | Test with the deployed process's resources |
Inspect the PDF's content and appearance
The current WeasyPrint format documentation says SVG images render as vectors in PDF output. That does not mean any file ending in .svg is entirely vector artwork: an SVG can contain an embedded bitmap. Check for that with the embedded-image diagnosis when the result still looks pixelated.
For this simple fixture, we inspected the saved PDFs with PyMuPDF 1.26.7:
| Input case | Drawing records | Embedded images | Visible frame |
|---|---|---|---|
| HTML string without a base | 0 | 0 | Missing; alternative text appeared |
| HTML string with the folder base | 1 | 0 | 60 × 30 mm, opening preserved |
| HTML loaded by filename | 1 | 0 | Same result as the resolved string |
These results apply to the supplied frame, not every SVG feature. One compound path also does not mean one visible shape: its inner subpath defines the opening.
To inspect your resolved.pdf, install pymupdf==1.26.7 and run:
import pymupdf
with pymupdf.open("resolved.pdf") as document:
page = document[0]
print("Drawing records:", len(page.get_drawings()))
print("Embedded images:", len(page.get_images()))
for drawing in page.get_drawings():
box = drawing["rect"]
print("Bounds in mm:", box.width * 25.4 / 72,
box.height * 25.4 / 72)PyMuPDF's page reference documents the drawing and image inspection methods. These counts are diagnostics, not a fidelity certificate. Open the PDF as well: check the opening, colors, proportions, and any lettering at the actual delivery size. Inspect complex artwork feature by feature and rerun the checks when upgrading the renderer.
When to prepare a new SVG first
When the only available logo is a PNG or JPG, PerfectVector's logo vectorizer can prepare an SVG candidate before you place it in the HTML document. Inspect the contours, small openings, and lettering in an editor, then test the SVG through the same WeasyPrint pipeline. The image vectorization guide explains what changes when pixels become paths.
Keep an original editable SVG when one exists. Correct its file reference or layout rather than tracing it again. Photographs can stay raster images, and exact brand lettering may need the original font or a manual redraw. Vectorization does not repair missing server files, authentication, or unsupported rendering features.
If you only need to convert an SVG into its own PDF page, the CairoSVG export workflow covers that smaller job. WeasyPrint fits the HTML document case, where the artwork shares a page with other content.
FAQ
Why does WeasyPrint create a PDF without my SVG logo? An image-loading failure can leave the rest of the document renderable. Check the resource log and the image URL. In the supplied test, an HTML string with a relative image path needed an explicit base URL.
Do I need to convert the SVG to PNG first? No. WeasyPrint accepts SVG images. Keep supported vector artwork as SVG when that is the intended output, and check the saved PDF. A raster export is an alternative when you deliberately choose appearance over vector geometry.
Does base_url change the logo's printed size? No. It provides the base for resolving relative resource URLs. Set the image's placement size with CSS and check the SVG's dimensions and viewBox separately.
Sources
- WeasyPrint API reference — Documents HTML inputs, base URLs, SVG output, and PDF zoom behavior.
- WeasyPrint First Steps — Covers installation and resource fetching, including authentication limits.
- PyMuPDF Page reference — Documents the drawing and embedded-image inspection methods used in the example.
Only have a raster logo? Prepare an SVG with PerfectVector, inspect its shapes and lettering, then place it in your HTML and verify its appearance and physical size in the saved PDF.
More from the blog

Graphviz SVG Images: Keep Node Artwork Portable
Add SVG artwork to a Graphviz node, inspect its image reference, and test a complete asset bundle against a moved diagram and a self-contained delivery copy.

Matplotlib SVG: Keep Plot and Logo Artwork as Paths
Export your Matplotlib plot as SVG, compose separate logo paths with svgutils, and check the delivered file for embedded pixels, placement, and font changes.