Skip to main content

CHANGELOG.md

# Changelog

All notable changes to this project are documented here. The format is based on
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres
to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) (against *our* API,
not pdfium-render's).

## [Unreleased]

## 0.2.0 - 2026-06-25

### Added
- Phase 6 — forms & annotations (read), completing the read-only scope:
  - `ExPdfium.form_type/1` → `:none` | `:acrobat` | `:xfa_full` | `:xfa_foreground`.
  - `ExPdfium.form_fields/1` → AcroForm fields, one entry per widget across all
    pages (`%{name, type, value, checked, read_only, required, page, bounds}`).
    For checkbox/radio groups, `value` is the group's selected on-state and
    `checked` flags the selected widget (pdfium does not expose per-option export
    names). XFA form data is unavailable without a V8-enabled pdfium build.
  - `ExPdfium.annotations/2` → a page's annotations, markup and widget alike
    (`%{type, bounds, contents, name, hidden, printed}`; `type` is the PDF
    `/Subtype`).
- Phase 5 — structure & navigation:
  - `ExPdfium.outline/1` → the bookmark tree (`%{title, page, children}` nodes).
  - `ExPdfium.links/2` → a page's links (`%{bounds, uri, page}`; `uri` for web
    links, `page` for internal destinations).
  - `ExPdfium.attachments/1` → embedded files (`%{index, name, size}`) and
    `ExPdfium.attachment_data/2` → an attachment's bytes.
- Phase 4 — metadata, page geometry & permissions:
  - `ExPdfium.metadata/1` → document info map (title/author/subject/keywords/
    creator/producer/creation_date; `modification_date` is usually `nil`, a
    pdfium-render limitation — see the docs).
  - `ExPdfium.page_info/2` → `%{width, height, rotation, label, boxes}` (size in
    points, rotation in degrees, boundary boxes media/crop/bleed/trim/art).
  - `ExPdfium.permissions/1` → map of 8 boolean permission flags.
- Phase 3 — text extraction & search:
  - `ExPdfium.extract_text/2` (one page) and `extract_text/1` (whole document,
    pages joined by a form feed).
  - `ExPdfium.text_segments/2` returns text runs with per-segment bounding boxes
    (PDF points, origin bottom-left).
  - `ExPdfium.search_text/3,4` with `:match_case` and `:whole_word` options; each
    match carries its text and bounding rects. Empty query → `{:error, :empty_query}`.
- Phase 2 — render a page to a bitmap: `ExPdfium.render_page/3` returns
  `{:ok, %ExPdfium.Bitmap{data, width, height, stride, format}}`, an uncompressed
  4-channel buffer ready for `Vix.Vips.Image.new_from_binary/5`.
  - Sizing by `:dpi` (default 72), `:scale`, or `:width`/`:height`.
  - `:format` `:rgba` (default) or `:bgra`; `:background` `:white` (default) or
    `:transparent`.
  - Errors: `:page_out_of_bounds`, `:document_closed`, `:render_failed`,
    `:unsupported_format`, `:unsupported_background`, `:bad_option`.
  - GC-driven document close is deferred to a dedicated cleanup thread so it never
    blocks a BEAM scheduler while a long render holds the pdfium lock.
- Phase 1 — open documents & page count:
  - `ExPdfium.open/1,2` opens a PDF from a file path or in-memory binary, with an
    optional `:password` for encrypted documents. Returns `{:ok, %ExPdfium.Document{}}`.
  - `ExPdfium.page_count/1` returns `{:ok, n}`.
  - `ExPdfium.close/1` releases the document early (idempotent); documents are
    also closed automatically on garbage collection (no manual-close leak).
  - Errors are mapped from pdfium: `:enoent`, `:invalid_pdf`, `:password_error`,
    `:unsupported_security`, `:file_error`, `:io_error`, `:document_closed`.
  - pdfium is not thread-safe, so all pdfium operations are serialized through a
    single global lock; calls are safe from any number of BEAM processes but run
    one at a time.

## [0.1.0] - 2026-06-24

First release — Phase 0: proves the toolchain and the precompiled-release path
end to end. PDF document/page/text APIs land in later phases (see `PORTING.md`).

### Added
- Project scaffold: `rustler_precompiled` config, tag-driven release pipeline,
  and the porting plan (`PORTING.md`).
- Phase 0 (toolchain): `ExPdfium.pdfium_version/0`, a load-proof NIF that binds
  and initializes pdfium. Pinned `pdfium-render = "=0.8.37"`. The dev/test build
  binds pdfium dynamically; the libpdfium directory is passed to the NIF via a
  `set_dynamic_lib_dir/1` function argument (env vars set with `System.put_env`
  don't reach a NIF).

### Changed
- pdfium-render must be built with the `sync` feature (not just `thread_safe`):
  only `sync` adds the `Send + Sync` impls that let the single global `Pdfium`
  live in a `static`. `release.yml` updated accordingly.
- Pinned pdfium binary tag bumped `chromium/7506` → `chromium/7543` to match the
  pdfium API version pdfium-render 0.8.37 binds (`pdfium_latest`).
- Shipping strategy: the precompiled NIF binds pdfium **dynamically** and bundles
  the dynamic `libpdfium` inside each per-target tarball (rustler_precompiled
  extracts it next to the NIF; the NIF self-locates it via `dladdr`). bblanchon
  ships no static `libpdfium.a`, so static linking isn't used. The optional
  `static`/`libcpp`/`libstdcpp` features remain for a user-supplied `.a`.