Compatibility

PrawnX draws the pages Prawn 2.5, prawn-table 0.2.2 and prawn-svg 0.40.4 draw. Every exception is on this page, declared in the repository and checked by the oracle.

Prawn bugs PrawnX fixes

A bug counts only when a specification or Prawn’s own documentation proves it; a mere preference becomes an option. test/divergences.yml lists each one, its proof, the examples that show it, and the exact differences the oracle must report: no more, no fewer.

Prawn’s behaviourProof
Encrypted documents have no /ID in their trailer, so qpdf and strict readers reject themISO 32000-1 §7.5.5, trailer dictionary, ID: “Required if an Encrypt entry is present”
Built-in fonts measure Ÿ (byte 159) as ÿ, so right-aligned or wrapped text holding it overflowsISO 32000-1 Annex D.2: WinAnsiEncoding maps byte 237 octal to Ydieresis, not ydieresis
The faces of one font collection share their width cache, so text in one face is measured with another’s widthsPrawn::Font#eql? compares the file name, family and size, which every face of a .ttc shares; Prawn documents each face as a font of its own
TrueType-flavoured subsetting of CFF fonts changes how their glyphs rasterizeThe subset Prawn embeds (through TTFunk) renders differently from the unmodified font program; PrawnX keeps outlines and hints untouched, and renders like the font itself

SVG web requests

SVG images do not fetch anything from the web unless asked to: svg data, enable_web_requests: true. prawn-svg 0.40 fetches by default, which lets a submitted SVG make the server request any URL. prawn-svg 1.0 plans the same default as PrawnX.

Lighter files

The pages are the same; the bytes are not. PrawnX writes at least PDF 1.5, with compressed streams, one compressed object stream and a cross-reference stream, where Prawn writes plain objects and an xref table. The oracle checks that each PrawnX file passes qpdf --check and weighs no more than Prawn’s. Tools that read PDF files byte for byte, rather than through a PDF parser, see a different file.

Off unless asked for

What would move pixels away from Prawn’s stays off unless asked for:

  • OpenType ligatures and GPOS pair kerning, per font: font "Inter.ttf", features: %i[liga kern];
  • PDF/A-3b and Factur-X: Prawn::Document.new(pdf_a: true), then factur_x.

OpenType features · PDF/A and Factur-X

Loading and versions

  • PrawnX raises a LoadError when the prawn gem is in the bundle too: remove prawn, prawn-table and prawn-svg from the Gemfile.
  • Prawn::VERSION is "2.5.0", the API PrawnX mirrors, since Prawn extensions check it; PrawnX::VERSION is PrawnX’s own.
  • The public methods of Prawn 2.5 are listed in test/contract/prawn-2.5.0.yml, generated by script/contract_extract.rb. PrawnX accepts every call Prawn accepts on those of its contract; test/contract/decisions.yml settles each method outside the Stable API, with the reason when it is left out.

How it is tested

  1. An oracle. It renders every example of Prawn’s and prawn-table’s manuals and every sample SVG of prawn-svg with Prawn 2.5, prawn-table 0.2.2 and prawn-svg 0.40.4 and with PrawnX, each in its own process, and compares their pages, their text and its positions to 0.01 pt, their pixels at 150 dpi in two renderers that share no code (MuPDF and Poppler), their validity (qpdf --check) and their weight. Examples listed in test/oracle_passing.txt must stay identical; one that becomes identical must join the list. test/oracle_examples holds documents of our own for what the manuals do not show.
  2. The oracle against itself. ORACLE_CANDIDATE=prawn bundle exec rake test runs Prawn as the candidate, which proves the comparison reports nothing when nothing differs.
  3. A document fuzzer. Seeded, it writes random documents (text in all its options, boxes, columns, tables, shapes, pages) and compares them the same way. CI adds 300 from a fresh seed on every run; script/fuzz_minimize.rb shrinks a failing one.
  4. An input fuzzer. It corrupts sample PNGs, JPEGs and fonts (flipped bytes, truncations, forged lengths): each must render or raise an error PrawnX names, never crash inside a parser, hang or exhaust memory.
  5. veraPDF. PDF/A documents are checked by veraPDF when it is installed.
FUZZ_SEED=42 FUZZ_CASES=500 bundle exec ruby -Itest test/fuzz_test.rb
ruby -Itest script/fuzz_minimize.rb tmp/fuzz/documents/fuzz_0042.rb