Mutable, in-memory PDF editor.
Open a document, apply changes, and write the result:
"form.pdf"
|> PdfElixide.Editor.open!()
|> PdfElixide.Form.put_value!("name", "Ada")
|> PdfElixide.Editor.save!("filled.pdf")
|> PdfElixide.Editor.close()
#=> :okNothing is written until save/3 or to_binary/2 runs, and neither consumes
the editor. Mutating calls return the editor so they compose in a pipeline,
except apply_redactions/1,2, sanitize/1,2 and
PdfElixide.Compliance.convert/3, which return a report of what they did.
close/1 discards unsaved edits.
Incremental saves support only field values and document information. Other pending changes require a full rewrite; see Saving edits for restrictions and errors.
An editor is a mutable handle: rebinding does not fork it. Earlier bindings see later edits too. The Forms guide covers both bang pipelines and tuple-returning calls, plus filling and deferred flattening.
Creating documents
from_markdown/2, from_html/2 and from_plain_text/2 lay text out on new
pages and return an editor holding them. They are simple typesetters rather
than round-trip conversions: lines are not wrapped, and only a limited
Markdown or HTML vocabulary is recognised. from_images/2 places one JPEG or
PNG image on each new page. See the
Creating documents guide for examples,
supported input and character limitations.
Page structure
delete_page/2, move_page/3 and keep_pages/2 use zero-based indices in
the current page order. Deleting a page is not redaction, and bookmarks and
links are not remapped. See Page structure.
Merging and splitting
merge/2 and merge_binary/2 append another document's pages, and
refuse a document they cannot carry intact. extract_pages/2 and
extract_page_ranges/2 write chosen pages to new binaries without changing the
editor, and split_by_bookmarks/2 writes one per bookmarked section. See the
Merging and splitting guide.
Page rotation
set_rotation/3, rotate_page_by/3 and rotate_all_by/2 change how a viewer
displays pages; rotation/2 includes pending changes. See
Page rotation for examples and page geometry.
Page boxes
media_box/2 and crop_box/2 read a page's boxes, pending changes included;
set_media_box/3, set_crop_box/3 and crop_margins/2 change them. Setting
the media box leaves an existing crop box alone. See
Page boxes for what a viewer does with each box
and the limitations.
Erasing regions
erase_region/3 and erase_regions/3 paint white rectangles when the document
is written. Erasing is not redaction: covered content remains recoverable.
Annotations remain above the overlay, and the page's graphics state can change
its coverage even when the call succeeds. See
Erasing regions for coordinates, unsupported
pages, verifying coverage, and flattening before erasing.
Redaction
mark_redactions/1,2 schedules the page's /Redact annotations to be painted
over when the document is written — cosmetic only, the text stays
extractable. apply_redactions/1,2 removes covered text from the page's
streams immediately and irreversibly, leaving covered images and vector
graphics in the file. sanitize/1,2 separately strips document-level metadata,
JavaScript and attachments without changing page content. See the
Redaction guide for workflows and limitations.
Attachments
embed_file/4 adds an attachment and embedded_files/1 includes pending ones.
Attaching to a document with an existing name tree is refused unless a
sanitize/1,2 emptied it. See
Attachments for the workflow and metadata limits.
Document information
set_title/2, set_author/2, set_subject/2, set_keywords/2,
set_creator/2, set_producer/2, set_creation_date/2 and set_mod_date/2
change the /Info dictionary; metadata/1 reads it with pending changes.
See Document information for what
writes preserve and how text, dates, XMP and document identifiers behave.
Encryption
save/3 and to_binary/2 accept :encryption; open/2 and from_binary/2
accept the password of an already-encrypted source. See the
Encryption guide for the workflow, supported
algorithms and operations refused on an encrypted source.
PDF/A
PdfElixide.Compliance.convert/3 rewrites the editor's document in place and
restricts later writes, such as incremental saves and encryption. See the
PDF/A conversion guide for which edits to make
first.
Concurrency
Every call that writes or mutates takes the handle's lock exclusively, so
concurrent editing of a single editor serializes; give each process its own
editor if you need them to work at once. Most accessors take the lock shared,
but not all of them — redaction_count/2 is one that does not. The
Concurrency guide lists which calls fall on which
side.
Summary
Opening and lifecycle
Releases the editor's native memory without waiting for garbage collection.
Returns whether the editor has been released with close/1.
Opens a PDF document for editing from the given binary data.
Opens a PDF document for editing from the given binary data, raising an error if it fails.
Returns whether the editor holds changes that have not been written out.
Opens a PDF document for editing from the specified file path.
Opens a PDF document for editing from the specified file path, raising an error if it fails.
Returns the number of pages the editor currently holds.
Returns the number of pages the editor currently holds, raising an error if it fails.
Returns the file path the editor was opened from, or nil if it has no
source file: one built with from_binary/2, from_markdown/2,
from_html/2, from_plain_text/2 or from_images/2.
Returns the PDF specification version of the document being edited, as a
{major, minor} tuple.
Creating documents
Creates a new document from HTML and opens it for editing.
Creates a new document from HTML and opens it for editing, raising an error if it fails.
Creates a new document from JPEG and PNG images and opens it for editing.
Creates a new document from JPEG and PNG images and opens it for editing, raising an error if it fails.
Creates a new document from Markdown and opens it for editing.
Creates a new document from Markdown and opens it for editing, raising an error if it fails.
Creates a new document from plain text and opens it for editing.
Creates a new document from plain text and opens it for editing, raising an error if it fails.
Saving
Writes the editor to a PDF file at the given path, and returns the editor.
Writes the editor to a PDF file at the given path, raising an error if it fails.
Serialises all in-memory changes into a PDF binary.
Serialises all in-memory changes into a PDF binary, raising an error if it fails.
Page structure
Deletes the page at the given zero-based index, and returns the editor.
Deletes the page at the given zero-based index, raising an error if it fails.
Keeps only the pages at the given zero-based indices, in the order given, and returns the editor.
Keeps only the pages at the given zero-based indices, in the order given, raising an error if it fails.
Moves the page at zero-based index from so that it sits at index to, and
returns the editor.
Moves the page at zero-based index from so that it sits at index to,
raising an error if it fails.
Merging and splitting
Plans a split of the editor's pages at the document's bookmarks, without
writing anything, as PdfElixide.Document.bookmark_segments/2 does.
Plans a split of the editor's pages at the document's bookmarks, raising an error if it fails.
Writes each range of zero-based page indices to its own PDF binary, as
extract_pages/2 does, and returns the binaries in the order of ranges.
Writes each range of zero-based page indices to its own PDF binary, raising an error if it fails.
Writes the pages at the given zero-based indices, in the order given, to a new
PDF binary. The editor's pages and pending edits are not changed, but an editor
with no unsaved edits can report modified?/1 as true afterwards if its
document has an information dictionary, or if an attachment added with
embed_file/4 has already been written.
Writes the pages at the given zero-based indices to a new PDF binary, raising an error if it fails.
Appends every page of the PDF file at path to the end of the editor, and
returns the editor.
Appends every page of the PDF file at path to the end of the editor, raising
an error if it fails.
Appends every page of a PDF binary to the end of the editor, and returns the
editor. Behaves as merge/2 does for a file.
Appends every page of a PDF binary to the end of the editor, raising an error if it fails.
Splits the editor's pages at the document's bookmarks, writing each part to
its own PDF binary as extract_page_ranges/2 does.
Splits the editor's pages at the document's bookmarks, raising an error if it fails.
Page rotation
Turns every page a further degrees clockwise from where it already is, and
returns the editor.
Turns every page a further degrees clockwise, raising an error if it fails.
Turns the page at the given zero-based index a further degrees clockwise from
where it already is, and returns the editor.
Turns the page at the given zero-based index a further degrees clockwise,
raising an error if it fails.
Returns the clockwise display rotation of the page at the given zero-based
index, as 0, 90, 180 or 270.
Returns the clockwise display rotation of the page at the given zero-based index, raising an error if it fails.
Sets the page at the given zero-based index to rotate by degrees clockwise,
and returns the editor.
Sets the page at the given zero-based index to rotate by degrees clockwise,
raising an error if it fails.
Page boxes
Returns the /CropBox of the page at the given zero-based index — the region
a viewer displays and prints — including pending changes. For an unchanged
page it behaves like PdfElixide.Document.Page.crop_box/1 and may return
nil.
Returns the /CropBox of the page at the given zero-based index, or nil,
raising an error if it fails.
Sets every page's /CropBox to its media box inset by margins, and returns
the editor.
Sets every page's /CropBox to its media box inset by margins, raising an
error if it fails.
Returns the /MediaBox of the page at the given zero-based index — the sheet
it is imposed on — including pending changes. For an unchanged page it
behaves like PdfElixide.Document.Page.media_box/1.
Returns the /MediaBox of the page at the given zero-based index, raising an
error if it fails.
Sets the /CropBox of the page at the given zero-based index to rect, and
returns the editor.
Sets the /CropBox of the page at the given zero-based index, raising an
error if it fails.
Sets the /MediaBox of the page at the given zero-based index to rect, and
returns the editor.
Sets the /MediaBox of the page at the given zero-based index, raising an
error if it fails.
Erasing regions
Discards the regions pending on the page at the given zero-based index, so the next write paints nothing over it, and returns the editor.
Discards the regions pending on the page at the given zero-based index, raising an error if it fails.
Paints a white rectangle over rect on the page at the given zero-based index
when the document is written, and returns the editor.
Paints a white rectangle over rect on the page at the given zero-based
index, raising an error if it fails.
Paints a white rectangle over each of rects on the page at the given
zero-based index when the document is written, and returns the editor.
Paints a white rectangle over each of rects on the page at the given
zero-based index, raising an error if it fails.
Redaction
Queues rect on the page at the given zero-based index for removal by
apply_redactions/1,2, and returns the editor.
Queues a region for removal by apply_redactions/1,2, raising an error if it
fails.
Removes covered page text from marked pages and pages with queued regions,
and returns a PdfElixide.RedactionReport. Glyphs whose boxes touch a region
are deleted and an overlay is drawn over the cleared area.
Removes the content covered by every queued region, raising an error if it fails.
Marks every page so its redaction annotations are painted over when the document is written, and returns the editor.
Marks the page at the given zero-based index so each of its /Redact
annotations is painted over when the document is written, and returns the
editor.
Marks every page for redaction, raising an error if it fails.
Marks the page at the given zero-based index for redaction, raising an error if it fails.
Returns whether the page at the given zero-based index is marked for redaction.
Same as marked_for_redaction/2 but returns the bare boolean, raising on
failure — a page that does not exist included. See the "Errors versus
exceptions" section of PdfElixide.Error.
Returns how many redaction regions the page at the given zero-based index carries.
Returns how many redaction regions a page carries, raising an error if it fails.
Strips document-level metadata, JavaScript and embedded files, and returns a
PdfElixide.SanitizeReport.
Strips document-level metadata, JavaScript and embedded files, raising an error if it fails.
Removes the redaction mark from the page at the given zero-based index, so the next write paints nothing over it, and returns the editor.
Removes the redaction mark from the page at the given zero-based index, raising an error if it fails.
Document information
Reads the document information the edited document will carry: values given
to the setters below over the source's /Info dictionary.
Reads the document information the edited document will carry, raising an error if it fails.
Sets the document's /Author and returns the editor. nil removes the
entry.
Sets the document's /Author, raising an error if it fails.
Sets the document's /CreationDate and returns the editor.
Sets the document's /CreationDate, raising an error if it fails.
Sets the document's /Creator, the application that made the original
document, and returns the editor. nil removes the entry.
Sets the document's /Creator, raising an error if it fails.
Sets the document's /Keywords and returns the editor. nil removes the
entry.
Sets the document's /Keywords, raising an error if it fails.
Sets the document's /ModDate and returns the editor.
Sets the document's /ModDate, raising an error if it fails.
Sets the document's /Producer, the application that produced the PDF, and
returns the editor. nil removes the entry.
Sets the document's /Producer, raising an error if it fails.
Sets the document's /Subject and returns the editor. nil removes the
entry.
Sets the document's /Subject, raising an error if it fails.
Sets the document's /Title and returns the editor. nil removes the entry.
Sets the document's /Title, raising an error if it fails.
Flattening
Marks every page's annotations for flattening.
Marks the annotations of the page at the given zero-based index for flattening.
Marks every page's annotations for flattening, raising an error if it fails.
Marks the annotations of the page at the given zero-based index for flattening, raising an error if it fails.
Lists the warnings collected while flattening.
Lists the warnings collected while flattening, raising an error if it fails.
Types
Options accepted by from_markdown/2, from_html/2 and their bang variants.
Options for embed_file/4.
The :encryption option of save_opts/0.
Options accepted by from_images/2 and from_images!/2.
A DateTime or complete PDF date string for set_creation_date/2 and
set_mod_date/2. See Dates for formatting details.
Margins for crop_margins/2, in points.
Options accepted by open/2, open!/2, from_binary/2, and from_binary!/2.
A page size for from_markdown/2, from_html/2, from_plain_text/2 and
from_images/2.
The :permissions option of encryption_opts/0.
Options accepted by from_plain_text/2 and from_plain_text!/2.
Options for apply_redactions/1,2 and apply_redactions!/1,2.
See apply_redactions/2 for defaults and allowed values.
Options for sanitize/1,2 and sanitize!/1,2.
See sanitize/2 for defaults and allowed values.
Options accepted by save/3, save!/3, to_binary/2, and to_binary!/2.
An open editor.
Opening and lifecycle
@spec close(t()) :: :ok
Releases the editor's native memory without waiting for garbage collection.
An editor holds the source document plus its pending edits in native memory,
normally freed only when the BEAM garbage-collects the handle.
close/1 frees it early, which matters for long-lived processes that open many
documents. Calling it is optional and idempotent. It waits for an in-flight
call on the same editor — a save can hold the handle's lock for seconds — and
releases the memory as soon as the handle is idle, not preemptively.
Unsaved edits are discarded — call save/3 or to_binary/2 first.
Afterwards, functions that read or mutate the editor return
{:error, %PdfElixide.Error{reason: :closed}}, and their bang variants raise
it. source_path/1 and version/1 keep working, since they read the struct
rather than the native handle.
"form.pdf"
|> PdfElixide.Editor.open!()
|> PdfElixide.Form.put_value!("name", "Ada")
|> PdfElixide.Editor.save!("filled.pdf")
|> PdfElixide.Editor.close()
#=> :ok
Returns whether the editor has been released with close/1.
@spec from_binary(binary(), open_opts()) :: {:ok, t()} | {:error, PdfElixide.Error.t()}
Opens a PDF document for editing 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.
Encrypted bytes need a password, as in open/2.
Incremental saves are unsupported; see Saving edits.
Opens a PDF document for editing 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.
Raises for encrypted bytes opened without their password, as in open/2.
Incremental saves are unsupported, as in from_binary/2.
Returns whether the editor holds changes that have not been written out.
false for a freshly opened editor, and true once something has changed it —
PdfElixide.Form.put_value/3, say.
A full rewrite clears it again, so save/3 and to_binary/2 both leave the
editor unmodified — to_binary/2 included, even though it writes no file. An
incremental save/3 does not: after save(editor, path, incremental: true)
the flag stays true. A refused save changes nothing at all.
@spec open(Path.t(), open_opts()) :: {:ok, t()} | {:error, PdfElixide.Error.t()}
Opens a PDF document for editing from the specified file path.
An encrypted document needs its password, as open_opts/0 describes;
without one the call returns
{:error, %PdfElixide.Error{reason: :encrypted}}.
The path is handed to the operating system unchanged — see the "File paths"
section of PdfElixide.
Opens a PDF document for editing from the specified file path, raising an error if it fails.
Raises for an encrypted document opened without its password; open/2
describes why.
The path is handed to the operating system unchanged — see the "File paths"
section of PdfElixide.
@spec page_count(t()) :: {:ok, non_neg_integer()} | {:error, PdfElixide.Error.t()}
Returns the number of pages the editor currently holds.
Counts the pages as edited rather than as found on disk, so unlike version/1
this asks the editor on every call.
Returns {:error, %PdfElixide.Error{reason: :closed}} after close/1.
@spec page_count!(t()) :: non_neg_integer()
Returns the number of pages the editor currently holds, raising an error if it fails.
Returns the file path the editor was opened from, or nil if it has no
source file: one built with from_binary/2, from_markdown/2,
from_html/2, from_plain_text/2 or from_images/2.
@spec version(t()) :: {non_neg_integer(), non_neg_integer()}
Returns the PDF specification version of the document being edited, as a
{major, minor} tuple.
This is the version of the document the editor was opened from, which editing
does not change. It is read from the struct, so it keeps working after
close/1.
Encryption does not raise the output version; see The declared version is not raised to match.
Creating documents
@spec from_html(String.t(), create_opts()) :: {:ok, t()} | {:error, PdfElixide.Error.t()}
Creates a new document from HTML and opens it for editing.
Only a small set of bare tags is recognised, and it is not a browser: see Creating documents for what is kept and what appears as it was written.
"<h1>Report</h1><p>All <b>good</b>.</p>"
|> PdfElixide.Editor.from_html!()
|> PdfElixide.Editor.to_binary!()
@spec from_html!(String.t(), create_opts()) :: t()
Creates a new document from HTML and opens it for editing, raising an error if it fails.
@spec from_images([binary()], image_create_opts()) :: {:ok, t()} | {:error, PdfElixide.Error.t()}
Creates a new document from JPEG and PNG images and opens it for editing.
Each binary in images becomes one page, in list order. Every page has the
same size, and each image is scaled, keeping its proportions, to fill the
area inside the margins and is centred there. Pages are not sized to their
images.
A JPEG is embedded without re-encoding. A PNG's pixels are stored losslessly, with transparency kept and 16-bit channels reduced to 8 bits. An EXIF orientation tag is not applied, and most CMYK JPEGs come out with inverted colours. See Images for these, and for making a page that matches one image.
jpeg = File.read!("scan.jpg")
[jpeg]
|> PdfElixide.Editor.from_images!(margin_top: 36, margin_bottom: 36)
|> PdfElixide.Editor.save!("scan.pdf")
|> PdfElixide.Editor.close()These return {:error, %PdfElixide.Error{reason: :unsupported}}:
- an image that is neither JPEG nor PNG;
- an image over 128 million pixels, or 32 million on a 32-bit system, or a 16-bit colour PNG over about 89 million (67 million with transparency). Scale it down first.
An image that cannot be decoded, including a 12-bit, arithmetic-coded or
lossless JPEG, returns {:error, %PdfElixide.Error{reason: :other}}. Each
error message gives the image's zero-based position in the list. An empty
list, or an element that is not a binary, raises ArgumentError.
The images are held in memory several times over while the document is
built, and the editor keeps the whole document until close/1. Close it once
it has been written.
@spec from_images!([binary()], image_create_opts()) :: t()
Creates a new document from JPEG and PNG images and opens it for editing, raising an error if it fails.
@spec from_markdown(String.t(), create_opts()) :: {:ok, t()} | {:error, PdfElixide.Error.t()}
Creates a new document from Markdown and opens it for editing.
Headings, emphasis, lists, quotes, code and tables are laid out on as many
pages as the text needs. The result is an editor like one from
from_binary/2: write it with save/3 or to_binary/2, or edit it first.
"# Report\n\nAll **good**."
|> PdfElixide.Editor.from_markdown!(title: "Report")
|> PdfElixide.Editor.save!("report.pdf")Only part of Markdown is recognised, and lines are not wrapped; see Creating documents.
@spec from_markdown!(String.t(), create_opts()) :: t()
Creates a new document from Markdown and opens it for editing, raising an error if it fails.
@spec from_plain_text(String.t(), plain_text_create_opts()) :: {:ok, t()} | {:error, PdfElixide.Error.t()}
Creates a new document from plain text and opens it for editing.
Each line of content becomes a line on the page, in 12-point Helvetica, with
no markup interpreted.
Only Windows-1252 characters can be set this way. Text containing any other
character, such as Cyrillic or Greek, returns
{:error, %PdfElixide.Error{reason: :unsupported}} naming the first one. A
leading byte order mark counts, so strip it from text read from a file.
from_markdown/2 renders many of those characters; see
Creating documents for which.
@spec from_plain_text!(String.t(), plain_text_create_opts()) :: t()
Creates a new document from plain text and opens it for editing, raising an error if it fails.
Saving
@spec save(t(), Path.t(), save_opts()) :: {:ok, t()} | {:error, PdfElixide.Error.t()}
Writes the editor to a PDF file at the given path, and returns the editor.
A full rewrite is the default. incremental: true returns
{:error, %PdfElixide.Error{reason: :unsupported}} without writing if the editor
has no source file — it came from from_binary/2, from_markdown/2,
from_html/2, from_plain_text/2 or from_images/2 — or holds unsupported
changes. See Saving edits for supported
changes and recovery.
Writing does not consume the editor: you can keep editing and write again.
Pass :encryption to write a password-protected PDF. It cannot be combined
with incremental: true, and a successful return does not by itself prove
every object was encrypted — see
A failed encryption is not reported.
A source that stores its XMP metadata unencrypted is refused; see What an encrypted source cannot do.
The path is handed to the operating system unchanged — see the "File paths"
section of PdfElixide.
Writes the editor to a PDF file at the given path, raising an error if it fails.
Uses the same write modes and refusals as save/3; see
Saving edits.
The path is handed to the operating system unchanged — see the "File paths"
section of PdfElixide.
@spec to_binary(t(), save_opts()) :: {:ok, binary()} | {:error, PdfElixide.Error.t()}
Serialises all in-memory changes into a PDF binary.
The result is a fully self-contained PDF that can be written to disk, stored in a database, or streamed over HTTP.
Accepts the same save_opts/0 keyword list as save/3, except
incremental: true, which returns
{:error, %PdfElixide.Error{reason: :invalid_pdf}}: an incremental update is an
append to the original file, so there is nothing to append to in memory. Use
save/3 for an incremental write; see
Saving edits.
That includes :encryption, which encrypts the returned binary exactly as it
encrypts a file — including the caveat in
A failed encryption is not reported.
A source that stores its XMP metadata unencrypted is refused, as in save/3.
The whole document is serialised in native memory before being copied
into the returned binary, so peak usage is roughly twice the output
size (on top of the editor itself). For very large documents prefer
save/3, which streams to the file without that second buffer.
Serialises all in-memory changes into a PDF binary, raising an error if it fails.
Page structure
@spec delete_page(t(), non_neg_integer()) :: {:ok, t()} | {:error, PdfElixide.Error.t()}
Deletes the page at the given zero-based index, and returns the editor.
Every later page moves down one index, and page_count/1 reflects the removal
at once — no save is needed. See Page structure
and Saving edits for the deletion's security
limitation and the incremental-save refusal.
Returns {:error, %PdfElixide.Error{reason: :out_of_range}} if the page does
not exist.
@spec delete_page!(t(), non_neg_integer()) :: t()
Deletes the page at the given zero-based index, raising an error if it fails.
@spec keep_pages(t(), [non_neg_integer(), ...]) :: {:ok, t()} | {:error, PdfElixide.Error.t()}
Keeps only the pages at the given zero-based indices, in the order given, and returns the editor.
keep_pages(editor, [2, 0]) on a three-page document leaves two pages: the
old last page, then the old first. Pending edits stay with their pages.
Dropping a page is not redaction; see
Page structure.
Returns {:error, %PdfElixide.Error{reason: :out_of_range}} if an index does
not exist, or :invalid_pdf if a listed page cannot be read. Raises
ArgumentError for an empty list or a repeated index.
@spec keep_pages!(t(), [non_neg_integer(), ...]) :: t()
Keeps only the pages at the given zero-based indices, in the order given, raising an error if it fails.
@spec move_page(t(), non_neg_integer(), non_neg_integer()) :: {:ok, t()} | {:error, PdfElixide.Error.t()}
Moves the page at zero-based index from so that it sits at index to, and
returns the editor.
to is where the page ends up once it has been lifted out, so
move_page(editor, 0, 2) on a three-page document leaves the first page last.
The pages it passes over shift by one to fill the gap; nothing else changes.
Returns {:error, %PdfElixide.Error{reason: :out_of_range}} if either index
does not exist. See Page structure for what
a move does not update and Saving edits for
the incremental-save refusal.
@spec move_page!(t(), non_neg_integer(), non_neg_integer()) :: t()
Moves the page at zero-based index from so that it sits at index to,
raising an error if it fails.
Merging and splitting
@spec bookmark_segments(t(), PdfElixide.Document.bookmark_opts()) :: {:ok, [PdfElixide.Document.BookmarkSegment.t()]} | {:error, PdfElixide.Error.t()}
Plans a split of the editor's pages at the document's bookmarks, without
writing anything, as PdfElixide.Document.bookmark_segments/2 does.
Segments follow the editor's current page order, and bookmarks on removed
pages are skipped. split_by_bookmarks/2 writes the same parts. See
Splitting at bookmarks.
@spec bookmark_segments!(t(), PdfElixide.Document.bookmark_opts()) :: [ PdfElixide.Document.BookmarkSegment.t() ]
Plans a split of the editor's pages at the document's bookmarks, raising an error if it fails.
@spec extract_page_ranges(t(), [Range.t(), ...]) :: {:ok, [binary()]} | {:error, PdfElixide.Error.t()}
Writes each range of zero-based page indices to its own PDF binary, as
extract_pages/2 does, and returns the binaries in the order of ranges.
Each range is inclusive with a step of 1, so [0..9, 10..19] splits a
twenty-page document in two. Ranges may overlap. All ranges are checked
before anything is written.
Every binary is held in memory until the call returns. To keep only one at a
time, call extract_pages/2 once per range. See
Parts and chunks.
Returns {:error, %PdfElixide.Error{reason: :out_of_range}} if a range reaches
past the last page, or :invalid_pdf if a listed page cannot be read. Raises
ArgumentError for an empty list, an empty range or a range whose step is not
1.
Writes each range of zero-based page indices to its own PDF binary, raising an error if it fails.
@spec extract_pages(t(), [non_neg_integer(), ...]) :: {:ok, binary()} | {:error, PdfElixide.Error.t()}
Writes the pages at the given zero-based indices, in the order given, to a new
PDF binary. The editor's pages and pending edits are not changed, but an editor
with no unsaved edits can report modified?/1 as true afterwards if its
document has an information dictionary, or if an attachment added with
embed_file/4 has already been written.
The result includes pending edits, like to_binary/2 with its default
options, and is never encrypted. Extraction is not a way to remove
confidential content; see
Extraction is not redaction.
Returns {:error, %PdfElixide.Error{reason: :out_of_range}} if an index does
not exist, or :invalid_pdf if a listed page cannot be read. Raises
ArgumentError for an empty list or a repeated index. A source that stores
its XMP metadata unencrypted is refused, as in save/3.
@spec extract_pages!(t(), [non_neg_integer(), ...]) :: binary()
Writes the pages at the given zero-based indices to a new PDF binary, raising an error if it fails.
@spec merge(t(), Path.t()) :: {:ok, t()} | {:error, PdfElixide.Error.t()}
Appends every page of the PDF file at path to the end of the editor, and
returns the editor.
A merge incorporates pending edits into the combined document. Afterwards,
modified?/1 reports true until the next full write, and an incremental
save/3 is refused. Documents whose pages cannot be carried safely return
:unsupported without changing the editor; encrypted documents return
:encrypted. See
Merging documents for the
complete behavior and limitations.
The path is handed to the operating system unchanged — see the "File paths"
section of PdfElixide.
Appends every page of the PDF file at path to the end of the editor, raising
an error if it fails.
@spec merge_binary(t(), binary()) :: {:ok, t()} | {:error, PdfElixide.Error.t()}
Appends every page of a PDF binary to the end of the editor, and returns the
editor. Behaves as merge/2 does for a file.
Appends every page of a PDF binary to the end of the editor, raising an error if it fails.
@spec split_by_bookmarks(t(), PdfElixide.Document.bookmark_opts()) :: {:ok, [{PdfElixide.Document.BookmarkSegment.t(), binary()}]} | {:error, PdfElixide.Error.t()}
Splits the editor's pages at the document's bookmarks, writing each part to
its own PDF binary as extract_page_ranges/2 does.
Returns a {segment, binary} pair per part, where each
PdfElixide.Document.BookmarkSegment is the one bookmark_segments/2 plans,
and {:ok, []} when there are no bookmarks or none matches opts; see
PdfElixide.Document.bookmark_opts/0. The editor's pages and pending edits
are not changed, but modified?/1 can report true afterwards, as after
extract_pages/2.
Parts may retain pages outside their visible page range through the document's bookmarks. Do not use splitting to separate confidential content; see Extraction is not redaction.
Every binary is held in memory until the call returns. See Splitting at bookmarks, which also shows how to write one part at a time.
Returns {:error, %PdfElixide.Error{reason: :invalid_pdf}} if a page cannot
be read. A source that stores its XMP metadata unencrypted is refused, as in
save/3.
@spec split_by_bookmarks!(t(), PdfElixide.Document.bookmark_opts()) :: [ {PdfElixide.Document.BookmarkSegment.t(), binary()} ]
Splits the editor's pages at the document's bookmarks, raising an error if it fails.
Page rotation
@spec rotate_all_by(t(), integer()) :: {:ok, t()} | {:error, PdfElixide.Error.t()}
Turns every page a further degrees clockwise from where it already is, and
returns the editor.
Each page is turned from its own current rotation, so a document whose pages
disagree keeps them disagreeing. degrees is a delta and is accepted on the
same terms as rotate_page_by/3. On a document with no pages this changes
nothing and succeeds.
If any page's rotation cannot be read, the call fails without changing the editor.
See Page rotation.
Turns every page a further degrees clockwise, raising an error if it fails.
@spec rotate_page_by(t(), non_neg_integer(), integer()) :: {:ok, t()} | {:error, PdfElixide.Error.t()}
Turns the page at the given zero-based index a further degrees clockwise from
where it already is, and returns the editor.
degrees is a delta rather than an absolute angle, so rotate_page_by(e, 0, 90)
takes a page already at 180 to 270. It must be a multiple of 90 — anything
else raises FunctionClauseError. A negative delta turns anticlockwise and one
past 360 wraps.
Returns {:error, %PdfElixide.Error{reason: :out_of_range}} if the page does
not exist. See Page rotation.
@spec rotate_page_by!(t(), non_neg_integer(), integer()) :: t()
Turns the page at the given zero-based index a further degrees clockwise,
raising an error if it fails.
@spec rotation(t(), non_neg_integer()) :: {:ok, PdfElixide.Document.Page.rotation()} | {:error, PdfElixide.Error.t()}
Returns the clockwise display rotation of the page at the given zero-based
index, as 0, 90, 180 or 270.
It reflects pending edits immediately. For unchanged pages, it matches
PdfElixide.Document.Page.rotation/1, including inheritance and normalization.
Returns {:error, %PdfElixide.Error{reason: :out_of_range}} if the page does
not exist.
@spec rotation!(t(), non_neg_integer()) :: PdfElixide.Document.Page.rotation()
Returns the clockwise display rotation of the page at the given zero-based index, raising an error if it fails.
@spec set_rotation(t(), non_neg_integer(), PdfElixide.Document.Page.rotation()) :: {:ok, t()} | {:error, PdfElixide.Error.t()}
Sets the page at the given zero-based index to rotate by degrees clockwise,
and returns the editor.
degrees is absolute, not a delta, and must be 0, 90, 180 or 270 —
anything else raises FunctionClauseError. Use rotate_page_by/3 to turn a
page relative to where it already is.
Returns {:error, %PdfElixide.Error{reason: :out_of_range}} if the page does
not exist. See Page rotation.
@spec set_rotation!(t(), non_neg_integer(), PdfElixide.Document.Page.rotation()) :: t()
Sets the page at the given zero-based index to rotate by degrees clockwise,
raising an error if it fails.
Page boxes
@spec crop_box(t(), non_neg_integer()) :: {:ok, PdfElixide.Geometry.Rect.t() | nil} | {:error, PdfElixide.Error.t()}
Returns the /CropBox of the page at the given zero-based index — the region
a viewer displays and prints — including pending changes. For an unchanged
page it behaves like PdfElixide.Document.Page.crop_box/1 and may return
nil.
Returns {:error, %PdfElixide.Error{reason: :out_of_range}} if the page does
not exist. See Page boxes.
@spec crop_box!(t(), non_neg_integer()) :: PdfElixide.Geometry.Rect.t() | nil
Returns the /CropBox of the page at the given zero-based index, or nil,
raising an error if it fails.
@spec crop_margins(t(), margins()) :: {:ok, t()} | {:error, PdfElixide.Error.t()}
Sets every page's /CropBox to its media box inset by margins, and returns
the editor.
It uses the normalized boxes from media_box/2, including pending changes,
and replaces existing crop boxes. All results are computed before any page is
changed, so a failure leaves every page unchanged. See
Page boxes.
Sets every page's /CropBox to its media box inset by margins, raising an
error if it fails.
@spec media_box(t(), non_neg_integer()) :: {:ok, PdfElixide.Geometry.Rect.t()} | {:error, PdfElixide.Error.t()}
Returns the /MediaBox of the page at the given zero-based index — the sheet
it is imposed on — including pending changes. For an unchanged page it
behaves like PdfElixide.Document.Page.media_box/1.
Returns {:error, %PdfElixide.Error{reason: :out_of_range}} if the page does
not exist. See Page boxes.
@spec media_box!(t(), non_neg_integer()) :: PdfElixide.Geometry.Rect.t()
Returns the /MediaBox of the page at the given zero-based index, raising an
error if it fails.
@spec set_crop_box(t(), non_neg_integer(), PdfElixide.Geometry.Rect.t()) :: {:ok, t()} | {:error, PdfElixide.Error.t()}
Sets the /CropBox of the page at the given zero-based index to rect, and
returns the editor.
Reversed corners are normalized. A rectangle whose corners do not fit a
32-bit float raises ArgumentError.
Returns {:error, %PdfElixide.Error{reason: :out_of_range}} if the page does
not exist. See Page boxes.
@spec set_crop_box!(t(), non_neg_integer(), PdfElixide.Geometry.Rect.t()) :: t()
Sets the /CropBox of the page at the given zero-based index, raising an
error if it fails.
@spec set_media_box(t(), non_neg_integer(), PdfElixide.Geometry.Rect.t()) :: {:ok, t()} | {:error, PdfElixide.Error.t()}
Sets the /MediaBox of the page at the given zero-based index to rect, and
returns the editor.
Reversed corners are normalized. A rectangle whose corners do not fit a
32-bit float raises ArgumentError.
Returns {:error, %PdfElixide.Error{reason: :out_of_range}} if the page does
not exist. See Page boxes.
@spec set_media_box!(t(), non_neg_integer(), PdfElixide.Geometry.Rect.t()) :: t()
Sets the /MediaBox of the page at the given zero-based index, raising an
error if it fails.
Erasing regions
@spec clear_erase_regions(t(), non_neg_integer()) :: {:ok, t()} | {:error, PdfElixide.Error.t()}
Discards the regions pending on the page at the given zero-based index, so the next write paints nothing over it, and returns the editor.
modified?/1 is left as it was, but the page stops blocking an incremental
save/3. Returns {:error, %PdfElixide.Error{reason: :out_of_range}} if the
page does not exist. See Erasing regions.
@spec clear_erase_regions!(t(), non_neg_integer()) :: t()
Discards the regions pending on the page at the given zero-based index, raising an error if it fails.
@spec erase_region(t(), non_neg_integer(), PdfElixide.Geometry.Rect.t()) :: {:ok, t()} | {:error, PdfElixide.Error.t()}
Paints a white rectangle over rect on the page at the given zero-based index
when the document is written, and returns the editor.
See Erasing regions for the coordinate
system and coverage limitations.
Reversed corners are normalized. A rectangle whose corners do not fit a
32-bit float raises ArgumentError.
Returns {:error, %PdfElixide.Error{reason: :out_of_range}} if the page does
not exist, and {:error, %PdfElixide.Error{reason: :unsupported}} if the
page stores its content streams as an indirect array. Nothing is recorded
in the latter case.
@spec erase_region!(t(), non_neg_integer(), PdfElixide.Geometry.Rect.t()) :: t()
Paints a white rectangle over rect on the page at the given zero-based
index, raising an error if it fails.
@spec erase_regions(t(), non_neg_integer(), [PdfElixide.Geometry.Rect.t(), ...]) :: {:ok, t()} | {:error, PdfElixide.Error.t()}
Paints a white rectangle over each of rects on the page at the given
zero-based index when the document is written, and returns the editor.
Accepts a nonempty list of PdfElixide.Geometry.Rect with the same
validation, errors and coverage limitations as erase_region/3.
An empty list raises ArgumentError.
@spec erase_regions!(t(), non_neg_integer(), [PdfElixide.Geometry.Rect.t(), ...]) :: t()
Paints a white rectangle over each of rects on the page at the given
zero-based index, raising an error if it fails.
Redaction
@spec add_redaction( t(), non_neg_integer(), PdfElixide.Geometry.Rect.t(), PdfElixide.Color.RGB.t() | nil ) :: {:ok, t()} | {:error, PdfElixide.Error.t()}
Queues rect on the page at the given zero-based index for removal by
apply_redactions/1,2, and returns the editor.
fill is the colour of the block drawn over the cleared area, each component
between 0.0 and 1.0; nil uses the :default_fill given to
apply_redactions/2.
rect is in raw, unrotated page space, as returned by
PdfElixide.Document.chars/1, PdfElixide.Document.spans/1 and
PdfElixide.Document.paths/1. For other extractors, see Rotated pages and
extracted
geometry; a
rectangle in the wrong frame is accepted without error.
Saving alone does not apply the queued rectangle. Queuing also marks the
page: if it has /Redact annotations, a full write paints their rectangles
and removes all annotations. Without them, the mark changes nothing.
See Queuing your own regions.
A queued region cannot be withdrawn, including with unmark_redactions/2.
Reopen the source to start again. An incremental save/3 is refused for the
life of the editor from the first queued region, since nothing can take it
back; see Saving edits.
Reversed corners are normalized. A rectangle whose corners do not fit a 32-bit
float raises ArgumentError, and so does a fill component outside
0.0..1.0. Returns {:error, %PdfElixide.Error{reason: :out_of_range}} if the
page does not exist, and {:error, %PdfElixide.Error{reason: :unsupported}},
queuing nothing, if the page stores its content in an indirect object that is
not itself a content stream, or if the editor was opened from an encrypted
document. Unlike mark_redactions/2, both restrictions apply even when the
page has no redaction annotations. See
What an encrypted source cannot do.
@spec add_redaction!( t(), non_neg_integer(), PdfElixide.Geometry.Rect.t(), PdfElixide.Color.RGB.t() | nil ) :: t()
Queues a region for removal by apply_redactions/1,2, raising an error if it
fails.
@spec apply_redactions(t(), redaction_opts()) :: {:ok, PdfElixide.RedactionReport.t()} | {:error, PdfElixide.Error.t()}
Removes covered page text from marked pages and pages with queued regions,
and returns a PdfElixide.RedactionReport. Glyphs whose boxes touch a region
are deleted and an overlay is drawn over the cleared area.
Immediate and irreversible. The call rewrites content in the editor;
saving is not required to apply it. Queue every region first: only one pass
is allowed per editor. A second call returns
{:error, %PdfElixide.Error{reason: :unsupported}} without changes.
Refused on an encrypted source; nothing is changed and the editor stays usable. See What an encrypted source cannot do.
Only glyphs drawn by the page itself are removed. Covered images, vector
graphics, form XObject text and /ActualText remain recoverable. Redact the
source document if those contain sensitive content. An unfinished path can
extend the overlay beyond its rectangle; see
Where the block lands and
Verifying.
A /Redact annotation alone does not schedule a page: use
mark_redactions/1,2 or add_redaction/3,4 first. Deleting a page does not
cancel its marks or regions, and its results still count in the report.
Mark and queue after deletions, or reopen the source.
Rewritten pages lose all annotations, whose objects remain recoverable; see Annotation removal. They also lose pending erase and annotation-flatten overlays; see Pending overlays.
Incremental saves are refused afterwards: the update would leave the removed
content readable. Metadata, JavaScript and embedded files require a separate
sanitize/1,2 call.
Returns {:error, %PdfElixide.Error{reason: :unsupported}} for undefined fonts,
composite fonts other than horizontal Identity-H, or text measured with state
discarded by a q … Q restore. A font refusal can leave earlier pages
rewritten: discard the editor and reopen the source. The text-state check
leaves the editor unchanged. See
Refused pages.
Options
:edge_padding— how far past a region's edge to clear, in points, as a floor on a proportional padding. Must be a non-negative number. Defaults to0.5.:default_fill— thePdfElixide.Color.RGBdrawn over a region queued withadd_redaction/3,4and given nofill. Each component must be between0.0and1.0. Defaults to black. It never reaches a region taken from a/Redactannotation: that carries a colour of its own, its/ICor black where it declares none.:draw_default_overlay— draw:default_fillover such a region at all.falseleaves the cleared area blank. The same scope: an annotation's region is painted either way. Defaults totrue.
@spec apply_redactions!(t(), redaction_opts()) :: PdfElixide.RedactionReport.t()
Removes the content covered by every queued region, raising an error if it fails.
@spec mark_redactions(t()) :: {:ok, t()} | {:error, PdfElixide.Error.t()}
Marks every page so its redaction annotations are painted over when the document is written, and returns the editor.
Equivalent to mark_redactions/2 on each page. Returns
{:error, %PdfElixide.Error{reason: :unsupported}}, marking nothing, if any
page would be refused there.
@spec mark_redactions(t(), non_neg_integer()) :: {:ok, t()} | {:error, PdfElixide.Error.t()}
Marks the page at the given zero-based index so each of its /Redact
annotations is painted over when the document is written, and returns the
editor.
This is not redaction. The annotation's rectangle is filled with its
/IC colour, or black if it declares none, over the existing page content —
the covered text and images remain in the written file and
PdfElixide.Document.text/1 still returns the covered words. Use
apply_redactions/1,2 to remove covered page text.
The mark is deferred: nothing happens until the next full write, save/3
without :incremental or to_binary/2. An incremental save/3 is refused
while the mark is pending, since it could only write the original back
unmarked. unmark_redactions/2 takes the mark back, and with it the refusal.
A page with no redaction annotations is marked and paints nothing. A page that has them writes without any annotations at all — links and form widgets go with them — so read the Redaction guide before marking a page whose annotations matter.
Returns {:error, %PdfElixide.Error{reason: :out_of_range}} if the page does
not exist, and {:error, %PdfElixide.Error{reason: :unsupported}} if it has
redaction annotations and stores its content streams in an indirect object
that is not itself a content stream. Nothing is recorded in the latter case.
Marks every page for redaction, raising an error if it fails.
@spec mark_redactions!(t(), non_neg_integer()) :: t()
Marks the page at the given zero-based index for redaction, raising an error if it fails.
@spec marked_for_redaction(t(), non_neg_integer()) :: {:ok, boolean()} | {:error, PdfElixide.Error.t()}
Returns whether the page at the given zero-based index is marked for redaction.
mark_redactions/1,2 and add_redaction/3,4 both mark a page, and so does
apply_redactions/1,2 for every page it actually redacted — that one answers
true afterwards whether or not it was marked before the pass.
Returns {:error, %PdfElixide.Error{reason: :out_of_range}} if the page does
not exist.
@spec marked_for_redaction?(t(), non_neg_integer()) :: boolean()
Same as marked_for_redaction/2 but returns the bare boolean, raising on
failure — a page that does not exist included. See the "Errors versus
exceptions" section of PdfElixide.Error.
@spec redaction_count(t(), non_neg_integer()) :: {:ok, non_neg_integer()} | {:error, PdfElixide.Error.t()}
Returns how many redaction regions the page at the given zero-based index carries.
Counts each of the page's /Redact annotations that carries a /Rect — one
without it is not a region and is not counted — plus the regions added with
add_redaction/3,4. An annotation contributes one region however many
quadrilaterals it declares.
Returns {:error, %PdfElixide.Error{reason: :out_of_range}} if the page does
not exist.
@spec redaction_count!(t(), non_neg_integer()) :: non_neg_integer()
Returns how many redaction regions a page carries, raising an error if it fails.
@spec sanitize(t(), sanitize_opts()) :: {:ok, PdfElixide.SanitizeReport.t()} | {:error, PdfElixide.Error.t()}
Strips document-level metadata, JavaScript and embedded files, and returns a
PdfElixide.SanitizeReport.
This removes nothing from any page. Text, images and annotations are left
as they are; only catalog-level and /Info entries go. Use
apply_redactions/1,2 to remove page content.
Like apply_redactions/1,2 this takes effect immediately and cannot be
undone, and an incremental save/3 is refused afterwards: the update would
leave the removed content readable.
garbage_collect: false on save/3 or to_binary/2 is refused afterwards
with {:error, %PdfElixide.Error{reason: :unsupported}}: a full rewrite with
garbage collection is required to remove the scrubbed objects.
With :scrub_metadata, an indirect /Info value returns
{:error, %PdfElixide.Error{reason: :unsupported}} without changes. Use
scrub_metadata: false to strip the rest, or rewrite the metadata first.
See Sanitization refusals.
Options
:scrub_metadata— clear/Infoand the catalog's XMP metadata. Defaults totrue.:remove_javascript— remove document JavaScript,/OpenActionand/AA. Defaults totrue.:remove_embedded_files— remove the embedded-file name tree, and discard any file queued withembed_file/4that has not been written yet. Emptying the tree also letsembed_file/4attach to a document that would otherwise be refused; see Attachments. A/FileAttachmentannotation is left alone and its bytes stay in the file, and nothing in this library removes them; see Sanitizing. Defaults totrue.
@spec sanitize!(t(), sanitize_opts()) :: PdfElixide.SanitizeReport.t()
Strips document-level metadata, JavaScript and embedded files, raising an error if it fails.
@spec unmark_redactions(t(), non_neg_integer()) :: {:ok, t()} | {:error, PdfElixide.Error.t()}
Removes the redaction mark from the page at the given zero-based index, so the next write paints nothing over it, and returns the editor.
modified?/1 is left as it was, but the page stops blocking an incremental
save/3. A region added with add_redaction/3,4 is not withdrawn — the
page keeps it, apply_redactions/1,2 still removes its content, and it goes on
blocking an incremental save/3. Nothing can withdraw a queued region; reopen
the source instead.
Returns {:error, %PdfElixide.Error{reason: :out_of_range}} if the page does
not exist.
@spec unmark_redactions!(t(), non_neg_integer()) :: t()
Removes the redaction mark from the page at the given zero-based index, raising an error if it fails.
Attachments
Document information
@spec metadata(t()) :: {:ok, PdfElixide.Document.Metadata.t()} | {:error, PdfElixide.Error.t()}
Reads the document information the edited document will carry: values given
to the setters below over the source's /Info dictionary.
:trapped is the source's value until sanitize/1,2 clears it. It cannot be
set, and any write that emits /Info drops it. A sanitize/1,2 that scrubbed
the metadata leaves every field here nil. See
Document information.
@spec metadata!(t()) :: PdfElixide.Document.Metadata.t()
Reads the document information the edited document will carry, raising an error if it fails.
@spec set_author(t(), String.t() | nil) :: {:ok, t()} | {:error, PdfElixide.Error.t()}
Sets the document's /Author and returns the editor. nil removes the
entry.
Sets the document's /Author, raising an error if it fails.
@spec set_creation_date(t(), info_date() | nil) :: {:ok, t()} | {:error, PdfElixide.Error.t()}
Sets the document's /CreationDate and returns the editor.
Takes a info_date/0 or nil, which removes the entry. A malformed date
string, or anything else, raises ArgumentError.
Sets the document's /CreationDate, raising an error if it fails.
@spec set_creator(t(), String.t() | nil) :: {:ok, t()} | {:error, PdfElixide.Error.t()}
Sets the document's /Creator, the application that made the original
document, and returns the editor. nil removes the entry.
Sets the document's /Creator, raising an error if it fails.
@spec set_keywords(t(), String.t() | nil) :: {:ok, t()} | {:error, PdfElixide.Error.t()}
Sets the document's /Keywords and returns the editor. nil removes the
entry.
PDF stores keywords as one string, comma-separated by convention, and
PdfElixide.Document.Metadata reads it back unsplit.
Sets the document's /Keywords, raising an error if it fails.
@spec set_mod_date(t(), info_date() | nil) :: {:ok, t()} | {:error, PdfElixide.Error.t()}
Sets the document's /ModDate and returns the editor.
Takes a info_date/0 or nil, which removes the entry. A malformed date
string, or anything else, raises ArgumentError.
Sets the document's /ModDate, raising an error if it fails.
@spec set_producer(t(), String.t() | nil) :: {:ok, t()} | {:error, PdfElixide.Error.t()}
Sets the document's /Producer, the application that produced the PDF, and
returns the editor. nil removes the entry.
Sets the document's /Producer, raising an error if it fails.
@spec set_subject(t(), String.t() | nil) :: {:ok, t()} | {:error, PdfElixide.Error.t()}
Sets the document's /Subject and returns the editor. nil removes the
entry.
Sets the document's /Subject, raising an error if it fails.
@spec set_title(t(), String.t() | nil) :: {:ok, t()} | {:error, PdfElixide.Error.t()}
Sets the document's /Title and returns the editor. nil removes the entry.
Anything but a string or nil raises ArgumentError. See
Document information.
Sets the document's /Title, raising an error if it fails.
Flattening
@spec flatten_annotations(t()) :: {:ok, t()} | {:error, PdfElixide.Error.t()}
Marks every page's annotations for flattening.
Flattening draws each annotation's appearance into the page content. Nothing
happens until the next full write: save/3 without :incremental, or
to_binary/2. An incremental save/3 is refused once a page is marked, and
because the mark cannot be removed it stays refused for the life of the
editor — reopen the source for an unflattened document.
On a page where at least one annotation appearance can be produced, this removes every annotation entry, including ones it could not draw and form field widgets. A skipped annotation can therefore be deleted without being rendered or reported. If the page produces no appearances, the write creates no flatten data for it and draws or removes nothing.
Refused on an encrypted source; see What an encrypted source cannot do.
Do not flatten annotations on a page whose form fields you also flatten with
PdfElixide.Form.flatten/1,2: where appearances are produced, the two marks
are applied independently and fields can be drawn twice.
Returns the editor. See the "Flattening" section of the Forms guide.
@spec flatten_annotations(t(), non_neg_integer()) :: {:ok, t()} | {:error, PdfElixide.Error.t()}
Marks the annotations of the page at the given zero-based index for flattening.
Deferred until the next full write, with the same all-or-nothing page behavior
around appearance production as flatten_annotations/1, and refused on an
encrypted source for the same reason.
Returns the editor, or {:error, %PdfElixide.Error{reason: :out_of_range}} if
the page does not exist. See the "Flattening" section of the
Forms guide.
Marks every page's annotations for flattening, raising an error if it fails.
@spec flatten_annotations!(t(), non_neg_integer()) :: t()
Marks the annotations of the page at the given zero-based index for flattening, raising an error if it fails.
@spec flatten_warnings(t()) :: {:ok, [String.t()]} | {:error, PdfElixide.Error.t()}
Lists the warnings collected while flattening.
Flattening is deferred, so warnings cannot appear before a full write processes a flatten mark. Each entry describes a problem encountered while flattening — most importantly a newly set non-Latin or emoji field value that this library cannot draw into an appearance, which is written with wrong glyphs or none while the PDF stays otherwise valid. The warning is the only signal that happened.
Warnings accumulate for the life of the editor and are never cleared, so a second write reports the first one's entries again. Read the list after the write you care about.
The list is a best effort, not an inventory. Some losses are recorded nowhere, so an empty list is not proof that the written document matches the original. The "Flattening" section of the Forms guide says which, and what each warning means.
Lists the warnings collected while flattening, raising an error if it fails.
Types
@type create_opts() :: [ title: String.t() | nil, author: String.t() | nil, subject: String.t() | nil, page_size: page_size(), margin_top: number(), margin_bottom: number(), margin_left: number(), font_size: number(), line_height: number() ]
Options accepted by from_markdown/2, from_html/2 and their bang variants.
:title,:author,:subject— written to the document information dictionary. Each defaults tonil, which leaves the entry out.:page_size— apage_size/0. Defaults to:letter.:margin_top,:margin_bottom,:margin_left— margins in points. Each defaults to72. There is no right margin, since lines are not wrapped; see Creating documents.:font_size— body text size in points. Defaults to12. Headings are set larger in proportion to it.:line_height— line spacing as a multiple of the text size. Defaults to1.5.
An unknown key, or a value of the wrong type, raises ArgumentError naming
the key. So does a non-positive page dimension, font size or line height, a
negative margin, a left margin as wide as the page, a page too short for
two of the largest supported lines, or line spacing too small for the page.
@type embed_opts() :: [ description: String.t(), relationship: PdfElixide.Document.EmbeddedFile.relationship() ]
Options for embed_file/4.
:description— a human-readable note about the attachment, shown by viewers beside its name. Absent by default.:relationship— how the attachment relates to the document, one of the values inPdfElixide.Document.EmbeddedFile.relationship/0. Absent by default.
Unknown keys and invalid values raise ArgumentError naming the key.
@type encryption_opts() :: [ user_password: String.t(), owner_password: String.t(), algorithm: :aes128 | :rc4_128, permissions: permission_opts() ]
The :encryption option of save_opts/0.
:user_password— password required to open the document. Defaults to"", which produces a document that opens without a prompt but still carries the permission flags.:owner_password— password granting full access and the right to change security settings. Defaults to"", which makes the user password serve as the owner password too.:algorithm—:aes128(the default) or:rc4_128.:permissions— what a reader may do with the document, as apermission_opts/0keyword list. Defaults to granting everything.
Both passwords are UTF-8 String.t/0, unlike PdfElixide.Document.open/2's
:password, which is a byte string. See
Passwords for their length limit,
interoperability and what each empty value means, and
Algorithms for the cipher choice and its
consequences for the output.
An unknown key here or in :permissions raises ArgumentError naming that
key. A declared key given a value of the wrong type raises naming
:encryption.
@type image_create_opts() :: [ title: String.t() | nil, author: String.t() | nil, subject: String.t() | nil, page_size: page_size(), margin_top: number(), margin_bottom: number(), margin_left: number(), margin_right: number() ]
Options accepted by from_images/2 and from_images!/2.
:title,:author,:subject— written to the document information dictionary. Each defaults tonil, which leaves the entry out.:page_size— apage_size/0, used for every page. Defaults to:letter.:margin_top,:margin_bottom,:margin_left,:margin_right— margins in points. Each defaults to72. Each image is fitted inside the area they leave.
An unknown key, or a value of the wrong type, raises ArgumentError naming
the key. So does a non-positive page dimension, a negative margin, or
margins that leave no room on the page.
@type info_date() :: DateTime.t() | String.t()
A DateTime or complete PDF date string for set_creation_date/2 and
set_mod_date/2. See Dates for formatting details.
Margins for crop_margins/2, in points.
:left,:right,:top,:bottom— how far the crop box is inset from that edge of the media box. Each is a non-negative number that fits a 32-bit float, and defaults to0.
Unknown keys and invalid values raise ArgumentError naming the key.
@type open_opts() :: [{:password, binary() | nil}]
Options accepted by open/2, open!/2, from_binary/2, and from_binary!/2.
:password— password used to authenticate against an encrypted PDF. It has the byte-string semantics ofPdfElixide.Document.open_opts/0. A wrong password returns:wrong_password; when omitted ornil, only the empty password is tried and a document that needs another returns:encrypted.
This authenticates the source. The :user_password of
encryption_opts/0 encrypts the output and is a String.t/0.
An unknown key, or a :password that is neither a binary nor nil, raises
ArgumentError — see the "Errors versus exceptions" section of
PdfElixide.Error.
A page size for from_markdown/2, from_html/2, from_plain_text/2 and
from_images/2.
:letter is 612 × 792 points, :a4 595 × 842, :legal 612 × 1008 and :a3
842 × 1190. {width, height} gives any other size in points (1/72 inch).
@type permission_opts() :: [ print_low_res: boolean(), print_high_res: boolean(), modify: boolean(), copy: boolean(), annotate: boolean(), fill_forms: boolean(), accessibility: boolean(), assemble: boolean() ]
The :permissions option of encryption_opts/0.
The keys are those of PdfElixide.Document.Permissions, which describes
what each one grants. Every key defaults to true.
See Permissions for how they interact and what they do and do not promise.
@type plain_text_create_opts() :: [ title: String.t() | nil, author: String.t() | nil, page_size: page_size(), margin_top: number(), margin_bottom: number(), margin_left: number(), line_height: number() ]
Options accepted by from_plain_text/2 and from_plain_text!/2.
The keys of create_opts/0 without :subject and :font_size: plain text
is always set at 12 points, and its output carries no subject.
@type redaction_opts() :: [ edge_padding: number(), default_fill: PdfElixide.Color.RGB.t(), draw_default_overlay: boolean() ]
Options for apply_redactions/1,2 and apply_redactions!/1,2.
See apply_redactions/2 for defaults and allowed values.
Unknown keys, invalid types and out-of-range values raise ArgumentError
naming the offending key.
@type sanitize_opts() :: [ scrub_metadata: boolean(), remove_javascript: boolean(), remove_embedded_files: boolean() ]
Options for sanitize/1,2 and sanitize!/1,2.
See sanitize/2 for defaults and allowed values.
@type save_opts() :: [ incremental: boolean(), compress: boolean(), garbage_collect: boolean(), encryption: encryption_opts() | nil ]
Options accepted by save/3, save!/3, to_binary/2, and to_binary!/2.
:incremental— write an incremental update instead of a full rewrite. Defaults tofalse. Seesave/3andto_binary/2for restrictions.:compress— compress streams. Defaults totrue. An editor converted to PDF/A-1 must be written withcompress: false; the default returns{:error, %PdfElixide.Error{reason: :unsupported}}. See the PDF/A conversion guide.:garbage_collect— drop unreferenced objects. Defaults totrue.falseis refused aftersanitize/1,2with{:error, %PdfElixide.Error{reason: :unsupported}}, since the write would carry the scrubbed values back into the file; see the Redaction guide.:encryption— encrypt the written document, as aencryption_opts/0keyword list. Defaults tonil, which writes an unencrypted PDF. Cannot be combined withincremental: true; see the Encryption guide. An editor converted to PDF/A refuses it; see the PDF/A conversion guide.
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 t() :: %PdfElixide.Editor{ ref: reference(), source_path: Path.t() | nil, version: {non_neg_integer(), non_neg_integer()} }
An open editor.
:version is cached when the editor is opened and served from the struct
thereafter: it is the version of the document the editor was opened from, and
no editing operation changes it.
:source_path is nil for an editor with no source file: one built with
from_binary/2, from_markdown/2, from_html/2, from_plain_text/2 or
from_images/2.
:ref is an internal handle; do not read it or construct the struct directly.