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.
authenticate/2, clear_search_index/1 and close/1 are the exceptions,
taking the lock exclusively. The Concurrency guide
has the rest, including 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, annotations/1, text/1, to_markdown/1 and
to_html/1 — as does search/2. 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. As the call returns, the
results exist twice — as the native vector and as the Elixir terms encoded
from it — so peak usage is roughly double the final list. search/2 given a
:max_results is the one that need not walk every page; see
search_opts/0.
Three of them 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, so bounding memory needs no
extra API — only one page's results are live at a time, and one page is the
floor:
Stream.flat_map(doc, &Page.chars!/1)Concatenating pages this way reproduces the whole-document arity exactly for
the list-returning extractors. The three that return one value 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 joins with a
--- break, and to_html/1 wraps each page in a <div class="page">.
Page boxes and the coordinate origin
Every coordinate this library reports — an extracted bbox, a path operation,
an image's placement — is in the page's own PDF user space: a bottom-left
origin, y increasing upward, measured in points.
The sheet those coordinates fall on is the page's /MediaBox, read with
PdfElixide.Document.Page.media_box/1 as a PdfElixide.Geometry.Rect. Its
origin is usually {0.0, 0.0}, but nothing requires that, and content
coordinates are not rebased on it: a glyph at the very left edge of a page
whose box starts at {10.0, 20.0} reports an x near 10.0. Subtract the
origin yourself when you want offsets from the page corner:
box = PdfElixide.Document.Page.media_box!(page)
{word.bbox.x - box.x, word.bbox.y - box.y}PdfElixide.Document.Page.width/1 and PdfElixide.Document.Page.height/1 are
that rect's :width and :height. All three are normalized to non-negative
dimensions, none is turned to match a rotated page (see below), and a page
with no /MediaBox anywhere above it is an
%PdfElixide.Error{reason: :invalid_pdf} rather than an assumed page size.
There is no reader for /CropBox, /BleedBox, /TrimBox or /ArtBox, which
pdf_oxide does not expose on a read-only document.
Which ancestor an inherited box comes from
/MediaBox and /Rotate are inheritable: a page declaring neither takes them
from an ancestor /Pages node, unambiguously where exactly one ancestor
declares the entry. Where two nested ancestors declare it, which wins is
not stable — upstream resolves the inheritable attributes differently
depending on how many pages have already been read, so the same page can
report a different box, and a different rotation, on a later call. Almost all
documents declare each entry at one level only and are unaffected.
Rotated pages and extracted geometry
A page may carry a /Rotate telling a viewer to display it turned — read it
with PdfElixide.Document.Page.rotation/1. It does not mean every
coordinate handed back is turned with it, and on a rotated page the
extractors do not all report in the same frame:
chars/1,spans/1,paths/1— withrects/1andlines/1, which are the same values narrowed — andimages/1stay in raw, unrotated user space, whatever the rotation.words/1,text_lines/1, the cell boxes oftables/1and the boxes on asearch/2match are mapped into the displayed frame, since upstream builds them on the span pipeline that reorders a rotated page for reading. The mapping is selective: a180-degree page maps everything, while a90- or270-degree page maps only text whose own text matrix is rotated, leaving a horizontal run raw.
So on a 180-degree page, spans/1 and words/1 describing the very same
line report mirrored boxes. Compare or lay out boxes from one extractor,
and use chars/1 or spans/1 when raw page space is what you want. Page
boxes are likewise never swapped, so computing the displayed page size is the
caller's job. A caller that only wants text is unaffected — the distinction
matters when bounding boxes do.
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 for inks/3.
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 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.
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 cached search index, and the page text and boxes it holds.
Releases the cached search 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.
Lists the Separation and DeviceN ink names the page at the given zero-based
index declares — the values :exclude_inks accepts, see text_opts/0.
Sorted and deduplicated.
Lists the page's ink names, raising an error if it fails.
Lists the names of the optional-content groups (OCG layers) the document
declares — the values :exclude_layers accepts, see text_opts/0. Pass one
back unchanged rather than retyping it; the filter matches the exact string.
Lists the document's optional-content group names, 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.
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.
Builds the search index for every page, so a later search/2 does not pay for
the whole document at once.
Builds the search index for every page, 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.
Finds every occurrence of pattern in the document's text, as
PdfElixide.Document.SearchMatch structs.
Finds every occurrence of pattern on the page at the given zero-based index.
Finds every occurrence of pattern, raising an error if it fails.
Finds every occurrence of pattern on the page at the given zero-based index,
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 tables, as PdfElixide.Document.Table structs.
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
@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".
@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— aPdfElixide.Geometry.Rectkeeping only the characters inside it. Defaults tonil.:region_mode— how:regionmatches; seeregion_mode/0. Defaults to:intersects.:exclude_layers— names of optional-content (OCG) layers to suppress, as listed bylayers/1. Defaults to[].:exclude_inks— names of Separation/DeviceN inks to suppress, as listed byinks/3. Defaults to[].
@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.
@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 the options that upstream actually reads on its HTML path are
exposed, so every one of them changes the output. :bold_markers,
:annotate_skipped_pages, :strip_running_headers_footers and
:expand_ligatures — all valid for to_markdown/2 — are therefore absent
here, and passing one raises ArgumentError, 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 (ptunits), 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_headingsand:extract_tableshave no effect under it. Defaults tofalse.The result is not directly renderable and needs two corrections from you. Upstream writes the PDF's own user-space coordinates verbatim, so
topis measured from the bottom of the page while CSStopmeasures from the top: flip it withtop = height - y, taking the page height fromPdfElixide.Document.Page.height/1. And the per-page wrapperto_html/1emits carries no styling, so it is not a positioned containing block — addposition: relativeand 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 totrue.:extract_tables— detect tables and render them as<table>/<thead>/<tbody>markup, withcolspanandrowspanon the cells. Defaults totrue.:include_images— emit<img>elements. Defaults tofalse, 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— whentrue, images are inlined as base64 data URIs and:image_output_diris ignored. Whenfalse, they are written to:image_output_dirand referenced by path — and if that option isnil, no image is emitted at all. Only applies when:include_imagesistrue. Defaults totrue.:image_output_dir— directory to write extracted images to, used only when:include_imagesistrueand:embed_imagesisfalse. 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 — seemarkdown_opts/0. Defaults tonil.It is interpolated into the
srcattribute 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 ofto_html/2.:include_form_fields— inline AcroForm field values at their positions on the page. Defaults totrue.:max_image_pixels— skip images whose width times height exceeds this count.nilmeanspdf_oxide's own 16 MP limit, not "no limit" — pass a large integer to lift it, or0to skip every image. Defaults tonil.: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 own conversion defaults, so calling
to_html/1 is equivalent to to_html/2 with no options.
@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 tofalse.
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.
@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(), 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 totrue.:extract_tables— detect tables and render them as Markdown tables. Defaults totrue.:include_images— emitimage syntax. Defaults tofalse, since embedded images can add hundreds of kilobytes per page.:embed_images— whentrue, images are inlined as base64 data URIs and:image_output_diris ignored. Whenfalse, they are written to:image_output_dirand referenced by path — and if that option isnil, no image is emitted at all. Only applies when:include_imagesistrue. Defaults totrue.:image_output_dir— directory to write extracted images to, used only when:include_imagesistrueand:embed_imagesisfalse. It is created if missing, and one that cannot be created is an:ioerror; the writes themselves are best-effort, since an image that fails to encode or write is dropped. Defaults tonil.This is a
String.t/0and not a path in the sense the "File paths" section ofPdfElixidedescribes: it is also pasted into thesrcattribute of every generated image reference, so it must be valid UTF-8 and a value that is not raisesArgumentErrornaming 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_pixelschanges which images are kept and so renumbers the rest.:include_form_fields— inline AcroForm field values at their positions on the page. Defaults totrue.:strip_running_headers_footers— drop text lines that repeat in the top/bottom band of a majority of pages. Defaults tofalse.:expand_ligatures— expandU+FB00–U+FB06ligatures to their component letters (fitofi, and so on). Accepted for forward compatibility, but currently has no effect on Markdown output; upstream applies it only on the plain-text path used bytext/2. Defaults tofalse.: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 totrue.:max_image_pixels— skip images whose width times height exceeds this count.nilmeanspdf_oxide's own 16 MP limit, not "no limit" — pass a large integer to lift it, or0to skip every image. Defaults tonil.: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—:conservativeapplies**only to content-bearing text;:aggressivealso wraps whitespace-only spans. Defaults to:conservative.
Defaults mirror pdf_oxide's own conversion defaults, 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.
@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 ornil, no authentication attempt is made beyondpdf_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. 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 not a binary, raises ArgumentError
— see the "Errors versus exceptions" section of PdfElixide.Error.
@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 whatpdf_oxideuses everywhere it does not take a mode.:fully_contained— the object's bounding box must lie entirely within the region.{:min_overlap, ratio}— at leastratioof 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_overlapanswers "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 scores0.0, and the comparison is>=. Use:intersectsif you meant "any overlap".
@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 totrue; see the Search guide for the regular expression syntaxliteral: falseaccepts.:case_insensitive— match regardless of case. Defaults tofalse.:whole_word— require a word boundary at each end of the match. Defaults tofalse.:max_results— stop after this many matches, counted across pages. Defaults to0, 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.
@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:aggressiveproduces 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— aadaptive_threshold_opts/0keyword list tuning that derivation. Only consulted when:use_adaptive_thresholdresolves totrue.: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— whenfalse, every text-matrix operator starts a fresh span.
Every key except :preset defaults to nil, meaning "keep the preset's
value".
@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. Note these values differ from the:reading_orderofmarkdown_opts/0, which is a separate setting.:span_merging— aspan_merging_opts/0keyword list, ornilfor upstream's default merging. Defaults tonil.:region— aPdfElixide.Geometry.Rectkeeping only the spans inside it. Defaults tonil.:region_mode— how:regionmatches; seeregion_mode/0. Defaults to:intersects.:exclude_layers— names of optional-content (OCG) layers to suppress, as listed bylayers/1. Defaults to[].:exclude_inks— names of Separation/DeviceN inks to suppress, as listed byinks/3. Defaults to[].
:span_merging drops the other options
A merging configuration is served by 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, being a post-filter.
@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.
: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.
@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 thetextpath, where upstream forces it tofalse.:enabled— set tofalseto disable detection entirely.
Every key except :preset defaults to nil, meaning "keep the preset's
value".
@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— aPdfElixide.Geometry.Rectkeeping only the tables overlapping it. Defaults tonil. 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.
:region keeps the detection options you passed, where pdf_oxide's own
region call substitutes its :relaxed preset. Pass preset: :relaxed to ask
for that explicitly.
@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.nillets upstream compute it. Defaults tonil, and is deprecated upstream alongside the other two.
@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 totrue, matchingpdf_oxide's ownextract_text.:expand_ligatures— expandU+FB00–U+FB06ligatures to their component letters (fitofi, and so on). Defaults tofalse. Unlike inmarkdown_opts/0, it is live here.:table_detection— a keyword list tuning the spatial table detector; seetable_detection_opts/0. Only consulted when:extract_tablesistrue, and its:text_fallbackkey is ignored here — upstream forces it tofalseon the text path, so a page with no ruling lines yields no tables regardless. Defaults tonil(the upstream default config).:region— aPdfElixide.Geometry.Rectkeeping only the text inside it. An extractedbboxcan be handed straight back in. Defaults tonil.:region_mode— how:regionmatches; seeregion_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_regionsmatch. Defaults to:intersects.:exclude_layers— names of optional-content (OCG) layers to suppress, as listed bylayers/1. Defaults to[].:exclude_inks— names of Separation/DeviceN inks to suppress, as listed byinks/3. Defaults to[].:on_page_error— what a page that fails to extract does to the whole-document result::skipit (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. Under the :skip default a failed page contributes an empty string,
its page separator is emitted either way, and the call still returns
{:ok, text} with content silently missing. :halt instead fails the call
with {:error, %PdfElixide.Error{}}, the failing page's zero-based index
prefixed to the message.
: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, which the other whole-document extractors fail on
unconditionally, except fonts/1, which skips it.
Layer and ink filtering drops the other options
Layer and ink filtering is served by a call that builds its own conversion
options internally, 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).
:reading_order, :include_form_fields and
:strip_running_headers_footers are valid for to_markdown/2 but not here,
since the text assembler never reads them; passing one raises
ArgumentError, as any other undeclared key does.
@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 totrue;falseselects the spec-correct variant.:region— aPdfElixide.Geometry.Rectkeeping only the words inside it. Defaults tonil.:region_mode— how:regionmatches; seeregion_mode/0. Defaults to:intersects.:word_gap_threshold— the inter-glyph gap in points that starts a new word.nillets upstream compute it adaptively from page statistics (median character width × 0.3). Defaults tonil.:profile— aextraction_profile/0, ornilfor none. Defaults tonil.
:word_gap_threshold and :profile are deprecated upstream
Both are pending removal in pdf_oxide, and :profile does more than its
name suggests: passing any profile switches span extraction to a legacy
ordering path, so it can change word order and not merely word
boundaries — even for :conservative, nominally the default profile. Prefer
leaving both at nil.
:region composes with everything else here: it is applied after
extraction, so it does not discard the thresholds or the profile.
Functions
@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.
@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.
@spec annotations!(t()) :: [PdfElixide.Document.Annotation.t()]
Reads the annotations of the whole document, raising an error if it fails.
@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.
@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.
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.
Same as authenticate/2 but raises on error.
Still returns false (does not raise) for a wrong password.
@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.
@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.
@spec chars!(t(), chars_opts() | non_neg_integer()) :: [PdfElixide.Document.Char.t()]
Extracts characters, raising an error if it fails.
@spec chars!(t(), non_neg_integer(), chars_opts()) :: [PdfElixide.Document.Char.t()]
Extracts the characters of the page at the given zero-based index, raising an error if it fails.
@spec clear_search_index(t()) :: :ok | {:error, PdfElixide.Error.t()}
Releases the cached search index, and the page text and boxes it holds.
The document stays usable; a later search/2 rebuilds what it needs. Nothing
evicts from this index on its own, so on a large document this is the only
release short of close/1 — see the Search guide.
Unlike the other reads here it takes the handle's lock exclusively, so it waits for calls already in flight — see Concurrency.
@spec clear_search_index!(t()) :: :ok
Releases the cached search index, raising an error if it fails.
@spec close(t()) :: :ok
Releases the document's native memory immediately.
A document holds its PDF data in memory on the Rust side, normally freed only
when the BEAM garbage-collects the handle. 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 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 or PdfElixide.Document.Font handles already
extracted from the document remain valid, owning their data independently.
doc = Document.open!("sample.pdf")
text = Document.text!(doc, 0)
:ok = Document.close(doc)
Returns whether the document has been released with close/1.
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.
@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 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.
@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.
@spec fonts!(t()) :: [PdfElixide.Document.Font.t()]
Extracts the fonts of the whole document, raising an error if it fails.
@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.
@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.
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.
@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. This keeps the three states apart: tagged
({:ok, true}), untagged ({:ok, false}) and unparseable
({:error, %PdfElixide.Error{}}).
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.
@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. Only a broken document reaches the
error: every structural absence — a catalog that is not a dictionary, a
missing /AcroForm, a missing /XFA — already answers {:ok, false}.
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.
@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.
@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.
@spec images!(t()) :: [PdfElixide.Document.Image.t()]
Extracts the raster images of the whole document, raising an error if it fails.
@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.
@spec inks(t(), non_neg_integer(), inks_opts()) :: {:ok, [String.t()]} | {:error, PdfElixide.Error.t()}
Lists the Separation and DeviceN ink names the page at the given zero-based
index declares — the values :exclude_inks accepts, see text_opts/0.
Sorted and deduplicated.
Only the page's own /Resources is read unless inks_opts/0's :deep is
set, which is where that trade-off is explained.
Some names never appear: All and None, DeviceN colorants the colour space
declares to be process components, and colorants declared only inside a pattern
object's own resources or an annotation's appearance stream.
Returns {:ok, []} for a page that declares no colour spaces. There is no
whole-document arity — take the union yourself:
doc |> Enum.flat_map(&PdfElixide.Document.Page.inks!(&1, deep: true)) |> Enum.uniq()
@spec inks!(t(), non_neg_integer(), inks_opts()) :: [String.t()]
Lists the page's ink names, raising an error if it fails.
@spec layers(t()) :: {:ok, [String.t()]} | {:error, PdfElixide.Error.t()}
Lists the names of the optional-content groups (OCG layers) the document
declares — the values :exclude_layers accepts, see text_opts/0. Pass one
back unchanged rather than retyping it; the filter matches the exact string.
They arrive in /OCGs order, neither sorted nor deduplicated, so a display
name two groups share appears twice. A group that cannot be read or declares no
name is skipped, and a document with no optional content yields {:ok, []}.
The list is not exhaustive of what :exclude_layers acts on: filtering matches
whatever group a page's content references, declared here or not.
Lists the document's optional-content group names, raising an error if it fails.
@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.
@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.
@spec lines!(t()) :: [PdfElixide.Document.Path.t()]
Extracts the straight lines of the whole document, raising an error if it fails.
@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.
@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.
@spec metadata!(t()) :: PdfElixide.Document.Metadata.t()
Reads the document's Info dictionary metadata, raising an error if it fails.
@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.
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.
@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. No real
table of contents comes close to the limit.
@spec outline!(t()) :: [PdfElixide.Document.OutlineItem.t()]
Reads the document outline, raising an error if it fails.
@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.
@spec page!(t(), non_neg_integer()) :: PdfElixide.Document.Page.t()
Same as page/2 but raises an error if it fails.
@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, and the real count afterwards.
@spec page_count!(t()) :: non_neg_integer()
Returns the number of pages in the given PDF document, raising an error if it fails.
@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.
Returns the document's logical page labels, raising an error if it fails.
@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.
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.
@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.
@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.
@spec paths!(t()) :: [PdfElixide.Document.Path.t()]
Extracts the vector paths of the whole document, raising an error if it fails.
@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.
@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.
@spec permissions!(t()) :: PdfElixide.Document.Permissions.t() | nil
Reads the document's permission flags, raising an error if it fails.
@spec prepare_search(t()) :: :ok | {:error, PdfElixide.Error.t()}
Builds the search index for every page, so a later search/2 does not pay for
the whole document at once.
Searching already builds the index lazily, so this only moves that cost — see the Search guide.
@spec prepare_search!(t()) :: :ok
Builds the search index for every page, raising an error if it fails.
@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.
@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.
@spec rects!(t()) :: [PdfElixide.Document.Path.t()]
Extracts the rectangles of the whole document, raising an error if it fails.
@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.
@spec search(t(), String.t(), search_opts() | non_neg_integer()) :: {:ok, [PdfElixide.Document.SearchMatch.t()]} | {:error, PdfElixide.Error.t()}
Finds every occurrence of pattern in the document's text, as
PdfElixide.Document.SearchMatch structs.
With a keyword list (or nothing) as the third argument, searches every page and returns the matches in page order. With a zero-based integer, searches that single page instead.
Document.search(doc, "Figure 3")
Document.search(doc, "figure 3", case_insensitive: true)
Document.search(doc, "Figure 3", 0)
Document.search(doc, ~S"Figure \d+", 0, literal: false)The pattern is plain text by default. Pass literal: false to treat it as a
regular expression, which reports an unparseable one as
%PdfElixide.Error{reason: :invalid_pattern}.
A match's boxes cover whole runs of text rather than the matched characters —
see PdfElixide.Document.SearchMatch. Searching builds a per-page index that
is reused by later searches and released by clear_search_index/1 or
close/1. The Search guide covers both.
The whole-document form builds every page's matches in memory at once — see the
"Whole-document extraction and memory" section of PdfElixide.Document for
when to prefer the per-page arity.
See search_opts/0 for the available options.
@spec search(t(), String.t(), non_neg_integer(), search_opts()) :: {:ok, [PdfElixide.Document.SearchMatch.t()]} | {:error, PdfElixide.Error.t()}
Finds every occurrence of pattern on the page at the given zero-based index.
See search_opts/0 for the available options.
@spec search!(t(), String.t(), search_opts() | non_neg_integer()) :: [ PdfElixide.Document.SearchMatch.t() ]
Finds every occurrence of pattern, raising an error if it fails.
@spec search!(t(), String.t(), non_neg_integer(), search_opts()) :: [ PdfElixide.Document.SearchMatch.t() ]
Finds every occurrence of pattern on the page at the given zero-based index,
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.
@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.
@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.
@spec spans!(t(), spans_opts() | non_neg_integer()) :: [PdfElixide.Document.Span.t()]
Extracts spans, raising an error if it fails.
@spec spans!(t(), non_neg_integer(), spans_opts()) :: [PdfElixide.Document.Span.t()]
Extracts the spans of the page at the given zero-based index, raising an error if it fails.
@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.
@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.
@spec tables!(t(), tables_opts() | non_neg_integer()) :: [ PdfElixide.Document.Table.t() ]
Detects tables, raising an error if it fails.
@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.
@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. 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.
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.
@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.
@spec text!(t(), text_opts() | non_neg_integer()) :: String.t()
Extracts text content, raising an error if it fails.
@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.
@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.
@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.
@spec text_lines!(t(), text_lines_opts() | non_neg_integer()) :: [ PdfElixide.Document.TextLine.t() ]
Extracts text lines, raising an error if it fails.
@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.
@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.
@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.
@spec to_html!(t(), html_opts() | non_neg_integer()) :: String.t()
Converts the document to HTML, raising an error if it fails.
@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.
@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.
@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.
@spec to_markdown!(t(), markdown_opts() | non_neg_integer()) :: String.t()
Converts the document to Markdown, raising an error if it fails.
@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.
@spec version(t()) :: {non_neg_integer(), non_neg_integer()}
Returns the PDF specification version of the given document as a {major, minor} tuple.
@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.
@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.
@spec words!(t(), words_opts() | non_neg_integer()) :: [PdfElixide.Document.Word.t()]
Extracts words, raising an error if it fails.
@spec words!(t(), non_neg_integer(), words_opts()) :: [PdfElixide.Document.Word.t()]
Extracts the words of the page at the given zero-based index, raising an error if it fails.
@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.
@spec xmp_metadata!(t()) :: PdfElixide.Document.XmpMetadata.t() | nil
Reads the document's XMP metadata, raising an error if it fails.