stet
A PostScript Level 3 interpreter and PDF rendering engine written in Rust.
stet renders PostScript, EPS, and PDF files to RGBA pixels, PDF output, or display lists. All resources (35 fonts, init scripts, encodings, ICC profiles) are embedded in the binary — no external files needed.
Rendering Correctness
stet handles two rendering issues that most PDF/PostScript renderers get wrong: no seams on adjacent clipped regions (binary clip coverage, not anti-aliased), and full overprint simulation with CMYK blend math, knockout groups, and spot channel preservation. See the repository README for detail — it matters for prepress and proofing workflows.
Quick Start
[]
= "0.8"
PostScript / EPS
use Interpreter;
PDF reading lives in the companion stet-pdf-reader
crate and is deliberately independent of stet — it does not depend on the
PostScript interpreter at all, so PDF-only users don't pay for the VM:
[]
= "0.8"
use PdfDocument;
let data = read?;
let doc = from_bytes?;
for page in 0..doc.page_count
Because both stet and stet-pdf-reader produce the same
DisplayList type, every downstream consumer (rasterizer, PDF writer,
custom output device) works with both sources interchangeably.
See crates/stet/examples/
for runnable render_ps, render_pdf, and display_list programs.
Output Modes
| Method | Returns | Feature |
|---|---|---|
render() |
RGBA pixels + display list | render (default) |
render_to_display_list() |
Display lists only | always available |
render_to_pdf() |
PDF document bytes | pdf-output (default) |
exec() |
Nothing (side effects only) | always available |
Render to RGBA
let mut interp = new;
let pages = interp.render?;
// pages[0].rgba: Vec<u8> — RGBA, 4 bytes/pixel, row-major
// pages[0].width: u32 — pixels
// pages[0].height: u32 — pixels
// pages[0].display_list — for viewport rendering
Render to PDF
let mut interp = new;
let pdf_bytes = interp.render_to_pdf?;
write?;
Display List Only
For custom rendering pipelines or viewport rendering:
let mut interp = new;
let pages = interp.render_to_display_list?;
// Render a viewport region later
let prepared = prepare_display_list;
let rgba = render_region_prepared;
Execute Without Rendering
let mut interp = new;
interp.exec?; // prints "3" to stdout
Choosing a DPI
The dpi parameter controls the coordinate space of the display list, not
just the output pixel count. PostScript paths are transformed to device
coordinates at the specified DPI during interpretation. For best quality,
use a DPI that matches your final output:
// Standard output (matches built-in device defaults)
let pages = interp.render?;
// Lower resolution for faster screen viewing
let pages = interp.render?;
// High-resolution proofing
let pages = interp.render?;
Configuration
let mut interp = builder
.no_icc // disable ICC color management
.suppress_output // silence PS print/==/= operators
.build;
Diagnostics
An empty page list is not necessarily an error. The usual cause is a program
that painted marks and then ended without calling showpage — the page is
discarded and render() returns Ok(vec![]), which reads like a legitimate
result. warnings() distinguishes the two:
let pages = interp.render?;
if pages.is_empty
Warnings describe the most recent render call and are cleared at the start of
the next one. Programs that install nulldevice are exempt — that is the
PLRM-sanctioned way to ask for no output, so unemitted marks are expected
there. The CLI prints the same diagnostic to stderr.
Features
Both features are enabled by default:
| Feature | Adds | Extra dependency |
|---|---|---|
render |
render() — RGBA pixel output |
stet-render (tiny-skia) |
pdf-output |
render_to_pdf() — PDF output |
stet-pdf |
To use only display lists (smallest dependency footprint):
[]
= { = "0.8", = false }
Power User: Direct Context Access
For advanced use cases, you can access the underlying PostScript interpreter context directly:
let mut interp = new;
let ctx = interp.context;
ps_exec?;
Crate Architecture
stet is a facade over a multi-crate workspace:
| Crate | Role |
|---|---|
stet |
Batteries-included library API (this crate) |
stet-core |
Interpreter infrastructure: types, VM, tokenizer |
stet-ops |
331 operator implementations (328 always registered, plus 3 on the PDF output path) |
stet-engine |
Execution engine (eval loop) |
stet-fonts |
Font parsing (Type 1, CFF, TrueType) |
stet-graphics |
Display list, color types, ICC |
stet-render |
stet-tiny-skia rendering backend |
stet-pdf |
PDF output device (display list → PDF) |
stet-pdf-reader |
PDF input parser (PDF → display list), independent of stet-core — no PostScript VM linked, even with rendering enabled |
stet-viewer |
Interactive egui desktop viewer |
stet-cli |
Command-line stet binary |
stet-tiny-skia / -path |
Vendored tiny-skia fork (BSD-3-Clause) |
License
Apache-2.0 OR MIT