Comparison
Stationery next to Prawn and sghtmltopdf: what each one does, what only one does, and when to pick which.
The three#
Different shapes of the same job.
| Stationery 0.11 | Prawn 2.5 + prawn-table 0.2 | sghtmltopdf 0.5 | |
|---|---|---|---|
| What it is | A layout engine with a Phlex-style component DSL | A cursor API over a PDF writer | An HTML and CSS renderer written in Rust |
| Input | Ruby components; html and markdown elements for user content | Ruby calls: text, bounding_box, table | An HTML document with CSS |
| Runtime | Pure Ruby, no runtime dependencies | Pure Ruby (pdf-core, ttfunk, matrix) | Native extension, precompiled for Linux and macOS; Windows delegates to a server process |
| Licence | MIT | Ruby / GPL-2 / GPL-3 | MIT |
| First release | September 2026 | 2008 | July 2026 |
Every claim below was checked against the code of these versions. "Partial" means the feature exists with a documented limit; the Limitations page has Stationery's.
Layout and pages#
| Stationery | Prawn | sghtmltopdf | |
|---|---|---|---|
| Layout model | Rows, columns, boxes, stack/layer, wrap, floats; the engine measures and places | You compute positions; bounding_box, move_down, cursor | CSS block, inline, flexbox, grid, floats, absolute positioning |
| Page breaks | page_break, break_inside: :avoid/:auto, keep_with_next, keep_together, min_height | start_new_page; group raises NotImplementedError | break-before/after/inside, page-break-* |
| Orphans and widows | orphans: and widows: on text, default 1 | No | Yes, default 2 |
| Headers, footers, page n of N | Page templates with any content, header/footer, regions | repeat(:all) blocks with number_pages | @page margin boxes and header/footer HTML with placeholders |
| Tables across pages | Split rows, repeated header, colspan/rowspan, split_rows: | prawn-table: header repeat, spans, splitting | Yes, thead repeats |
| Balanced columns | columns(count:, gap:, balance:, rule:), across pages | No | No |
| Text wrap around images and boxes | float: :left/:right on image and box, and on img in html | No | Floats |
| Page labels | Yes | No | No |
Text and fonts#
| Stationery | Prawn | sghtmltopdf | |
|---|---|---|---|
| Kerning | GPOS pairs and kern table, on by default | kern table, on by default | HarfBuzz-class shaping (harfrust) |
| Ligatures and OpenType features | liga by default; smcp, onum, tnum, ss01… on request; no contextual lookups | No | Full shaping through harfrust |
| Hyphenation | Soft hyphens; patterns for English, German and Swedish | Soft hyphens only | Soft hyphens only (hyphens: auto has no dictionary) |
| Justification | Yes, widens spaces | Yes | Yes |
| Fallback fonts | Per character, then bundled Inter | fallback_fonts per box | Font stack per element |
| CJK line breaking | Between ideographs, kana and Hangul with kinsoku for closing and opening marks; zero-width space and <wbr> | Partial: zero-width spaces | Yes, UAX #14, word-break |
| Right-to-left, Arabic, Indic | Not built in: drawn glyph by glyph, left to right. A shaper hook draws what the application's HarfBuzz places, within a stretch of one font and style; no reordering across stretches or lines | direction: :rtl reverses runs; no shaping | Shaping yes; direction: rtl and vertical writing no |
| Colour emoji | No, draws .notdef | No | CBDT, sbix, COLR v0 |
| Font formats | TTF, OTF/CFF, TTC, WOFF | TTF, OTF/CFF, TTC, DFont | TTF, OTF |
| WOFF2, variable fonts | No | No | No |
| Subsetting | Yes, TrueType and CFF | Yes | Yes |
| Bundled fonts | Inter; font packs by CLI | None (standard 14 unembedded) | None; system fonts optional |
Images and drawing#
| Stationery | Prawn | sghtmltopdf | |
|---|---|---|---|
| JPEG, PNG | Yes | Yes | Yes |
| WebP, GIF | Lossless WebP; lossy WebP and GIF no | No | WebP yes, GIF no |
| SVG | Inline and file: shapes, paths, gradients, text, stylesheets, use, symbol, clipPath; no mask, pattern, filter | No (prawn-svg is a separate gem) | By reference through <img> (usvg); inline <svg> no |
| Oversized-image warning, downscaling | Yes; PNG and WebP resampling | No | No |
| Rotation, shadows, overlays | rotate: on images and boxes, shadow:, stack/layer | rotate blocks; transparent; no shadows | CSS transform, box-shadow, positioning |
| Direct drawing | canvas with paths, clips, transforms | Full graphics API | No |
PDF features#
| Stationery | Prawn | sghtmltopdf | |
|---|---|---|---|
| Links, outline, table of contents | Yes, yes, table_of_contents element | Links yes, outline yes, no TOC element | Links yes; outline no; --toc |
| Forms (AcroForm) | Text, checkbox, radio, select, signature fields; appearances in the document's fonts | No | No (<input> is drawn, not interactive) |
| Tagged PDF, PDF/UA-1 | Yes, veraPDF-verified | No | No |
| PDF/A | 2b and 3b, veraPDF-verified | No | No |
| XMP metadata | Yes | No | No |
| Embedded files, Factur-X / ZUGFeRD | Yes; profiles MINIMUM to XRECHNUNG, Mustang-validated | No | No |
| Encryption | AES-256, AES-128, RC4-128 | RC4 40 and 128 bit | No |
| Digital signatures | CAdES detached, PAdES baseline B-B and B-T (RFC 3161 timestamp), RSA or ECDSA; no LTV, one signature | No | No |
| Page labels | Yes | No | No |
Working with it#
| Stationery | Prawn | sghtmltopdf | |
|---|---|---|---|
| HTML or Markdown input | html and markdown elements: structure and inline marks | No (third-party gems) | Its whole input |
| CSS | Partial: a property subset on the html element (colours, font size, weight and style, decoration, alignment, backgrounds, padding, margins, table borders and widths, page breaks) with element, class and id selectors. float on img only. No display, flexbox, grid, positioning, font-family or selectors with combinators | No | A cascade with custom properties, calc(), :has(); flexbox, grid, floats, positioning |
| Untrusted content | Link scheme allow-list, no remote fetches, image base_path: sandbox, bounded nesting | Not applicable | Node, depth and image size caps, file sandbox |
| Testing helpers | Inspector, RSpec matchers, Minitest assertions | pdf-inspector (separate gem) | None |
| Instrumentation | ActiveSupport::Notifications events | No | No |
| Warnings instead of silent drops | document.warnings, strict | No | Warnings on stderr |
| Rails | render pdf:, send_pdf, previews | Community gems | render pdf: in the wicked_pdf style |
| Streaming output | to_pdf { |chunk| } streams the write phase; incremental: true writes each page as it is painted, so memory follows what is left to paint (the document is built in full first) | render_file only | Yes: page-by-page streaming to a block or a sink, bounded memory |
| Performance regression gate | rake metrics in CI | No | cargo bench metrics in CI |
Speed#
Same documents, same fonts; see Performance for the method.
sghtmltopdf is native code and is faster on text and tables (2 to 3.5 times); on a page of photographs the two are level. Stationery renders faster than Prawn (1.6 to 6 times) and writes smaller files than both. The numbers, the machine and the way they were taken are on the Performance page, and bundle exec rake bench runs all three engines on yours.
When to pick which#
Pick Stationery when the PDF is the product: invoices, contracts, reports, flyers and forms that need bookmarks, accessibility, PDF/A, e-invoicing, encryption or a signature, built from the same Ruby components as the rest of the app, with tests that read the PDF back. It stays pure Ruby, so there is nothing to compile, no browser and no server process.
Pick sghtmltopdf when you already have HTML templates and CSS, need CSS layout (flexbox, grid, floats), complex-script text or colour emoji, or must render very large documents with bounded memory, and can live without outlines, forms, tagged PDF, PDF/A and encryption. It is faster on text and tables, at the price of a native extension.
Pick Prawn when you have a Prawn codebase, want the lowest-level control over every coordinate, or need its long track record. New documents that want layout done for them are less work in either of the other two.