Engine chain for PDF text extraction: pdfium (in-app, precompiled NIF)
first, poppler (pdfinfo/pdftotext system binaries) as the fallback
when installed.
The chain exists so a host needs NO system packages for PDF search to
work: ex_pdfium ships Chrome's PDF engine as a precompiled binary
fetched during mix deps.get. Hosts that already have poppler keep it
as a safety net for files pdfium cannot open. Benchmarked 2026-08-16
on the tim-dev corpus: pdfium word recall vs pdftotext -layout was
98.4–100% per document (dev_docs has the numbers).
open_best/1 tries each engine in order and commits to the first one
that can open the document and report a page count — per-page failures
after that point are the worker's business (it keeps partial results).
When every engine refuses, the per-engine reasons are returned
together so the stored error message names them all.
Summary
Functions
Releases engine resources: the pdfium document handle is closed eagerly instead of waiting for BEAM GC to collect the NIF resource. No-op for poppler.
Extracts one page's raw text (1-based page_number) with the engine
that opened the document.
Opens path with the first engine that accepts it.
Types
@type engine() :: %{name: String.t(), page_count: non_neg_integer(), state: term()}
An opened document, bound to the engine that opened it.
Functions
@spec close(engine()) :: :ok
Releases engine resources: the pdfium document handle is closed eagerly instead of waiting for BEAM GC to collect the NIF resource. No-op for poppler.
@spec extract_page(engine(), pos_integer()) :: {:ok, String.t()} | {:error, term()}
Extracts one page's raw text (1-based page_number) with the engine
that opened the document.
Opens path with the first engine that accepts it.
Returns {:ok, engine} or {:error, attempts} where attempts is a
[{engine_name, reason}] list covering every engine tried (including
unavailable ones, so "poppler: not installed" shows up in the stored
failure message rather than silently narrowing the chain).