A PDF renderer in pure Go, aiming to be the fastest and leanest in the Go
ecosystem: MuPDF-class speed on technical drawings (≤ 1.5× MuPDF on one core,
faster than MuPDF on all cores), ≈ 0 allocations per page in steady state,
0 panics, no cgo, every GOOS/GOARCH including js/wasm.
- Text, images, transparency, shadings, patterns, colour management
- Optional content (layers), annotations, interactive forms
- Display list per page: parse once, draw only the visible region, on all cores
- Own lock-free PDF reader: pages of one document render concurrently
- Every bound on work and memory budgeted; damaged input draws less, never fails
Time per page relative to MuPDF (lower is faster), one core, 150 dpi, the pinned corpus of 113 pages:
| all pages | papers | text, fonts | shadings | transparency | scans | |
|---|---|---|---|---|---|---|
| MuPDF | 1.00× | 1.00× | 1.00× | 1.00× | 1.00× | 1.00× |
| cera | 0.65× | 0.82× | 0.83× | 0.48× | 0.73× | 0.70× |
| PDFium | 1.14× | 1.25× | 1.31× | 1.80× | 0.69× | 1.62× |
| hayro | 1.38× | 2.01× | 1.70× | 1.70× | 0.55× | 1.28× |
| pdf.js | 3.80× | 6.25× | 5.86× | 0.98× | 2.07× | 3.54× |
- All 16 cores: 0.19× MuPDF on one-page files (cera splits a page into bands), 0.54× on documents of several pages.
- Drawing into the bitmap of the page before, as a viewer does: 0.49× on one core, 0.12× and 0.36× on 16. The table gives cera a new bitmap per page, as the other engines' bindings give them a new pixmap.
- Command line, PDF to PNG:
cmd/cera0.55×mutool drawon one core, 0.47× on 16 (PNGs written with calamus at the fastest level, 1.27× the size of MuPDF's; withimage/png0.62× and 0.73×; compressing bands while drawing,-png stream, 0.55× and 0.48×); Poppler 2.64×, Ghostscript 3.88×. - WebAssembly, against native MuPDF: cera 1.66× on V8 (Chrome, Node), 3.74× on wazero; PDFium 2.02× on wazero (its module runs on wazero only).
- One allocation per page (median), 38 MB peak memory per file (median).
cera's own synthetic drawings, on which MuPDF is unusually slow (cera 0.11×), are left out of this table. Ratios only, never times: engines take turns page by page, so the load of the machine falls on all alike; ratios can still shift between processors, and cera's by up to 10 % with the machine's load. Method, every category, where cera loses and how to reproduce: Performance. AMD Ryzen 7 5800H, Windows, 2026-10-06.
go get github.com/timzifer/ceracera's own modules build with a fork of
go-highway, which comes in
through the JPEG 2000 decoder; the fork silences a warning of upstream
(#105, #162). A replace holds only in the module that is built, so a
program that imports cera gets upstream's go-highway, warning and all. To
build with the fork, repeat the line in your own go.mod:
replace github.com/ajroetker/go-highway => github.com/timzifer/go-highway v0.0.13-timzifer.1
It goes when upstream has the fix; cera draws the same with either.
doc, err := cera.Open(data)
page, err := doc.Page(0)
dst := image.NewRGBA(page.Bounds(150.0 / 72)) // owned by the caller, reusable
err = page.Render(ctx, dst, cera.RenderOptions{
Scale: 150.0 / 72,
Background: color.RGBA{255, 255, 255, 255},
})
page.Release() // drop the cached display list when the page leaves the view
text, err := page.Text(ctx)
fmt.Println(text.String())Command line:
go build -C cmd/cera -o "$PWD/cera" . # a module of its own: it writes PNGs with calamus
./cera -dpi 150 -o 'page-%d.png' input.pdfEditing (ADR 0013): delete, move and turn pages, import pages of other files, save as a new file or as an incremental update:
e := doc.Edit() // or cera.NewEditor() for an empty document
err = e.ImportPages(0, cover, cera.EditPage{Index: 0})
err = e.DeletePages(e.NumPages() - 1)
err = e.Save(w) // a new file, or e.Update(w): the file and an incremental update| Usage | rendering options, font providers, layers, annotations, forms, devices |
| Features | what is drawn, read and written today, and what is counted as unsupported |
| Security | where to report a crash, a hang or a budget that does not hold, and what counts |
| Architecture | building blocks, display list, glyph and image caches, transparency, shadings, colour |
| Budgets | every bound on work and memory per input |
| Performance | speed against MuPDF, PDFium, hayro, pdf.js, Poppler and Ghostscript, as ratios |
| Development | CI, corpora, accuracy against PDFium and other engines |
| ADRs | architecture decisions; next up: GPU backend (ADR 0011) |
MIT. internal/pdf and internal/pdffont contain code ported from
go-pdfkit under the BSD 3-Clause License;
see their LICENSE-go-pdfkit files.
The writer of linearised files lays them out by the rules of
qpdf (Jay Berkenbilt and Manfred Holger,
Apache License 2.0), learned from its sources and tested against its
check; no code of qpdf is in cera. See internal/pdfwrite/NOTICE-qpdf.