Changelog
View Source1.0.0 - 2026-10-05
First stable release. The documented public API and supported workflows are covered by the version 1.x compatibility promise, including documented rendering pipeline interfaces and intermediate fields used in the examples. Intentional breaking changes require a new major version.
This release introduces no breaking API changes from v0.21.0.
Fixed
- Preserved explicit heights in flex rows and explicit widths in flex columns
when alignment is
stretch. Images and other explicitly sized items now retain their dimensions, matching Chromium. Items with automatic dimensions continue to stretch. Added layout regression tests and Chromium comparisons, including compact stock labels with square QR images. - Corrected public typespecs to include existing form, outline, and text-encoding
errors, and
falseornilheader/footer templates. Runtime return values and accepted options are unchanged.
Documentation
- Expanded the HTML-to-PDF guide with complete, runnable examples for simple documents, tables, flex and grid layouts, multiple pages, labels, images, fonts, metadata, bookmarks, and forms. Examples include downloadable HTML, generated PDFs, and previews, with a script to regenerate the outputs.
- Added a purchase-order example with a measured repeating header, automatic row heights, and page breaks.
- Simplified the contribution guide around local development and GitHub pull requests, and replaced the release-candidate roadmap with planned 1.x milestones.
- Clarified the compatibility promise for documented rendering pipeline interfaces and updated browser parity coverage documentation.
0.21.0 - 2026-09-29
Performance
Up to 10× faster in the measured rendering workloads. The ten-image workload reuses the same 512px PNG ten times. The 100-row document used 77% fewer BEAM reductions and produced a 90% smaller PDF, shrinking from 1,632,177 to 166,852 bytes.
| Workload | v0.20.0 | v0.21.0 | Speedup |
|---|---|---|---|
| 100-row HTML render | 353.0 ms | 159.2 ms | 2.2× |
| 100-row PDF text extraction | 256.9 ms | 67.2 ms | 3.8× |
| repeated text layout | 52.2 ms | 28.2 ms | 1.8× |
| ten repeated 512px PNG images | 4462.4 ms | 447.6 ms | 10.0× |
Median of five warm runs on Ryzen 5 7600, Elixir 1.20.4 / OTP 29.1, using identical workloads and each version's defaults. Small documents can render more slowly with compression and font subsetting enabled.
- Reduced repeated text measurements, font loading, stylesheet parsing, and PDF tokenization. Split outputs share document preparation, and wide outline trees now build in linear time.
Changed
- Enabled TrueType font subsetting and eligible PDF stream compression by
default. Set
subset_fonts: falseandcompress_streams: falseto retain full fonts and uncompressed streams. Subsets preserve glyph IDs and composite glyph dependencies; fonts whose license forbids subsetting stay fully embedded. - Scoped page font, image, and graphics-state resources to the pages that use them, so selecting or splitting pages does not retain unrelated resources.
- Shared prepared stylesheets now count once toward CSS processing budgets; separate stylesheet entries still count separately.
Added
- Configurable limits for text-measurement caches, font-subsetting work,
assembled PDF output, extracted UTF-8 text, expanded ToUnicode mappings, and
cumulative CMap processing. These limits can reject workloads accepted by
earlier releases with
:resource_limit_exceededdiagnostics. - Performance regression checks for eight document fixtures and eleven focused workloads, covering rendering, extraction, repeated images, and PDF writing. Local and CI quality checks enforce runtime-specific reduction baselines and output-size limits.
- Browser-parity coverage for
white-space: nowrap, leading-decimal CSS lengths, and TrueType fonts with signed glyph deltas.
Fixed
- Preserved
white-space: nowrapthrough layout, including inline content and table cells, while retaining explicit<br>breaks. - Accepted leading-decimal CSS lengths such as
.5mmconsistently in supported body, page, and border declarations. - Applied signed TrueType character-map glyph deltas with the required 16-bit wrapping, preserving glyph selection and text extraction.
- Preserved existing form appearance requests on partial fills. Only a complete
fill clears
NeedAppearancesafter preparing all field appearances. - Reserved cross-reference revision capacity before incremental edits, including both revisions needed for filling and flattening forms. No-op updates do not consume a revision.
- Checked assembled merge, transform, and split output sizes before allocating the final binary, also keeping each output within the reader's byte limit.
- Bounded expanded extraction text and cumulative CMap work, including repeated Form execution, overwritten mappings, and codespace lookups.
0.20.0 - 2026-09-25
Changed
- Brought all 67 Chromium comparison fixtures, covering 80 pages, below the strict 1% changed-pixel threshold at 72 DPI with matched bundled fonts. Comparisons retain page-count and average channel-delta checks, and record browser, rasterizer, and font details with each result.
Fixed
- Improved text placement, line wrapping, table sizing, borders, images, and font handling to meet the browser-parity threshold on supported layouts.
- Preserved collapsed table outer borders across page breaks, letter spacing through text shaping, and original characters in ligature text mappings.
- Kept body padding, backgrounds, and grid layout when page margins are zero.
- Rejected unsafe numeric render options, derived aspect-ratio dimensions, and invalid stamp geometry with diagnostics instead of allowing overflow.
- Preserved resource-limit diagnostics for repeated background tiles.
- Distinguished clearing a single-choice form field from selecting an option whose export value is an empty string.
0.19.0 - 2026-09-22
Breaking changes
- SVG validation now rejects DTD/entity declarations, embedded image elements,
external references, and escaped CSS resource syntax before conversion.
Remove
DOCTYPEdeclarations from exported SVGs and use self-contained assets. New SVG structure/output and PNG working-buffer limits can reject workloads accepted by earlier releases. - Merge preparation failures retain their original reason, stage, and message
instead of being rewritten as
:invalid_pdf_input. Callers matching that catch-all reason should also handle the underlying reasons, including:resource_limit_exceeded.
Added
- Support for every legal static PNG color type and sample depth, including packed grayscale and palette samples, 16-bit color and alpha, and Adam7 interlacing. PDF images preserve 16-bit sample precision. Animated PNGs use their default image only.
- SVG validation before Resvg conversion, with XML location diagnostics and configurable limits for nodes, nesting, path data, filter primitives, references, and converted output bytes. Converter failures retain their explanation in the shared diagnostic contract.
- A configurable PNG working-buffer allowance checked before decompression, alongside sample-depth-aware decoded-image budgets.
- An SVG-to-PNG form and OpenAPI endpoint in the development testing website, with file upload, pasted SVG XML, optional dimensions, and PNG download.
- PNG format browser-parity coverage, image fixture generation, and an image benchmark script.
Fixed
- Preserved small and high-precision decimal values when merging, selecting, rotating, and splitting PDFs instead of rounding them during rebuilding.
- Returned text-extraction diagnostics for arithmetic overflow in composed transforms and text positioning instead of raising exceptions.
- Rejected HTML radio groups mixing enabled and disabled controls in interactive mode, since PDF read-only status applies to the whole field. Static rendering remains available for these groups.
- Rejected malformed JPEG tables, scan selectors and parameters, progressive scan sequences, restart marker order, missing component scans, empty scans, and truncated image termination before embedding.
- Rejected existing FIFOs before opening file inputs, while retaining checks on the opened handle. Local paths must still be trusted; concurrent FIFO substitution cannot be prevented by the portable file API.
- Preserved page annotation order when flattening overlapping form widgets, even when the field-tree order differs.
- Rejected metadata dates that cannot be represented with a four-digit PDF year, including negative-year date structs and ISO strings.
- Preserved merge reader and preparation diagnostics and identified the failing input when the diagnostic had no source.
0.18.0 - 2026-09-18
Breaking changes
- Supported HTML text inputs, textareas, checkboxes, radio buttons, and
single-selection controls now generate interactive AcroForm fields by
default. Pass
forms: :statictoHtmlToPdf.render/2orrender_file/3to retain the pre-0.18 static artwork behavior. - New configurable limits can reject rendering, PDF reading, font loading,
form, attachment, appearance, and incremental-update workloads that were
previously accepted without aggregate bounds. Document-selected local assets
also reject existing symlink components beneath
base_url.
Added
- Added
NativeElixirPdfUtilities.Formswithfields/1,fill/3, andflatten/2for inspecting, filling, and flattening supported AcroForm text, button, and choice fields. Filling supports fully qualified names, repeated widgets, field alignment, read-only checks, and optional selective flattening. - Added interactive form generation to HTML-to-PDF output. Supported controls retain their rendered styling, explicit HTML names become field names, and unnamed controls receive page-local generated names after pagination.
- Added
NativeElixirPdfUtilities.Attachmentswithembed/3andlist/1for incrementally embedding caller-approved bytes and reading attachment metadata without extracting files. Existing attachments and unrelated catalog name trees are preserved. - Added bounded MIME detection for common image, document, archive, audio, and video signatures. ZIP and OLE inspection distinguishes supported legacy and Open XML Office formats and rejects conflicting type evidence.
- Added resource limits for HTML and CSS sources and work, layout and output, CSS variables, fonts and caches, PDF reader expansion, forms, attachments, appearances, and font kerning.
- Added manual testing endpoints and OpenAPI entries for form inspection, filling, flattening, and attachment workflows.
Changed
- Bounded HTML parsing, CSS matching and variable expansion, layout, pagination, page furniture, PDF serialization, PDF tokenization and value expansion, font discovery and parsing, and process-wide font caches. Nested stages share one operation budget and report actionable resource diagnostics.
- Local HTML, CSS, image, font, and text-extraction inputs now use portable, bounded regular-file reads. Font and system-font caches evict by retained bytes as well as entry count.
- Tightened browser-parity coverage so every fixture permits at most 3% changed pixels with the installed default fonts, while retaining page-count and average-channel-delta checks.
Fixed
- Resolved indirect form values, preserved hidden and
NoViewwidget behavior, retained decimal appearance transforms during flattening, and rejected form actions, signatures, XFA, malformed field trees, and unsupported field semantics with diagnostics. - Accounted for attachment payloads, metadata, object headers, cross-reference data, trailers, existing stored attachments, and the original PDF when enforcing incremental output limits.
- Validated PNG chunk checksums, ordering, uniqueness, and critical chunk types before decoding.
- Preserved grayscale, RGB, YCbCr, ordinary CMYK, Adobe CMYK, and Adobe YCCK JPEG conventions through validation and PDF serialization, including the required color transforms and sample polarity.
- Validated embedded font models before PDF serialization, bounded kerning and glyph-width work, and preserved shaped text advances and default widths for high glyph indexes.
- Improved browser-compatible layout for mixed inline baselines, word and nonbreaking hyphens, inline flex and grid containers, margin collapsing, automatic table columns, collapsed borders, text line bounds, CSS pixel snapping, and image edge placement.
- Kept table headers with the first body row, honored
break-inside: avoidon semantic body groups when they fit, split oversized groups between rows, and closed collapsed table borders correctly at automatic and explicit page breaks. - Serialized very small and large finite PDF numbers as valid round-trippable decimal values instead of losing nonzero transforms or emitting invalid notation.
0.17.0 - 2026-09-10
Breaking changes
- Tokenizer failures now return
{:error, {reason, diagnostic}}directly fromnext/1,peek/1,next_with_span/1,tokenize_all/1, andtokenize_all_with_spans/1. They no longer appear as error tokens inside a token/state pair or a list. Bulk calls stop at the first failure without returning partial tokens. Successful return values are unchanged. - Code using the tokenizer directly must match the diagnostic error before
handling tokens or lists. Diagnostics include the operation, a specific
syntax explanation, one-based line and byte column, and a zero-based byte
offset in the message. Positions refer to the binary passed to
new/1. - New numeric and font-processing limits can reject inputs previously accepted
without bounds. Configure them through
NativeElixirPdfUtilities.Limits.
Added
- Added
NativeElixirPdfUtilities.Stampwithtext/3,watermark/3,page_numbers/2, andoverlay/3for adding content to existing PDFs through incremental updates. - Added page selection, named or explicit positions, font selection, size, color, opacity, and rotation for text stamps. Coordinates account for page cropping, rotation, and UserUnit scaling.
- Added configurable page-number formats with document or selection numbering, and PDF overlays with repeated or matched pages and exact, contain, cover, or stretch sizing. Overlays import artwork and resources only.
- Added
max_stamp_text_bytes,max_stamp_decoded_content_bytes,max_css_numeric_magnitude,max_pdf_numeric_magnitude, andmax_font_cmap_workresource limits. - Added stamping forms and OpenAPI entries to the manual testing app, visual stamp regressions, and browser comparisons for the rendering fixes below.
Changed
- Reworked public guides around user tasks, supported values, examples, and cross-references. Added stamping to the README and HexDocs navigation.
- Updated the roadmap with remaining work and a browser-parity milestone targeting less than 2% changed pixels before performance work.
- Reader and text-extraction callers reuse tokenizer error explanations and
locations while retaining their existing
:invalid_pdf_inputreason. The manual tokenizer form now displays syntax failures as diagnostic errors.
Fixed
- Isolated stamps from the target page's graphics state and applied overlay opacity to the composed artwork, including overlays with their own opacity.
- Enforced the decoded overlay-content budget before accumulating streams.
- Resolved indirect page content arrays and imported streams with indirect lengths during PDF assembly and stamping.
- Preserved nested named links during merging, page transforms, and splitting, including page inheritance and reachable resources when rewriting objects.
- Preserved metadata control characters through PDF text encoding.
- Rejected inline images during text extraction before their pixel bytes could be interpreted as text operators.
- Validated oversized PDF text numbers, font metrics, and page geometry before numeric conversion. Bounded CSS numbers and page-option lengths before conversion across supported Elixir runtimes.
- Bounded TrueType character-map preparation and validated subtable ranges before expanding mappings.
- Invalidated cached fonts when file contents change, including replacements with unchanged file size and timestamps.
- Preserved quoted text in CSS variables and at-rules, and inherited computed custom-property values without resolving them again in the child scope.
- Inherited font weight through ordinary block elements and rendered documents containing zero-size text without losing other content.
- Distributed flex and grid alignment space independently of gaps, and retained the height of empty flex containers.
- Preserved explicit grid-item dimensions, resolved spans from definite end lines, and retained sparse grid placement order.
- Rendered tables with empty visible rows while respecting hidden content.
- Preserved RGB PNG transparent-color masks and accounted for their decoded memory. Recognized Adobe CMYK and YCCK JPEG polarity when writing images.
0.16.0 - 2026-09-08
Added
- Added
NativeElixirPdfUtilities.Outlineswithget/1,put/2,detect/1, andautomatic/1to read, replace, detect, and write PDF bookmarks. - Added HTML-to-PDF outlines through
outlines: :headingsor an exact outline list. Heading destinations follow their final positions after pagination. - Added outline preservation during merging, page transforms, and splitting.
- Added limits for outline items, depth, title bytes, name-tree nodes, named destinations, and spaces reconstructed during text extraction.
- Added manual testing forms for outline workflows and a synthetic 250-item HTML render benchmark with stage timings, reductions, and sampled VM memory.
Changed
- Indexed CSS selectors, separated pseudo-element matching, and prepared class membership once per element to reduce repeated style-matching work.
- Cached inline declaration parsing, font resolution, and fallback candidate preparation within each style or fallback computation.
- Replaced repeated list appends in selected style, layout, font-run, and cross-reference paths with linear list construction.
- Updated the guides for outline workflows, supported rendering behavior, parsing, resource limits, and benchmarking.
Fixed
- Preserved resolvable named internal links when assembling PDFs. Output links use explicit destinations; unresolved links and links to removed pages are omitted.
- Applied page
UserUnitscaling to dimensions returned byInfo.page_sizes/1. - Preserved the permanent trailer identifier and refreshed the revision identifier during metadata and outline updates.
- Corrected outline depth checks and leaf expansion state, validated detected outlines, and enforced combined outline limits and output object capacity.
- Preserved heading bookmarks across forced page breaks and in flex and grid containers. Repeated table headers produce one bookmark, and heading line breaks retain word separation.
- Limited cross-reference parsing to the current revision and stopped object
parsing and stream lookahead at
endobj. - Accepted PDF decimal spellings such as
.5and1., normalized literal-string line endings, and rejected truncated Flate streams instead of returning partial decoded data. - Retained pending operands and array state across page content streams during text extraction. CMap parsing now ignores comments and applies mappings in source order while enforcing aggregate mapping limits.
- Preserved case-sensitive CSS custom properties and quoted CSS syntax, distinguished identical siblings in positional selectors, and restored enclosing counter scopes after nested resets.
- Resolved percentage widths against containers, gave minimum dimensions precedence over smaller maxima, and stopped repeated wrapping attempts for indivisible graphemes.
- Clamped table rowspans to their row groups and moved image clipping regions with page headers and footers.
- Rejected SVG resource references and XML entity declarations before rasterization. SVGs must use self-contained content and internal references.
0.15.0 - 2026-09-01
Added
- Added
NativeElixirPdfUtilities.Transformwithpick_pages/2,delete_pages/2, androtate_pages/3for selecting, reordering, removing, and rotating pages in PDF binaries. - Added
NativeElixirPdfUtilities.Splitwithby_page/1,by_ranges/2, andafter_page/2for rebuilding one PDF as multiple documents. - Added page-aware document assembly that copies the objects reachable from retained pages, materializes inherited page values, remaps internal links to retained pages, and preserves external URI links.
- Added
max_split_outputs,max_split_object_writes, andmax_aggregate_split_output_bytesresource limits. - Added realistic transform fixtures, focused validation and reconstruction tests, and a page transforms and splitting guide.
Changed
- Merging, page transforms, and splitting now share the same validated PDF assembly and serialization path.
- Reworked the public documentation so diagnostics, resource limits, HTML-to-PDF examples, compatibility, and browser parity each have a shorter, distinct guide.
Fixed
- Fixed retained page dependencies with missing indirect objects raising during
assembly. Transform and split APIs now return an actionable
:invalid_pdf_inputdiagnostic. - Fixed oversized page ranges being expanded before bounds validation, and changed duplicate-page checks and deletion membership checks to use bounded incremental validation.
- Fixed split operations allocating work before enforcing output-count limits,
including the two-output requirement for
after_page/2.
0.14.0 - 2026-08-29
Added
- Added cross-platform installed-font discovery through Fontconfig on Linux, Core Text on macOS, and DirectWrite on Windows.
- Added host resolution for named and generic CSS font families, including
serif,sans-serif,monospace, andsystem-ui. Explicitly configured fonts and@font-facesources retain precedence, and bundled DejaVu Sans is the final fallback. - Added the
:system_font_discoveryHTML-to-PDF option. It defaults totrueand can be disabled when identical output across hosts is more important than matching locally installed fonts. - Added bounded caching for successful and unsuccessful system font lookups
through the
max_system_font_cache_entriesresource limit. - Added browser parity coverage for inherited system fonts.
Changed
- Changed the default HTML-to-PDF font from a built-in PDF font to bundled DejaVu Sans so text measurement and embedded output use the same face.
- Updated
elixir_font_discoveryto0.2.0for explicit unavailable-service and variable-font results. - System and configured font loading now respects font embedding restrictions exposed by OpenType metadata.
Fixed
- Fixed variable OpenType fonts being embedded as their default instance. Configured variable fonts now return an actionable diagnostic, while system lookup continues through the CSS fallback list. A static font instance is required for embedding.
- Fixed scanned PDF streams treating
endstreamprefixes inside stream data as the end of the stream. Both sides of the keyword must now be valid PDF token boundaries. - Fixed font loading stopping at an empty preferred
cmapsubtable instead of continuing to a later usable subtable.
0.13.0 - 2026-08-27
Added
- Added
NativeElixirPdfUtilities.Infofor reading and updating the common PDF information fieldstitle,author,subject,keywords,producer,creation_date, andmodification_date. - Added
Info.page_count/1,Info.page_sizes/1, andInfo.encrypted?/1for validated page counts, effective rotated MediaBox geometry, and encryption detection without attempting decryption. - Added incremental information updates through
Info.put/2. Updates preserve the original PDF bytes, unspecified fields, unknown information dictionary entries, and the permanent trailer identifier. Passingnilremoves a common field. - Added
:producerto HTML-to-PDF metadata and addedmax_pdf_info_value_bytesandmax_pdf_info_total_bytesresource limits for HTML-to-PDF metadata and information updates. - Added a local manual testing application under
dev/manual_webwith a browser interface and OpenAPI document for rendering, merging, text extraction, information inspection and updates, and tokenization.
Fixed
- Fixed PDF merging dropping inherited page resources when a page declared
/Resources null. - Fixed indirect-reference depth and aggregate-work limits being bypassed by stream aliases, cached reference suffixes, or separate page chains.
- Fixed body content and repeated page furniture receiving separate image budgets instead of sharing the render-wide limits.
- Fixed merged PDFs and incremental information updates being able to emit an object count that the configured reader limit would reject.
- Fixed embedded fonts losing Unicode distinctions when two characters mapped to the same glyph. The writer now assigns distinct CIDs and emits an explicit CID-to-glyph map so text extraction preserves both characters.
- Fixed Type 0 text extraction applying PDF word spacing to a decoded CID or
Unicode value instead of only a one-byte source code
0x20. - Fixed generated ToUnicode CMaps writing more than 100 entries in one
beginbfcharsection. - Fixed the HTML parser treating
>inside quoted attributes as the end of a tag, treating<inside<style>as markup, and mishandling RCDATA in<title>and<textarea>. - Fixed Liberation Sans lookup across common Linux installation paths, which restores requested font metrics for right alignment and narrow table headers.
0.12.0 - 2026-08-20
Added
- Added relative and absolute positioning with
top,right,bottom,left, percentage insets, positioned containing blocks, removal of absolute boxes from normal flow, and integerz-indexpaint ordering. - Added
object-fitandobject-positionfor replaced images, includingfill,contain,cover,none, andscale-downfitting. - Added CSS background images with explicit sizes,
cover,contain, positioning, and repeat modes. - Added
:assetsand:asset_resolverrendering options. Callers can supply approved bytes or local files without giving the renderer network access. - Added browser parity fixtures for positioning, image fitting, background images, and realistic invoice, statement, and multi-page report documents.
Changed
- Tightened the Chromium changed-pixel thresholds for HTML-to-PDF browser parity fixtures to approximately 5%, with stricter geometric coverage for absolute positioning.
- Reworked inline, block, table, image, and pagination layout so all 38 synthetic fixtures and 8 realistic documents pass their configured browser parity thresholds.
- Rewrote the public guides for clearer API contracts, examples, compatibility limits, diagnostics, and browser parity coverage.
Fixed
- Fixed repeated backgrounds allocating tile coordinates before enforcing the tile limit, and stopped zero-sized background tiles from reaching the PDF writer.
- Fixed valid
background-size: min(...)declarations raising from the public render API. - Fixed the
backgroundshorthand retaining an earlierbackground-imagewhen the shorthand omitted an image. - Fixed root-positioned boxes moving to later pages during pagination.
- Fixed collapsed-table background images being painted again after cell content.
- Fixed positioned inline elements losing their positioned descendants or using the wrong containing block.
- Fixed mixed inline-block and block children failing layout.
- Fixed resolver-backed
@font-facedeclarations losing the ordered source candidates needed for fallback. - Fixed style diagnostics splitting declarations at semicolons inside URLs.
0.11.0 - 2026-08-17
Added
- Added static rendering for text inputs, checkboxes, radio buttons, selects, textareas, and buttons. Form state is rendered as deterministic visible PDF content without creating interactive AcroForm fields or widget annotations.
- Added support for the form
type,value,name,checked,selected, anddisabledattributes, including valueless and quoted Boolean attributes, selected-option defaults, textarea and button value fallbacks, and strict form-structure validation. - Added configurable process-wide resource limits through
NativeElixirPdfUtilities.Limits, including validated application startup configuration for HTML, image, SVG, layout, PDF reader, merge, text extraction, CMap, Form XObject, and font-cache workloads. - Added a configurable resource-limits guide and browser parity coverage for a static form-control matrix and a realistic government application form.
- Added a PDF tokenizer guide covering tokens, byte spans, stream-length hints, lexical errors, and the boundary between tokenization and document parsing.
Changed
- Changed
base_urlinto an explicit authorization root for document-selected images and fonts. Relative and absolute resources must remain beneath the configured root, and traversal and symlink components are rejected; explicitly configured stylesheet files retain trusted local font resolution relative to their own directory. - Changed text extraction to cache decoded streams and parsed instructions for the duration of one extraction while still charging every stream use, Form expansion, and executed instruction against aggregate work limits.
- Changed sparse PDF merging to preflight input count and aggregate bytes and densely renumber reachable source objects before writing the merged cross-reference table.
- Changed image rendering, complex grid/table layout, PDF reference validation, object-stream parsing, and nested PDF value traversal to enforce their limits before expensive allocation or recursive work begins.
- Changed the generated ExDoc navigation to keep the changelog prominent, consolidate all license texts under one expandable page, organize the guides by subject, and group public, low-level PDF, HTML pipeline, and validation modules in the API reference.
Fixed
- Fixed oversized or malformed SVG raster dimensions reaching the native rasterizer before source-byte, per-axis, and total-pixel limits were checked.
- Fixed repeated images allowing excessive source counts, encoded bytes, or decoded PNG allocation across one HTML render.
- Fixed complex grid definitions, placements, and table spans performing unbounded work before layout cardinality validation.
- Fixed shared PDF streams being repeatedly decoded and tokenized during text extraction. A 500-page shared-stream benchmark improved from approximately 3.26 seconds to 28 milliseconds while preserving per-use safety accounting.
- Fixed PDF object streams scanning oversized headers before their entry count was validated.
- Fixed sparse PDF inputs causing merge work and output size to scale with high source object numbers instead of the reachable object set.
- Fixed repeated page-parent and indirect-reference chains being revalidated without request-scoped caching or an aggregate reference-resolution budget.
- Fixed deeply nested PDF arrays and dictionaries entering recursive descent before the shared value-depth limit was enforced.
- Fixed local HTML images and document-selected fonts accepting paths outside
base_url, including absolute paths, parent traversal, and symlink escapes.
0.10.0 - 2026-08-14
Added
- Added layered validator modules for shared PDF structure, text extraction, merging, HTML rendering, and PDF writing, with prepared contexts that keep validation separate from execution and serialization.
- Added
NativeElixirPdfUtilities.Pdf.Reader.read_validated/1for consumers that need the complete shared PDF validation context while preserving the existingread/1document projection. - Added recoverable unsupported-glyph rendering. Missing glyphs now use the
Unicode replacement character by default, while
unsupported_glyphs: :errorretains strict failure behavior with an actionable diagnostic. - Added HTML table column definitions through
colgroupandcol, including column spans, percentage widths, fixed table layout, and separate and collapsed border-spacing behavior. - Added documentation for the layered PDF validation pipeline and browser parity fixtures for table column layout, repeated headers near page breaks, border-box sizing, and unsupported-glyph replacement.
Changed
- Centralized caller input, option, document-structure, and semantic validation in dedicated validators. PDF reading, text extraction, merging, HTML layout, pagination, font fallback, and writing now consume validated and normalized data instead of repeating validation rules in execution code.
- Separated normalization from validation throughout the HTML renderer so downstream layout and writing stages can rely on stable normalized values.
- Changed text resource preparation to validate only fonts, encodings, CMaps, graphics states, and Form XObjects reached by executable page content.
- Changed the HTML renderer and text extraction APIs to reject unknown options with the shared actionable diagnostics contract.
Fixed
- Fixed PDF validation for page-tree depth limits,
/Parentrelationships, page aliases, null-valued optional entries, malformed object records, and the required free object-zero cross-reference entry. - Fixed stream loading for indirect lengths in ordinary and cross-reference streams, including compressed length objects and stream-boundary ambiguity.
- Fixed text extraction from ExtGState font selections and Form XObjects with indirect transformation matrices, and rejected unbalanced graphics or text scopes with diagnostics instead of unsafe execution.
- Fixed merge validation for page resources, terminal page references, top-level page rewrites, exact object generations, and complete indirect reference remapping.
- Fixed resource bounds for predictor row allocation, CID font width expansion, and page-tree traversal.
- Fixed repeated table headers overflowing page bounds and page furniture bounds omitting line height.
- Fixed auto-sized
border-boxelements losing horizontal padding and bounded emitted PDF color channels to their valid range.
0.9.0 - 2026-08-07
Added
- Added generated CSS content for
::beforeand::after, including quotedcontentfragments andattr(...)values. - Added attribute-presence and attribute-equality selectors,
:not(...), odd/even:nth-child(...), and:first-of-typeand:last-of-typeselectors for document templates. - Added named CSS counters through
counter-reset,counter-increment, andcounter(name), including multiple counters and explicit integer values. - Added browser-parity fixtures for generated content, counters, the expanded
selector set, and
white-space: pre-linerendering. - Added a local quality matrix and matching GitHub Actions coverage for the supported Elixir 1.19 and 1.20 runtimes. The matrix checks compilation, formatting, unused dependencies, 100% test coverage, Dialyzer, and Chromium browser parity.
- Added code-style regression checks for the library's documented public API and function-head conventions.
Changed
- Changed the minimum supported Elixir version from 1.18 to 1.19.
- Changed inline whitespace layout to collapse ordinary HTML whitespace across
element boundaries, preserve normalized CRLF, CR, and LF line breaks with
white-space: pre-line, retain explicit<br>breaks, and treat literal escaped newline sequences as text. - Updated GitHub Actions dependencies to Node 24-compatible releases.
- Refactored HTML-to-PDF parser, style, layout, pagination, page-geometry, page furniture, and PDF visual-comparison internals without changing their public APIs.
Fixed
- Fixed
rgba(), eight-digit hexadecimal, andtransparentCSS colors losing their alpha values. Text, backgrounds, and borders now write the required PDF transparency graphics states, including shaded border variants. - Fixed PDF merging misclassifying an object as a page when
/Type /Pageappeared only in a nested dictionary. Page-tree traversal now uses the shared reader's semantic dictionaries and resolves indirect/Kidsand/Countvalues. - Fixed stream tokenization allowing a nested dictionary's
/Lengthto replace or leak into the enclosing stream dictionary's length. - Fixed source-order text extraction inserting spaces between consecutive PDF
text-showing operations. Operator-defined continuity is now preserved across
TjandTJoperands and exposed through each span'sjoins_previous?field, while positioning, text-object, Form XObject, and graphics-matrix boundaries still begin separate segments.
0.8.0 - 2026-07-30
Added
- Added opt-in running page furniture through
:page_furniture, with independently configured headers and footers,:default,:first,:odd, and:evenvariants, first-page-only and except-first-page behavior, and{{page}}and{{pages}}tokens. - Added complete supported page geometry shared by renderer options and bare
@pagerules: named and explicit page sizes, portrait and landscape forms, one-to-four-value margins, margin longhands, asymmetric layout, and explicit renderer-option precedence. - Added bundled DejaVu Sans regular, bold, oblique, and bold-oblique fonts for deterministic Unicode glyph fallback after requested and configured font families.
- Added browser-parity coverage for page furniture, page-number tokens, asymmetric page geometry, paragraph fragmentation, border variants, flex and grid constraints, HTML character references, and multi-row table headers.
Changed
- Changed
:stylesheetsentries to require explicit{:css, css}or{:file, path}tags. Bare strings are rejected so empty or comment-only CSS cannot be mistaken for a path and file access is always explicit. - Changed font loading to reuse successful parsed font files through a supervised, bounded, process-wide cache. Cache entries invalidate when file metadata changes, concurrent cold loads are deduplicated, failed parses are not retained, and no host-application configuration is required.
- Changed the package metadata and documentation to identify the MIT, Bitstream Vera, and BSD 3-Clause licenses used by the source, bundled DejaVu fonts, and generated WHATWG character-reference data.
- Documented that repeated page furniture uses the explicit renderer option;
CSS
position: fixedremains deferred until positioned layout can remove elements from normal flow and apply offsets correctly.
Fixed
- Fixed CSS cascade precedence so stylesheet
!importantdeclarations beat normal inline declarations, while inline!importantdeclarations retain priority over important stylesheet declarations. - Fixed table
rowspanlayout to reserve occupied columns and span the combined height of all covered rows. - Fixed paragraphs taller than the remaining or complete printable page being
clipped or kept together indefinitely; pagination now fragments them at
complete visual lines, including oversized
break-inside: avoidparagraphs. - Fixed paginated tables repeating only one header row; all
<thead>rows now repeat together when the table body continues. - Fixed Unicode text being rejected or encoded with the wrong face by resolving each grapheme through the selected, requested, configured, and bundled fallback fonts before layout.
- Fixed text extraction across nested
qandQoperators so saved graphics and text state is restored in LIFO order. - Fixed Type 0 text width calculation by mapping source codes through the Encoding CMap before applying descendant CID widths, with strict diagnostics and resource limits for unsupported or malformed CMaps.
- Fixed merging pages with inherited or indirect
MediaBox,CropBox,Resources,Rotate,BleedBox,TrimBox,ArtBox, andUserUnitvalues by materializing the nearest effective page-tree values. - Fixed the shared reader accepting duplicate page-tree references or
inconsistent descendant
/Countvalues. - Fixed ASCII85 decoding of delimiters, whitespace,
zgroups, partial final groups, and values that overflow the 32-bit group range. - Fixed HTML named and numeric character references using the complete WHATWG table, including legacy semicolon rules, invalid numeric normalization, multi-code-point references, non-breaking spaces, and decode-once behavior.
- Fixed CSS custom properties being resolved before their cascade completed; ordinary declarations now use the final winning custom-property values.
- Fixed
none,hidden,dotted,dashed,solid,double,groove,ridge,inset, andoutsetborders, including independent side styles and transparent border spacing. - Fixed valid page-context declarations being rejected and malformed
size, margin, orientation, marks, bleed, unknown, and incomplete declarations lacking actionable CSS diagnostics. - Fixed zero, negative, or excessive margins being accepted when they leave no positive printable page area.
- Fixed merged output corrupting PDF names that require
#xxescaping, including whitespace, delimiters, literal#, control bytes, and non-printable bytes. - Fixed unsupported named or pseudo-page selectors and misspelled
@pageat-rules being silently applied to every page; they now return strict diagnostics. - Fixed declaration-order-dependent computed CSS values by resolving
em,rem,currentColor, relative semantic margins, custom properties, and inherited line heights against the element's final computed style. - Fixed grid
minmax()tracks discarding their minimum and added redistribution when fractional tracks reach a minimum bound. - Fixed flex grow and shrink distribution overwriting item
min-width,max-width,min-height, andmax-heightconstraints. - Fixed text extraction rejecting valid inherited indirect page
/Rotatevalues. - Fixed merge failures replacing the reader's machine-readable reason and diagnostic stage; callers can again distinguish encryption, page-tree, resource-limit, malformed-input, and unsupported-feature failures.
- Fixed valid empty or comment-only inline stylesheets and paths containing braces being misclassified by replacing content-based guessing with explicit stylesheet source tags.
- Fixed the public
:default_fonttypespec so its documented fallback-list form accepts[String.t()]. - Fixed a severe CSS
remperformance regression that recursively traversed complete font registries and decoded runtime payloads for every element. Runtime font and image data is now opaque to CSS length resolution, with a deterministic reductions-based regression test.
0.7.0 - 2026-07-23
Added
- Added local CSS
@font-facesupport for embedded and configured stylesheets, includingfont-family, orderedsrc: url(...)fallbacks,font-weight,font-style, and supportedfont-displayvalues. - Added TrueType font loading from
.ttffiles and.otffiles that use TrueType outlines. Relative font URLs resolve against the configured stylesheet directory or renderer:base_url. - Added print media handling for
@media print,@media only print,@media all, and@media only all, while non-print media rules are omitted from the active print cascade. - Added PDF document metadata through the HTML renderer's
:metadataoption for title, author, subject, keywords, creation date, and modification date. Metadata dates acceptDate,NaiveDateTime,DateTime, and ISO 8601 strings, and non-ASCII values are written as Unicode PDF strings. - Added automatic PDF title metadata from the first non-empty HTML
<title>when no explicit metadata title is provided. - Added Chromium parity coverage for CSS-declared fonts and print media, plus examples and compatibility documentation for fonts, print CSS, metadata, supported formats, URL resolution, and conversion boundaries.
Changed
- Changed configured stylesheet handling to preserve each file's directory for
relative assets and to apply configured
@pagerules when deriving default page options. - Changed explicit and CSS-declared font registration to try ordered local source candidates until a supported font loads.
- Changed PNG decoding to stream decompression, require the exact expected decoded size, and reject images whose decoded scanlines exceed 100 MB.
- Changed RunLengthDecode to operate on binaries and enforce the PDF reader's decoded-stream and decompression-ratio limits.
- Changed text extraction to accumulate spans and output text without repeated
list or binary copying, and capped extraction at 25,000 text spans per page
with a
:resource_limit_exceededdiagnostic. - Changed merge failures to retain the PDF reader's reason and stage in the actionable merge diagnostic.
Fixed
- Fixed merging PDFs that contain unrelated or stale catalog objects by
resolving the active catalog from the trailer's
/Rootreference. - Fixed hexadecimal strings with non-hexadecimal bytes being silently
sanitized; the tokenizer now emits
:invalid_hex_stringwith the offending byte position. - Fixed PDF
DateTimemetadata formatting so UTC and non-UTC offsets are encoded correctly. - Fixed CSS
@font-faceparsing for quoted URLs containing commas, ordered fallback sources, invalid or missing descriptors, unsupported formats, and malformed declarations. - Fixed CSS diagnostics for malformed font and media rules so rendering returns
actionable
:invalid_cssdetails with source, line, and column context.
0.6.0 - 2026-07-20
Added
- Added
NativeElixirPdfUtilities.Pdf.Reader, a shared PDF document layer with classic cross-reference tables, cross-reference streams, object streams, incremental and hybrid revision chains, recursive indirect resolution, supported stream filters, page-tree validation, and resource limits. - Added committed reader fixtures for classic, xref-stream, object-stream, hybrid, incremental, encrypted, and malformed PDFs.
- Added
Text.extract_spans/2andText.extract_file_spans/2for page-preserving decoded text operations with source indexes, baseline coordinates, font and matrix context, and text rendering-mode metadata. - Added strict Unicode decoding for standard simple-font encodings,
font-specific
Differences, Adobe glyph names, Type 0 fonts, and ToUnicode CMaps. - Added PDF reader, text extraction, and merging guides covering supported structures, public behavior, diagnostics, limits, and known boundaries.
- Added GitHub Actions checks for compilation warnings, formatting, unused dependencies, tests, 100% coverage, Dialyzer, and Chromium browser parity.
Changed
- Changed text extraction and PDF merging to consume the shared reader model so both utilities honor active revisions, generations, free entries, compressed objects, and the validated page tree.
- Changed text extraction to reject malformed content and unreliable font encodings with actionable diagnostics instead of guessing or returning partial text.
- Changed string extraction to project from the same positioned page spans
while preserving the existing
layout: trueandlayout: falseoutput.
Fixed
- Fixed merging pages with malformed inherited
/MediaBoxvalues by applying the default page box. - Fixed valid cross-reference offsets that point to PDF whitespace immediately before an indirect object header being rejected.
- Fixed tokenizer comments ending at end-of-input being emitted as tokens.
0.5.1 - 2026-07-10
Changed
Merge.merge/1now rejects malformed classic PDF input with an:invalid_pdf_inputdiagnostic rather than producing an empty PDF or raising.NativeElixirPdfUtilities.Tokenizernow emits explicit error tokens for unterminated literal and hexadecimal strings.Text.extract/2rejects malformed token streams with an:invalid_pdf_inputdiagnostic. It also ignores unusually large ToUnicode CMaps to bound memory and CPU use during extraction.
Fixed
- Fixed merge crashes caused by malformed tokens, incomplete streams, duplicate object identifiers, and invalid object identifiers.
- Fixed merged PDFs losing inherited
/MediaBoxand/Resourcesvalues from intermediate/Pagestree nodes. - Fixed remapping of non-page
/Parentreferences during a merge. - Fixed malformed TTF font input causing HTML-to-PDF rendering to crash.
- Reduced avoidable repeated binary and list copying while writing larger PDFs.
0.5.0 - 2026-07-10
Added
- Added
NativeElixirPdfUtilities.Diagnosticsas the shared public diagnostic contract. - Added standardized diagnostic details for merge, text extraction, HTML rendering, pagination, PDF writing, and file failures.
- Added developer guidance for using the shared diagnostics contract in future public APIs.
- Added a diagnostics guide under
docs/.
Changed
- Changed
Merge.merge/1to return diagnostic errors instead of raising for empty input. - Changed recoverable failures from merge, text extraction, HTML rendering,
pagination, PDF writing, and file operations to return
{:error, {reason, diagnostic}}with:stage,:reason,:message,:operation,:module, and:sourcecontext when available.
0.4.0 - 2026-07-09
Added
- Added a Chromium-backed browser parity test suite for the supported HTML/CSS rendering surface.
- Added browser parity fixtures for common layout, CSS cascade, tables, flexbox, grid, pagination, image, link, unit, and production-document scenarios.
- Added browser parity coverage documentation so supported renderer behavior is tied to explicit fixtures.
Changed
- Improved HTML-to-PDF browser accuracy for nested table, flexbox, and grid compositions.
- Improved collapsed table border sizing and painting to better match browser output.
- Improved
@pagehandling in parity tests so native and Chromium renders use the same page size and margins. - Updated contribution guidance to require focused tests and browser parity coverage for visible HTML-to-PDF feature work.
Fixed
- Fixed CSS custom property resolution inside supported compound values such as padding and side-specific borders.
- Fixed
box-sizing: border-boxhandling across block, flex, grid, table, and image layout paths. - Fixed table layout inside flex and grid items, and flex layout directly inside table cells.
- Fixed declared table row heights and pagination metadata propagation for table rows.
- Fixed pagination edge cases around first-page parent padding, overlapping parent/child groups, and zero-height metadata groups.
0.3.0 - 2026-07-08
Added
- Added
NativeElixirPdfUtilities.HtmlToPdf, a native HTML/CSS to PDF renderer for document-oriented templates. - Added support for common document HTML including text, headings, paragraphs, spans, lists, links, tables, images, and nested document structure.
- Added support for common print-oriented CSS including cascade handling, box model sizing, text styling, borders, backgrounds, tables, flexbox, grid, page sizes, page breaks, and
@media printbehavior. - Added embedded image, SVG rasterization, and custom TTF font rendering support for generated PDFs.
- Added multi-page pagination and PDF writing for rendered HTML documents.
- Added detailed render diagnostics for invalid HTML, unsupported HTML, invalid CSS, invalid layout, and invalid document failures.
- Added fixture coverage for purchase orders, material requisitions, stock stickers, and trim cards with realistic scrambled data.
- Added dedicated HTML-to-PDF compatibility and examples documentation.
Changed
- Updated the package description to include native HTML/CSS rendering.
- Updated HexDocs metadata to include the HTML-to-PDF guides.
0.2.0 - 2026-07-03
Added
- Added embedded text extraction so callers can consume readable text data from PDF binaries.
Changed
- Refactored tokenizer, merge, and text internals to use explicit
case/condbranching instead of guarded multi-head private functions. - Split tests into focused tokenizer, merge, and text suites.
- Improved package documentation and HexDocs metadata for the release.
Fixed
- Fixed page dictionary rewriting around empty arrays and MediaBox validation.
- Added 100% test coverage across the current public library modules.
0.1.0 - 2025-09-08
Added
- Initial PDF tokenizer.
- Initial PDF merge utility.