PdfElixide.Document (pdf_elixide v0.23.0)

Copy Markdown View Source

Read-only representation of a PDF document.

Sharing a document across processes

A %Document{} is safe to pass to other processes. Operations that read the native document take its handle lock shared, so N processes extracting from one document — or rendering different pages of it — do not queue on that lock. version/1, source_path/1 and, normally, page_count/1 read cached struct fields instead and take no native lock. authenticate/2, clear_search_index/1 and close/1 take the handle lock exclusively, as does PdfElixide.Compliance.validate/2 on a document that needs a password, and rasterize/2 serializes against itself across the node. The Concurrency guide has the rest, including contention inside the PDF reader and the tagged-PDF hazard that makes fanning out by page the shape to prefer.

Whole-document extraction and memory

Every extractor here has a whole-document arity. chars/1, words/1, text_lines/1, spans/1, tables/1, paths/1, rects/1, lines/1, images/1, fonts/1 and annotations/1 return one flat list, as does search/2; structured/1 returns one PdfElixide.Document.StructuredPage per page; text/1, to_markdown/1, to_html/1 and to_plain_text/1 return a single string with the pages joined. Each shape walks all pages inside a single native call, so its cost scales with the whole document rather than with what the caller keeps. As the call returns, the result exists twice — natively and as the Elixir terms encoded from it — so peak usage is roughly double what the caller ends up holding. search/2 given a :max_results is the one that need not walk every page; see search_opts/0. The Office exports, to_docx/2, to_pptx/2 and to_xlsx/2, are whole-document calls of the same kind, returning the file as one binary.

These hold native memory after the call as well, since each returned struct carries a handle that BEAM memory accounting cannot see:

  • images/1 — every image's decoded pixels, or its original JPEG bytes.
  • fonts/1 — one handle per page per font, with no sharing across pages.
  • tables/1 — the full detected table, including the per-glyph metrics the struct itself omits.

On a large document, release them with PdfElixide.Document.Image.close/1, PdfElixide.Document.Font.close/1 or PdfElixide.Document.Table.close/1 as you finish with each.

Working a page at a time

PdfElixide.Document implements Enumerable over its pages, and PdfElixide.Document.Page offers every extractor. Enumeration raises when the page count cannot be read; use pages/1 to receive that error instead. Process results as they arrive without accumulating them to keep only one page's extraction results at a time:

doc
|> Stream.flat_map(&Page.chars!/1)
|> Stream.each(&IO.write(&1.text))
|> Stream.run()

Collecting the stream with Enum.to_list/1, Enum.map/2 or a comprehension retains the results across pages. Close image, font and table handles after processing them, as above; lazy enumeration does not release them for you.

Concatenating per-page results reproduces the list-returning extractors apart from fresh image, font and table handles. Enum.map(doc, &Page.structured!/1) reproduces structured/1. The ones that return a single string do not, since each joins pages itself: text/1 separates them with a form feed and applies :on_page_error (see text_opts/0), to_markdown/1 and to_plain_text/1 each join with a --- break, and to_html/1 wraps each page in a <div class="page">.

When a page cannot be read

A whole-document extractor or converter that reaches a page it cannot read fails with {:error, %PdfElixide.Error{}}. fonts/1 is the exception and always skips such a page; text/1 skips one only when given on_page_error: :skip. A capped search/2 may stop before reaching a later unreadable page; if it reaches one, it fails. See search_opts/0.

Most damaged pages extract as empty rather than failing; see the :on_page_error section of text_opts/0.

To tolerate one with any other extractor, work a page at a time and decide per page, keeping which page failed and why; the "When a page fails" section of the Text extraction guide has the recipe.

Choosing an extractor for search and matching

text/1 reads a page as it is laid out, inferring word breaks from where glyphs sit, so runs separated by only a narrow gap can still be joined.

:extract_tables, which defaults to true, controls how a recognised table is laid out, not whether its values are returned. With it on, detected cells are rendered as column-aligned rows; with it off, their values appear as ordinary flow text. A table aligned only by whitespace is flow text either way. See text_opts/0 and "Tables inside a text result" in the Text extraction guide.

To find or match tokens rather than read the page, use words/2. It clusters characters by their own advance widths and returns a bounding box for every word. Prefer search/2 when the query is known in advance.

to_plain_text/2 is a third reading of the same page — prose paragraphs rather than the page's visual lines. The Text extraction guide says when it is the one you want.

Page boxes and the coordinate origin

Raw page coordinates use PDF points with y increasing upward. Page boxes are unrotated; some extracted boxes use a displayed frame instead. See Page geometry for origins and page boxes.

What the crop box hides from extraction

Text-family extractors omit cropped-away runs; chars/2 does not. See What the crop box hides from extraction.

Which ancestor an inherited box comes from

Page boxes and rotation inherit from the nearest declaring ancestor. See Which ancestor an inherited box comes from.

Rotated pages and extracted geometry

Extractors can report different coordinate frames on the same rotated page. See Rotated pages and extracted geometry before combining their boxes or using them for editing or rendering.

Telling which frame a box is in

The frame depends on the extractor, page rotation and run rotation. See Telling which frame a box is in.

Bringing boxes into one frame

Convert displayed boxes to raw user space before editing or region rendering. See Bringing boxes into one frame for the conversion calls and a search-match example.

Summary

Opening and lifecycle

Authenticates against the document's encryption with the given password.

Same as authenticate/2 but raises on error.

Releases the document's native memory without waiting for garbage collection.

Returns whether the document has been released with close/1.

Returns whether the PDF document is encrypted.

Opens a PDF document from the given binary data.

Opens a PDF document from the given binary data, raising an error if it fails.

Opens a PDF document from the specified file path.

Opens a PDF document from the specified file path, raising an error if it fails.

Returns a PdfElixide.Document.Page handle for the page at the given zero-based index.

Same as page/2 but raises an error if it fails.

Returns the number of pages in the given PDF document.

Returns the number of pages in the given PDF document, raising an error if it fails.

Returns a PdfElixide.Document.Page handle for every page in the document.

Same as pages/1 but raises an error if it fails.

Returns the file path from which the document was loaded, or nil if it was loaded from binary data.

Returns the PDF specification version of the given document as a {major, minor} tuple.

Text extraction

Extracts characters, each with its bounding box, font metadata and typographic placement as a PdfElixide.Document.Char struct.

Extracts the characters of the page at the given zero-based index.

Extracts characters, raising an error if it fails.

Extracts the characters of the page at the given zero-based index, raising an error if it fails.

Finds the running headers and footers of the whole document.

Finds the running headers and footers of the whole document, raising an error if it fails.

Extracts spans — runs of text sharing one text state — as PdfElixide.Document.Span structs.

Extracts the spans of the page at the given zero-based index.

Extracts spans, raising an error if it fails.

Extracts the spans of the page at the given zero-based index, raising an error if it fails.

Reads the page at the given zero-based index as typed regions.

Reads pages as typed regions, raising an error if it fails.

Reads the page at the given zero-based index as typed regions, raising an error if it fails.

Detects the tables of the page at the given zero-based index.

Detects tables, raising an error if it fails.

Detects the tables of the page at the given zero-based index, raising an error if it fails.

Extracts text content.

Extracts the text content of the page at the given zero-based index.

Extracts text content, raising an error if it fails.

Extracts the text content of the page at the given zero-based index, raising an error if it fails.

Extracts text lines, each with its bounding box and constituent words as a PdfElixide.Document.TextLine struct.

Extracts the text lines of the page at the given zero-based index.

Extracts text lines, raising an error if it fails.

Extracts the text lines of the page at the given zero-based index, raising an error if it fails.

Converts the document to plain text.

Converts the page at the given zero-based index to plain text.

Converts the document to plain text, raising an error if it fails.

Converts the page at the given zero-based index to plain text, raising an error if it fails.

Extracts words, each with its bounding box and font metadata as a PdfElixide.Document.Word struct.

Extracts the words of the page at the given zero-based index.

Extracts words, raising an error if it fails.

Extracts the words of the page at the given zero-based index, raising an error if it fails.

Conversion

Converts the whole document to a Word (DOCX) file and returns its bytes. See office_opts/0 for options.

Converts the whole document to a Word (DOCX) file, raising an error if it fails.

Converts the document to HTML.

Converts the page at the given zero-based index to HTML.

Converts the document to HTML, raising an error if it fails.

Converts the page at the given zero-based index to HTML, raising an error if it fails.

Converts the document to Markdown.

Converts the page at the given zero-based index to Markdown.

Converts the document to Markdown, raising an error if it fails.

Converts the page at the given zero-based index to Markdown, raising an error if it fails.

Converts the whole document to a PowerPoint (PPTX) file and returns its bytes. See office_opts/0 for options.

Converts the whole document to a PowerPoint (PPTX) file, raising an error if it fails.

Converts the whole document to an Excel (XLSX) file and returns its bytes. See office_opts/0 for options.

Converts the whole document to an Excel (XLSX) file, raising an error if it fails.

Graphics, images and annotations

Reads the annotations of the whole document.

Reads the annotations of the page at the given zero-based index.

Reads the annotations of the whole document, raising an error if it fails.

Reads the annotations of the page at the given zero-based index, raising an error if it fails.

Extracts the fonts of the whole document.

Extracts the fonts referenced by the page at the given zero-based index.

Extracts the fonts of the whole document, raising an error if it fails.

Extracts the fonts referenced by the page at the given zero-based index, raising an error if it fails.

Extracts the raster images of the whole document.

Extracts the raster images of the page at the given zero-based index.

Extracts the raster images of the whole document, raising an error if it fails.

Extracts the raster images of the page at the given zero-based index, raising an error if it fails.

Extracts the straight lines of the whole document.

Extracts the straight lines of the page at the given zero-based index.

Extracts the straight lines of the whole document, raising an error if it fails.

Extracts the straight lines of the page at the given zero-based index, raising an error if it fails.

Extracts the vector paths of the whole document.

Extracts the vector paths of the page at the given zero-based index.

Extracts the vector paths of the whole document, raising an error if it fails.

Extracts the vector paths of the page at the given zero-based index, raising an error if it fails.

Extracts the rectangles of the whole document.

Extracts the rectangles of the page at the given zero-based index.

Extracts the rectangles of the whole document, raising an error if it fails.

Extracts the rectangles of the page at the given zero-based index, raising an error if it fails.

Rendering

Renders every page to an image and returns a new PDF built from those images. See dpi_opts/0 for options.

Renders the whole document to a raster PDF, raising an error if it fails.

Renders the page at the given zero-based index to a PdfElixide.Document.RenderedPage. See render_opts/0 for options.

Renders the page to a raster image, raising an error if it fails.

Renders a single named ink's plate for the page at the given zero-based index. See dpi_opts/0 for options.

Renders one named ink's plate, raising an error if it fails.

Renders ink plates for the page at the given zero-based index as PdfElixide.Document.SeparationPlate structs. See dpi_opts/0 for options.

Renders every ink plate for the page, raising an error if it fails.

Types

Options accepted as the :adaptive key of span_merging_opts/0, tuning how a space threshold is derived from a page's gap statistics. Consulted only when :use_adaptive_threshold resolves to true.

Options accepted by the chars and chars! functions.

A span-extraction tuning preset. A profile changes the TJ-offset and word-margin thresholds used to turn glyphs into spans, before any word clustering happens.

Options accepted by the to_html and to_html! functions.

Options for inks/3.

Options accepted by the to_markdown and to_markdown! functions.

Options accepted by to_docx/2, to_pptx/2 and to_xlsx/2, and their bang variants.

Options accepted by open/2, open!/2, from_binary/2, and from_binary!/2.

Options for per-page text extraction: every text_opts/0 key except :on_page_error.

Options accepted by the to_plain_text and to_plain_text! functions.

How a :region (or :exclude_regions) decides whether an object is inside it.

Options for render/3.

Options accepted by the running_regions and running_regions! functions.

Options accepted by the search and search! functions.

Options tuning how glyph runs are merged into spans, accepted as :span_merging.

Options accepted by the spans and spans! functions.

Options accepted by the structured and structured! functions.

t()

A handle on an open PDF document.

Options tuning the spatial table detector, accepted directly by the tables functions. For the text-conversion variant, see text_table_detection_opts/0.

Options accepted by the tables and tables! functions: every table_detection_opts/0 key, plus

Options accepted by the text_lines and text_lines! functions.

Options accepted by the text and text! functions.

The :table_detection option of the text and to_plain_text functions: every table_detection_opts/0 key except :text_fallback.

Options accepted by the words and words! functions.

Opening and lifecycle

authenticate(document, password)

@spec authenticate(t(), binary()) :: {:ok, boolean()} | {:error, PdfElixide.Error.t()}

Authenticates against the document's encryption with the given password.

Returns {:ok, true} if authentication succeeded (or the PDF is not encrypted), {:ok, false} if the password was wrong, or {:error, reason} on a PDF/crypto error.

This is a password check, so a wrong password is a normal {:ok, false} result — unlike open/2's :password option, where it is an {:error, %PdfElixide.Error{reason: :wrong_password}} because the document cannot be produced.

The password is a byte string and is not required to be valid UTF-8 — see open_opts/0, whose :password option takes the same values.

An encrypted document read before it is authenticated extracts as empty — text/2 answers "", search/2 answers []. A first successful authentication reloads the document, so everything after it answers as though the handle had been opened with open/2's :password, and it costs about what opening the document cost — a rejected password too. What that re-read records is listed by structured_warnings/1, whether the password was accepted, rejected, or the check failed with an error.

Like clear_search_index/1 and close/1, and unlike every other read here, this takes the document's lock exclusively, so it waits for in-flight calls on the handle and blocks new ones for its duration. Authenticate before sharing the document with other processes, not after — see the Concurrency guide.

authenticate!(doc, password)

@spec authenticate!(t(), binary()) :: boolean()

Same as authenticate/2 but raises on error.

Still returns false (does not raise) for a wrong password.

close(document)

@spec close(t()) :: :ok

Releases the document's native memory without waiting for garbage collection.

A document holds its PDF data in native memory, normally freed only when the BEAM garbage-collects the handle. close/1 frees it early, which matters for long-lived processes that open many documents. Calling it is optional and idempotent.

It takes the handle's lock exclusively, where reads take it shared, so it waits for every in-flight call on the same document — and an extraction can hold its share of that lock for seconds. The memory is released as soon as the handle is idle, not preemptively. That is one of the exclusive calls the Concurrency guide is about.

Afterwards, functions that read the document return {:error, %PdfElixide.Error{reason: :closed}}, and their bang variants raise it. version/1, source_path/1 and page_count/1 keep working, since they read the struct rather than the native handle — page_count/1 only for a document whose count was determined at open. Any PdfElixide.Document.Image, PdfElixide.Document.Font or PdfElixide.Document.Table handles already extracted from the document remain valid, owning their data independently. Closing also releases the document's internal warning list.

doc = Document.open!("sample.pdf")
text = Document.text!(doc, 0)
:ok = Document.close(doc)

closed?(document)

@spec closed?(t()) :: boolean()

Returns whether the document has been released with close/1.

encrypted?(document)

@spec encrypted?(t()) :: boolean()

Returns whether the PDF document is encrypted.

A closed document or a :panic error raises — see the "Errors versus exceptions" section of PdfElixide.Error. Nothing else can fail here.

from_binary(bytes, opts \\ [])

@spec from_binary(binary(), open_opts()) ::
  {:ok, t()} | {:error, PdfElixide.Error.t()}

Opens a PDF document from the given binary data.

Takes bytes you already have — an HTTP response body, a database blob — so no path is involved; use open/2 to read a file. source_path/1 is nil for the resulting document.

from_binary!(bytes, opts \\ [])

@spec from_binary!(binary(), open_opts()) :: t()

Opens a PDF document from the given binary data, raising an error if it fails.

Takes bytes you already have — an HTTP response body, a database blob — so no path is involved; use open!/2 to read a file.

open(path, opts \\ [])

@spec open(Path.t(), open_opts()) :: {:ok, t()} | {:error, PdfElixide.Error.t()}

Opens a PDF document from the specified file path.

The path is handed to the operating system unchanged — see the "File paths" section of PdfElixide.

open!(path, opts \\ [])

@spec open!(Path.t(), open_opts()) :: t()

Opens a PDF document from the specified file path, raising an error if it fails.

The path is handed to the operating system unchanged — see the "File paths" section of PdfElixide.

page(doc, index)

@spec page(t(), non_neg_integer()) ::
  {:ok, PdfElixide.Document.Page.t()} | {:error, PdfElixide.Error.t()}

Returns a PdfElixide.Document.Page handle for the page at the given zero-based index.

Builds nothing but the handle itself, and reads no page content until an extractor is called on it. An index at or past the page count is %PdfElixide.Error{reason: :out_of_range}.

page!(doc, index)

Same as page/2 but raises an error if it fails.

page_count(document)

@spec page_count(t()) :: {:ok, non_neg_integer()} | {:error, PdfElixide.Error.t()}

Returns the number of pages in the given PDF document.

The count arrives with the handle from the call that opens the document and is cached on the struct, so this normally costs nothing and keeps working after close/1, as version/1 does.

If no count could be cached at open, each call reads it from the live document and may return an error. Authenticating an encrypted document can make the count available; closing the document makes this fallback fail.

page_count!(doc)

@spec page_count!(t()) :: non_neg_integer()

Returns the number of pages in the given PDF document, raising an error if it fails.

pages(doc)

@spec pages(t()) ::
  {:ok, [PdfElixide.Document.Page.t()]} | {:error, PdfElixide.Error.t()}

Returns a PdfElixide.Document.Page handle for every page in the document.

The list is eager, but its handles hold no native resources. Returns an error if the page count cannot be read; see page_count/1. Enumerate the document directly to walk its pages lazily.

pages!(doc)

@spec pages!(t()) :: [PdfElixide.Document.Page.t()]

Same as pages/1 but raises an error if it fails.

source_path(document)

@spec source_path(t()) :: Path.t() | nil

Returns the file path from which the document was loaded, or nil if it was loaded from binary data.

version(document)

@spec version(t()) :: {non_neg_integer(), non_neg_integer()}

Returns the PDF specification version of the given document as a {major, minor} tuple.

Metadata and structure

Text extraction

chars(doc, page_index_or_opts \\ [])

@spec chars(t(), chars_opts() | non_neg_integer()) ::
  {:ok, [PdfElixide.Document.Char.t()]} | {:error, PdfElixide.Error.t()}

Extracts characters, each with its bounding box, font metadata and typographic placement as a PdfElixide.Document.Char struct.

With a keyword list (or nothing) as the second argument, returns every page's characters concatenated into a single flat list, in page order. With a zero-based integer, returns that single page's characters instead.

Document.chars(doc)
Document.chars(doc, region: heading.bbox)
Document.chars(doc, 0)
Document.chars(doc, 0, region_mode: :fully_contained)

This is the most memory-hungry extractor here — one struct per glyph, each carrying its own text and font-name binary — so the whole-document form is the one most worth avoiding on a large document. See the "Whole-document extraction and memory" section of PdfElixide.Document, and consider spans/1, which describes the same text in runs.

See chars_opts/0 for the available options.

chars(document, page_index, opts)

@spec chars(t(), non_neg_integer(), chars_opts()) ::
  {:ok, [PdfElixide.Document.Char.t()]} | {:error, PdfElixide.Error.t()}

Extracts the characters of the page at the given zero-based index.

See chars_opts/0 for the available options.

chars!(doc, page_index_or_opts \\ [])

@spec chars!(t(), chars_opts() | non_neg_integer()) :: [PdfElixide.Document.Char.t()]

Extracts characters, raising an error if it fails.

chars!(doc, page_index, opts)

Extracts the characters of the page at the given zero-based index, raising an error if it fails.

running_regions(document, opts \\ [])

@spec running_regions(t(), running_regions_opts()) ::
  {:ok, %{required(non_neg_integer()) => [PdfElixide.Document.Span.t()]}}
  | {:error, PdfElixide.Error.t()}

Finds the running headers and footers of the whole document.

Returns a map from zero-based page index to the spans of that page's running header and footer text. A page with none is absent from the map, and a document with none returns %{}. Each page's spans are listed top to bottom, then left to right.

regions = Document.running_regions!(doc)
#=> %{0 => [%Span{text: "Quarterly Report"}, ...], 1 => [...]}

The call records nothing on the document, so later extractions are not filtered by it. To drop the text from an extraction, pass a page's boxes back as :exclude_regions:

boxes = regions |> Map.get(3, []) |> Enum.map(& &1.bbox)
Document.text(doc, 3, exclude_regions: boxes)

Detection is chosen separately for each area. Tagged header or footer artifacts are used when present. Otherwise, text must repeat at about the same position in the top or bottom 15% of at least :threshold of the document's pages. A one-page document therefore reports only tagged headers and footers. Bare page numbers are excluded. A margin line whose number changes, such as "Page 3 of 10", is usually reported on every page except the first, whatever :threshold is; a later page's box also covers the first page's copy.

The spans use the same coordinate-frame rules as the spans of a search/2 match. See Rotated pages and extracted geometry.

Every page is read, so a page that cannot be read fails the call. Like any span extraction, the call can change what a later extraction returns on a document with competing /ActualText replacements; see "The /ActualText hazard" in the Concurrency guide.

See running_regions_opts/0 for the available options, and the "Running headers and footers" section of the Text extraction guide for removing the text from extraction or from a saved copy.

running_regions!(doc, opts \\ [])

@spec running_regions!(t(), running_regions_opts()) :: %{
  required(non_neg_integer()) => [PdfElixide.Document.Span.t()]
}

Finds the running headers and footers of the whole document, raising an error if it fails.

spans(doc, page_index_or_opts \\ [])

@spec spans(t(), spans_opts() | non_neg_integer()) ::
  {:ok, [PdfElixide.Document.Span.t()]} | {:error, PdfElixide.Error.t()}

Extracts spans — runs of text sharing one text state — as PdfElixide.Document.Span structs.

With a keyword list (or nothing) as the second argument, returns every page's spans concatenated into a single flat list, in page order. With a zero-based integer, returns that single page's spans instead.

Document.spans(doc)
Document.spans(doc, reading_order: :column_aware)
Document.spans(doc, 0)
Document.spans(doc, 0, span_merging: [preset: :aggressive])

A span covers a run of text rather than one glyph, so this is the cheaper way to ask for what chars/1 returns. The whole-document form still builds every page's spans in memory at once — see the "Whole-document extraction and memory" section of PdfElixide.Document.

See spans_opts/0 for the available options.

spans(document, page_index, opts)

@spec spans(t(), non_neg_integer(), spans_opts()) ::
  {:ok, [PdfElixide.Document.Span.t()]} | {:error, PdfElixide.Error.t()}

Extracts the spans of the page at the given zero-based index.

See spans_opts/0 for the available options.

spans!(doc, page_index_or_opts \\ [])

@spec spans!(t(), spans_opts() | non_neg_integer()) :: [PdfElixide.Document.Span.t()]

Extracts spans, raising an error if it fails.

spans!(doc, page_index, opts)

Extracts the spans of the page at the given zero-based index, raising an error if it fails.

structured(doc, page_index_or_opts \\ [])

Reads pages as PdfElixide.Document.StructuredPage structs containing typed regions. See PdfElixide.Document.StructuredPage.Region for grouping and PdfElixide.Document.StructuredPage.Region.kind/0 for roles.

With options or no second argument, returns a list of page structs in page order. With a zero-based page index, returns one page struct.

Document.structured(doc)
Document.structured(doc, 0)
Document.structured(doc, 0, column_mode: :two)

The whole-document form builds every page's regions in memory at once — see the "Whole-document extraction and memory" section of PdfElixide.Document.

See structured_opts/0 for the available options.

structured(document, page_index, opts)

@spec structured(t(), non_neg_integer(), structured_opts()) ::
  {:ok, PdfElixide.Document.StructuredPage.t()} | {:error, PdfElixide.Error.t()}

Reads the page at the given zero-based index as typed regions.

See structured_opts/0 for the available options.

structured!(doc, page_index_or_opts \\ [])

Reads pages as typed regions, raising an error if it fails.

structured!(doc, page_index, opts)

Reads the page at the given zero-based index as typed regions, raising an error if it fails.

tables(doc, page_index_or_opts \\ [])

@spec tables(t(), tables_opts() | non_neg_integer()) ::
  {:ok, [PdfElixide.Document.Table.t()]} | {:error, PdfElixide.Error.t()}

Detects tables, as PdfElixide.Document.Table structs.

With a keyword list (or nothing) as the second argument, returns every page's tables concatenated into a single flat list, in page order. With a zero-based integer, returns that single page's tables instead.

Document.tables(doc)
Document.tables(doc, 0)
Document.tables(doc, 0, preset: :strict, row_tolerance: 1.5)

Detection is heuristic — see PdfElixide.Document.Table for the :real_grid? flag and how to filter out likely false positives, and tables_opts/0 for the available options.

Every returned table stays resident behind its handle until PdfElixide.Document.Table.close/1 or GC — see the "Whole-document extraction and memory" section of PdfElixide.Document.

tables(document, page_index, opts)

@spec tables(t(), non_neg_integer(), tables_opts()) ::
  {:ok, [PdfElixide.Document.Table.t()]} | {:error, PdfElixide.Error.t()}

Detects the tables of the page at the given zero-based index.

Returns {:ok, []} when the page has no detectable table. See tables_opts/0 for the available options.

tables!(doc, page_index_or_opts \\ [])

@spec tables!(t(), tables_opts() | non_neg_integer()) :: [
  PdfElixide.Document.Table.t()
]

Detects tables, raising an error if it fails.

tables!(doc, page_index, opts)

@spec tables!(t(), non_neg_integer(), tables_opts()) :: [
  PdfElixide.Document.Table.t()
]

Detects the tables of the page at the given zero-based index, raising an error if it fails.

text(doc, page_index_or_opts \\ [])

@spec text(t(), text_opts() | non_neg_integer()) ::
  {:ok, String.t()} | {:error, PdfElixide.Error.t()}

Extracts text content.

With a keyword list (or nothing) as the second argument, extracts the whole document — every page's text concatenated in order, separated by a form-feed (\f) page separator. With a zero-based integer, extracts that single page instead.

Document.text(doc)
Document.text(doc, extract_tables: false)
Document.text(doc, 0)
Document.text(doc, 0, region: word.bbox)

By default, a page that fails to extract fails the whole-document call. Pass on_page_error: :skip to tolerate it; see the :on_page_error section of text_opts/0 for the complete behavior. A document with no pages returns "".

The whole-document form builds every page's text in memory at once — see the "Whole-document extraction and memory" section of PdfElixide.Document.

See text_opts/0 for the available options.

text(document, page_index, opts)

@spec text(t(), non_neg_integer(), page_text_opts()) ::
  {:ok, String.t()} | {:error, PdfElixide.Error.t()}

Extracts the text content of the page at the given zero-based index.

See page_text_opts/0 for the available options.

text!(doc, page_index_or_opts \\ [])

@spec text!(t(), text_opts() | non_neg_integer()) :: String.t()

Extracts text content, raising an error if it fails.

text!(doc, page_index, opts)

@spec text!(t(), non_neg_integer(), page_text_opts()) :: String.t()

Extracts the text content of the page at the given zero-based index, raising an error if it fails.

text_lines(doc, page_index_or_opts \\ [])

@spec text_lines(t(), text_lines_opts() | non_neg_integer()) ::
  {:ok, [PdfElixide.Document.TextLine.t()]} | {:error, PdfElixide.Error.t()}

Extracts text lines, each with its bounding box and constituent words as a PdfElixide.Document.TextLine struct.

With a keyword list (or nothing) as the second argument, returns every page's lines concatenated into a single flat list, in page order. With a zero-based integer, returns that single page's lines instead.

Document.text_lines(doc)
Document.text_lines(doc, include_artifacts: false)
Document.text_lines(doc, 0)
Document.text_lines(doc, 0, region: heading.bbox)

The whole-document form builds every page's lines in memory at once — see the "Whole-document extraction and memory" section of PdfElixide.Document for when to prefer the per-page arity.

See text_lines_opts/0 for the available options.

text_lines(document, page_index, opts)

@spec text_lines(t(), non_neg_integer(), text_lines_opts()) ::
  {:ok, [PdfElixide.Document.TextLine.t()]} | {:error, PdfElixide.Error.t()}

Extracts the text lines of the page at the given zero-based index.

See text_lines_opts/0 for the available options.

text_lines!(doc, page_index_or_opts \\ [])

@spec text_lines!(t(), text_lines_opts() | non_neg_integer()) :: [
  PdfElixide.Document.TextLine.t()
]

Extracts text lines, raising an error if it fails.

text_lines!(doc, page_index, opts)

@spec text_lines!(t(), non_neg_integer(), text_lines_opts()) :: [
  PdfElixide.Document.TextLine.t()
]

Extracts the text lines of the page at the given zero-based index, raising an error if it fails.

to_plain_text(doc, page_index_or_opts \\ [])

@spec to_plain_text(t(), plain_text_opts() | non_neg_integer()) ::
  {:ok, String.t()} | {:error, PdfElixide.Error.t()}

Converts the document to plain text.

This generally reflows prose paragraphs and orders them by detected column; tables and some columnar layouts retain internal line breaks. text/2 instead preserves inferred visual rows. The Text extraction guide compares the two.

With a keyword list (or nothing) as the second argument, converts the whole document, joining pages with "\n\n---\n\n" — close to the --- break to_markdown/1 uses, and not the form feed of text/1. With a zero-based integer, converts that single page instead.

Document.to_plain_text(doc)
Document.to_plain_text(doc, extract_tables: false)
Document.to_plain_text(doc, 0)

There is no :on_page_error here: a page that cannot be converted fails the whole call, as it does for to_markdown/1 and to_html/1. text/1 is the only whole-document text call that can skip one — see the "When a page cannot be read" section of PdfElixide.Document. A document that is encrypted and could not be decrypted converts to an empty string rather than failing.

The whole-document form builds the entire conversion in memory at once — see the "Whole-document extraction and memory" section of PdfElixide.Document.

See plain_text_opts/0 for the available options.

to_plain_text(document, page_index, opts)

@spec to_plain_text(t(), non_neg_integer(), plain_text_opts()) ::
  {:ok, String.t()} | {:error, PdfElixide.Error.t()}

Converts the page at the given zero-based index to plain text.

See plain_text_opts/0 for the available options, and to_plain_text/2 for how this differs from text/3.

to_plain_text!(doc, page_index_or_opts \\ [])

@spec to_plain_text!(t(), plain_text_opts() | non_neg_integer()) :: String.t()

Converts the document to plain text, raising an error if it fails.

to_plain_text!(doc, page_index, opts)

@spec to_plain_text!(t(), non_neg_integer(), plain_text_opts()) :: String.t()

Converts the page at the given zero-based index to plain text, raising an error if it fails.

words(doc, page_index_or_opts \\ [])

@spec words(t(), words_opts() | non_neg_integer()) ::
  {:ok, [PdfElixide.Document.Word.t()]} | {:error, PdfElixide.Error.t()}

Extracts words, each with its bounding box and font metadata as a PdfElixide.Document.Word struct.

With a keyword list (or nothing) as the second argument, returns every page's words concatenated into a single flat list, in page order. With a zero-based integer, returns that single page's words instead.

Document.words(doc)
Document.words(doc, include_artifacts: false)
Document.words(doc, 0)
Document.words(doc, 0, region: heading.bbox)

The whole-document form builds every page's words in memory at once — see the "Whole-document extraction and memory" section of PdfElixide.Document for when to prefer the per-page arity.

See words_opts/0 for the available options.

words(document, page_index, opts)

@spec words(t(), non_neg_integer(), words_opts()) ::
  {:ok, [PdfElixide.Document.Word.t()]} | {:error, PdfElixide.Error.t()}

Extracts the words of the page at the given zero-based index.

See words_opts/0 for the available options.

words!(doc, page_index_or_opts \\ [])

@spec words!(t(), words_opts() | non_neg_integer()) :: [PdfElixide.Document.Word.t()]

Extracts words, raising an error if it fails.

words!(doc, page_index, opts)

Extracts the words of the page at the given zero-based index, raising an error if it fails.

Conversion

to_docx(document, opts \\ [])

@spec to_docx(t(), office_opts()) :: {:ok, binary()} | {:error, PdfElixide.Error.t()}

Converts the whole document to a Word (DOCX) file and returns its bytes. See office_opts/0 for options.

An encrypted document must be authenticated first; until then this returns {:error, %PdfElixide.Error{reason: :encrypted}}. See the "Whole-document extraction and memory" section of PdfElixide.Document for its memory use, and the Office conversion guide for fidelity and conversion back to PDF.

to_docx!(doc, opts \\ [])

@spec to_docx!(t(), office_opts()) :: binary()

Converts the whole document to a Word (DOCX) file, raising an error if it fails.

to_html(doc, page_index_or_opts \\ [])

@spec to_html(t(), html_opts() | non_neg_integer()) ::
  {:ok, String.t()} | {:error, PdfElixide.Error.t()}

Converts the document to HTML.

With a keyword list (or nothing) as the second argument, converts the whole document, wrapping each page in a <div class="page" data-page="N"> element whose N is the one-based page number. With a zero-based integer, converts that single page instead, without the wrapper.

Document.to_html(doc)
Document.to_html(doc, detect_headings: false)
Document.to_html(doc, 0)

The result is an HTML fragment, not a standalone document: there is no doctype, no <html>/<body>, and no stylesheet — bring your own, or wrap the fragment yourself. A page with no extractable content converts to an empty string, as does a document that is encrypted and could not be decrypted.

The whole-document form builds the entire conversion in memory at once, which :include_images can make considerably larger — see the "Whole-document extraction and memory" section of PdfElixide.Document.

Escaping

Text taken from the PDF is escaped before it reaches the fragment — &, <, > and " become entities in span text, headings and table cells alike, in :preserve_layout mode as well — so a crafted document cannot inject markup. ' is left as-is, which is safe only because every attribute the converter emits is double-quoted: don't re-quote the fragment with single quotes.

A /Link annotation's URI is escaped too, and an anchor is emitted only for the http, https, mailto, tel, ftp and ftps schemes; any other target keeps its link text and loses the link. Anchors carry rel="noopener noreferrer".

The one input that is not escaped is :image_output_dir; see html_opts/0. So the fragment is safe to render as raw HTML as long as that path is yours and not an untrusted one.

See html_opts/0 for the available options.

to_html(document, page_index, opts)

@spec to_html(t(), non_neg_integer(), html_opts()) ::
  {:ok, String.t()} | {:error, PdfElixide.Error.t()}

Converts the page at the given zero-based index to HTML.

See html_opts/0 for the available options, and the "Escaping" section of to_html/2 for what in the fragment is escaped.

to_html!(doc, page_index_or_opts \\ [])

@spec to_html!(t(), html_opts() | non_neg_integer()) :: String.t()

Converts the document to HTML, raising an error if it fails.

to_html!(doc, page_index, opts)

@spec to_html!(t(), non_neg_integer(), html_opts()) :: String.t()

Converts the page at the given zero-based index to HTML, raising an error if it fails.

to_markdown(doc, page_index_or_opts \\ [])

@spec to_markdown(t(), markdown_opts() | non_neg_integer()) ::
  {:ok, String.t()} | {:error, PdfElixide.Error.t()}

Converts the document to Markdown.

With a keyword list (or nothing) as the second argument, converts the whole document, joining pages with a --- thematic break — note that this differs from text/1, which uses a form feed. With a zero-based integer, converts that single page instead.

Document.to_markdown(doc)
Document.to_markdown(doc, detect_headings: false)
Document.to_markdown(doc, 0)

The whole-document form builds the entire conversion in memory at once, which :include_images can make considerably larger — see the "Whole-document extraction and memory" section of PdfElixide.Document.

A page that is a scan with no text layer converts to nothing rather than to a note saying so, and an image too large to inline is left out. Both are reported by structured_warnings/1 instead, as :no_text_layer and :image_suppressed, each naming the page — so a caller decides whether to surface them, where, and in what wording.

See markdown_opts/0 for the available options.

to_markdown(document, page_index, opts)

@spec to_markdown(t(), non_neg_integer(), markdown_opts()) ::
  {:ok, String.t()} | {:error, PdfElixide.Error.t()}

Converts the page at the given zero-based index to Markdown.

See markdown_opts/0 for the available options.

to_markdown!(doc, page_index_or_opts \\ [])

@spec to_markdown!(t(), markdown_opts() | non_neg_integer()) :: String.t()

Converts the document to Markdown, raising an error if it fails.

to_markdown!(doc, page_index, opts)

@spec to_markdown!(t(), non_neg_integer(), markdown_opts()) :: String.t()

Converts the page at the given zero-based index to Markdown, raising an error if it fails.

to_pptx(document, opts \\ [])

@spec to_pptx(t(), office_opts()) :: {:ok, binary()} | {:error, PdfElixide.Error.t()}

Converts the whole document to a PowerPoint (PPTX) file and returns its bytes. See office_opts/0 for options.

It accepts the same options and has the same authentication requirement as to_docx/2.

to_pptx!(doc, opts \\ [])

@spec to_pptx!(t(), office_opts()) :: binary()

Converts the whole document to a PowerPoint (PPTX) file, raising an error if it fails.

to_xlsx(document, opts \\ [])

@spec to_xlsx(t(), office_opts()) :: {:ok, binary()} | {:error, PdfElixide.Error.t()}

Converts the whole document to an Excel (XLSX) file and returns its bytes. See office_opts/0 for options.

It accepts the same options and has the same authentication requirement as to_docx/2.

to_xlsx!(doc, opts \\ [])

@spec to_xlsx!(t(), office_opts()) :: binary()

Converts the whole document to an Excel (XLSX) file, raising an error if it fails.

Graphics, images and annotations

annotations(document)

@spec annotations(t()) ::
  {:ok, [PdfElixide.Document.Annotation.t()]} | {:error, PdfElixide.Error.t()}

Reads the annotations of the whole document.

Returns every page's annotations concatenated into a single flat list, in page order, as PdfElixide.Document.Annotation structs. Each carries its zero-based :page index. Returns {:ok, []} when the document has no annotations.

This builds every page's annotations in memory at once — see the "Whole-document extraction and memory" section of PdfElixide.Document for when to prefer annotations/2.

annotations(document, page_index)

@spec annotations(t(), non_neg_integer()) ::
  {:ok, [PdfElixide.Document.Annotation.t()]} | {:error, PdfElixide.Error.t()}

Reads the annotations of the page at the given zero-based index.

Returns {:ok, []} when the page has no annotations. Each annotation — a link, sticky note, highlight, form widget, and so on — is carried as a PdfElixide.Document.Annotation struct.

annotations!(doc)

@spec annotations!(t()) :: [PdfElixide.Document.Annotation.t()]

Reads the annotations of the whole document, raising an error if it fails.

annotations!(doc, page_index)

@spec annotations!(t(), non_neg_integer()) :: [PdfElixide.Document.Annotation.t()]

Reads the annotations of the page at the given zero-based index, raising an error if it fails.

fonts(document)

@spec fonts(t()) ::
  {:ok, [PdfElixide.Document.Font.t()]} | {:error, PdfElixide.Error.t()}

Extracts the fonts of the whole document.

Returns every page's fonts concatenated into a single flat list, in page order, as PdfElixide.Document.Font structs. A font used on several pages appears once per page.

A page whose fonts cannot be read contributes nothing and does not fail the call — see fonts/2 for what that covers. This is the only whole-document extractor that tolerates such a page by default, and it has no option to say otherwise; the "When a page cannot be read" section of PdfElixide.Document has the rule for the rest.

Each returned font holds the embedded font program behind its handle until PdfElixide.Document.Font.close/1 or GC, one handle per page per font — see the "Whole-document extraction and memory" section of PdfElixide.Document.

fonts(document, page_index)

@spec fonts(t(), non_neg_integer()) ::
  {:ok, [PdfElixide.Document.Font.t()]} | {:error, PdfElixide.Error.t()}

Extracts the fonts referenced by the page at the given zero-based index.

Returns {:ok, []} when the page references no fonts. Each font is carried as a PdfElixide.Document.Font struct with its metadata; the raw embedded font program (when present) is pulled on demand with PdfElixide.Document.Font.data/1.

An empty list also covers a page that could not be read — one whose page-tree entry, /Resources reference or fonts do not resolve yields no fonts rather than an error. Only an out-of-range index and a failed handle are errors, and there is no strict variant that reports the difference.

fonts!(doc)

@spec fonts!(t()) :: [PdfElixide.Document.Font.t()]

Extracts the fonts of the whole document, raising an error if it fails.

fonts!(doc, page_index)

@spec fonts!(t(), non_neg_integer()) :: [PdfElixide.Document.Font.t()]

Extracts the fonts referenced by the page at the given zero-based index, raising an error if it fails.

images(document)

@spec images(t()) ::
  {:ok, [PdfElixide.Document.Image.t()]} | {:error, PdfElixide.Error.t()}

Extracts the raster images of the whole document.

Returns every page's images concatenated into a single flat list, in page order, as PdfElixide.Document.Image structs. The pixel data is not carried on the struct — encode it on demand with PdfElixide.Document.Image.to_binary/2 or PdfElixide.Document.Image.save/3.

What is deferred there is the encode, not the load: every image's decoded pixels — or its original JPEG bytes — stay resident behind its handle until PdfElixide.Document.Image.close/1 or GC. See the "Whole-document extraction and memory" section of PdfElixide.Document.

An image that is too small, or whose encoding cannot be decoded, is left out of the list rather than reported — see the "Which images are extracted" section of PdfElixide.Document.Image. That omission is per image: a page that cannot be read still fails the call, as the "When a page cannot be read" section of PdfElixide.Document describes.

images(document, page_index)

@spec images(t(), non_neg_integer()) ::
  {:ok, [PdfElixide.Document.Image.t()]} | {:error, PdfElixide.Error.t()}

Extracts the raster images of the page at the given zero-based index.

Returns {:ok, []} when the page has no images. Each image — a photo, logo, or scanned picture — is carried as a PdfElixide.Document.Image struct, which holds the metadata and a handle rather than the pixel data itself; encode it on demand with PdfElixide.Document.Image.to_binary/2 or PdfElixide.Document.Image.save/3.

It also returns {:ok, []} when every image on the page was skipped, or the page's content stream could not be parsed — see the "Which images are extracted" section of PdfElixide.Document.Image.

images!(doc)

@spec images!(t()) :: [PdfElixide.Document.Image.t()]

Extracts the raster images of the whole document, raising an error if it fails.

images!(doc, page_index)

@spec images!(t(), non_neg_integer()) :: [PdfElixide.Document.Image.t()]

Extracts the raster images of the page at the given zero-based index, raising an error if it fails.

lines(document)

@spec lines(t()) ::
  {:ok, [PdfElixide.Document.Path.t()]} | {:error, PdfElixide.Error.t()}

Extracts the straight lines of the whole document.

Returns every page's lines concatenated into a single flat list, in page order, as PdfElixide.Document.Path structs — the values paths/1 returns, narrowed to those classified as single straight segments (see the "Rectangles and straight lines" section of PdfElixide.Document.Path for which shapes those are). These are vector graphics; text_lines/1 is the unrelated text extractor.

This builds every page's lines in memory at once — see the "Whole-document extraction and memory" section of PdfElixide.Document for when to prefer lines/2.

lines(document, page_index)

@spec lines(t(), non_neg_integer()) ::
  {:ok, [PdfElixide.Document.Path.t()]} | {:error, PdfElixide.Error.t()}

Extracts the straight lines of the page at the given zero-based index.

Returns {:ok, []} when the page draws no shape classified as a single straight segment. See the "Rectangles and straight lines" section of PdfElixide.Document.Path for which shapes qualify.

lines!(doc)

@spec lines!(t()) :: [PdfElixide.Document.Path.t()]

Extracts the straight lines of the whole document, raising an error if it fails.

lines!(doc, page_index)

@spec lines!(t(), non_neg_integer()) :: [PdfElixide.Document.Path.t()]

Extracts the straight lines of the page at the given zero-based index, raising an error if it fails.

paths(document)

@spec paths(t()) ::
  {:ok, [PdfElixide.Document.Path.t()]} | {:error, PdfElixide.Error.t()}

Extracts the vector paths of the whole document.

Returns every page's paths concatenated into a single flat list, in page order, as PdfElixide.Document.Path structs.

This builds every page's paths in memory at once — see the "Whole-document extraction and memory" section of PdfElixide.Document for when to prefer paths/2.

paths(document, page_index)

@spec paths(t(), non_neg_integer()) ::
  {:ok, [PdfElixide.Document.Path.t()]} | {:error, PdfElixide.Error.t()}

Extracts the vector paths of the page at the given zero-based index.

Returns {:ok, []} when the page has no vector graphics. Each path — a line, curve, rectangle, or filled shape — is carried as a PdfElixide.Document.Path struct.

paths!(doc)

@spec paths!(t()) :: [PdfElixide.Document.Path.t()]

Extracts the vector paths of the whole document, raising an error if it fails.

paths!(doc, page_index)

@spec paths!(t(), non_neg_integer()) :: [PdfElixide.Document.Path.t()]

Extracts the vector paths of the page at the given zero-based index, raising an error if it fails.

rects(document)

@spec rects(t()) ::
  {:ok, [PdfElixide.Document.Path.t()]} | {:error, PdfElixide.Error.t()}

Extracts the rectangles of the whole document.

Returns every page's rectangles concatenated into a single flat list, in page order, as PdfElixide.Document.Path structs — the values paths/1 returns, narrowed to those classified as rectangles (see the "Rectangles and straight lines" section of PdfElixide.Document.Path for which shapes those are).

This builds every page's rectangles in memory at once — see the "Whole-document extraction and memory" section of PdfElixide.Document for when to prefer rects/2.

rects(document, page_index)

@spec rects(t(), non_neg_integer()) ::
  {:ok, [PdfElixide.Document.Path.t()]} | {:error, PdfElixide.Error.t()}

Extracts the rectangles of the page at the given zero-based index.

Returns {:ok, []} when the page draws no shape classified as a rectangle. See the "Rectangles and straight lines" section of PdfElixide.Document.Path for which shapes qualify.

rects!(doc)

@spec rects!(t()) :: [PdfElixide.Document.Path.t()]

Extracts the rectangles of the whole document, raising an error if it fails.

rects!(doc, page_index)

@spec rects!(t(), non_neg_integer()) :: [PdfElixide.Document.Path.t()]

Extracts the rectangles of the page at the given zero-based index, raising an error if it fails.

Rendering

rasterize(document, opts \\ [])

@spec rasterize(t(), dpi_opts()) :: {:ok, binary()} | {:error, PdfElixide.Error.t()}

Renders every page to an image and returns a new PDF built from those images. See dpi_opts/0 for options.

{:ok, flat} = PdfElixide.Document.rasterize(doc, dpi: 200)
File.write!("flat.pdf", flat)

Removes the text layer, fields, annotations, links, outline and tagging. Visible content remains in the pixels, so this is not redaction.

Every output page is US Letter with one-inch margins. Source page sizes are not preserved; rotation is baked into the image and /Rotate resets to 0.

Memory grows with page count and compressed image size; the size limit applies per page. See "Rasterizing a whole document" in the Rendering guide.

Calls are serialized across the node; see Concurrency.

rasterize!(doc, opts \\ [])

@spec rasterize!(t(), dpi_opts()) :: binary()

Renders the whole document to a raster PDF, raising an error if it fails.

render(document, page_index, opts \\ [])

@spec render(t(), non_neg_integer(), render_opts()) ::
  {:ok, PdfElixide.Document.RenderedPage.t()} | {:error, PdfElixide.Error.t()}

Renders the page at the given zero-based index to a PdfElixide.Document.RenderedPage. See render_opts/0 for options.

{:ok, rendered} = PdfElixide.Document.render(doc, 0, dpi: 150)
File.write!("page0.png", rendered.data)

Renders the page's CropBox intersected with its MediaBox, falling back to the MediaBox or, when unreadable, US Letter. A :region render instead returns :invalid_pdf for an unreadable MediaBox. Default layer visibility is honoured and output depends on available fonts.

Returns {:error, %PdfElixide.Error{reason: :unsupported}} when the requested size exceeds the pixel limit. Lower :dpi, use a smaller :fit box, or set :max_output_pixels to have the page rendered smaller instead — a budget that is itself over the limit is refused rather than applied. See the Rendering guide for sizing, page coverage, fonts and differences from text extraction.

Takes the document lock shared; see Concurrency.

render!(doc, page_index, opts \\ [])

Renders the page to a raster image, raising an error if it fails.

separation(document, page_index, ink, opts \\ [])

@spec separation(t(), non_neg_integer(), String.t(), dpi_opts()) ::
  {:ok, PdfElixide.Document.SeparationPlate.t()}
  | {:error, PdfElixide.Error.t()}

Renders a single named ink's plate for the page at the given zero-based index. See dpi_opts/0 for options.

Use a name reported by inks/3 or separations/3, such as "Cyan" or "PANTONE 185 C". An unpainted ink yields an all-zero plate.

Faster than separations/3 for one ink, but the whole ink set still counts toward the pixel limit and can return an :unsupported error. Use separations/3 for several inks; see "Separation plates" in the Rendering guide for the cost of each call.

separation!(doc, page_index, ink, opts \\ [])

Renders one named ink's plate, raising an error if it fails.

separations(document, page_index, opts \\ [])

@spec separations(t(), non_neg_integer(), dpi_opts()) ::
  {:ok, [PdfElixide.Document.SeparationPlate.t()]}
  | {:error, PdfElixide.Error.t()}

Renders ink plates for the page at the given zero-based index as PdfElixide.Document.SeparationPlate structs. See dpi_opts/0 for options.

{:ok, plates} = PdfElixide.Document.separations(doc, 0, dpi: 300)
Enum.map(plates, &{&1.ink, &1.width, &1.height})

Includes the four process inks and the page's spot inks; declared but unpainted inks yield all-zero plates. See "Separation plates" in the Rendering guide for ordering and ink discovery.

Returns {:error, %PdfElixide.Error{reason: :unsupported}} when the whole plate set exceeds the pixel limit; lower :dpi. Use separation/4 for one ink.

separations!(doc, page_index, opts \\ [])

@spec separations!(t(), non_neg_integer(), dpi_opts()) :: [
  PdfElixide.Document.SeparationPlate.t()
]

Renders every ink plate for the page, raising an error if it fails.

Types

adaptive_threshold_opts()

@type adaptive_threshold_opts() :: [
  median_multiplier: float() | nil,
  min_threshold_pt: float() | nil,
  max_threshold_pt: float() | nil,
  use_iqr: boolean() | nil,
  min_samples: non_neg_integer() | nil
]

Options accepted as the :adaptive key of span_merging_opts/0, tuning how a space threshold is derived from a page's gap statistics. Consulted only when :use_adaptive_threshold resolves to true.

Every key defaults to nil, meaning "keep the preset's value".

bookmark_opts()

@type bookmark_opts() :: [
  depth: pos_integer() | :all,
  title_prefix: String.t() | nil,
  ignore_case: boolean(),
  include_front_matter: boolean()
]

Options for bookmark_segments/2, PdfElixide.Editor.bookmark_segments/2 and PdfElixide.Editor.split_by_bookmarks/2.

  • :depth — how deep in the outline a bookmark may sit and still start a segment: a positive integer, 1 being the top level, or :all. Defaults to 1.
  • :title_prefix — start segments only at bookmarks whose title, with surrounding whitespace trimmed, begins with this string. Defaults to nil, which matches every bookmark.
  • :ignore_case — match :title_prefix ignoring case, including letters whose case forms differ in length, such as ß and SS. Defaults to false.
  • :include_front_matter — return the pages before the first matching bookmark as a segment of their own, with a nil title. Defaults to true.

chars_opts()

@type chars_opts() :: [
  region: PdfElixide.Geometry.Rect.t() | nil,
  region_mode: region_mode(),
  exclude_layers: [String.t()],
  exclude_inks: [String.t()]
]

Options accepted by the chars and chars! functions.

  • :region — a PdfElixide.Geometry.Rect keeping only the characters inside it. Defaults to nil.
  • :region_mode — how :region matches; see region_mode/0. Defaults to :intersects.
  • :exclude_layers — names of optional-content (OCG) layers to suppress, as listed by layers/1. Defaults to [].
  • :exclude_inks — names of Separation/DeviceN inks to suppress, as listed by inks/3. Defaults to [].

dpi_opts()

@type dpi_opts() :: [{:dpi, pos_integer()}]

Options for separations/3, separation/4 and rasterize/2.

  • :dpi — resolution in dots per inch. Defaults to 150, and must be positive.

These calls have a fixed per-page limit and return :unsupported above it. See Sizing the output.

extraction_profile()

@type extraction_profile() ::
  :conservative
  | :tj_heavy
  | :aggressive
  | :balanced
  | :academic
  | :policy
  | :form
  | :government
  | :scanned_ocr
  | :adaptive

A span-extraction tuning preset. A profile changes the TJ-offset and word-margin thresholds used to turn glyphs into spans, before any word clustering happens.

Passing one also changes how a rotated page's geometry is reported — see the "Legacy extraction controls" section of words_opts/0.

html_opts()

@type html_opts() :: [
  preserve_layout: boolean(),
  detect_headings: boolean(),
  extract_tables: boolean(),
  include_images: boolean(),
  embed_images: boolean(),
  image_output_dir: String.t() | nil,
  include_form_fields: boolean(),
  max_image_pixels: non_neg_integer() | nil,
  reading_order: :structure_tree | :column_aware | :top_to_bottom
]

Options accepted by the to_html and to_html! functions.

Only options that affect HTML output are exposed. Any other key raises ArgumentError — to_markdown/2's :bold_markers or :strip_running_headers_footers, say, or text/2's :expand_ligatures — as does a declared key given a value of the wrong type. See the "Errors versus exceptions" section of PdfElixide.Error.

  • :preserve_layout — emit one absolutely positioned <div> per text span, carrying that span's coordinates and font size in inline CSS (pt units), in place of the semantic flow of <p>/<h1>/<ul> elements. Colour is written only for non-black text. This mode emits only those positioned spans: headings, lists and tables are not produced, so :detect_headings and :extract_tables have no effect under it. Defaults to false.

    The result is not directly renderable and needs two corrections from you. The generated CSS uses the PDF's own user-space coordinates verbatim, so top is measured from the bottom of the page while CSS top measures from the top: flip it with top = height - y, taking the page height from PdfElixide.Document.Page.height/1. And the per-page wrapper to_html/1 emits carries no styling, so it is not a positioned containing block — add position: relative and an explicit size to each wrapper, or every page will pile up in the same place.

  • :detect_headings — cluster font sizes to emit <h1>–<h6> elements instead of plain <p> paragraphs. Defaults to true.

  • :extract_tables — detect tables and render them as <table>/<thead>/<tbody> markup, with colspan and rowspan on the cells. Defaults to true.

  • :include_images — emit <img> elements. Defaults to false for the reason given in markdown_opts/0. Images are appended to the end of the page in a <div class="page-images"> rather than placed at their position in the content.

  • :embed_images — behaves exactly as it does for Markdown; see markdown_opts/0. Defaults to true.

  • :image_output_dir — directory to write extracted images to, used only when :include_images is true and :embed_images is false. Behaves exactly as it does for Markdown, including the filename-collision caveat and the requirement that it be valid UTF-8 rather than an arbitrary path — see markdown_opts/0. Defaults to nil.

    It is interpolated into the src attribute without HTML escaping, so a directory whose name contains " or & produces malformed markup and a crafted one can inject attributes. Never build it from untrusted input. It is the only unescaped input here — see the "Escaping" section of to_html/2.

  • :include_form_fields — inline AcroForm field values at their positions on the page. Defaults to true.

  • :max_image_pixels — behaves exactly as it does for Markdown, including nil meaning the built-in 16 MP limit rather than "no limit"; see markdown_opts/0. Defaults to nil.

  • :reading_order — behaves exactly as it does for Markdown; see markdown_opts/0. Defaults to :structure_tree.

Calling to_html/1 is equivalent to to_html/2 with no options.

inks_opts()

@type inks_opts() :: [{:deep, boolean()}]

Options for inks/3.

  • :deep — also walk into the Form XObjects the page's content stream invokes, returning every ink reachable from the page rather than only those it declares itself. Defaults to false.

The default reads the page's own /Resources and nothing else, so it can miss inks the page actually paints with — a colorant declared inside a Form XObject's own resources is absent from the list even though the extraction filters still honor it. deep: true returns the complete set.

What it costs is failure modes: deep: true parses the page's content stream and every form's, so it reports an error where the default would have succeeded — an undecryptable or malformed stream, or an XObject tree nested deeper than the parser follows. Prefer the default when the page's own declarations are enough, and deep: true when building a picker a user will choose from.

markdown_opts()

@type markdown_opts() :: [
  detect_headings: boolean(),
  extract_tables: boolean(),
  include_images: boolean(),
  embed_images: boolean(),
  image_output_dir: String.t() | nil,
  include_form_fields: boolean(),
  strip_running_headers_footers: boolean(),
  max_image_pixels: non_neg_integer() | nil,
  reading_order: :structure_tree | :column_aware | :top_to_bottom,
  bold_markers: :conservative | :aggressive
]

Options accepted by the to_markdown and to_markdown! functions.

  • :detect_headings — cluster font sizes to emit # headings instead of plain paragraphs. Defaults to true.

  • :extract_tables — detect tables and render them as Markdown tables. Defaults to true.

  • :include_images — emit ![](…) image syntax. Defaults to false, since embedded images can add hundreds of kilobytes per page.

  • :embed_images — when true, images are inlined as base64 data URIs and :image_output_dir is ignored. When false, they are written to :image_output_dir and referenced by path — and if that option is nil, no image is emitted at all. Only applies when :include_images is true. Defaults to true.

  • :image_output_dir — directory to write extracted images to, used only when :include_images is true and :embed_images is false. It is created if missing, and one that cannot be created is an :io error; the writes themselves are best-effort, since an image that fails to encode or write is dropped. Defaults to nil.

    This is a String.t/0 and not a path in the sense the "File paths" section of PdfElixide describes: it is also pasted into the src attribute of every generated image reference, so it must be valid UTF-8 and a value that is not raises ArgumentError naming the key. A directory whose name has no UTF-8 spelling cannot be used here even where the filesystem allows one.

    Give every concurrent conversion its own directory. Filenames are fixed — pageN_M.png, one-based page then one-based position in that page's kept image list — so two conversions writing to one directory overwrite each other's files, non-atomically. That includes two conversions of the same document, since :max_image_pixels changes which images are kept and so renumbers the rest.

  • :include_form_fields — inline AcroForm field values at their positions on the page. Defaults to true.

  • :strip_running_headers_footers — drop text lines that repeat in the top/bottom band of a majority of pages. Defaults to false.

  • :max_image_pixels — skip images whose width times height exceeds this count. nil means the built-in 16 MP limit, not "no limit" — pass a large integer to lift it, or 0 to skip every image. Defaults to nil.

  • :reading_order — how text blocks are ordered: :structure_tree (follow a tagged PDF's structure tree, falling back to an XY-cut), :column_aware, or :top_to_bottom. Defaults to :structure_tree.

  • :bold_markers — :conservative applies ** only to content-bearing text; :aggressive also wraps whitespace-only spans. Defaults to :conservative.

Calling to_markdown/1 is equivalent to to_markdown/2 with no options.

An unknown key, or a declared key given a value of the wrong type, raises ArgumentError naming the offending key; see the "Errors versus exceptions" section of PdfElixide.Error.

office_opts()

@type office_opts() :: [{:mode, :auto | :layout | :flow}]

Options accepted by to_docx/2, to_pptx/2 and to_xlsx/2, and their bang variants.

  • :mode — how pages are laid out in the Office file. Defaults to :auto.
    • :layout places each run of text at its position on the PDF page.
    • :flow groups text into paragraphs or cells that flow like an ordinary document.
    • :auto uses :layout for short documents and :flow for long ones.

See Choosing a mode in the Office conversion guide for the trade-off, and Round trips before converting the result back to PDF.

An unknown key, or a :mode other than those three, raises ArgumentError.

open_opts()

@type open_opts() :: [{:password, binary() | nil}]

Options accepted by open/2, open!/2, from_binary/2, and from_binary!/2.

  • :password — password used to authenticate against an encrypted PDF. When the password is wrong, the call returns {:error, %PdfElixide.Error{reason: :wrong_password}} (or raises, for the bang variants). When omitted or nil, only the empty password is tried automatically.

The password is a byte string, not necessarily valid UTF-8: a password for a PDF of encryption revision 4 or lower is PDFDocEncoded, so "caf" <> <<0xE9>> is a legitimate password that no UTF-8 spelling can express. authenticate/2 accepts and rejects exactly the same values.

To check a password against an already-open document without treating a wrong one as an error, use authenticate/2, which returns {:ok, false} rather than a :wrong_password error.

An unknown key, or a :password that is neither a binary nor nil, raises ArgumentError — see the "Errors versus exceptions" section of PdfElixide.Error.

page_text_opts()

@type page_text_opts() :: [
  extract_tables: boolean(),
  expand_ligatures: boolean(),
  table_detection: text_table_detection_opts() | nil,
  region: PdfElixide.Geometry.Rect.t() | nil,
  region_mode: region_mode(),
  exclude_regions: [PdfElixide.Geometry.Rect.t()],
  exclude_regions_mode: region_mode(),
  exclude_layers: [String.t()],
  exclude_inks: [String.t()]
]

Options for per-page text extraction: every text_opts/0 key except :on_page_error.

plain_text_opts()

@type plain_text_opts() :: [
  extract_tables: boolean(),
  table_detection: text_table_detection_opts() | nil,
  reading_order: :structure_tree | :column_aware | :top_to_bottom,
  include_form_fields: boolean()
]

Options accepted by the to_plain_text and to_plain_text! functions.

  • :extract_tables — detect tables and render them inline as space-padded, column-aligned rows. Defaults to true.
  • :table_detection — a keyword list tuning the spatial table detector; see text_table_detection_opts/0. Only consulted when :extract_tables is true. Defaults to nil.
  • :reading_order — how text blocks are ordered: :structure_tree, :column_aware or :top_to_bottom. Defaults to :structure_tree. On an untagged document, :structure_tree and :column_aware produce the same column-aware order; :top_to_bottom does not detect columns.
  • :include_form_fields — inline AcroForm field values at their positions on the page. Defaults to true. text/2 has no such option and always includes them.

:reading_order and :include_form_fields have no effect when a trustworthy structure tree supplies the reading order. The Text extraction guide covers that case and compares these options with text_opts/0.

Calling to_plain_text/1 is equivalent to to_plain_text/2 with no options.

An unknown key, or a declared key given a value of the wrong type, raises ArgumentError naming the offending key; see the "Errors versus exceptions" section of PdfElixide.Error.

region_mode()

@type region_mode() :: :intersects | :fully_contained | {:min_overlap, float()}

How a :region (or :exclude_regions) decides whether an object is inside it.

  • :intersects — any overlap at all counts. This is the default.
  • :fully_contained — the object's bounding box must lie entirely within the region.
  • {:min_overlap, ratio} — at least ratio of the object's area must lie within the region.

ratio must be between 0.0 and 1.0; anything outside that range raises ArgumentError, like any other bad option value. Two behaviors are worth knowing before choosing one:

  • The fraction is of the object's own area, not the region's. A large element clipped by a small region scores low however much of the region it covers, so :min_overlap answers "how much of this object is in the region?", never the reverse.
  • {:min_overlap, 0.0} matches everything, including objects that do not touch the region at all — a non-overlapping object scores 0.0, and the comparison is >=. Use :intersects if you meant "any overlap".

render_opts()

@type render_opts() :: [
  dpi: pos_integer(),
  format: PdfElixide.Document.RenderedPage.format(),
  background: PdfElixide.Color.RGB.t() | nil,
  render_annotations: boolean(),
  jpeg_quality: 1..100,
  exclude_layers: [String.t()],
  fit: {pos_integer(), pos_integer()} | nil,
  region: PdfElixide.Geometry.Rect.t() | nil,
  max_output_pixels: pos_integer() | nil
]

Options for render/3.

  • :dpi — positive resolution in dots per inch. Defaults to 150.
  • :format — :png (the default), :jpeg, or :rgba8 for bare premultiplied pixels; see PdfElixide.Document.RenderedPage for the layout.
  • :background — a PdfElixide.Color.RGB with components in 0.0..1.0, or nil for transparent untouched areas. Defaults to opaque white. With :jpeg, nil leaves untouched areas black.
  • :render_annotations — paint annotation appearances (form fields, stamps, highlights) over the page content. Defaults to true.
  • :jpeg_quality — 1..100, defaulting to 85. Inert unless :format is :jpeg.
  • :exclude_layers — names of optional-content groups to leave unpainted, from layers/1. Defaults to [].
  • :fit — {width, height} in pixels to scale the page into, preserving its aspect ratio, so the result is no larger than the box on either axis. Both dimensions must be positive.
  • :region — a PdfElixide.Geometry.Rect in PDF points in the page's raw, unrotated user space, clipped to the page. An empty intersection returns {:error, %PdfElixide.Error{reason: :out_of_range}}. There is no :region_mode. See Cropping with :region for using extractor boxes on rotated pages; PdfElixide.Geometry.Rect.to_user_space/3 maps a displayed box back.
  • :max_output_pixels — a positive pixel budget. When set, scales an oversized render down instead of refusing it. It can only lower the library's own ceiling, never raise it: a budget above that ceiling is refused with %PdfElixide.Error{reason: :unsupported} naming the key. Defaults to nil, which preserves the requested scale or returns an error.

Cropping still renders the full page, so :region does not reduce rendering cost or the size limit. A :region render returns :unsupported when :max_output_pixels would require scaling the page first.

:region cannot be combined with format: :rgba8, and :fit cannot be combined with :dpi or :region. These combinations raise ArgumentError.

running_regions_opts()

@type running_regions_opts() :: [area: :header | :footer | :both, threshold: float()]

Options accepted by the running_regions and running_regions! functions.

  • :area — which running content to look for: :header, :footer or :both. Defaults to :both.
  • :threshold — the fraction of pages, from 0.0 to 1.0, on which a line must repeat for heuristic detection. It is ignored for an area answered from tagged artifacts. Values outside the range raise ArgumentError. Defaults to 0.8.

search_opts()

@type search_opts() :: [
  literal: boolean(),
  case_insensitive: boolean(),
  whole_word: boolean(),
  max_results: non_neg_integer()
]

Options accepted by the search and search! functions.

  • :literal — treat the pattern as plain text rather than a regular expression. Defaults to true; see the Text search guide for the regular expression syntax literal: false accepts.
  • :case_insensitive — match regardless of case. Defaults to false.
  • :whole_word — require a word boundary at each end of the match, so "cat" finds cat but not category. Defaults to false. The boundaries enclose the whole pattern: under literal: false, "cat|dog" finds cat and dog as whole words. A word boundary needs a word character on the inside, so a pattern that begins or ends in punctuation or whitespace — "(a)" on either path — matches only where the text has a word character right against it.
  • :max_results — stop after this many matches, counted across pages. Defaults to 0, meaning no limit. This bounds the work as well as the list: searching the whole document stops at the page that reaches the limit, leaving the ones after it unread and unindexed.

There is no :page_range: searching one page is what search/3 and search/4 are for.

span_merging_opts()

@type span_merging_opts() :: [
  preset: :default | :aggressive | :conservative | :adaptive | :legacy,
  space_threshold_em_ratio: float() | nil,
  conservative_threshold_pt: float() | nil,
  column_boundary_threshold_pt: float() | nil,
  severe_overlap_threshold_pt: float() | nil,
  use_adaptive_threshold: boolean() | nil,
  adaptive: adaptive_threshold_opts() | nil,
  detect_email_patterns: boolean() | nil,
  email_threshold_multiplier: float() | nil,
  detect_citation_markers: boolean() | nil,
  citation_font_size_ratio: float() | nil,
  merge_tm_tj_runs: boolean() | nil
]

Options tuning how glyph runs are merged into spans, accepted as :span_merging.

  • :preset — the base configuration every other key overrides: :default, :aggressive (splits more readily), :conservative (merges across wider gaps), :adaptive (derives thresholds from page gap statistics), or :legacy. Defaults to :default. The names mean aggression about inserting spaces, so :aggressive produces more word boundaries, not fewer.
  • :space_threshold_em_ratio — gap, as a fraction of font size, that becomes a space.
  • :conservative_threshold_pt — floor gap in points below which no space is inserted.
  • :column_boundary_threshold_pt — gap in points treated as a column break rather than a space.
  • :severe_overlap_threshold_pt — negative gap indicating real glyph overlap.
  • :use_adaptive_threshold — derive the space threshold from page gap statistics.
  • :adaptive — a adaptive_threshold_opts/0 keyword list tuning that derivation. Only consulted when :use_adaptive_threshold resolves to true.
  • :detect_email_patterns / :email_threshold_multiplier — keep addresses from being split at the @.
  • :detect_citation_markers / :citation_font_size_ratio — treat small raised runs as citation markers.
  • :merge_tm_tj_runs — when false, every text-matrix operator starts a fresh span. When true, one that stays on the same line and moves forward by less than a column gap continues the open span instead.

Every key except :preset defaults to nil, meaning "keep the preset's value".

spans_opts()

@type spans_opts() :: [
  reading_order: :top_to_bottom | :column_aware | :structure_tree,
  span_merging: span_merging_opts() | nil,
  region: PdfElixide.Geometry.Rect.t() | nil,
  region_mode: region_mode(),
  exclude_layers: [String.t()],
  exclude_inks: [String.t()]
]

Options accepted by the spans and spans! functions.

  • :reading_order — how spans are ordered: :top_to_bottom (simple geometric sorting), :column_aware (XY-cut column detection), or :structure_tree (column-aware order, with the cells of a tagged table reordered row-major by the structure tree; identical to :column_aware on an untagged document). The same values as markdown_opts/0, but the default here is :top_to_bottom.
  • :span_merging — a span_merging_opts/0 keyword list, or nil for the default merging behavior. Defaults to nil.
  • :region — a PdfElixide.Geometry.Rect keeping only the spans inside it. Defaults to nil.
  • :region_mode — how :region matches; see region_mode/0. Defaults to :intersects.
  • :exclude_layers — names of optional-content (OCG) layers to suppress, as listed by layers/1. Defaults to [].
  • :exclude_inks — names of Separation/DeviceN inks to suppress, as listed by inks/3. Defaults to [].

:span_merging is exclusive with the other options

When :span_merging is set, only :region and :region_mode can be combined with it. Giving :reading_order, :exclude_layers or :exclude_inks a value other than its default raises ArgumentError naming the key.

structured_opts()

@type structured_opts() :: [{:column_mode, :auto | :two | :single}]

Options accepted by the structured and structured! functions.

  • :column_mode — how a two-column body is recognised. :auto splits only when a clear vertical gutter separates two columns of body text; :two always splits, at the detected gutter or else at the page's horizontal midpoint; :single never splits, so every region's :column is nil. Decides only :column; roles are unaffected. Defaults to :auto.

t()

@type t() :: %PdfElixide.Document{
  page_count: non_neg_integer() | nil,
  ref: reference(),
  source_path: Path.t() | nil,
  version: {non_neg_integer(), non_neg_integer()}
}

A handle on an open PDF document.

:version and :page_count are cached when the document is opened and served from the struct thereafter, since both are immutable for a read-only document.

:page_count is nil when the count could not be determined at open — for example, when an encrypted document's page tree needs a password — in which case page_count/1 asks the document instead.

:ref is an internal handle; do not read it or construct the struct directly.

table_detection_opts()

@type table_detection_opts() :: [
  preset: :default | :strict | :relaxed,
  enabled: boolean() | nil,
  horizontal_strategy: :lines | :text | :both | nil,
  vertical_strategy: :lines | :text | :both | nil,
  column_tolerance: float() | nil,
  row_tolerance: float() | nil,
  min_table_cells: non_neg_integer() | nil,
  min_table_columns: non_neg_integer() | nil,
  regular_row_ratio: float() | nil,
  max_table_columns: non_neg_integer() | nil,
  column_merge_threshold: float() | nil,
  v_split_gap: float() | nil,
  text_fallback: boolean() | nil
]

Options tuning the spatial table detector, accepted directly by the tables functions. For the text-conversion variant, see text_table_detection_opts/0.

  • :preset — the base configuration every other key overrides: :default, :strict (demands ruling lines and regular rows) or :relaxed (tolerant, text-driven). Defaults to :default.
  • :horizontal_strategy / :vertical_strategy — what evidence delimits cells on that axis: :lines (ruling lines only), :text (glyph alignment only) or :both.
  • :column_tolerance / :row_tolerance — coordinate slack in points when grouping cells into columns and rows.
  • :min_table_cells / :min_table_columns — smallest grid accepted as a table.
  • :max_table_columns — reject anything wider as a false positive.
  • :regular_row_ratio — fraction of rows that must share the column count.
  • :column_merge_threshold — slack for the post-clustering column merge pass.
  • :v_split_gap — minimum gap between vertical-line groups that splits a cluster.
  • :text_fallback — allow text-only detection when a page has no ruling lines.
  • :enabled — set to false to disable detection entirely.

Every key except :preset defaults to nil, meaning "keep the preset's value".

tables_opts()

@type tables_opts() :: [
  region: PdfElixide.Geometry.Rect.t() | nil,
  region_mode: region_mode(),
  preset: :default | :strict | :relaxed,
  enabled: boolean() | nil,
  horizontal_strategy: :lines | :text | :both | nil,
  vertical_strategy: :lines | :text | :both | nil,
  column_tolerance: float() | nil,
  row_tolerance: float() | nil,
  min_table_cells: non_neg_integer() | nil,
  min_table_columns: non_neg_integer() | nil,
  regular_row_ratio: float() | nil,
  max_table_columns: non_neg_integer() | nil,
  column_merge_threshold: float() | nil,
  v_split_gap: float() | nil,
  text_fallback: boolean() | nil
]

Options accepted by the tables and tables! functions: every table_detection_opts/0 key, plus

  • :region — a PdfElixide.Geometry.Rect keeping only the tables matching it. Defaults to nil. A table whose bbox is nil never matches a region.
  • :region_mode — how :region matches; see region_mode/0. Defaults to :intersects.

Unlike the :table_detection option of the text functions, the detection keys are given flat here rather than nested under one key.

:region keeps the detection options you passed rather than loosening them. Pass preset: :relaxed to ask for that explicitly.

text_lines_opts()

@type text_lines_opts() :: [
  include_artifacts: boolean(),
  region: PdfElixide.Geometry.Rect.t() | nil,
  region_mode: region_mode(),
  word_gap_threshold: float() | nil,
  line_gap_threshold: float() | nil,
  profile: extraction_profile() | nil
]

Options accepted by the text_lines and text_lines! functions.

The same options as words_opts/0 — including the legacy controls :word_gap_threshold and :profile documented there — plus:

  • :line_gap_threshold — the vertical gap in points that starts a new line. nil computes it automatically. Defaults to nil; like the other two legacy controls, prefer leaving it unset.

:word_gap_threshold behaves differently here than it does on words/2,3: nothing re-joins the words a line is built from, so the threshold decides the boundaries within a run that a space has not already fixed.

text_opts()

@type text_opts() :: [
  extract_tables: boolean(),
  expand_ligatures: boolean(),
  table_detection: text_table_detection_opts() | nil,
  region: PdfElixide.Geometry.Rect.t() | nil,
  region_mode: region_mode(),
  exclude_regions: [PdfElixide.Geometry.Rect.t()],
  exclude_regions_mode: region_mode(),
  exclude_layers: [String.t()],
  exclude_inks: [String.t()],
  on_page_error: :skip | :halt
]

Options accepted by the text and text! functions.

  • :extract_tables — detect tables and render them inline as space-padded, column-aligned rows, the padding collapsed to single spaces. Defaults to true; with it off, a recognised table's values come back as ordinary flow text. See "Tables inside a text result" in the Text extraction guide.
  • :expand_ligatures — expand U+FB00–U+FB06 ligatures to their component letters (fi to fi, and so on). Defaults to false.
  • :table_detection — a keyword list tuning the spatial table detector; see text_table_detection_opts/0. Only consulted when :extract_tables is true. Defaults to nil.
  • :region — a PdfElixide.Geometry.Rect keeping only the text inside it. An extracted bbox can be handed straight back in. Defaults to nil.
  • :region_mode — how :region matches; see region_mode/0. Defaults to :intersects.
  • :exclude_regions — a list of rects whose text is dropped. Applied before :region, so exclusion wins. Defaults to [].
  • :exclude_regions_mode — how :exclude_regions match. Defaults to :intersects.
  • :exclude_layers — names of optional-content (OCG) layers to suppress, as listed by layers/1. Defaults to [].
  • :exclude_inks — names of Separation/DeviceN inks to suppress, as listed by inks/3. Defaults to [].
  • :on_page_error — what a page that fails to extract does to the whole-document result: :halt, failing the call with the first such page's error (the default), or :skip it. See below.

:on_page_error and partly extractable documents

Accepted only on the whole-document arity; per-page calls raise ArgumentError. Under the :halt default a failed page fails the call with {:error, %PdfElixide.Error{}}, the failing page's zero-based index prefixed to the message. :skip instead makes a failed page contribute an empty string; its page separator is emitted either way, and the call returns {:ok, text} with that page's content silently missing.

:halt is not a general corruption detector, though: almost every damaged page degrades to empty text rather than an error — an undecodable content stream, missing fonts, a scan with no text layer and an undecryptable document all extract as "". All it can catch is a page whose page-tree entry does not resolve at all — the same page every other whole-document call but fonts/1 fails on, as the "When a page cannot be read" section of PdfElixide.Document describes. Nor do those conditions leave a warning behind: neither structured_warnings/1 nor PdfElixide.Logging.structured_warnings/0 records them, and only PdfElixide.Logging capture makes them visible. The one exception is a page whose object ran into the end of the file before it could be read: it extracts as "" and records an :eof_premature warning on the document.

Layer and ink filtering is exclusive with the other options

When :exclude_layers or :exclude_inks is non-empty, only :region and :region_mode can be combined with it. Giving :extract_tables, :expand_ligatures, :table_detection, :exclude_regions or :exclude_regions_mode a value other than its default raises ArgumentError naming the key.

:reading_order, :include_form_fields and :strip_running_headers_footers are valid for to_markdown/2 but have no effect here, so they are not accepted; passing one raises ArgumentError, as any other undeclared key does.

There is a second way to read a page as text — to_plain_text/2, which returns reflowed paragraphs rather than the page's visual lines and takes a different, smaller set of options. The Text extraction guide compares the two.

text_table_detection_opts()

@type text_table_detection_opts() :: [
  preset: :default | :strict | :relaxed,
  enabled: boolean() | nil,
  horizontal_strategy: :lines | :text | :both | nil,
  vertical_strategy: :lines | :text | :both | nil,
  column_tolerance: float() | nil,
  row_tolerance: float() | nil,
  min_table_cells: non_neg_integer() | nil,
  min_table_columns: non_neg_integer() | nil,
  regular_row_ratio: float() | nil,
  max_table_columns: non_neg_integer() | nil,
  column_merge_threshold: float() | nil,
  v_split_gap: float() | nil
]

The :table_detection option of the text and to_plain_text functions: every table_detection_opts/0 key except :text_fallback.

These functions never use the text-only fallback, so a page with no ruling lines yields no inline table. Passing :text_fallback raises ArgumentError; tables/2,3 accepts it.

words_opts()

@type words_opts() :: [
  include_artifacts: boolean(),
  region: PdfElixide.Geometry.Rect.t() | nil,
  region_mode: region_mode(),
  word_gap_threshold: float() | nil,
  profile: extraction_profile() | nil
]

Options accepted by the words and words! functions.

  • :include_artifacts — keep spans tagged /Artifact (running headers and footers, page numbers, watermarks; ISO 32000-1 §14.8.2.2.1). Defaults to true; false selects the spec-correct variant.
  • :region — a PdfElixide.Geometry.Rect keeping only the words inside it. Defaults to nil.
  • :region_mode — how :region matches; see region_mode/0. Defaults to :intersects.
  • :word_gap_threshold — the inter-glyph gap in points that starts a new word. nil computes it adaptively from page statistics (median character width × 0.3). Defaults to nil.
  • :profile — a extraction_profile/0, or nil for none. Defaults to nil.

Legacy extraction controls

:word_gap_threshold and :profile are retained for compatibility but are deprecated. Prefer leaving them at nil.

  • :word_gap_threshold may have no visible effect because words/2,3 can merge adjacent pieces again. text_lines/2,3 preserves those word boundaries; see text_lines_opts/0.

  • :profile may change word order and bounding boxes, even when set to :conservative: a box shifts slightly on any page, and on a rotated one it comes back in raw user space rather than the displayed frame — see Rotated pages and extracted geometry.

:region composes with everything else here: it is applied after extraction, so it does not discard the thresholds or the profile.