First release under the Tincture name.
Tincture is a fork of ex_guten by Hugh Watkins (MIT), itself an Elixir port of Joe Armstrong's Erlang erlguten. Most of the engine — the TrueType/OpenType parser, the Knuth-Plass line breaker, the TeX hyphenation port, the PDF object serialiser — is his work, carried forward here. See NOTICE for the full attribution chain.
{:tincture, "~> 0.1.0"}Why this exists
The two most-used PDF options in Elixir both shell out to a browser. Tincture is pure Elixir and Erlang/OTP — no Chrome, no wkhtmltopdf, no NIFs, no ports, and no runtime dependencies. It runs anywhere the BEAM runs.
The differentiator is typography: TeX hyphenation and Knuth-Plass line breaking, the same global optimisation TeX uses, rather than greedy first-fit.
New in this release
- Hyperlinks and annotations — external URLs and internal page targets. The library previously emitted no
/Annotobjects at all. - Interactive forms (AcroForm) — text fields, checkboxes and choice fields, with field flags, max length, tooltips and initial values.
- AES-256 encryption — standard security handler revision 6 (
/V 5 /R 6, PDF 2.0), user and owner passwords, permission flags. - Font subsetting on by default — only the glyphs a document draws are embedded, typically cutting an embedded font by 70–90%.
Six real bugs fixed
Found while building the test suite out from 444 tests to 1,011:
- Subsetting silently embedded the whole font for any text containing a space. A glyph with no outline is zero bytes, which the subsetter treated as malformed and aborted on — so subsetting was effectively never applied to real text.
- A malformed
nametable crashed the parse.:unicode.characters_to_binary/3signals bad input by returning a tuple rather than raising, so the rescue never fired. - The
OS/2Unicode range table was 91% missing — 11 of ~123 bits mapped, so any CJK, Hangul, punctuation or math codepoint was reported unsupported. - A deprecated
File.stream!/3argument order on the default hyphenation locale. - A type declared as a plain map where every caller requires a struct.
- A divide-by-zero in the benchmark harness on fast machines.
Quality
| Tests | 1,011 |
| Coverage | 88.3% (80% floor enforced) |
Credo --strict |
0 issues |
| Dialyzer | passing |
| CI | Elixir 1.16, 1.17, 1.18, 1.19 |
Upstream's CI had never passed — all 13 runs failed at mix format --check-formatted, so its test suite never executed.
What's missing
ROADMAP.md sets out the gaps honestly and in order, including the ones that matter for enterprise adoption: tagged PDF (PDF/UA), PDF/A, digital signatures, spot colour, transparency and shading, and complex-script shaping for Arabic and Indic. Every gap listed was verified absent against the source.
Full detail in CHANGELOG.md.