PdfElixide.Document (pdf_elixide v0.10.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, and its reads run concurrently: every function here takes the native handle's lock shared, so N processes extracting from one document do not queue behind each other. There is no need to keep a document inside the process that opened it, nor to open the same file once per worker. authenticate/2 and close/1 are the two exceptions, taking the lock exclusively.

The Concurrency guide has the rest — the shape to fan work out in, what the two exclusive calls do to calls already in flight, why throughput is not linear, and the one 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, images/1, fonts/1, annotations/1, text/1, to_markdown/1 and to_html/1. Each walks all pages inside a single native call and returns one flat list, so its cost scales with the whole document rather than with what the caller keeps. At the moment the call returns, the results exist twice — once as the native vector and once as the Elixir terms encoded from it — so peak usage is roughly double the final list. chars/1 is the extreme: one struct per glyph, each with its own text and font-name binary. spans/1 describes the same text in runs rather than glyphs and is usually the cheaper way to ask.

Three of them hold native memory after the call as well, because each returned struct carries a handle:

  • images/1 — every image's decoded pixels, or its original JPEG bytes, one buffer per image. The struct does not carry the pixel data, but the handle behind it does, from extraction onward; PdfElixide.Document.Image.to_binary/2 defers the encode, not the load.
  • fonts/1 — one handle per page per font, with no sharing across pages, so a single font used throughout a long document yields as many handles as there are pages.
  • tables/1 — the full detected table, including the per-glyph metrics the struct itself omits.

A handle is invisible to the BEAM's memory accounting, so nothing pressures the VM to collect it. 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, so bounding memory needs no extra API — only one page's results are live at a time:

# Constant memory: each page's chars become garbage when the function returns.
Enum.reduce(doc, 0, fn page, acc -> acc + length(Page.chars!(page)) end)

# Or as a lazy sequence.
Stream.flat_map(doc, &Page.chars!/1)

One page is the floor: a page's own results are still built and encoded whole, since there is no incremental encoder at the native boundary.

Concatenating pages this way reproduces the whole-document arity exactly for the list-returning extractors. The three that return one value do not round-trip so simply, since each joins pages itself: text/1 separates them with a form feed and applies :on_page_error (see the ":on_page_error and partly extractable documents" section of text_opts/0), to_markdown/1 joins with a --- break, and to_html/1 wraps each page in a <div class="page">.

Summary

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, named after pdf_oxide's own ExtractionProfile constants. 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 accepted by the to_markdown and to_markdown! functions.

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

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

Options tuning how glyph runs are merged into spans, accepted as :span_merging. pdf_oxide's own bindings expose none of this.

Options accepted by the spans and spans! functions.

t()

A handle on an open PDF document.

Options tuning pdf_oxide's spatial table detector. Accepted directly by the tables functions, and as the :table_detection option of the text functions.

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.

Options accepted by the words and words! functions.

Functions

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.

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

Same as authenticate/2 but raises on error.

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.

Releases the document's native memory immediately.

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

Returns whether the PDF document is encrypted.

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.

Opens a PDF document from the given binary data.

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

Returns whether the PDF document is a Tagged PDF with a structure tree, reporting a structure tree that cannot be read.

Returns whether the PDF document is a Tagged PDF with a structure tree.

Returns whether the PDF document contains XFA (XML Forms Architecture) form data, reporting a catalog that cannot be read.

Returns whether the PDF document contains XFA (XML Forms Architecture) form data.

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.

Reads the document's Info dictionary metadata (title, author, dates, etc.).

Reads the document's Info dictionary metadata, 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.

Reads the document outline — its bookmarks / table of contents.

Reads the document outline, 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 the document's logical page labels — one per page, in page order.

Returns the document's logical page labels, raising an error if it fails.

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

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.

Reads the document's /P permission flags.

Reads the document's permission flags, raising an error if it fails.

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

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.

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

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

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.

Reads the document's XMP (Extensible Metadata Platform) metadata.

Reads the document's XMP metadata, 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".

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. Defaults to [].
  • :exclude_inks — names of Separation/DeviceN inks to suppress. Defaults to [].

extraction_profile()

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

A span-extraction tuning preset, named after pdf_oxide's own ExtractionProfile constants. A profile changes the TJ-offset and word-margin thresholds used to turn glyphs into spans, before any word clustering happens.

html_opts()

@type html_opts() :: [
  preserve_layout: boolean(),
  detect_headings: boolean(),
  extract_tables: boolean(),
  include_images: boolean(),
  embed_images: boolean(),
  image_output_dir: Path.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 the options that upstream actually reads on its HTML path are exposed, so every one of them changes the output. In particular :bold_markers, :annotate_skipped_pages, :strip_running_headers_footers and :expand_ligatures — all valid for to_markdown/2 — are not part of this list, because pdf_oxide never consults them while converting to HTML. Passing one raises ArgumentError, as any other undeclared key does, and so does a declared key given a value of the wrong type; the message names the offending key. 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. Note that 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 reproducing the page needs two corrections from you. Upstream writes the PDF's own user-space coordinates verbatim, so the top value is measured from the bottom of the page while CSS top measures from the top: flip it yourself 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 and gives the spans no page-sized box to resolve against — 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, since embedded images can add hundreds of kilobytes per page. 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 — 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: upstream drops an image that fails to encode or write, so a successful call does not guarantee every image reached disk. It must be a valid-UTF-8 binary like every other path — see the "File paths" section of PdfElixide. Defaults to nil.

    Give every concurrent conversion its own directory. Filenames are upstream's and 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: :max_image_pixels changes which images are kept and so renumbers the rest, making one name mean different pictures in different calls.

    Upstream interpolates the resulting path into the src attribute without HTML escaping, so a directory whose name contains " or & produces malformed markup. Never build this path from untrusted input: a crafted directory name can close the attribute and inject others. It is the only unescaped input on this path — every string taken from the PDF itself is escaped, 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 — skip images whose width times height exceeds this count. nil means pdf_oxide's own 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.

Defaults mirror pdf_oxide's ConversionOptions::default(), so calling to_html/1 is equivalent to to_html/2 with no options.

markdown_opts()

@type markdown_opts() :: [
  detect_headings: boolean(),
  extract_tables: boolean(),
  include_images: boolean(),
  embed_images: boolean(),
  image_output_dir: Path.t() | nil,
  include_form_fields: boolean(),
  strip_running_headers_footers: boolean(),
  expand_ligatures: boolean(),
  annotate_skipped_pages: 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: upstream drops an image that fails to encode or write, so a successful call does not guarantee every image reached disk. It must be a valid-UTF-8 binary like every other path — see the "File paths" section of PdfElixide. Defaults to nil.

    Give every concurrent conversion its own directory. Filenames are upstream's and 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: :max_image_pixels changes which images are kept and so renumbers the rest, making one name mean different pictures in different calls.

  • :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.

  • :expand_ligatures — expand U+FB00U+FB06 ligatures to their component letters ( to fi, and so on). Accepted for forward compatibility, but currently has no effect on Markdown output: upstream applies it only on its plain-text assembly path, which the Markdown converter does not use. Defaults to false.

  • :annotate_skipped_pages — emit a block quote naming any page that is a scan with no usable text layer, rather than rendering it blank. Defaults to true.

  • :max_image_pixels — skip images whose width times height exceeds this count. nil means pdf_oxide's own 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.

Defaults mirror pdf_oxide's ConversionOptions::default(), so 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.

open_opts()

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

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, no authentication attempt is made beyond pdf_oxide's built-in empty-password try.

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. These bytes reach the same upstream check authenticate/2 uses, so the two accept and reject 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 not a binary, raises ArgumentError — 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. The default, and what pdf_oxide uses everywhere it does not take a mode.
  • :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.

:min_overlap details

ratio must be between 0.0 and 1.0; anything outside that range raises ArgumentError, like any other bad option value. The bound is checked here rather than upstream, which validates it nowhere — no other pdf_oxide binding can even select this mode, so an out-of-range value would otherwise fail silently and wrongly (a negative one matches every object, and one above 1.0 matches none).

Two behaviors worth knowing before choosing a value:

  • 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".

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. pdf_oxide's own bindings expose none of this.

  • :preset — the base configuration every other key overrides: :default, :aggressive (merges across wider gaps), :conservative (splits more readily), :adaptive (derives thresholds from page gap statistics), or :legacy. Defaults to :default.
  • :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.

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,
  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 (follow a tagged PDF's structure tree). Defaults to :top_to_bottom. This is pdf_oxide's span-level reading order and names its values differently from the :reading_order of markdown_opts/0, which is a separate upstream type.
  • :span_merging — a span_merging_opts/0 keyword list, or nil for upstream's default merging. 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. Defaults to [].
  • :exclude_inks — names of Separation/DeviceN inks to suppress. Defaults to [].

:span_merging drops the other options

Upstream serves a merging configuration through a call that accepts neither a reading order nor layer/ink filters. So when :span_merging is set, :reading_order, :exclude_layers and :exclude_inks are ignored. :region still applies — it is a post-filter, not an upstream argument.

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 arrive with the handle, from the same native call that opens the document, and are served from the struct thereafter, since both are immutable for a read-only document. A cached count cannot go stale: for an encrypted document upstream either fails or returns the real /Count, and authentication changes only whether the count is readable, never what it is.

:page_count is nil when the count could not be determined at open — an encrypted document whose page tree needs a password, opened without one — in which case page_count/1 asks the document instead.

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 pdf_oxide's spatial table detector. Accepted directly by the tables functions, and as the :table_detection option of the text functions.

  • :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. Ignored on the text path, where upstream forces it to false.
  • :enabled — set to false to disable detection entirely.

Every key except :preset defaults to nil, meaning "keep the preset's value". The Python bindings' table_settings dict reaches only five of these.

tables_opts()

@type tables_opts() :: [
  region: PdfElixide.Geometry.Rect.t() | nil,
  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 overlapping it. Defaults to nil. There is no :region_mode: upstream filters tables by bounding-box intersection only.

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

Note that :region here keeps the detection options you passed, whereas pdf_oxide's own region call silently substitutes its :relaxed preset. 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 upstream deprecation of :word_gap_threshold and :profile documented there — plus:

  • :line_gap_threshold — the vertical gap in points that starts a new line. nil lets upstream compute it. Defaults to nil, and is deprecated upstream alongside the other two.

text_opts()

@type text_opts() :: [
  extract_tables: boolean(),
  expand_ligatures: boolean(),
  table_detection: 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. Defaults to true, matching pdf_oxide's own extract_text.
  • :expand_ligatures — expand U+FB00U+FB06 ligatures to their component letters ( to fi, and so on). Defaults to false. Unlike in markdown_opts/0, this one is live: upstream applies it on the plain-text assembly path, which is exactly the path this function uses.
  • :table_detection — a keyword list tuning the spatial table detector; see table_detection_opts/0. Only consulted when :extract_tables is true, and its :text_fallback key is ignored here — upstream forces it to false on the text path, so a page with no ruling lines yields no tables regardless. Defaults to nil (the upstream default config).
  • :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. Defaults to [].
  • :exclude_inks — names of Separation/DeviceN inks to suppress. Defaults to [].
  • :on_page_error — what a page that fails to extract does to the whole-document result: :skip it (the default) or :halt, failing the call with the first page's error. See below.

:on_page_error and partly extractable documents

Read only on the whole-document arity. text/3 and PdfElixide.Document.Page.text/2 extract one page and always return its error, so the option is accepted and ignored there.

:skip matches pdf_oxide's extract_all_text, which is why it is the default: a failed page contributes an empty string, the page separator is emitted for it either way, and the call still returns {:ok, text} with content silently missing. That is the wrong default for an indexing, archival or compliance pipeline, where a document that lost a page must not look like one that never had it — :halt gives those callers {:error, %PdfElixide.Error{}} instead, with the failing page's zero-based index prefixed to the message.

Note that :halt is not a general corruption detector: upstream degrades almost every damaged page 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 "". What remains, and all :halt can catch, is a page whose page-tree entry cannot be resolved at all. Most other whole-document extractors (chars/1, words/1, text_lines/1, spans/1, paths/1, images/1, annotations/1, tables/1) fail the call on such a page unconditionally, having no upstream counterpart whose policy they must match.

fonts/1 is the exception, and it needs no option: it does have an upstream counterpart — PdfDocument::page_font_face_lookups — and follows its policy, so an unresolvable page contributes no fonts and the document still succeeds. fonts/1 and fonts/2 document what that makes an empty list mean.

Layer and ink filtering drops the other options

pdf_oxide serves layer and ink filtering only through extract_text_filtered / extract_text_filtered_in_rect, which build their own conversion options internally and offer no way to pass ours. So when :exclude_layers or :exclude_inks is non-empty, only :region and :region_mode still apply:extract_tables, :expand_ligatures, :table_detection, :exclude_regions and :exclude_regions_mode fall back to their upstream defaults (:extract_tables to true, the rest to off). The Python bindings have the same limitation and do not document it.

Options to_markdown/2 accepts but this function does not — :reading_order, :include_form_fields, :strip_running_headers_footers — are omitted because upstream's text assembler never reads them. Passing one raises ArgumentError, as any other undeclared key does; see the "Errors versus exceptions" section of PdfElixide.Error.

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, which is the current behavior and what the Python bindings default to; false selects upstream's 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 lets upstream compute 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.

:word_gap_threshold and :profile are deprecated upstream

pdf_oxide plans to move both to a separate advanced API (its Python bindings already emit a DeprecationWarning for them), and :profile in particular is documented as pending removal. :profile also does more than its name suggests: passing any profile switches span extraction to a different, legacy ordering path (XY-cut plus a row-aware sort), so it can change word order and not merely word boundaries — even for :conservative, which is nominally the default profile. Prefer leaving both at nil.

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

Functions

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.

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.

Unlike open/2's :password option — where a wrong password is an {:error, %PdfElixide.Error{reason: :wrong_password}} because the document cannot be produced — this is a password check, so a wrong password is a normal {:ok, false} result and {:error, _} is reserved for PDF/crypto errors.

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.

This is the one read-side function that 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, whose other exclusive call is close/1.

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.

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 rather than glyphs.

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.

close(document)

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

Releases the document's native memory immediately.

A document holds its PDF data in memory on the Rust side, which is normally freed only when the BEAM garbage-collects the handle — invisible to the VM's memory accounting, so nothing pressures it to happen promptly. close/1 frees it now, 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. Immediately means as soon as the handle is idle, not preemptively. That is one of the two 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, which is every document except an encrypted one opened without a password. Any PdfElixide.Document.Image or PdfElixide.Document.Font handles already extracted from the document remain valid — they own their data independently.

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 native panic raises — see the "Errors versus exceptions" section of PdfElixide.Error. Nothing else can fail here: pdf_oxide's encryption check is infallible.

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. Along with text/1 this is the only whole-document extractor that tolerates such a page, and the only one that does so with no option to say otherwise.

Each returned font keeps a native handle, and fonts are not shared across pages, so a single font used throughout a long document yields one handle per page — all of them live at once here. Prefer fonts/2, or PdfElixide.Document.Font.close/1 as you go; 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. Unlike has_structure_tree?/1, there is no strict variant to fall back on; a caller who must know whether the page is readable at all can ask text/3, which does propagate.

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.

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.

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.

has_structure_tree(document)

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

Returns whether the PDF document is a Tagged PDF with a structure tree, reporting a structure tree that cannot be read.

The strict counterpart of has_structure_tree?/1, which cannot distinguish false from a failure. pdf_oxide keeps the three states apart — tagged ({:ok, true}), untagged ({:ok, false}) and unparseable ({:error, %PdfElixide.Error{}}) — and this is where that third one survives.

has_structure_tree?(document)

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

Returns whether the PDF document is a Tagged PDF with a structure tree.

Answers false for a document whose structure tree cannot be read as well as for one that has none: a corrupt /StructTreeRoot is reported the same way an untagged document is. Use has_structure_tree/1 to tell the two apart. A handle that cannot be used at all — a closed document, a native panic — still raises.

has_xfa(document)

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

Returns whether the PDF document contains XFA (XML Forms Architecture) form data, reporting a catalog that cannot be read.

The strict counterpart of has_xfa?/1. Note that only a broken document reaches the error: pdf_oxide already answers {:ok, false} for every structural absence — a catalog that is not a dictionary, a missing /AcroForm, a missing /XFA — so an error here means the catalog itself or the /AcroForm reference could not be resolved.

has_xfa?(document)

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

Returns whether the PDF document contains XFA (XML Forms Architecture) form data.

Answers false for a document whose catalog or /AcroForm entry cannot be read as well as for one that carries no XFA. Use has_xfa/1 to tell the two apart; a closed document or a native panic still raises.

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: each struct's handle holds that image's decoded pixels — or its original JPEG bytes — from extraction onward, so this arity keeps every image in the document resident at once. Prefer images/2, or PdfElixide.Document.Image.close/1 as you go; see the "Whole-document extraction and memory" section of PdfElixide.Document.

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.

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.

metadata(document)

@spec metadata(t()) ::
  {:ok, PdfElixide.Document.Metadata.t()} | {:error, PdfElixide.Error.t()}

Reads the document's Info dictionary metadata (title, author, dates, etc.).

Always returns a PdfElixide.Document.Metadata struct; a document with no /Info dictionary yields one with every field nil. For XMP metadata, see xmp_metadata/1.

metadata!(doc)

@spec metadata!(t()) :: PdfElixide.Document.Metadata.t()

Reads the document's Info dictionary metadata, raising an error if it fails.

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 must be a valid-UTF-8 binary — 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 must be a valid-UTF-8 binary — see the "File paths" section of PdfElixide.

outline(document)

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

Reads the document outline — its bookmarks / table of contents.

Returns the top-level PdfElixide.Document.OutlineItem structs, each of which may carry nested :children, forming a tree. Returns {:ok, []} when the document has no outline.

Nesting deeper than 256 levels is rejected with %PdfElixide.Error{reason: :unsupported} rather than truncated, so a malformed or hostile bookmark tree cannot overflow the native stack while the NIF walks it — an overflow in Rust aborts the OS process rather than raising, so it would take the whole VM down. No real table of contents comes close to the limit.

outline!(doc)

@spec outline!(t()) :: [PdfElixide.Document.OutlineItem.t()]

Reads the document outline, raising an error if it fails.

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.

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.

The exception is a document whose page tree could not be read at open — an encrypted one opened without a password. Nothing is cached for it, so the count is read from the document on every call: an error until authenticate/2 succeeds, the real count afterwards, and {:error, %PdfElixide.Error{reason: :closed}} once the document is closed.

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.

page_labels(document)

@spec page_labels(t()) :: {:ok, [String.t()]} | {:error, PdfElixide.Error.t()}

Returns the document's logical page labels — one per page, in page order.

Page labels are the human-facing page numbers a PDF may define (e.g. "i", "ii", "iii", "1", "2"), independent of the zero-based physical page index. Pages outside any declared label range fall back to their decimal page number, so the returned list always has one entry per page.

See also PdfElixide.Document.Page.label/1 for a single page's label.

page_labels!(doc)

@spec page_labels!(t()) :: [String.t()]

Returns the document's logical page labels, raising an error if it fails.

pages(doc)

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

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

The list is built eagerly, but each handle is just the document and a zero-based index and holds no native resource, so building one costs nothing — see PdfElixide.Document.Page. To walk a large document without materializing every handle, enumerate the document itself: it implements Enumerable over its pages.

Reads the page count cached on the struct, so it raises only for a document whose count could not be determined at open — see page_count/1.

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.

permissions(document)

@spec permissions(t()) ::
  {:ok, PdfElixide.Document.Permissions.t() | nil}
  | {:error, PdfElixide.Error.t()}

Reads the document's /P permission flags.

Returns {:ok, %PdfElixide.Document.Permissions{}} for an encrypted document, or {:ok, nil} when the document is not encrypted (no permission dictionary).

Per the PDF specification these flags are advisory; see PdfElixide.Document.Permissions.

permissions!(doc)

@spec permissions!(t()) :: PdfElixide.Document.Permissions.t() | nil

Reads the document's permission flags, raising 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.

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 when per-glyph detail is not needed. 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.

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 keeps the detected table alive on the Rust side for PdfElixide.Document.Table.to_markdown/2 and friends, so the whole-document form holds all of them at once. Prefer the per-page arity, or PdfElixide.Document.Table.close/1 as you go, on a table-dense document — 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)

A page that fails to extract contributes an empty string to the whole-document result rather than failing the call, matching pdf_oxide. Its separator is emitted regardless, so the result always splits into exactly page_count/1 parts and a skipped page reads as a blank one. Pass on_page_error: :halt to fail the call instead; text_opts/0 describes when a page can fail at all and why the other extractors do not offer the choice.

The whole-document form builds every page's text in memory at once — see the "Whole-document extraction and memory" section of PdfElixide.Document, which also explains why extracting page by page does not reproduce this function's page separators.

See text_opts/0 for the available options.

text(document, page_index, opts)

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

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

See 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(), 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_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 by pdf_oxide 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 — a javascript: or data: target is dropped, keeping the link text and losing the link. Anchors carry rel="noopener noreferrer".

The one input that is not escaped is :image_output_dir, which upstream interpolates into src verbatim; 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.

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.

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.

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.

xmp_metadata(document)

@spec xmp_metadata(t()) ::
  {:ok, PdfElixide.Document.XmpMetadata.t() | nil}
  | {:error, PdfElixide.Error.t()}

Reads the document's XMP (Extensible Metadata Platform) metadata.

Returns {:ok, %PdfElixide.Document.XmpMetadata{}} when the document carries an XMP packet, or {:ok, nil} when it does not. For the classic Info dictionary metadata, see metadata/1.

xmp_metadata!(doc)

@spec xmp_metadata!(t()) :: PdfElixide.Document.XmpMetadata.t() | nil

Reads the document's XMP metadata, raising an error if it fails.