SVG and PDF are both vector formats, which means converting between them is mathematically lossless. The shapes, paths, gradients, and text in your SVG land in the PDF at infinite resolution, ready to print at any size without pixelation. The only conversion challenges are font embedding, page sizing, and the occasional CSS filter that one renderer handles and another does not. This guide walks through the four practical paths, with code that runs and a comparison table to help you pick.
Why SVG to PDF is easy
Both formats describe vector primitives: lines, curves, text, fills, gradients. There is no rasterisation step, no DPI to choose, no JPEG quality slider to second-guess. A PDF can contain native vector graphics directly, and most renderers translate SVG paths into PDF content streams without intermediate bitmap conversion. The output PDF stays small (often a few kilobytes), prints crisply at any size, and zooms infinitely in any PDF viewer.
The only catches are fonts (the renderer needs access to the font, or the font has to be embedded in the SVG, or the text has to be converted to paths) and page sizing (you have to tell the renderer how big the PDF page should be). Everything else is downhill.
Which tool should you pick?
Four practical methods cover every use case: a graphical editor with a CLI mode, a tiny native binary, a full headless browser, and a pure JavaScript library that runs client-side. They trade install size, fidelity, and integration effort.
| Method | Install size | Fidelity | Speed | Best for |
|---|---|---|---|---|
| Inkscape CLI | ~150 MB | Highest (full SVG 1.1 + filters) | Medium (1-3s) | One-off conversions, design files |
| rsvg-convert | ~10 MB | High (librsvg, used by GNOME) | Fast (under 200ms) | Servers, batch jobs, low-RAM hosts |
| Playwright (Chromium) | ~300 MB | Highest (browser-grade) | Fast (200-500ms) | Node apps, automation, dynamic SVGs |
| svg2pdf.js + jsPDF | ~100 KB (browser bundle) | Medium (no advanced filters) | Instant | In-browser export, no upload |
If you are inside a web app and you want the file to never leave the user's machine, use svg2pdf.js. If you are on a server and need a tiny dependency, use rsvg-convert. If you want maximum fidelity (filters, foreign objects, CSS animations frozen at frame zero), use Playwright. If you are converting Adobe Illustrator or Figma exports interactively, use Inkscape.
Method 1: Inkscape CLI (highest fidelity)
Inkscape is the canonical SVG editor and renders SVG to PDF using its native engine, which supports the entire SVG 1.1 spec including filters, masks, clip paths, and gradient meshes. Since version 1.0, Inkscape runs headlessly on Linux, macOS, and Windows without a display server, which makes it a fine batch tool.
Install Inkscape:
# macOS
brew install --cask inkscape
# Ubuntu / Debian
apt install inkscapeConvert a single file:
inkscape input.svg --export-type=pdf --export-filename=output.pdfPin the output to A4:
inkscape input.svg \
--export-type=pdf \
--export-area-page \
--export-pdf-version=1.5 \
--export-text-to-path \
--export-filename=output.pdfThe flags worth knowing:
--export-area-pageexports the SVG viewbox as a single page. Use--export-area-drawingto crop tightly to the visible drawing.--export-text-to-pathconverts every text element to outline paths, which removes the font embedding problem entirely. The PDF will render identically on a machine that has never heard of your font.--export-pdf-version=1.5selects the PDF version. 1.5 supports transparency natively; 1.4 falls back to flattening.
Batch every SVG in a folder:
for svg in *.svg; do
inkscape "$svg" --export-type=pdf --export-area-page \
--export-text-to-path --export-filename="${svg%.svg}.pdf"
doneIf you need to keep the text as searchable, selectable text in the PDF
(rather than outlined paths), drop --export-text-to-path and make sure
the system has the font installed. On Ubuntu CI runners, this usually
means apt install fonts-inter fonts-noto or copying the .ttf files
into /usr/share/fonts/ before calling Inkscape.
Method 2: rsvg-convert (smallest footprint)
rsvg-convert is a thin CLI on top of librsvg, the SVG renderer used by GNOME, Wikipedia, and most Linux desktops. It is roughly fifteen times smaller than Inkscape, starts in milliseconds, and handles 99% of real-world SVG files. It is the right pick for batch jobs, container images, and anywhere you want the smallest possible binary.
# macOS
brew install librsvg
# Ubuntu / Debian
apt install librsvg2-binConvert a single SVG to PDF:
rsvg-convert -f pdf -o output.pdf input.svgSet the output page size to 800 by 600 points:
rsvg-convert -f pdf -w 800 -h 600 -o output.pdf input.svgRender at 2x scale (useful when the SVG viewbox is small but you want a larger page):
rsvg-convert -f pdf -z 2 -o output.pdf input.svgThe trade-off is that librsvg historically lagged on advanced features: Gaussian blur filters, foreign objects, and some CSS3 selectors. The 2.50+ release line closed most of these gaps, and modern versions handle nearly everything you would export from Figma or Illustrator.
Method 3: Playwright (Node.js workhorse)
This is the path most production apps end up on. You wrap the SVG in a minimal HTML host, hand it to a headless Chromium, and export the PDF. The renderer is the same one that powers your browser, so anything Chrome can display, the PDF will contain. CSS filters, web fonts, foreign objects, gradient meshes, even SMIL animations frozen at their first frame: all of it works.
npm install playwright
npx playwright install chromiumimport { chromium } from "playwright";
import { readFileSync, writeFileSync } from "node:fs";
async function svgToPdf(svgPath: string, pdfPath: string) {
const svg = readFileSync(svgPath, "utf-8");
// Wrap the SVG in a minimal HTML host. The body has zero margin and the
// SVG fills the viewport so page.pdf() captures only the drawing.
const html = `<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
html, body { margin: 0; padding: 0; }
svg { display: block; width: 100vw; height: 100vh; }
</style>
</head>
<body>${svg}</body>
</html>`;
const browser = await chromium.launch();
const page = await browser.newPage();
await page.setContent(html, { waitUntil: "networkidle" });
// Wait for any web fonts referenced inside the SVG to load
await page.evaluate(() => document.fonts.ready);
// Read the SVG dimensions so the PDF page matches exactly
const dims = await page.evaluate(() => {
const el = document.querySelector("svg") as SVGSVGElement;
const box = el.getBoundingClientRect();
return { width: box.width, height: box.height };
});
const pdf = await page.pdf({
width: `${dims.width}px`,
height: `${dims.height}px`,
printBackground: true,
margin: { top: 0, bottom: 0, left: 0, right: 0 },
});
writeFileSync(pdfPath, pdf);
await browser.close();
}
await svgToPdf("logo.svg", "logo.pdf");The two non-obvious bits: read the SVG bounding box from the live DOM and pass it back to page.pdf() so the PDF page is exactly the same size as the SVG (no whitespace, no clipping). And call document.fonts.ready before exporting, so any web font referenced inside the SVG has fully loaded before the snapshot.
Method 4: svg2pdf.js (client-side, in the browser)
svg2pdf.js walks an SVG DOM tree and emits PDF drawing commands directly through jsPDF, with no browser print pipeline involved. It runs entirely in the user's browser, which means files never leave the device. Perfect for client-side dashboards, chart export buttons, and any privacy-sensitive workflow.
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/jspdf@2/dist/jspdf.umd.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/svg2pdf.js@2/dist/svg2pdf.umd.min.js"></script>
</head>
<body>
<svg id="chart" width="600" height="400" xmlns="http://www.w3.org/2000/svg">
<rect x="0" y="0" width="600" height="400" fill="#f3f4f6"/>
<circle cx="300" cy="200" r="120" fill="#7c3aed"/>
<text x="300" y="210" text-anchor="middle" font-family="Inter" font-size="32" fill="white">SVG</text>
</svg>
<button onclick="exportPdf()">Download PDF</button>
<script>
async function exportPdf() {
const svg = document.getElementById("chart");
const { jsPDF } = window.jspdf;
const doc = new jsPDF({
orientation: "landscape",
unit: "px",
format: [600, 400],
});
await doc.svg(svg, { x: 0, y: 0, width: 600, height: 400 });
doc.save("chart.pdf");
}
</script>
</body>
</html>Fidelity is lower than headless Chromium for advanced features (Gaussian blur, complex masks, foreign objects), but for the SVGs that most apps need to export (D3 charts, Chart.js renders, Mermaid diagrams, icon sets), the result is indistinguishable from a server-rendered PDF.
How do I preserve fonts in an SVG to PDF conversion?
Three options, ranked by reliability. The first is bulletproof, the second is convenient, the third is fragile.
Outline the text to paths. This bakes every glyph into geometric paths in the SVG itself. The conversion no longer cares whether the renderer has the font installed: there is no font, only shapes. Inkscape does this with --export-text-to-path. In Figma, choose "Outline stroke" on text layers before exporting. In Illustrator, "Create Outlines" in the Type menu. The downside is that the resulting PDF text is no longer selectable or searchable.
Embed the font in the SVG via a data URI. Drop a <style> block inside the SVG with a @font-face rule whose src is a base64-encoded data URI of the font file. Every renderer that supports CSS fonts (Inkscape, Chromium, librsvg 2.50+) picks up the font and uses it for that element. Searchability stays intact, but the SVG file gets larger.
<svg xmlns="http://www.w3.org/2000/svg" width="400" height="100">
<style>
@font-face {
font-family: "Inter";
src: url("data:font/woff2;base64,d09GMgABAAAAA...") format("woff2");
}
text { font-family: "Inter", sans-serif; }
</style>
<text x="20" y="60" font-size="40">Hello, vector world</text>
</svg>Install the font on the conversion machine. On a Linux server, apt install fonts-inter puts Inter in /usr/share/fonts/, which both Inkscape and Chromium discover automatically. This is the simplest option for in-house pipelines but breaks the moment the SVG moves to a different machine.
How do I scale an SVG to fit a specific PDF page size?
The SVG viewBox attribute defines the drawing's coordinate system. The output PDF page size is independent: you tell the renderer how many points wide and tall the page should be, and the SVG scales to fit. With Inkscape:
# Force the page size to A4 portrait, scale the drawing to fit
inkscape input.svg \
--export-type=pdf \
--export-area-page \
--export-width=595 \
--export-height=842 \
--export-filename=output.pdfWith rsvg-convert:
rsvg-convert -f pdf -w 595 -h 842 -a -o output.pdf input.svgThe -a flag preserves the aspect ratio. Without it, rsvg-convert stretches the drawing to fill the requested width and height exactly.
With Playwright, set the SVG width and height in the HTML wrapper to the page size, then pass the same dimensions to page.pdf():
const html = `<!DOCTYPE html><html><head><style>
html, body { margin: 0; }
svg { width: 595px; height: 842px; }
</style></head><body>${svg}</body></html>`;
await page.setContent(html);
await page.pdf({ width: "595px", height: "842px", printBackground: true });595 by 842 points is A4. 612 by 792 is US Letter. 1 mm equals roughly 2.83 points.
How do I handle external image references inside an SVG?
SVG files can reference external images via <image href="logo.png">. Each renderer handles the lookup differently. Inkscape resolves relative paths from the directory of the SVG file. rsvg-convert does the same. Playwright resolves them from the document base URL, which is about:blank when you call setContent, so relative paths fail silently.
The portable fix is to inline the image as a base64 data URI. Once embedded, the reference is resolved by every renderer:
import { readFileSync } from "node:fs";
function inlineImages(svgString, basePath) {
return svgString.replace(/href="([^"]+\.(png|jpg|jpeg|gif))"/gi, (_, src) => {
const data = readFileSync(`${basePath}/${src}`).toString("base64");
const ext = src.split(".").pop().toLowerCase();
const mime = ext === "jpg" ? "jpeg" : ext;
return `href="data:image/${mime};base64,${data}"`;
});
}Common gotchas
Backgrounds disappear in Playwright: pass printBackground: true to
page.pdf(). Without it, the SVG's <rect> background fill is dropped
along with every other CSS background.
Page is too big: by default, Playwright defaults to A4. If your SVG is
smaller, you get whitespace around the drawing. Always read the SVG
bounding box at runtime and pass it back to page.pdf() for a tight crop.
CSS animations: Chromium snapshots animations at frame zero. If your
SVG uses CSS or SMIL animations, you see the starting state in the PDF. To
capture a different frame, use page.evaluate to seek the animation
before exporting.
FAQ
Does converting SVG to PDF lose quality?
No. SVG and PDF are both vector formats, so the conversion is mathematically lossless. Shapes, paths, and text remain infinitely scalable inside the PDF and can be printed at any size without pixelation. Quality only drops if the SVG embeds raster images and the converter re-encodes them at lower resolution, which none of the four methods above do by default.
What is the best tool to convert SVG to PDF?
For one-off conversions and design files, Inkscape on the command line gives the highest fidelity with full SVG 1.1 support including filters and gradient meshes. For automation in a Node.js app, headless Chromium via Playwright is the most reliable path because it uses the same renderer as your browser, so anything Chrome can display becomes a faithful PDF.
How do I preserve fonts when converting SVG to PDF?
Three options. The most reliable is to outline the text to paths in your SVG editor before export. The most convenient is to embed the font in the SVG via a base64 data URI inside an @font-face rule. The simplest for in-house pipelines is to install the font on the conversion machine.
Can I set a specific page size when converting SVG to PDF?
Yes. Inkscape accepts --export-area-page plus --export-width and --export-height. rsvg-convert takes -w and -h flags. With Playwright, set both the SVG dimensions in the HTML wrapper and the width and height arguments to page.pdf() to the same values.
How do I convert SVG to PDF in the browser?
Use svg2pdf.js together with jsPDF. Both run entirely client-side, so the file never leaves the user's browser. Fidelity is lower than headless Chromium for advanced filters and CSS, but it is enough for icons, charts from D3 or Chart.js, and most diagrams.
Does Inkscape need a display server to convert SVG to PDF?
No. Inkscape 1.0 and later run headlessly on servers and in CI environments without an X server or Xvfb wrapper. On Linux, install the inkscape package and call it directly from a script.
Why is my SVG text missing in the PDF?
The font is not available on the system running the conversion. Either install the font on the conversion machine, embed the font in the SVG via a data URI, or convert the text to outline paths in your SVG editor before exporting.
Wrapping up
For a single design file, run Inkscape. For a tiny dependency on a server, use rsvg-convert. For dynamic SVGs inside a Node app, use Playwright with a minimal HTML host. For an in-browser export button, use svg2pdf.js. All four paths produce true vector PDFs that print sharply at any size.
If you would rather skip the headless browser plumbing, route the same HTML wrapper through our managed renderer:
Html To PdfTry it freeFree tools mentioned:
Start generating PDFs
Build PDF templates with a visual editor. Render them via API from any language in ~300ms.



