Guides Charts

Charts in PDFs: vector SVG or 300 DPI PNG

Updated 7 min read

Short answer

Use a vector chart (SVG) if you can. The PDF stores it as shapes and real text, so it stays sharp at any zoom. If it must be an image, make it the width in inches × 300 pixels for print: 1,800 pixels for a 6-inch chart.

Avoid printing a page with a canvas chart to PDF. Chromium flattens the canvas into a bitmap at screen resolution, 96 pixels per inch when printing headless: soft on screen, worse on paper.

Chart Link exports any chart as SVG for print, or as PNG, from a CSV in your browser. Free, with no account. Make a chart

Units: points, pixels and inches

Three units meet in a PDF pipeline:

  • PDF point: 1/72 inch. It is PDF’s default unit of length (ISO 32000-1, section 8.3.2.3).
  • CSS pixel: 1/96 inch. CSS defines 1in = 96px (CSS Values and Units).
  • Image pixel: no fixed size. A PNG’s width in pixels divided by its printed width in inches gives its pixels per inch (ppi).

So an A4 page is 595 × 842 points, a chart that is 600 CSS pixels wide prints 6.25 inches wide, and a 1,200-pixel PNG placed at that size prints at 192 ppi.

Option 1: keep the chart vector

When Chromium prints HTML to PDF, an inline <svg> stays vector: rectangles become PDF paths and labels become selectable text in an embedded font. We checked this by printing the page below with Playwright and inspecting the PDF with Poppler’s tools: pdfimages -list found no images at all, pdffonts listed the fonts as embedded subsets, and pdftotext extracted the axis labels as text.

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<style>
  @page { size: A4; margin: 20mm; }
  body { font-family: Helvetica, Arial, sans-serif; color: #17181d; }
  figure { margin: 0; break-inside: avoid; }
  .chart { width: 160mm; height: auto; display: block; }
</style>
</head>
<body>
<h1>Monthly report</h1>
<figure>
  <svg class="chart" viewBox="0 0 640 320" role="img" aria-label="Signups per month, April to September 2026">
    <rect width="640" height="320" fill="#ffffff"/>
    <g fill="#2b3cd8">
      <rect x="60"  y="180" width="70" height="100"/>
      <rect x="150" y="160" width="70" height="120"/>
      <rect x="240" y="140" width="70" height="140"/>
      <rect x="330" y="120" width="70" height="160"/>
      <rect x="420" y="90"  width="70" height="190"/>
      <rect x="510" y="60"  width="70" height="220"/>
    </g>
    <line x1="40" y1="280" x2="600" y2="280" stroke="#17181d"/>
    <g font-size="16" text-anchor="middle" fill="#17181d">
      <text x="95" y="304">Apr</text><text x="185" y="304">May</text><text x="275" y="304">Jun</text>
      <text x="365" y="304">Jul</text><text x="455" y="304">Aug</text><text x="545" y="304">Sep</text>
    </g>
  </svg>
  <figcaption>Signups per month, April to September 2026.</figcaption>
</figure>
</body>
</html>
// make-pdf.mjs  (npm install playwright, then npx playwright install chromium)
import { chromium } from 'playwright';

const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto(new URL('./report.html', import.meta.url).href);
await page.pdf({ path: 'report.pdf', preferCSSPageSize: true, printBackground: true });
await browser.close();
$ pdfinfo report.pdf | grep 'Page size'
Page size:       594.96 x 841.92 pts (A4)
$ pdfimages -list report.pdf
page   num  type   width height color comp bpc  enc interp  object ID x-ppi y-ppi size ratio
--------------------------------------------------------------------------------------------
$ pdffonts report.pdf
name                                 type              encoding         emb sub uni object ID
------------------------------------ ----------------- ---------------- --- --- --- ---------
AAAAAA+Helvetica-Bold                CID TrueType      Identity-H       yes yes yes      4  0
BAAAAA+Helvetica                     CID TrueType      Identity-H       yes yes yes      6  0
CAAAAA+Helvetica                     CID TrueType      Identity-H       yes yes yes      7  0

The settings that matter, from Playwright’s page.pdf() documentation:

  • preferCSSPageSize: true uses the @page { size } from your CSS (MDN). Without it, format decides, and it “defaults to ‘Letter’”, which surprises anyone outside North America.
  • printBackground: true: backgrounds are left out by default (“Defaults to false”). A chart that relies on a CSS background color loses it otherwise. Drawing the background inside the SVG, as above, avoids depending on this.
  • Print CSS. page.pdf() renders with print media, so @media print rules apply, and “by default, page.pdf() generates a pdf with modified colors for printing”; add -webkit-print-color-adjust: exact (or the standard print-color-adjust) to keep exact colors.
  • PDF generation needs Chromium: in Firefox and WebKit, Playwright 1.64 throws “PDF generation is only supported for Headless Chromium” (source), though headed Chromium printed a PDF in our test too. Puppeteer’s page.pdf() works the same way.

Fonts in vector charts

Chromium embeds the fonts a PDF uses, as subsets, so the document looks the same everywhere. What matters is which font the chart was drawn with:

  • Inline SVG (in the page’s HTML, as above) uses the page’s fonts, including web fonts. Wait for them to load (await page.evaluate(() => document.fonts.ready)) before calling page.pdf().
  • SVG as an image (<img src="chart.svg">) can’t load external resources, including web fonts referenced by a stylesheet (MDN). Inline the SVG instead, embed the font in it, or convert text to paths.

Option 2: a PNG at the right size

When the chart only exists as a bitmap (a screenshot, a chart rendered on a server, an image from another system), size it for the page:

pixels = printed width in inches × ppi

Printed size 150 ppi (screen) 300 ppi (print)
3 × 2 in, half width 450 × 300 900 × 600
6 × 3.5 in, a report figure 900 × 525 1,800 × 1,050
6.69 in wide, A4 text width with 20 mm margins 1,004 wide 2,008 wide
6.5 in wide, US Letter text width with 1 in margins 975 wide 1,950 wide

Then place the image at its printed size, not at its pixel size. In HTML, set the size in physical units: <img src="chart.png" style="width: 6in; height: 3.5in">. We checked this case too: a 1,800 × 1,050 PNG placed at 6 × 3.5 inches came out of Chromium at exactly 300 ppi, as pdfimages -list reported.

Use PNG, not JPEG: JPEG compression smears the thin lines and small text that charts are made of.

The canvas trap

Canvas-based charts (Chart.js, ECharts in canvas mode, most dashboard libraries) are bitmaps drawn at the screen’s resolution. Print the page to PDF and the canvas is embedded as an image at its pixel size. At a pixel ratio of 1, the default in headless Chromium, that is 96 ppi: a 6 × 3.5 inch Chart.js chart (with maintainAspectRatio: false, so it fills the box) came out as a 576 × 336 image in our test. That is soft on screen when zoomed and visibly blurry on paper.

Two fixes:

  • Raise the canvas resolution before printing. Chart.js’s devicePixelRatio option scales the canvas; with devicePixelRatio: 3 the same chart came out as 1,728 × 1,008 pixels, 288 ppi. Also turn off animation (animation: false) so the PDF doesn’t capture a half-drawn chart. See Chart.js to PNG.
  • Use an SVG renderer. Some libraries can draw to SVG instead of canvas; or export the chart as an SVG file and place that.

Other ways to make the PDF

HTML printed by Chromium is one pipeline. LaTeX, Typst, report libraries and office suites each have their own way to place a figure, and the same two rules apply to all of them: a vector format where the tool accepts one, and a bitmap sized for print where it doesn’t. Whatever the tool, check the result: open the PDF, zoom to 400% on the chart, and see whether the edges stay crisp.

Checklist

  1. Prefer SVG, inlined in the HTML, so the chart stays vector with real, embedded fonts.
  2. If it must be a bitmap, size it as printed inches × 300 and place it at its printed size.
  3. Never print a canvas chart at its default resolution; raise devicePixelRatio or switch to SVG.
  4. Set the page size in CSS and use preferCSSPageSize; set printBackground if anything depends on backgrounds.
  5. Wait for fonts and turn off animation before generating the PDF.

Making the chart

Chart Link exports any chart it makes as SVG, ready to inline or place in a document, or as a PNG. It runs in your browser, so nothing you paste is uploaded. For charts in other places, see charts in email and charts in a GitHub README.

Sources

All sources were checked on October 14, 2026. The PDF results were produced with Playwright 1.64.1 and Chromium 156, and inspected with Poppler 26.04.