# Getting Started

ExPdfInspector exposes Firecrawl's Rust `pdf-inspector` library to Elixir through
a Rustler NIF. It is designed for local PDF classification, OCR routing, native
text extraction, and Markdown generation.

It does not run OCR. Instead, it reports which pages need OCR so your application
can route only those pages to an OCR engine or service.

## Requirements

The package compiles its Rust NIF during `mix compile`. Your development, CI, and
release build environments need:

- a Rust toolchain and Cargo;
- a supported Elixir and Erlang/OTP installation;
- the system build tools required by the Rust target platform.

Runtime hosts do not need Cargo when you deploy a release that already contains
the compiled NIF. Build the release for the same operating system and CPU
architecture as the runtime host.

## Installation

Add the package to `mix.exs`:

```elixir
def deps do
  [
    {:ex_pdf_inspector, "~> 0.1.0"}
  ]
end
```

Then fetch and compile it:

```console
mix deps.get
mix compile
```

## Process a PDF

`process_pdf/1` performs classification, layout analysis, and Markdown
extraction in one call:

```elixir
{:ok, result} = ExPdfInspector.process_pdf("report.pdf")

result.pdf_type
#=> :text_based

result.page_count
#=> 12

result.markdown
#=> "# Quarterly report\n..."
```

Use `process_pdf_bytes/1` when the document is already in memory, for example
after an upload:

```elixir
{:ok, result} =
  upload_path
  |> File.read!()
  |> ExPdfInspector.process_pdf_bytes()
```

Both variants return the same result shape.

## Route pages to OCR

The result combines a document-level classification with page-level routing
information:

```elixir
{:ok, result} = ExPdfInspector.detect_pdf("archive.pdf")

case result.pages_needing_ocr do
  [] -> {:native_text, result.pdf_type}
  pages -> {:ocr_required, pages, result.ocr_reasons_by_page}
end
```

Possible document types are `:text_based`, `:scanned`, `:image_based`, and
`:mixed`. `detect_pdf/1` and `detect_pdf_bytes/1` skip Markdown extraction and
return `nil` in the `:markdown` field.

Page filters accepted as input are zero-based. The `pages_needing_ocr` values in
results come from the upstream library and are human-readable page numbers.

## Extract content directly

For plain text only:

```elixir
{:ok, text} = ExPdfInspector.extract_text("report.pdf")
```

For position, font, style, links, and form-field metadata:

```elixir
{:ok, items} = ExPdfInspector.extract_text_with_positions("report.pdf")

Enum.map(items, fn item ->
  {item.page, item.text, item.x, item.y, item.font_size, item.item_type}
end)
```

To retain page boundaries and OCR metadata:

```elixir
{:ok, result} = ExPdfInspector.extract_pages_markdown("report.pdf", nil)

Enum.each(result.pages, fn page ->
  IO.puts("Page #{page.page}: #{page.markdown}")
end)
```

Pass a list such as `[0, 2]` instead of `nil` to select pages.

## Handle errors

PDF-reading functions return tagged tuples:

```elixir
case ExPdfInspector.process_pdf(path) do
  {:ok, result} ->
    {:ok, result}

  {:error, %{code: :encrypted}} ->
    {:error, :password_required}

  {:error, %{code: code, message: message}} ->
    Logger.warning("PDF processing failed: #{code}: #{message}")
    {:error, :invalid_pdf}
end
```

Error codes are stable atoms defined by ExPdfInspector and are intended for
pattern matching: `:io_error`, `:not_a_pdf`, `:invalid_structure`,
`:parse_error`, and `:encrypted`. They are never created from document content
or other external strings. Error messages remain strings for logging and
diagnostics.

For an encrypted document, pass a password with `ExPdfInspector.PdfOptions`:

```elixir
options = %ExPdfInspector.PdfOptions{password: System.fetch_env!("PDF_PASSWORD")}
ExPdfInspector.process_pdf("protected.pdf", options)
```

Do not log passwords or untrusted document contents.

## Next steps

Read [Processing and Options](processing-and-options.md) for processing modes,
detection strategies, and Markdown controls, or open the `ExPdfInspector` module
documentation for the complete API.
