Editing Overview
PDF Oxide provides two levels of API for editing existing PDFs: the high-level Pdf class (recommended) and the lower-level DocumentEditor. Both allow you to open a PDF, modify its content and metadata, track changes, and save the result.
Binding coverage for editing. The richest editing surface is in Python, Rust, and WASM — they expose page operations, text/image editing, annotation work, encryption, and redaction. Go exposes a substantial editor API (metadata, page rotate/move/delete, erase-region, form fill/flatten, crop, merge, save-encrypted). C# currently exposes metadata edit (
Title/Author/Subject), form fill (SetFormFieldValue),FlattenForms, andSave/SaveAsynconDocumentEditor; page-level operations, encryption, redaction, and text/image editing are not yet surfaced in the C# public API — use the CLI, Go, Python, or Rust bindings for those paths.
Opening a PDF for Editing
Python
from pdf_oxide import PdfDocument
doc = PdfDocument("input.pdf")
The editor is initialized lazily on the first modification. You can start reading immediately and the editor activates when you call any mutating method such as set_title() or page().
WASM
import { WasmPdfDocument } from "pdf-oxide-wasm";
const bytes = new Uint8Array(/* file bytes */);
const doc = new WasmPdfDocument(bytes);
Rust
Use the unified Pdf API:
use pdf_oxide::api::Pdf;
let mut doc = Pdf::open("input.pdf")?;
Or use DocumentEditor directly for lower-level control:
use pdf_oxide::editor::DocumentEditor;
let mut editor = DocumentEditor::open("input.pdf")?;
Go
package main
import (
"log"
pdfoxide "github.com/yfedoseev/pdf_oxide/go"
)
func main() {
editor, err := pdfoxide.OpenEditor("input.pdf")
if err != nil { log.Fatal(err) }
defer editor.Close()
}
C#
using PdfOxide;
using var editor = DocumentEditor.Open("input.pdf");
Checking for Modifications
Before saving, you can check whether any changes have been made:
Python
doc = PdfDocument("input.pdf")
print(doc.is_modified) # False -- no changes yet
doc.set_title("Updated Title")
print(doc.is_modified) # True
Rust
let mut doc = Pdf::open("input.pdf")?;
assert!(!doc.is_modified());
doc.editor().unwrap().set_title("Updated Title");
assert!(doc.is_modified());
Go
editor, _ := pdfoxide.OpenEditor("input.pdf")
defer editor.Close()
modified, _ := editor.IsModified()
fmt.Println(modified) // false
_ = editor.SetTitle("Updated Title")
modified, _ = editor.IsModified()
fmt.Println(modified) // true
C#
using var editor = DocumentEditor.Open("input.pdf");
Console.WriteLine(editor.IsModified); // false
editor.Title = "Updated Title";
Console.WriteLine(editor.IsModified); // true
Saving
Python
doc = PdfDocument("input.pdf")
doc.set_title("New Title")
doc.save("output.pdf")
WASM
import { WasmPdfDocument } from "pdf-oxide-wasm";
const bytes = new Uint8Array(/* file bytes */);
const doc = new WasmPdfDocument(bytes);
doc.setTitle("New Title");
const output = doc.save();
doc.free();
Rust
let mut doc = Pdf::open("input.pdf")?;
doc.editor().unwrap().set_title("New Title");
doc.save("output.pdf")?;
// Or save to a new path
doc.save_as("copy.pdf")?;
Go
editor, _ := pdfoxide.OpenEditor("input.pdf")
defer editor.Close()
_ = editor.SetTitle("New Title")
_ = editor.Save("output.pdf")
C#
using var editor = DocumentEditor.Open("input.pdf");
editor.Title = "New Title";
editor.Save("output.pdf");
The save() method performs a full rewrite of the PDF by default. For advanced save options (incremental updates, encryption), see Encryption & Security.
Document Metadata
Read and write the standard PDF metadata fields: title, author, subject, and keywords.
Python
from pdf_oxide import PdfDocument
doc = PdfDocument("input.pdf")
# Set metadata
doc.set_title("Quarterly Report")
doc.set_author("Jane Smith")
doc.set_subject("Q4 2025 Financial Results")
doc.set_keywords("finance, quarterly, 2025")
doc.save("output.pdf")
WASM
import { WasmPdfDocument } from "pdf-oxide-wasm";
const bytes = new Uint8Array(/* file bytes */);
const doc = new WasmPdfDocument(bytes);
// Set metadata
doc.setTitle("Quarterly Report");
doc.setAuthor("Jane Smith");
doc.setSubject("Q4 2025 Financial Results");
doc.setKeywords("finance, quarterly, 2025");
const output = doc.save();
doc.free();
Rust
use pdf_oxide::editor::DocumentEditor;
let mut editor = DocumentEditor::open("input.pdf")?;
// Read metadata
if let Some(title) = editor.title()? {
println!("Current title: {}", title);
}
if let Some(author) = editor.author()? {
println!("Current author: {}", author);
}
if let Some(subject) = editor.subject()? {
println!("Current subject: {}", subject);
}
if let Some(keywords) = editor.keywords()? {
println!("Current keywords: {}", keywords);
}
// Set metadata
editor.set_title("Quarterly Report");
editor.set_author("Jane Smith");
editor.set_subject("Q4 2025 Financial Results");
editor.set_keywords("finance, quarterly, 2025");
editor.save("output.pdf")?;
Go
editor, _ := pdfoxide.OpenEditor("input.pdf")
defer editor.Close()
if t, err := editor.Title(); err == nil { fmt.Println("Current title:", t) }
if a, err := editor.Author(); err == nil { fmt.Println("Current author:", a) }
_ = editor.SetTitle("Quarterly Report")
_ = editor.SetAuthor("Jane Smith")
_ = editor.SetSubject("Q4 2025 Financial Results")
_ = editor.Save("output.pdf")
C#
using var editor = DocumentEditor.Open("input.pdf");
if (editor.Title is { } t) Console.WriteLine($"Current title: {t}");
if (editor.Author is { } a) Console.WriteLine($"Current author: {a}");
if (editor.Subject is { } s) Console.WriteLine($"Current subject: {s}");
editor.Title = "Quarterly Report";
editor.Author = "Jane Smith";
editor.Subject = "Q4 2025 Financial Results";
editor.Save("output.pdf");
Document Information
Source Path and Version
use pdf_oxide::editor::DocumentEditor;
let editor = DocumentEditor::open("input.pdf")?;
// Path to the original file
println!("Source: {}", editor.source_path());
// PDF version as (major, minor)
let (major, minor) = editor.version();
println!("PDF version: {}.{}", major, minor);
// Number of pages
println!("Pages: {}", editor.current_page_count());
Full API Reference
DocumentEditor
| Method | Returns | Description |
|---|---|---|
open(path) |
Result<DocumentEditor> |
Open a PDF for editing |
is_modified() |
bool |
Check if any changes have been made |
source_path() |
&str |
Path to the source PDF |
source() |
&PdfDocument |
Read-only access to the source document |
version() |
(u8, u8) |
PDF version (major, minor) |
current_page_count() |
usize |
Number of pages in the document |
title() |
Result<Option<String>> |
Get document title |
set_title(title) |
() |
Set document title |
author() |
Result<Option<String>> |
Get document author |
set_author(author) |
() |
Set document author |
subject() |
Result<Option<String>> |
Get document subject |
set_subject(subject) |
() |
Set document subject |
keywords() |
Result<Option<String>> |
Get document keywords |
set_keywords(keywords) |
() |
Set document keywords |
save(path) |
Result<()> |
Save with full rewrite |
save_with_options(path, options) |
Result<()> |
Save with custom options |
Pdf (Unified API)
| Method | Returns | Description |
|---|---|---|
Pdf::open(path) |
Result<Pdf> |
Open a PDF for editing |
Pdf::open_editor(path) |
Result<DocumentEditor> |
Open directly as DocumentEditor |
is_modified() |
bool |
Check if changes exist |
save(path) |
Result<()> |
Save the document |
save_as(path) |
Result<()> |
Save to a new path |
page(index) |
Result<PdfPage> |
Get a page for DOM editing |
save_page(page) |
Result<()> |
Save a modified page back |
editor() |
Option<&mut DocumentEditor> |
Access the underlying editor |
EditableDocument Trait
The EditableDocument trait defines the core editing contract:
pub trait EditableDocument {
fn get_info(&mut self) -> Result<DocumentInfo>;
fn set_info(&mut self, info: DocumentInfo) -> Result<()>;
fn page_count(&mut self) -> Result<usize>;
fn get_page_info(&mut self, index: usize) -> Result<PageInfo>;
fn remove_page(&mut self, index: usize) -> Result<()>;
fn move_page(&mut self, from: usize, to: usize) -> Result<()>;
fn duplicate_page(&mut self, index: usize) -> Result<usize>;
fn save(&mut self, path: impl AsRef<Path>) -> Result<()>;
fn save_with_options(&mut self, path: impl AsRef<Path>, options: SaveOptions) -> Result<()>;
}
Complete DocumentEditor Method Surface
The tables below enumerate the full public DocumentEditor / Pdf-editor method surface in Rust. Each row links to the deep-dive guide where applicable. Binding availability is noted; see the individual pages for Go / C# examples.
Metadata
| Method | Rust | Python | WASM | Go | C# | Page |
|---|---|---|---|---|---|---|
title / set_title |
✓ | ✓ | ✓ | ✓ | ✓ | Overview |
author / set_author |
✓ | ✓ | ✓ | ✓ | ✓ | Overview |
subject / set_subject |
✓ | ✓ | ✓ | ✓ | ✓ | Overview |
keywords / set_keywords |
✓ | ✓ | ✓ | ✓ | — | Overview |
producer / set_producer |
✓ | ✓ | — | ✓ | — | Overview |
creation_date / set_creation_date |
✓ | ✓ | — | ✓ | — | Overview |
apply_metadata(info) |
✓ | ✓ | — | ✓ | — | Overview |
Page operations
| Method | Rust | Python | WASM | Go | C# | Page |
|---|---|---|---|---|---|---|
remove_page / delete_page |
✓ | ✓ | ✓ | ✓ | — | Pages |
move_page(from, to) |
✓ | ✓ | — | ✓ | — | Pages |
duplicate_page(i) |
✓ | ✓ | — | — | — | Pages |
get_page_rotation / set_page_rotation |
✓ | ✓ | ✓ | ✓ | — | Pages |
rotate_page_by(i, deg) |
✓ | ✓ | — | — | — | Pages |
rotate_all_pages(deg) |
✓ | ✓ | ✓ | — | — | Pages |
get_page_media_box / set_page_media_box |
✓ | ✓ | ✓ | — | — | Pages |
get_page_crop_box / set_page_crop_box |
✓ | ✓ | ✓ | — | — | Pages |
crop_margins(l, r, t, b) |
✓ | ✓ | ✓ | ✓ | — | Pages |
erase_region(page, rect) |
✓ | ✓ | ✓ | ✓ | — | Pages |
erase_regions(page, rects) |
✓ | ✓ | ✓ | — | — | Pages |
clear_erase_regions(page) |
✓ | ✓ | — | — | — | Pages |
Merging / splitting
| Method | Rust | Python | WASM | Go | C# | Page |
|---|---|---|---|---|---|---|
merge_from(path) |
✓ | ✓ | — | ✓ | — | Merge & Split |
merge_pages_from(path, pages) |
✓ | ✓ | — | — | — | Merge & Split |
extract_pages(pages, output) |
✓ | ✓ | ✓ | — | — | Merge & Split |
Merge([]paths) top-level |
— | — | — | ✓ | — | Merge & Split |
Forms
| Method | Rust | Python | WASM | Go | C# | Page |
|---|---|---|---|---|---|---|
get_form_fields() |
✓ | ✓ | ✓ | ✓ | ✓ | Forms |
get_form_field_value(name) |
✓ | ✓ | ✓ | — | — | Forms |
has_form_field(name) |
✓ | ✓ | — | — | — | Forms |
set_form_field_value(name, value) |
✓ | ✓ | ✓ | ✓ | ✓ | Forms |
add_form_field(widget, page) |
✓ | — | — | — | — | Form Creation |
add_parent_field / add_child_field |
✓ | — | — | — | — | Form Creation |
remove_form_field(name) |
✓ | ✓ | — | — | — | Forms |
set_form_field_* (readonly, required, tooltip, rect, max_length, alignment, colors, flags) |
✓ | ✓ | — | — | — | Forms |
flatten_forms_on_page(page) |
✓ | ✓ | ✓ | ✓ | — | Forms |
flatten_forms() |
✓ | ✓ | ✓ | ✓ | ✓ | Forms |
export_form_data_fdf(path) |
✓ | ✓ | ✓ | — | — | Forms |
export_form_data_xfdf(path) |
✓ | ✓ | ✓ | — | — | Forms |
Annotations
| Method | Rust | Python | WASM | Go | C# | Page |
|---|---|---|---|---|---|---|
flatten_page_annotations(page) |
✓ | ✓ | ✓ | ✓ | — | Annotations |
flatten_all_annotations() |
✓ | ✓ | ✓ | ✓ | — | Annotations |
is_page_marked_for_annotation_flatten(page) |
✓ | ✓ | — | — | — | Annotations |
unmark_page_for_annotation_flatten(page) |
✓ | ✓ | — | — | — | Annotations |
Redaction
| Method | Rust | Python | WASM | Go | C# | Page |
|---|---|---|---|---|---|---|
apply_page_redactions(page) |
✓ | ✓ | ✓ | — | — | Redaction |
apply_all_redactions() |
✓ | ✓ | ✓ | — | — | Redaction |
is_page_marked_for_redaction(page) |
✓ | ✓ | — | — | — | Redaction |
unmark_page_for_redaction(page) |
✓ | ✓ | — | — | — | Redaction |
XFA
| Method | Rust | Python | WASM | Go | C# | Page |
|---|---|---|---|---|---|---|
has_xfa() |
✓ | ✓ | ✓ | ✓ | ✓ | XFA Forms |
analyze_xfa() |
✓ | ✓ | — | — | — | XFA Forms |
convert_xfa_to_acroform(opts) |
✓ | — | — | — | — | XFA Forms |
Save
| Method | Rust | Python | WASM | Go | C# | Page |
|---|---|---|---|---|---|---|
save(path) |
✓ | ✓ | ✓ | ✓ | ✓ | Overview |
save_with_options(path, opts) |
✓ | ✓ | — | — | — | Encryption |
save_encrypted(path, user, owner) |
✓ | ✓ | ✓ | ✓ | — | Encryption |
save_with_encryption(path, cfg) |
✓ | ✓ | — | — | — | Encryption |
save_async / SaveAsync |
— | — | — | — | ✓ | Async |
Housekeeping (Go convenience)
Methods exposed as DocumentEditor methods in Go, equivalents via editor.source() in Rust:
| Method | Rust | Python | WASM | Go | C# |
|---|---|---|---|---|---|
IsModified |
is_modified() |
is_modified |
— | IsModified() |
IsModified |
SourcePath |
source_path() |
source_path |
— | SourcePath() |
SourcePath |
Version |
version() |
version() |
— | Version() |
— |
PageCount |
current_page_count() |
page_count() |
pageCount() |
PageCount() |
PageCount |
RemoveHeaders / RemoveFooters / RemoveArtifacts |
✓ | ✓ | — | ✓ | ✓ |
Close / Dispose |
Drop |
context manager | free() |
Close() |
Dispose() |
Complete Edit Workflow
This example demonstrates a complete editing session: open, inspect, modify metadata, edit content, and save.
Python
from pdf_oxide import PdfDocument
# Open the document
doc = PdfDocument("report.pdf")
print(f"Pages: {doc.page_count()}")
# Update metadata
doc.set_title("Annual Report 2025")
doc.set_author("Finance Team")
# Edit text on page 0
page = doc.page(0)
for text in page.find_text_containing("DRAFT"):
page.set_text(text.id, "FINAL")
doc.save_page(page)
# Save
doc.save("report-final.pdf")
Rust
use pdf_oxide::api::Pdf;
let mut doc = Pdf::open("report.pdf")?;
println!("Pages: {}", doc.page_count()?);
// Update metadata
{
let editor = doc.editor().unwrap();
editor.set_title("Annual Report 2025");
editor.set_author("Finance Team");
}
// Edit text on page 0
let mut page = doc.page(0)?;
let drafts = page.find_text_containing("DRAFT");
for t in &drafts {
page.set_text(t.id(), "FINAL")?;
}
doc.save_page(page)?;
// Save
doc.save("report-final.pdf")?;
Related Pages
- Text Editing – find and replace text, modify fonts and positioning
- Page Operations – rotation, cropping, merging, and extracting pages
- Form Field Editing – fill, add, and flatten form fields
- Encryption & Security – password-protect and set permissions