Mutable, in-memory PDF editor backed by pdf_oxide's DocumentEditor.
Where PdfElixide.Document only reads, an editor accumulates changes in
memory and writes them out on demand. The shape is open, mutate, write:
editor = PdfElixide.Editor.open!("form.pdf")
:ok = PdfElixide.Form.set_value(editor, "name", {:text, "Ada"})
:ok = PdfElixide.Editor.save(editor, "filled.pdf")
:ok = PdfElixide.Editor.close(editor)Nothing is written until save/3 or to_binary/2 runs, and neither consumes
the editor — you can keep editing and write again. close/1 discards
unsaved edits, so write before you close.
Every call that writes or mutates takes the handle's lock exclusively — and so
does PdfElixide.Form.fields/1, which only reads — so concurrent editing of
a single editor serializes. Only page_count/1 and modified?/1 take the lock
shared. Give each process its own editor if you need them to work at once; see
the Concurrency guide.
Summary
Functions
Releases the editor's native memory immediately.
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.
Writes all in-memory changes to a PDF file at the given path.
Writes all in-memory changes to a PDF file at the given path, raising an error if it fails.
Returns the file path from which the editor was loaded, or nil if it
was loaded from binary data.
Serialises all in-memory changes into a PDF binary.
Serialises all in-memory changes into a PDF binary, raising an error if it fails.
Returns the PDF specification version of the document being edited, as a
{major, minor} tuple.
Types
@type save_opts() :: [ incremental: boolean(), compress: boolean(), linearize: boolean(), garbage_collect: boolean() ]
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.:compress— compress streams. Defaults totrue.:linearize— linearize the output for fast web view. Defaults tofalse.:garbage_collect— drop unreferenced objects. Defaults totrue.
Defaults mirror pdf_oxide's own full-rewrite defaults, so calling save/2
is equivalent to save/3 with no options.
An unknown key, or a declared key given a value that is not a boolean,
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 arrives with the handle, from the same native call that opens the
editor, and is 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 built with from_binary/1.
Functions
@spec close(t()) :: :ok
Releases the editor's native memory immediately.
An editor holds the source document plus its pending edits 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 waits for an in-flight
call on the same editor — a save can hold the handle's lock for seconds — so
immediately means 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.
editor = PdfElixide.Editor.open!("form.pdf")
:ok = PdfElixide.Form.set_value(editor, "name", {:text, "Ada"})
:ok = PdfElixide.Editor.save(editor, "filled.pdf")
:ok = PdfElixide.Editor.close(editor)
Returns whether the editor has been released with close/1.
@spec from_binary(binary()) :: {: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/1 to read a file.
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!/1 to read a file.
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.set_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.
@spec open(Path.t()) :: {:ok, t()} | {:error, PdfElixide.Error.t()}
Opens a PDF document for editing 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 for editing 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 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.
@spec save(t(), Path.t(), save_opts()) :: :ok | {:error, PdfElixide.Error.t()}
Writes all in-memory changes to a PDF file at the given path.
The path is handed to the operating system unchanged — see the "File paths"
section of PdfElixide.
Writes all in-memory changes to a PDF file at the given path, raising an error if it fails.
The path is handed to the operating system unchanged — see the "File paths"
section of PdfElixide.
Returns the file path from which the editor was loaded, or nil if it
was loaded from binary data.
@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. Note that
:incremental is not supported here — upstream returns
{:error, _} because incremental updates can only be appended to
the original file.
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.
@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.