# stet
[](https://crates.io/crates/stet)
[](https://docs.rs/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](https://github.com/AndyCappDev/stet#rendering-correctness) for
detail — it matters for prepress and proofing workflows.
## Quick Start
```toml
[dependencies]
stet = "0.8"
```
### PostScript / EPS
```rust
use stet::Interpreter;
fn main() -> Result<(), stet::StetError> {
let mut interp = Interpreter::new();
let pages = interp.render(include_bytes!("test.ps"), 300.0)?;
for (i, page) in pages.iter().enumerate() {
println!("Page {}: {}x{} pixels", i + 1, page.width, page.height);
// page.rgba contains RGBA pixel data (4 bytes per pixel, row-major)
}
Ok(())
}
```
### PDF
PDF reading lives in the companion [`stet-pdf-reader`](https://crates.io/crates/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:
```toml
[dependencies]
stet-pdf-reader = "0.8"
```
```rust
use stet_pdf_reader::PdfDocument;
let data = std::fs::read("document.pdf")?;
let doc = PdfDocument::from_bytes(&data)?;
for page in 0..doc.page_count() {
// DisplayList — same type the PS interpreter produces.
let display_list = doc.render_page(page, 300.0)?;
// …or go straight to RGBA with the `render` feature (default):
let (rgba, w, h) = doc.render_page_to_rgba(page, 300.0)?;
}
```
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/`](https://github.com/AndyCappDev/stet/tree/main/crates/stet/examples)
for runnable `render_ps`, `render_pdf`, and `display_list` programs.
## Output Modes
| `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
```rust
let mut interp = stet::Interpreter::new();
let pages = interp.render(ps_data, 300.0)?;
// 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
```rust
let mut interp = stet::Interpreter::new();
let pdf_bytes = interp.render_to_pdf(ps_data, 300.0)?;
std::fs::write("output.pdf", &pdf_bytes)?;
```
### Display List Only
For custom rendering pipelines or viewport rendering:
```rust
let mut interp = stet::Interpreter::new();
let pages = interp.render_to_display_list(ps_data, 300.0)?;
// Render a viewport region later
let prepared = stet::prepare_display_list(&pages[0].display_list);
let rgba = stet::render_region_prepared(
&pages[0].display_list,
&prepared,
vp_x, vp_y, vp_w, vp_h, // viewport in device pixels
pixel_w, pixel_h, // output dimensions
pages[0].dpi,
None, // ICC cache
None, // image cache
false, // anti-aliasing
);
```
### Execute Without Rendering
```rust
let mut interp = stet::Interpreter::new();
interp.exec(b"1 2 add ==")?; // 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:
```rust
// Standard output (matches built-in device defaults)
let pages = interp.render(ps_data, 300.0)?;
// Lower resolution for faster screen viewing
let pages = interp.render(ps_data, 150.0)?;
// High-resolution proofing
let pages = interp.render(ps_data, 600.0)?;
```
## Configuration
```rust
let mut interp = stet::Interpreter::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:
```rust
let pages = interp.render(ps_data, 300.0)?;
if pages.is_empty() {
for w in interp.warnings() {
eprintln!("warning: {}\n {}", w, w.hint());
}
}
```
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:
| `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):
```toml
[dependencies]
stet = { version = "0.8", default-features = false }
```
## Power User: Direct Context Access
For advanced use cases, you can access the underlying PostScript interpreter
context directly:
```rust
let mut interp = stet::Interpreter::new();
let ctx = interp.context();
stet::ps_exec(ctx, b"/greeting (Hello, PostScript!) def greeting print")?;
```
## Crate Architecture
`stet` is a facade over a multi-crate workspace:
| `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