# `PhoenixKitCatalogue.Catalogue.PdfEngines`
[🔗](https://github.com/BeamLabEU/phoenix_kit_catalogue/blob/v0.19.1/lib/phoenix_kit_catalogue/catalogue/pdf_engines.ex#L1)

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.

# `engine`

```elixir
@type engine() :: %{name: String.t(), page_count: non_neg_integer(), state: term()}
```

An opened document, bound to the engine that opened it.

# `close`

```elixir
@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.

# `extract_page`

```elixir
@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.

# `open_best`

```elixir
@spec open_best(String.t()) :: {:ok, engine()} | {:error, [{String.t(), term()}]}
```

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).

---

*Consult [api-reference.md](api-reference.md) for complete listing*
