Images
Add images to a document, size them for sharp print and screen output, pick the right format, and keep vectors vector.
How an image renders in the final PDF comes down to three things: the format you ship (file size), the source resolution (sharpness), and whether a CSS effect forces it onto the raster path. The rest of this page works through each, alongside how to reference images, handle SVG, and deal with a source that won't load.
Adding an image
There are four ways to put an image on the page, and all of them work the way they do on the web.
<!-- An <img> element -->
<img src="./logo.png" alt="Acme" />
<!-- An inline SVG, written straight into your markup -->
<svg viewBox="0 0 24 24"><path d="..." /></svg>
<!-- A CSS background -->
<style>
.hero {
background-image: url("./banner.jpg");
}
</style>
<div class="hero"></div>
<!-- A data URI: the image bytes inlined, no separate file -->
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA..." alt="Acme" />The first three reference a file by relative path, resolved against your assets bundle: ./logo.png means the file sits next to the document that references it. See Assets for how paths resolve and what goes in the bundle.
A data URI is the exception. It carries the image bytes inline, so there's no separate file and no bundle lookup. It's handy for a small image generated at render time, though it inflates your HTML and isn't shared between references, so prefer a bundled file for anything reused.
Local assets only
Format reads images from the bundle you ship, never from a remote URL at render time. A path like https://cdn.acme.com/logo.png is rejected.
This keeps every render deterministic and offline-safe: a server that's down or a link that's changed can't break a document in production. The full reasoning is in Assets.
What happens when a reference can't be resolved depends on where you are:
- In Studio, Format renders a loud animated placeholder in the image's place and a warning message. It's deliberately hard to miss in preview.
- With the API, Format raises
INVALID_ASSET_REFERENCEso a broken reference fails the render rather than shipping a PDF with a missing image.
Sizing and fitting
An image sizes with ordinary CSS inside the page's fixed box (see Page sizes). Set a width, cap it with max-width: 100%, or leave it to size intrinsically from the source:
img {
width: 100%;
max-width: 480px;
}When you constrain an image to a box whose aspect ratio differs from the source, object-fit controls how the image resolves the mismatch. Use it for a photo dropped into a fixed flow, or a cover image that spans the page:
img {
width: 100%;
height: 200px;
object-fit: cover; /* fill the box, cropping the overflow */
object-position: center; /* choose which part survives the crop */
}The cover value fills the box and crops the overflow, while contain fits the whole image inside and leaves letterboxing.
For a full-bleed page background, set the image on :host with background-size: cover and use padding rather than margin so it reaches the page edge. See Margins and Bleed and trim.
Sizing for sharpness
CSS measures in pixels at 96 DPI, but high quality print output needs 300 DPI. The gap between the two is often why an image that looks fine on screen can come out blurry in print, and it's primarily decided by your source's pixel dimensions.
How an image renders depends on whether it's on the plain path (drawn directly) or the raster path (passed through an effect that has no PDF equivalent).
Plain path
This is the default. Format embeds the source bitmap at its full resolution, independent of the CSS display size. A 6000 px wide source shown in a 500 px box stores all 6000 px in the PDF, so a viewer or printer can use the extra detail. A 200 px source in the same box stores 200 px and is upscaled at view time, and the quality depends on the viewer.
The takeaway: on the plain path, more source pixels never hurt sharpness. They only cost file size.
Raster path
This is the fallback for CSS effects that have no PDF equivalent (see What triggers the raster path). Format flattens the affected content to a bitmap at 300 DPI. That's destructive both ways: a source larger than 300 DPI is downsampled (wasted bytes and render time), and a source smaller than 300 DPI is upsampled and blurs.
How much source to ship
Match your source to the highest density it will be viewed at:
- Screen-only PDFs: ship at 2× the CSS display size, the same principle as
@2xretina images on the web. This stays sharp at 100% zoom on retina displays. - Print, or any image on the raster path: ship at 3.125× the CSS display size so it lands exactly at 300 DPI with no upscaling.
| CSS box | Minimum for screen (2×) | Minimum for print (3.125×) |
|---|---|---|
| 200 × 200 px | 400 × 400 px | 625 × 625 px |
| 500 × 333 px | 1000 × 666 px | 1563 × 1041 px |
| 1000 × 750 px | 2000 × 1500 px | 3125 × 2344 px |
For an image on the raster path, supply exactly 3.125× and no more: anything larger is downsampled back to 300 DPI, so the extra pixels are pure waste.
Choosing a format
Format doesn't transcode images at render time, so the format you ship is the format that lands in the PDF. Convert before the file enters your assets bundle.
| Format | Use it for | Notes |
|---|---|---|
| JPEG | Photographs | Embedded byte-for-byte, no re-encoding. |
| PNG | Flat color: logos, diagrams, UI, transparency | Re-encoded under FlateDecode, which compresses flat color extremely well. |
| SVG | Vectors: icons, charts, pattern backgrounds | Stays vector; <text> stays selectable and currentColor cascades. See SVGs. |
| WebP | Not recommended | Re-encoded under FlateDecode, roughly 8 to 9× bloat for photos. Use JPEG. |
| AVIF | Not recommended | Re-encoded under FlateDecode, roughly 8 to 9× bloat for photos. Use JPEG. |
| GIF | Not recommended | Renders the first frame only. Convert to JPEG (photos) or PNG (flat color). |
| BMP | Not recommended | Convert to JPEG (photos) or PNG (flat color). |
| TIFF | Not supported | Convert to JPEG (photos) or PNG (flat color). |
Why format changes file size
PDF stores images differently depending on the source format, and the differences can be huge.
JPEG passes through untouched because PDF supports the JPEG codec natively. A 2 MB photo.jpg stays embedded as a 2 MB stream, byte for byte. Everything else (PNG, WebP, AVIF, GIF, BMP) is decoded to raw pixels and re-encoded with FlateDecode (zlib).
For flat color images, FlateDecode is excellent; zlib compresses solid regions and sharp edges down to almost nothing. Therefore, you should use PNG for transparent logos, diagrams, UI mockups, anything with flat color and sharp edges.
Screenshots are content-dependent. Terminal output, code editors, and simple UI screens compress well as PNG. Screens containing photographs, video frames, or complex art typically compress better as JPEG.
For photographic content, FlateDecode fails. zlib has no model for the noisy variation in real photos, so the stream balloons: a 173 KB WebP photo becomes ~1.4 MB inside the PDF, typically 8 to 9× larger than the source. That's why WebP and AVIF are a poor choice for PDF documents in general. They render correctly, but every photo pays a significant bloat cost. Use JPEG for photographic content instead.
Why do modern image formats bloat PDFs?
The age of the PDF format and its focus on backwards-compatibility is the underlying reason. PDF was released in 1993 and the codecs it natively supports (JPEG among them) come from that era. WebP (2010) and AVIF (2019) didn't exist at the time, so they fall back to FlateDecode.
SVGs
An SVG is resolution-independent and stays crisp at any scale, so the 3.125× sizing rule doesn't apply. SVGs can be used like you would on the web: inline <svg>, an <img src="*.svg">, or background-image: url("*.svg").
What's preserved
Inside an SVG, Format keeps:
<text>elements, which stay selectable and searchable in the PDF rather than flattening to outlines.currentColor, which cascades from the surrounding CSS so one icon recolors with its context.<pattern>fills and repeating SVG backgrounds, which stay vector instead of rasterizing.
An SVG referenced as an image (<img> or background-image) runs in a restricted mode and can't pull in external resources, such as an <image href> pointing at another file or an external font. Inline the SVG, or embed what it needs directly inside the file.
What triggers the raster path
Most content stays vector. Format only flattens to a 300 DPI bitmap when a CSS effect has no PDF equivalent:
filter: blur, contrast, hue-rotate, and the rest.- Some
mix-blend-modecombinations, particularly when stacked with gradients. See Mix blend modes.
You don't need to preemptively avoid common effects. Format keeps several things vector that a browser would otherwise rasterize on export, with no work from you. This includes: box-shadow, SVG <pattern> fills, and CSS background-repeat.
When you can't avoid the raster path, ship the affected image at 3.125× its display size so it lands at 300 DPI without upscaling.