@gum-jsx/pdf
v2.0.0
Published
Gum rendering to PDF.
Readme
@gum-jsx/pdf
PDF export for completed Gum fragments, preserving vector drawings and embedded PNG images. Layout, text
shaping, and glyph outlines are supplied by @gum-jsx/core (and optionally
@gum-jsx/math); the exporter does not load fonts or perform layout.
See the Gum project for getting started and the package overview.
Usage
import { LayoutPass, Text, px } from '@gum-jsx/core'
import { render_pdf } from '@gum-jsx/pdf'
const fragment = new LayoutPass().layout(
new Text({ children: 'Hello, PDF!', font_size: px(32) }),
)
const bytes = render_pdf(fragment, { title: 'Hello', background: 'white' })
await Bun.write('hello.pdf', bytes)render_pdf(fragmentOrPages, options?): Uint8Array is synchronous and works in Bun or
the browser. Numeric serialization uses core's shared formatter; PNG decoding and compression use
fast-png and fflate, with no native bindings or filesystem access. In a browser,
the returned bytes can be used in a Blob with type application/pdf.
Known limitation: fast-png 8.0.0 rejects RGB PNGs with only one or two pixels
and a tRNS transparency key. PDF export throws for these images; convert them
to RGBA before embedding. Ordinary RGBA PNGs, including transparent 1×1 images,
are unaffected. The package uses the unmodified decoder.
| Option | Default | Meaning |
| --- | --- | --- |
| title | omitted | Unicode PDF document title. |
| background | omitted | Page background color; otherwise unpainted. |
| points_per_pixel | 0.75 | Physical scale: 96 layout pixels per inch, 72 PDF points per inch. Use 1 to treat each layout pixel as one point. |
| precision | 10 | Decimal places in numeric output; use 0–100 or 'full' for unrounded values. |
Pass a fragment for one page, or a nonempty array of fragments for a multipage
document. Pages follow array order, and each page matches its own fragment.size,
clipping any overflow to that viewport. Options apply to the whole document;
images and drawing resources are reused across pages.
const pages = ['First slide', 'Second slide'].map(children =>
new LayoutPass().layout(new Text({ children, font_size: px(32) })),
)
await Bun.write('slides.pdf', render_pdf(pages, { title: 'Slides' }))Both page dimensions and the scale must be positive and finite. PDF 1.4 page dimensions are limited to 14,400 points per side; larger dimensions are rejected.
Supported drawing features:
- PNG images, including grayscale, RGB, indexed color, interlacing, and alpha. Image samples are compressed losslessly at their original dimensions; 16-bit samples retain their precision. Transparency uses a grayscale soft mask, and repeated images share one embedded resource. PNG color profiles and gamma metadata are not applied; samples use PDF DeviceRGB or DeviceGray.
- Rectangles, ellipses, individually rounded corners, and paths (
M,L,Q,C,Z). Quadratics convert exactly to cubics; elliptical arcs use the usual cubic approximation. - Affine placement transforms, including rotation, reflection, skew, and nonuniform scaling. Consecutive placements are combined without flattening stroke geometry or pushing a graphics state for every layout wrapper.
- Nested rectangular and rounded clipping, nonzero winding fills, strokes, line caps, joins, miter limits, and dash patterns.
- Color alpha and drawing opacity. When necessary, isolated transparency groups apply opacity to the combined fill and stroke, matching SVG compositing.
- All CSS named colors,
transparent,none, 3/4/6/8-digit hex, and numericrgb()/rgba()/hsl()/hsla()colors with comma or space/slash syntax. Percentages and hue units (deg,rad,grad,turn) are supported.
Unsupported color expressions (including var(), currentColor, paint URLs,
wide-gamut colors, and CSS calculations) throw a descriptive error. Resolve them
to one of the supported color formats before export.
Text remains vector outlines: appearance is preserved, but text is not searchable or selectable. Live text from a color font, such as emoji, has no outline and no embedded font data here, so exporting it throws an error naming the family. Fragment labels and debug overlays are not exported. The exporter writes deterministic PDF 1.4 files with uncompressed vector content and compressed image streams; it does not automatically split content across pages or produce tagged/accessibility or archival PDF variants.
Development
From the workspace root:
bun --filter @gum-jsx/pdf test
bun --filter @gum-jsx/pdf typecheck
bun --filter @gum-jsx/pdf test:visualThe regular tests require only workspace development dependencies. The optional
visual checks additionally require qpdf, pdfinfo, and pdftoppm on PATH.
They validate actual PDFs and compare their rasterized pixels with the WASM fragment renderer,
leaving PDF, SVG, PNG, and PPM artifacts in out/visual/. Small edge differences
are expected between rasterizers. The PNG package and math package are used only
for development checks.
