# PDF Oxide — The Fastest PDF Library for Python and Rust — Complete Documentation
> The fastest PDF library for Python and Rust.
> Text extraction, image extraction, PDF creation, editing, and markdown conversion.
> Mean 0.8ms per document. 100% pass rate on 3,830 real-world PDFs.
> 5× faster than PyMuPDF, 15× faster than pypdf.
> MIT / Apache-2.0 license. v0.3.34.
>
> Install: pip install pdf-oxide (Python) | cargo add pdf_oxide (Rust)
> GitHub: https://github.com/yfedoseev/pdf_oxide
> Docs: https://pdf.oxide.fyi
---
# Getting Started with PDF Oxide (Python)
PDF Oxide is the complete PDF toolkit. One library for extracting, creating, and editing PDFs with a unified API. Built on a Rust core for maximum performance.
## Installation
```bash
pip install pdf_oxide
```
**Requirements:** Python 3.8+. Pre-built wheels are available for Linux, macOS, and Windows on both x86_64 and ARM64 architectures. No compiler or system dependencies needed.
## Opening a PDF
Use `PdfDocument` to open and inspect any PDF file.
```python
from pdf_oxide import PdfDocument
doc = PdfDocument("research-paper.pdf")
print(f"Pages: {doc.page_count}")
print(f"PDF version: {doc.version}")
```
## Text Extraction
### Single Page
Extract plain text from any page by its zero-based index.
```python
from pdf_oxide import PdfDocument
doc = PdfDocument("report.pdf")
text = doc.extract_text(0)
print(text)
```
### All Pages
```python
from pdf_oxide import PdfDocument
doc = PdfDocument("book.pdf")
for i in range(doc.page_count):
text = doc.extract_text(i)
print(f"--- Page {i + 1} ---")
print(text)
```
## Character-Level Extraction
`extract_chars()` returns a list of `TextChar` objects with precise positioning and font metadata for every character on the page.
```python
from pdf_oxide import PdfDocument
doc = PdfDocument("paper.pdf")
chars = doc.extract_chars(0)
for ch in chars[:10]:
print(f"'{ch.char}' at ({ch.x:.1f}, {ch.y:.1f}) "
f"size={ch.font_size:.1f} font={ch.font_name} "
f"bbox={ch.bbox}")
```
Each `TextChar` has the following fields:
| Field | Type | Description |
|---------------|-------------------|--------------------------------------|
| `char` | `str` | The Unicode character |
| `x` | `float` | Horizontal position in points |
| `y` | `float` | Vertical position in points |
| `font_size` | `float` | Font size in points |
| `font_name` | `str` | PostScript font name |
| `bbox` | `tuple[float, 4]` | Bounding box `(x0, y0, x1, y1)` |
## Text Spans
`extract_spans()` groups consecutive characters that share the same font and size into spans, giving you structured text with font metadata.
```python
from pdf_oxide import PdfDocument
doc = PdfDocument("paper.pdf")
spans = doc.extract_spans(0)
for span in spans:
print(f"'{span.text}' font={span.font_name} size={span.font_size}")
```
## Markdown Conversion
Convert a PDF page to Markdown with optional heading detection.
```python
from pdf_oxide import PdfDocument
doc = PdfDocument("paper.pdf")
md = doc.to_markdown(0, detect_headings=True)
print(md)
```
## HTML Conversion
Convert a PDF page to HTML.
```python
from pdf_oxide import PdfDocument
doc = PdfDocument("paper.pdf")
html = doc.to_html(0)
print(html)
```
## Image Extraction
`extract_images()` returns a list of `ImageInfo` objects for every image embedded on a page, including images referenced in content streams and nested Form XObjects.
```python
from pdf_oxide import PdfDocument
doc = PdfDocument("brochure.pdf")
images = doc.extract_images(0)
for i, img in enumerate(images):
print(f"Image {i}: {img.width}x{img.height} "
f"{img.color_space} {img.bits_per_component}bpc "
f"({len(img.data)} bytes)")
img.save(f"image_{i}.png")
```
Each `ImageInfo` has the following fields:
| Field | Type | Description |
|----------------------|---------|------------------------------------|
| `width` | `int` | Image width in pixels |
| `height` | `int` | Image height in pixels |
| `color_space` | `str` | Color space (e.g. `DeviceRGB`) |
| `bits_per_component` | `int` | Bits per color channel |
| `data` | `bytes` | Raw image data |
## Password-Protected PDFs
Pass the user password as a second argument to open encrypted documents.
```python
from pdf_oxide import PdfDocument
doc = PdfDocument("confidential.pdf", password="secret")
text = doc.extract_text(0)
print(text)
```
## PDF Creation
The `Pdf` class provides factory methods to create PDFs from various source formats.
### From Markdown
```python
from pdf_oxide import Pdf
pdf = Pdf.from_markdown("# Hello World\n\nThis is a PDF.")
pdf.save("output.pdf")
```
### From HTML
```python
from pdf_oxide import Pdf
pdf = Pdf.from_html("
Invoice
Amount due: $42.00
")
pdf.save("invoice.pdf")
```
### From Plain Text
```python
from pdf_oxide import Pdf
pdf = Pdf.from_text("Plain text document.\n\nSecond paragraph.")
pdf.save("notes.pdf")
```
### From Images
```python
from pdf_oxide import Pdf
pdf = Pdf.from_image("scan.jpg")
pdf.save("scan.pdf")
```
## Search
Search for text across the entire document or within a single page.
```python
from pdf_oxide import PdfDocument
doc = PdfDocument("manual.pdf")
# Search all pages
results = doc.search("configuration")
for r in results:
print(f"Page {r.page}: '{r.text}' at ({r.x:.0f}, {r.y:.0f})")
# Search a single page
page_results = doc.search_page(0, "configuration")
```
## Error Handling
PDF Oxide raises `PdfError` for PDF-specific failures and standard Python exceptions for I/O problems.
```python
from pdf_oxide import PdfDocument, PdfError
try:
doc = PdfDocument("document.pdf")
text = doc.extract_text(0)
except PdfError as e:
print(f"PDF error: {e}")
except FileNotFoundError:
print("File not found")
```
## Next Steps
- [Rust Getting Started](/docs/getting-started/rust) -- using PDF Oxide from Rust
- [Text Extraction](/docs/extraction/text) -- detailed extraction options and recipes
- [PDF Creation](/docs/creation/from-markdown) -- advanced creation with PdfBuilder, encryption, and metadata
- [Editing](/docs/editing/overview) -- modifying existing PDFs, annotations, and form fields
- [API Reference](https://docs.rs/pdf_oxide) -- full API documentation
---
# Getting Started with PDF Oxide (Rust)
PDF Oxide is the complete PDF toolkit for Rust. One library for extracting, creating, and editing PDFs with a unified API.
## Installation
Add `pdf_oxide` to your `Cargo.toml`:
```toml
[dependencies]
pdf_oxide = "0.3"
```
### Feature Flags
Enable only the capabilities you need:
```toml
# Default -- text extraction, creation, editing
pdf_oxide = "0.3"
# Page rendering to images
pdf_oxide = { version = "0.3", features = ["rendering"] }
# Barcode generation
pdf_oxide = { version = "0.3", features = ["barcodes"] }
# Digital signatures
pdf_oxide = { version = "0.3", features = ["signatures"] }
# Office document conversion (DOCX, XLSX, PPTX)
pdf_oxide = { version = "0.3", features = ["office"] }
# Everything
pdf_oxide = { version = "0.3", features = ["full"] }
```
## Opening a PDF
Use `PdfDocument::open()` to load a file and inspect its metadata.
```rust
use pdf_oxide::PdfDocument;
let doc = PdfDocument::open("research-paper.pdf")?;
println!("Pages: {}", doc.page_count());
println!("PDF version: {}", doc.version());
```
## Text Extraction
### Plain Text
```rust
use pdf_oxide::PdfDocument;
let doc = PdfDocument::open("report.pdf")?;
let text = doc.extract_text(0)?;
println!("{text}");
```
### Text Spans
`extract_spans()` returns a `Vec` with font metadata for each run of identically-styled text.
```rust
use pdf_oxide::PdfDocument;
let doc = PdfDocument::open("paper.pdf")?;
let spans = doc.extract_spans(0)?;
for span in &spans {
println!("'{}' at ({:.1}, {:.1}) font={} size={:.1}",
span.text, span.x, span.y, span.font_name, span.font_size);
}
```
`TextSpan` fields:
| Field | Type | Description |
|-------------|----------|----------------------------------|
| `text` | `String` | The text content |
| `x` | `f64` | Horizontal position in points |
| `y` | `f64` | Vertical position in points |
| `font_name` | `String` | PostScript font name |
| `font_size` | `f64` | Font size in points |
| `bbox` | `Rect` | Bounding rectangle |
## Character-Level Extraction
`extract_chars()` returns a `Vec` with precise positioning for every character.
```rust
use pdf_oxide::PdfDocument;
let doc = PdfDocument::open("paper.pdf")?;
let chars = doc.extract_chars(0)?;
for ch in chars.iter().take(10) {
println!("'{}' at ({:.1}, {:.1}) size={:.1} font={}",
ch.char, ch.x, ch.y, ch.font_size, ch.font_name);
}
```
`TextChar` fields:
| Field | Type | Description |
|-------------|----------|----------------------------------|
| `char` | `char` | The Unicode character |
| `x` | `f64` | Horizontal position in points |
| `y` | `f64` | Vertical position in points |
| `font_size` | `f64` | Font size in points |
| `font_name` | `String` | PostScript font name |
| `bbox` | `Rect` | Bounding rectangle |
## Markdown Conversion
Convert a page to Markdown with configurable options.
```rust
use pdf_oxide::PdfDocument;
use pdf_oxide::MarkdownOptions;
let doc = PdfDocument::open("paper.pdf")?;
let options = MarkdownOptions { detect_headings: true, ..Default::default() };
let md = doc.to_markdown(0, &options)?;
println!("{md}");
```
## HTML Conversion
```rust
use pdf_oxide::PdfDocument;
let doc = PdfDocument::open("paper.pdf")?;
let html = doc.to_html(0)?;
println!("{html}");
```
## Image Extraction
`extract_images()` returns metadata and raw data for every image on a page, including images in content streams and nested Form XObjects.
```rust
use pdf_oxide::PdfDocument;
let doc = PdfDocument::open("brochure.pdf")?;
let images = doc.extract_images(0)?;
for (i, img) in images.iter().enumerate() {
println!("Image {i}: {}x{} {} {}bpc ({} bytes)",
img.width, img.height, img.color_space,
img.bits_per_component, img.data.len());
}
```
Write images directly to disk with `extract_images_to_files()`:
```rust
let doc = PdfDocument::open("brochure.pdf")?;
let paths = doc.extract_images_to_files(0, "output_dir")?;
for path in &paths {
println!("Saved: {}", path.display());
}
```
## PDF Creation
### Factory Methods
The `Pdf` type provides high-level factory methods.
```rust
use pdf_oxide::api::Pdf;
let mut pdf = Pdf::from_markdown("# Hello World\n\nThis is a PDF.")?;
pdf.save("output.pdf")?;
```
```rust
let mut pdf = Pdf::from_html("
Invoice
Amount: $42
")?;
pdf.save("invoice.pdf")?;
```
```rust
let mut pdf = Pdf::from_text("Plain text content.")?;
pdf.save("notes.pdf")?;
```
```rust
let mut pdf = Pdf::from_image("scan.jpg")?;
pdf.save("scan.pdf")?;
```
### PdfBuilder Fluent API
For full control over metadata, page size, and margins:
```rust
use pdf_oxide::api::PdfBuilder;
use pdf_oxide::writer::PageSize;
let mut pdf = PdfBuilder::new()
.title("Annual Report")
.author("Acme Corp")
.page_size(PageSize::A4)
.margins(72.0, 72.0, 72.0, 72.0)
.font_size(11.0)
.from_markdown("# Annual Report\n\n...")?;
pdf.save("annual-report.pdf")?;
```
### DocumentBuilder Low-Level API
For pixel-level placement of text, shapes, and images:
```rust
use pdf_oxide::writer::DocumentBuilder;
let mut builder = DocumentBuilder::new();
builder.add_page(612.0, 792.0)
.text("Hello, world!", 72.0, 720.0, 12.0)
.rect(100.0, 600.0, 200.0, 50.0)
.image_at("logo.png", 400.0, 700.0, 100.0, 50.0)?;
builder.save("custom.pdf")?;
```
## Search
Search for text across the document or with fine-grained options.
```rust
use pdf_oxide::api::Pdf;
let pdf = Pdf::open("manual.pdf")?;
// Simple search across all pages
let results = pdf.search("configuration")?;
for r in &results {
println!("Page {}: '{}' at ({:.0}, {:.0})", r.page, r.text, r.x, r.y);
}
```
```rust
use pdf_oxide::api::{Pdf, SearchOptions};
let pdf = Pdf::open("manual.pdf")?;
let opts = SearchOptions {
case_sensitive: false,
whole_word: true,
max_results: Some(50),
..Default::default()
};
let results = pdf.search_with_options("configuration", &opts)?;
```
## Editing
### DocumentEditor
Open an existing PDF for structural edits like page rotation and form field manipulation.
```rust
use pdf_oxide::api::Pdf;
let mut pdf = Pdf::open_editor("form-template.pdf")?;
// Rotate a page
pdf.rotate_page(0, 90)?;
// Add a form field
pdf.add_text_field("name", [100.0, 700.0, 300.0, 720.0])?;
pdf.add_checkbox("agree", [100.0, 650.0, 120.0, 670.0], false)?;
pdf.save("modified.pdf")?;
```
### DOM-Like Page Editing
Navigate page elements and modify text in place.
```rust
use pdf_oxide::api::Pdf;
let mut pdf = Pdf::open("document.pdf")?;
let mut page = pdf.page(0)?;
// Find text elements
for t in page.find_text_containing("Draft") {
println!("Found '{}' at {:?}", t.text(), t.bbox());
}
// Replace text
let matches = page.find_text_containing("Draft");
for t in &matches {
page.set_text(t.id(), "Final")?;
}
pdf.save_page(page)?;
pdf.save("updated.pdf")?;
```
## Error Handling
All fallible operations return `Result`. The `PdfError` enum covers the main failure modes.
```rust
use pdf_oxide::PdfDocument;
use pdf_oxide::PdfError;
fn extract(path: &str) -> Result {
let doc = PdfDocument::open(path)?;
doc.extract_text(0)
}
match extract("file.pdf") {
Ok(text) => println!("{text}"),
Err(PdfError::Io(e)) => eprintln!("I/O error: {e}"),
Err(PdfError::Parse(msg)) => eprintln!("Parse error: {msg}"),
Err(PdfError::Password) => eprintln!("Password required"),
Err(PdfError::PageOutOfRange { index, count }) => {
eprintln!("Page {index} does not exist ({count} pages total)");
}
Err(e) => eprintln!("Error: {e}"),
}
```
`PdfError` variants:
| Variant | Description |
|------------------|-------------------------------------------------|
| `Io` | File system or I/O failure |
| `Parse` | Malformed PDF structure |
| `Password` | Document is encrypted and no password was given |
| `PageOutOfRange` | Requested page index exceeds page count |
## Next Steps
- [Python Getting Started](/docs/getting-started/python) -- using PDF Oxide from Python
- [Text Extraction](/docs/extraction/text) -- detailed extraction options and recipes
- [PDF Creation](/docs/creation/from-markdown) -- advanced creation with PdfBuilder, encryption, and metadata
- [Editing](/docs/editing/overview) -- modifying existing PDFs, annotations, and form fields
- [API Reference](https://docs.rs/pdf_oxide) -- full API documentation
---
# Text Extraction
PDF Oxide provides multiple levels of text extraction: full-page text, styled spans with font metadata, and individual characters with precise positioning. Use `extract_text()` for quick content retrieval, `extract_spans()` when you need font and position data, and `extract_chars()` for per-character analysis such as custom layout engines or OCR post-processing.
For Tagged PDFs, text extraction automatically follows the document's structure tree for correct reading order. For untagged PDFs, extraction uses page content order with intelligent line-break detection.
## Quick Example
**Python**
```python
from pdf_oxide import PdfDocument
doc = PdfDocument("report.pdf")
text = doc.extract_text(0)
print(text)
```
**Rust**
```rust
use pdf_oxide::PdfDocument;
let mut doc = PdfDocument::open("report.pdf")?;
let text = doc.extract_text(0)?;
println!("{}", text);
```
---
## API Reference
### `extract_text(page_index) -> str`
Extract all text from a page as a single string. Automatically detects Tagged PDFs and uses the structure tree for reading order when available. Inserts line breaks and spaces based on vertical and horizontal gaps between spans.
| Parameter | Type | Description |
|-----------|------|-------------|
| `page_index` | `int` / `usize` | Zero-based page index |
**Returns:** The full text content of the page.
**Python**
```python
doc = PdfDocument("report.pdf")
for i in range(doc.page_count()):
text = doc.extract_text(i)
print(f"--- Page {i + 1} ---")
print(text)
```
**Rust**
```rust
let mut doc = PdfDocument::open("report.pdf")?;
let page_count = doc.page_count()?;
for i in 0..page_count {
let text = doc.extract_text(i)?;
println!("--- Page {} ---", i + 1);
println!("{}", text);
}
```
---
### `extract_spans(page_index) -> list[TextSpan]`
Extract text as spans -- contiguous runs of text with the same font and style. Each span includes the text content, bounding box, font name, font size, weight, italic flag, and color. This is the recommended approach for most extraction tasks that need layout or font information.
| Parameter | Type | Description |
|-----------|------|-------------|
| `page_index` | `int` / `usize` | Zero-based page index |
**Returns:** A list/vector of `TextSpan` objects.
#### TextSpan Fields
| Field | Type | Description |
|-------|------|-------------|
| `text` | `str` | The text content of the span |
| `bbox` | `Rect` | Bounding box (x, y, width, height) |
| `font_name` | `str` | Font name/family (e.g., "Helvetica", "TimesNewRoman") |
| `font_size` | `f32` | Font size in points |
| `font_weight` | `FontWeight` | Weight: Normal, Bold, Light, SemiBold, etc. |
| `is_italic` | `bool` | Whether the span is italic |
| `color` | `Color` | RGB color (r, g, b) with values 0.0--1.0 |
| `mcid` | `Option` | Marked Content ID for Tagged PDFs |
| `sequence` | `usize` | Extraction order (tie-breaker for Y-coordinate sorting) |
| `char_spacing` | `f32` | Character spacing (Tc parameter) |
| `word_spacing` | `f32` | Word spacing (Tw parameter) |
| `horizontal_scaling` | `f32` | Horizontal scaling percentage (Tz, default 100.0) |
**Rust**
```rust
let mut doc = PdfDocument::open("paper.pdf")?;
let spans = doc.extract_spans(0)?;
for span in &spans {
println!(
"'{}' at ({:.1}, {:.1}) font={} size={:.1}pt bold={} italic={}",
span.text,
span.bbox.x, span.bbox.y,
span.font_name,
span.font_size,
span.font_weight == FontWeight::Bold,
span.is_italic,
);
}
```
---
### `extract_spans_with_config(page_index, config) -> Vec`
Extract spans with custom span-merging configuration. Use this when the default merging behavior produces incorrect word boundaries for your document.
| Parameter | Type | Description |
|-----------|------|-------------|
| `page_index` | `usize` | Zero-based page index |
| `config` | `SpanMergingConfig` | Configuration controlling extraction parameters |
**Rust**
```rust
use pdf_oxide::extractors::SpanMergingConfig;
let mut doc = PdfDocument::open("report.pdf")?;
let config = SpanMergingConfig::adaptive();
let spans = doc.extract_spans_with_config(0, config)?;
```
---
### `extract_chars(page_index) -> list[TextChar]`
Extract individual characters with precise bounding boxes, font metadata, and transformation properties. This is a low-level API -- prefer `extract_text()` or `extract_spans()` for most use cases. Character extraction is 30--50% faster than span extraction because it skips text grouping and merging.
| Parameter | Type | Description |
|-----------|------|-------------|
| `page_index` | `int` / `usize` | Zero-based page index |
**Returns:** A list/vector of `TextChar` objects.
#### TextChar Fields
| Field | Type | Description |
|-------|------|-------------|
| `char` | `char` | The character |
| `bbox` | `Rect` | Bounding box (x, y, width, height) |
| `font_name` | `str` | Font name/family |
| `font_size` | `f32` | Font size in points |
| `font_weight` | `FontWeight` | Weight (Normal, Bold, etc.) |
| `is_italic` | `bool` | Italic flag |
| `color` | `Color` | RGB color (0.0--1.0 per component) |
| `mcid` | `Option` | Marked Content ID |
| `origin_x` | `f32` | Baseline origin X coordinate |
| `origin_y` | `f32` | Baseline origin Y coordinate |
| `rotation_degrees` | `f32` | Text rotation angle (0--360, clockwise) |
| `advance_width` | `f32` | Horizontal distance to next character position |
| `matrix` | `[f32; 6]` | Full transformation matrix [a, b, c, d, e, f] |
**Python**
```python
doc = PdfDocument("report.pdf")
chars = doc.extract_chars(0)
for ch in chars:
print(f"'{ch.char}' at ({ch.bbox[0]:.1f}, {ch.bbox[1]:.1f}) "
f"font={ch.font_name} size={ch.font_size:.1f}")
```
**Rust**
```rust
let mut doc = PdfDocument::open("report.pdf")?;
let chars = doc.extract_chars(0)?;
for ch in &chars {
println!(
"'{}' origin=({:.1}, {:.1}) rotation={:.0} advance={:.1}",
ch.char, ch.origin_x, ch.origin_y,
ch.rotation_degrees, ch.advance_width,
);
}
```
---
### `to_plain_text(page_index, options) -> str`
Convert a single page to plain text. Accepts conversion options for API consistency, although most options apply primarily to Markdown/HTML output.
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `page_index` | `int` / `usize` | -- | Zero-based page index |
| `preserve_layout` | `bool` | `false` | Preserve visual layout |
| `detect_headings` | `bool` | `true` | Detect headings |
| `include_images` | `bool` | `true` | Include images |
| `image_output_dir` | `str` / `None` | `None` | Image output directory |
**Python**
```python
doc = PdfDocument("paper.pdf")
text = doc.to_plain_text(0)
```
**Rust**
```rust
use pdf_oxide::converters::ConversionOptions;
let mut doc = PdfDocument::open("paper.pdf")?;
let options = ConversionOptions::default();
let text = doc.to_plain_text(0, &options)?;
```
---
### `extract_hierarchical_content(page_index) -> Option`
Extract page content as a hierarchical structure tree. Returns `None` for untagged PDFs. For Tagged PDFs, returns a `StructureElement` tree that represents the document's logical structure (headings, paragraphs, tables, figures).
| Parameter | Type | Description |
|-----------|------|-------------|
| `page_index` | `int` / `usize` | Zero-based page index |
**Rust**
```rust
let mut doc = PdfDocument::open("tagged-report.pdf")?;
if let Some(root) = doc.extract_hierarchical_content(0)? {
println!("Structure type: {:?}", root.structure_type);
for child in &root.children {
println!(" Child: {:?}", child.structure_type);
}
}
```
---
## Advanced Examples
### Build a word-frequency table from spans
```python
from collections import Counter
from pdf_oxide import PdfDocument
doc = PdfDocument("report.pdf")
words = Counter()
for page in range(doc.page_count()):
text = doc.extract_text(page)
for word in text.split():
words[word.lower().strip(".,;:!?\"'()[]")] += 1
for word, count in words.most_common(20):
print(f"{word:20s} {count}")
```
### Detect bold headings using span metadata
```rust
use pdf_oxide::PdfDocument;
use pdf_oxide::layout::FontWeight;
let mut doc = PdfDocument::open("paper.pdf")?;
let spans = doc.extract_spans(0)?;
let headings: Vec<_> = spans.iter()
.filter(|s| s.font_weight == FontWeight::Bold && s.font_size > 14.0)
.collect();
for h in headings {
println!("Heading: '{}' ({}pt)", h.text, h.font_size);
}
```
### Export per-character data to CSV
```python
import csv
from pdf_oxide import PdfDocument
doc = PdfDocument("report.pdf")
chars = doc.extract_chars(0)
with open("characters.csv", "w", newline="") as f:
writer = csv.writer(f)
writer.writerow(["char", "x", "y", "width", "height", "font", "size"])
for ch in chars:
writer.writerow([
ch.char, ch.bbox[0], ch.bbox[1],
ch.bbox[2], ch.bbox[3],
ch.font_name, ch.font_size,
])
```
---
## Related Pages
- [Markdown Conversion](/docs/extraction/markdown) -- Convert text to structured Markdown
- [HTML Conversion](/docs/extraction/html) -- Convert text to HTML with formatting
- [Text Search](/docs/extraction/search) -- Search extracted text with regex
- [Metadata & XMP](/docs/extraction/metadata) -- Read document-level metadata
---
# Image Extraction
PDF Oxide extracts images from PDF pages by parsing the content stream, resolving XObject references via `Do` operators, recursing into nested Form XObjects, and decoding inline images. Use `extract_images()` to get image objects in memory, or `extract_images_to_files()` to save them directly to disk as PNG or JPEG files.
Since v0.3.5, image extraction processes the full page content stream rather than only scanning the XObject dictionary. This correctly handles images placed via `Do` operators, nested Form XObjects with cycle detection, and inline images embedded with `BI`/`ID`/`EI` sequences.
## Quick Example
**Python**
```python
from pdf_oxide import PdfDocument
doc = PdfDocument("report.pdf")
page = doc.page(0)
for img in page.images():
print(f"{img.width}x{img.height}")
```
**Rust**
```rust
use pdf_oxide::PdfDocument;
let mut doc = PdfDocument::open("report.pdf")?;
let images = doc.extract_images(0)?;
for img in &images {
println!("{}x{} {:?}", img.width(), img.height(), img.color_space());
}
```
---
## API Reference
### `extract_images(page_index) -> Vec`
Extract all images from a page. Parses the page content stream to find:
1. **XObject images** referenced via `Do` operators
2. **Form XObjects** containing nested images (recursive, with cycle detection)
3. **Inline images** embedded with `BI`/`ID`/`EI` sequences
CTM (Current Transformation Matrix) tracking provides bounding boxes for each image.
| Parameter | Type | Description |
|-----------|------|-------------|
| `page_index` | `int` / `usize` | Zero-based page index |
**Returns:** A vector of `PdfImage` objects.
#### PdfImage Fields and Methods
| Method / Field | Type | Description |
|----------------|------|-------------|
| `width()` | `u32` | Image width in pixels |
| `height()` | `u32` | Image height in pixels |
| `color_space()` | `&ColorSpace` | Color space (DeviceRGB, DeviceGray, DeviceCMYK, etc.) |
| `bits_per_component()` | `u8` | Bits per color component (typically 8) |
| `data()` | `&ImageData` | Raw image data (JPEG bytes or raw pixels) |
| `bbox()` | `Option<&Rect>` | Bounding box in PDF user space (if CTM was tracked) |
| `save_as_png(path)` | `Result<()>` | Save image as PNG file |
| `save_as_jpeg(path)` | `Result<()>` | Save image as JPEG file |
| `to_png_bytes()` | `Result>` | Encode as PNG bytes in memory |
| `to_jpeg_bytes()` | `Result>` | Encode as JPEG bytes in memory |
#### ColorSpace Variants
| Variant | Description |
|---------|-------------|
| `DeviceRGB` | 3-channel RGB |
| `DeviceGray` | Single-channel grayscale |
| `DeviceCMYK` | 4-channel CMYK |
| `Indexed` | Palette-based color |
| `ICCBased` | ICC profile-based color |
| `CalGray` | Calibrated grayscale |
| `CalRGB` | Calibrated RGB |
| `Lab` | CIE L*a*b* color |
#### ImageData Variants
| Variant | Description |
|---------|-------------|
| `Jpeg(Vec)` | JPEG-compressed data (DCT pass-through) |
| `Raw { pixels, format }` | Decoded pixel data with `PixelFormat` (RGB, Gray, CMYK, RGBA) |
**Rust**
```rust
let mut doc = PdfDocument::open("report.pdf")?;
let images = doc.extract_images(0)?;
for (i, image) in images.iter().enumerate() {
println!(
"Image {}: {}x{} {:?} {}bpc",
i, image.width(), image.height(),
image.color_space(), image.bits_per_component(),
);
if let Some(bbox) = image.bbox() {
println!(" Position: ({:.1}, {:.1})", bbox.x, bbox.y);
}
image.save_as_png(&format!("output/image_{}.png", i))?;
}
```
---
### `extract_images_to_files(page_index, output_dir, prefix, start_index) -> Vec`
Extract images from a page and save them directly to files. JPEG images are saved in their original format (zero re-encoding loss); other images are saved as PNG.
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `page_index` | `usize` | -- | Zero-based page index |
| `output_dir` | `impl AsRef` | -- | Directory to save images (created if absent) |
| `prefix` | `Option<&str>` | `"img"` | Filename prefix |
| `start_index` | `Option` | `1` | Starting index for filenames |
**Returns:** A vector of `ExtractedImageRef` describing saved files.
#### ExtractedImageRef Fields
| Field | Type | Description |
|-------|------|-------------|
| `filename` | `String` | Saved filename (e.g., `"img_001.png"`) |
| `format` | `ImageFormat` | `Png` or `Jpeg` |
| `width` | `u32` | Image width in pixels |
| `height` | `u32` | Image height in pixels |
**Rust**
```rust
let mut doc = PdfDocument::open("report.pdf")?;
let refs = doc.extract_images_to_files(0, "output/images", Some("fig"), Some(1))?;
for img_ref in &refs {
println!("Saved: {} ({}x{}, {:?})", img_ref.filename, img_ref.width, img_ref.height, img_ref.format);
}
```
---
## Advanced Examples
### Extract all images from all pages
```rust
use pdf_oxide::PdfDocument;
use std::path::Path;
let mut doc = PdfDocument::open("book.pdf")?;
let page_count = doc.page_count()?;
let mut total = 0;
for page in 0..page_count {
let refs = doc.extract_images_to_files(
page,
"output/images",
Some(&format!("page{}", page + 1)),
Some(1),
)?;
total += refs.len();
println!("Page {}: {} images", page + 1, refs.len());
}
println!("Total: {} images extracted", total);
```
### Get image bytes in memory (no disk I/O)
```rust
let mut doc = PdfDocument::open("report.pdf")?;
let images = doc.extract_images(0)?;
for image in &images {
let png_bytes = image.to_png_bytes()?;
println!("PNG size: {} bytes", png_bytes.len());
// Use png_bytes with an HTTP response, database, etc.
}
```
### Filter images by size
```rust
let mut doc = PdfDocument::open("report.pdf")?;
let images = doc.extract_images(0)?;
// Only keep images larger than 100x100 pixels
let large_images: Vec<_> = images.iter()
.filter(|img| img.width() > 100 && img.height() > 100)
.collect();
println!("{} large images on page 1", large_images.len());
for img in &large_images {
println!(" {}x{} {:?}", img.width(), img.height(), img.color_space());
}
```
### Distinguish JPEG pass-through from re-encoded images
```rust
use pdf_oxide::extractors::ImageData;
let mut doc = PdfDocument::open("report.pdf")?;
let images = doc.extract_images(0)?;
for (i, image) in images.iter().enumerate() {
match image.data() {
ImageData::Jpeg(bytes) => {
// Original JPEG data -- save directly for zero quality loss
std::fs::write(format!("image_{}.jpg", i), bytes)?;
println!("Image {}: JPEG pass-through ({} bytes)", i, bytes.len());
}
ImageData::Raw { pixels, format } => {
// Raw pixels -- must encode to a file format
image.save_as_png(&format!("image_{}.png", i))?;
println!("Image {}: raw {:?} ({}x{})", i, format, image.width(), image.height());
}
}
}
```
---
## Related Pages
- [Text Extraction](/docs/extraction/text) -- Extract text alongside images
- [HTML Conversion](/docs/extraction/html) -- Embed extracted images in HTML output
- [Markdown Conversion](/docs/extraction/markdown) -- Include images in Markdown output
---
# Markdown Conversion
PDF Oxide converts PDF pages to clean, readable Markdown with automatic heading detection based on font size clustering, bold/italic formatting, table preservation, and optional image embedding. Use `to_markdown()` for a single page or `to_markdown_all()` to convert the entire document in one call.
The conversion pipeline works by extracting text spans, clustering them into lines, detecting heading levels from font size distribution, grouping paragraphs, and emitting Markdown syntax. For Tagged PDFs, the structure tree is consulted for reading order.
## Quick Example
**Python**
```python
from pdf_oxide import PdfDocument
doc = PdfDocument("paper.pdf")
md = doc.to_markdown(0, detect_headings=True)
print(md)
```
**Rust**
```rust
use pdf_oxide::PdfDocument;
use pdf_oxide::converters::ConversionOptions;
let mut doc = PdfDocument::open("paper.pdf")?;
let options = ConversionOptions { detect_headings: true, ..Default::default() };
let md = doc.to_markdown(0, &options)?;
println!("{}", md);
```
---
## API Reference
### `to_markdown(page_index, ...) -> str`
Convert a single page to Markdown.
**Python Signature**
```python
doc.to_markdown(
page: int,
preserve_layout: bool = False,
detect_headings: bool = True,
include_images: bool = True,
image_output_dir: str | None = None,
embed_images: bool = True,
) -> str
```
**Rust Signature**
```rust
pub fn to_markdown(
&mut self,
page_index: usize,
options: &ConversionOptions,
) -> Result
```
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `page_index` | `int` / `usize` | -- | Zero-based page index |
| `preserve_layout` | `bool` | `false` | Preserve visual layout positioning |
| `detect_headings` | `bool` | `true` | Detect headings based on font size and weight |
| `include_images` | `bool` | `true` | Include images in output |
| `image_output_dir` | `str` / `None` | `None` | Directory to save extracted images |
| `embed_images` | `bool` | `true` | Embed images as base64 data URIs |
**Returns:** Markdown string for the page.
---
### `to_markdown_all(...) -> str`
Convert all pages to Markdown, separated by horizontal rules (`---`).
**Python Signature**
```python
doc.to_markdown_all(
preserve_layout: bool = False,
detect_headings: bool = True,
include_images: bool = True,
image_output_dir: str | None = None,
embed_images: bool = True,
) -> str
```
**Rust Signature**
```rust
pub fn to_markdown_all(
&mut self,
options: &ConversionOptions,
) -> Result
```
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `preserve_layout` | `bool` | `false` | Preserve visual layout |
| `detect_headings` | `bool` | `true` | Detect headings |
| `include_images` | `bool` | `true` | Include images |
| `image_output_dir` | `str` / `None` | `None` | Image output directory |
| `embed_images` | `bool` | `true` | Embed images as base64 |
**Returns:** Markdown string for all pages joined with `---` separators.
---
### `to_markdown_with_ocr(page_index, model_path, options) -> str`
Convert a page to Markdown with OCR fallback for scanned pages. When the page has little or no extractable text, OCR is used to recognize text from the rendered page image. Requires the `ocr` feature.
| Parameter | Type | Description |
|-----------|------|-------------|
| `page_index` | `usize` | Zero-based page index |
| `model_path` | `&str` | Path to the OCR model files |
| `options` | `&ConversionOptions` | Conversion options |
**Rust**
```rust
let mut doc = PdfDocument::open("scanned.pdf")?;
let options = ConversionOptions { detect_headings: true, ..Default::default() };
let md = doc.to_markdown_with_ocr(0, "/path/to/models", &options)?;
println!("{}", md);
```
---
### ConversionOptions
The `ConversionOptions` struct controls all conversion behavior.
| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `preserve_layout` | `bool` | `false` | Preserve visual layout with positioning |
| `detect_headings` | `bool` | `true` | Auto-detect headings from font size clusters |
| `extract_tables` | `bool` | `false` | Extract tables (experimental) |
| `include_images` | `bool` | `true` | Include images in output |
| `image_output_dir` | `Option` | `None` | Save images to this directory |
| `embed_images` | `bool` | `true` | Embed images as base64 data URIs |
| `reading_order_mode` | `ReadingOrderMode` | `Auto` | How to determine reading order |
| `bold_marker_behavior` | `BoldMarkerBehavior` | `Conservative` | Bold marker application strategy |
---
## How It Works
The Markdown conversion pipeline operates in several stages:
1. **Text Extraction** -- Extracts `TextSpan` objects from the page content stream, capturing text, position, font, size, weight, and color.
2. **Character Clustering** -- Groups characters into words based on inter-character gaps, then words into lines based on vertical proximity.
3. **Reading Order** -- Determines reading order using either the Tagged PDF structure tree (preferred) or a graph-based spatial analysis of text block positions.
4. **Heading Detection** -- When `detect_headings` is enabled, clusters font sizes across the page to identify heading levels. Larger and bolder text is mapped to `#`, `##`, `###` headings.
5. **Formatting** -- Applies bold (`**text**`) and italic (`*text*`) markers based on font weight and style metadata.
6. **Table Detection** -- Identifies tabular layouts using spatial analysis of aligned text blocks and emits GFM-style Markdown tables.
7. **Whitespace Cleanup** -- Normalizes spacing, removes redundant blank lines, and ensures consistent paragraph breaks.
---
## Advanced Examples
### Convert entire PDF to a Markdown file
```python
from pdf_oxide import PdfDocument
doc = PdfDocument("book.pdf")
md = doc.to_markdown_all(detect_headings=True)
with open("book.md", "w", encoding="utf-8") as f:
f.write(md)
```
### Convert with images saved to a directory
```rust
use pdf_oxide::PdfDocument;
use pdf_oxide::converters::ConversionOptions;
let mut doc = PdfDocument::open("report.pdf")?;
let options = ConversionOptions {
detect_headings: true,
include_images: true,
embed_images: false,
image_output_dir: Some("output/images".to_string()),
..Default::default()
};
let md = doc.to_markdown_all(&options)?;
std::fs::write("output/report.md", &md)?;
```
### Page-by-page conversion with progress
```python
from pdf_oxide import PdfDocument
doc = PdfDocument("report.pdf")
pages = doc.page_count()
parts = []
for i in range(pages):
md = doc.to_markdown(i, detect_headings=True)
parts.append(md)
print(f"Converted page {i + 1}/{pages}")
full_md = "\n\n---\n\n".join(parts)
with open("report.md", "w") as f:
f.write(full_md)
```
### Disable heading detection for flat text
```python
doc = PdfDocument("form.pdf")
md = doc.to_markdown(0, detect_headings=False)
# All text rendered as paragraphs, no # headings
```
---
## Related Pages
- [Text Extraction](/docs/extraction/text) -- Raw text and span extraction
- [HTML Conversion](/docs/extraction/html) -- Convert to HTML instead of Markdown
- [Image Extraction](/docs/extraction/images) -- Extract images separately
---
# HTML Conversion
PDF Oxide converts PDF pages to structured HTML with heading detection, font styling, and optional CSS-based layout preservation. Use `to_html()` for a single page or `to_html_all()` to convert the entire document. When `preserve_layout` is enabled, elements are positioned with CSS absolute coordinates matching the original PDF layout. When disabled, the output is semantic HTML with natural flow.
## Quick Example
**Python**
```python
from pdf_oxide import PdfDocument
doc = PdfDocument("report.pdf")
html = doc.to_html(0, detect_headings=True)
print(html)
```
**Rust**
```rust
use pdf_oxide::PdfDocument;
use pdf_oxide::converters::ConversionOptions;
let mut doc = PdfDocument::open("report.pdf")?;
let options = ConversionOptions { detect_headings: true, ..Default::default() };
let html = doc.to_html(0, &options)?;
println!("{}", html);
```
---
## API Reference
### `to_html(page_index, ...) -> str`
Convert a single page to HTML.
**Python Signature**
```python
doc.to_html(
page: int,
preserve_layout: bool = False,
detect_headings: bool = True,
include_images: bool = True,
image_output_dir: str | None = None,
embed_images: bool = True,
) -> str
```
**Rust Signature**
```rust
pub fn to_html(
&mut self,
page_index: usize,
options: &ConversionOptions,
) -> Result
```
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `page_index` | `int` / `usize` | -- | Zero-based page index |
| `preserve_layout` | `bool` | `false` | Use CSS absolute positioning to match PDF layout |
| `detect_headings` | `bool` | `true` | Auto-detect heading levels from font sizes |
| `include_images` | `bool` | `true` | Include images in the HTML output |
| `image_output_dir` | `str` / `None` | `None` | Directory to save extracted images |
| `embed_images` | `bool` | `true` | Embed images as base64 data URIs |
**Returns:** HTML string for the page.
When `preserve_layout` is `true`, the output uses `
` elements with absolute CSS positioning:
```html
Introduction
```
When `preserve_layout` is `false`, the output uses semantic elements:
```html
Introduction
This report examines the quarterly results...
```
---
### `to_html_all(...) -> str`
Convert all pages to HTML. Each page is wrapped in a `
` element.
**Python Signature**
```python
doc.to_html_all(
preserve_layout: bool = False,
detect_headings: bool = True,
include_images: bool = True,
image_output_dir: str | None = None,
embed_images: bool = True,
) -> str
```
**Rust Signature**
```rust
pub fn to_html_all(
&mut self,
options: &ConversionOptions,
) -> Result
```
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `preserve_layout` | `bool` | `false` | Use CSS absolute positioning |
| `detect_headings` | `bool` | `true` | Detect headings |
| `include_images` | `bool` | `true` | Include images |
| `image_output_dir` | `str` / `None` | `None` | Image output directory |
| `embed_images` | `bool` | `true` | Embed images as base64 |
**Returns:** HTML string for all pages.
---
### ConversionOptions
See the [Markdown Conversion](/docs/extraction/markdown) page for the full `ConversionOptions` reference. The same options struct is shared between Markdown and HTML conversion.
---
## Advanced Examples
### Create a complete HTML file
```python
from pdf_oxide import PdfDocument
doc = PdfDocument("report.pdf")
body = doc.to_html_all(detect_headings=True)
html = f"""
Report
{body}
"""
with open("report.html", "w", encoding="utf-8") as f:
f.write(html)
```
### Layout-preserved HTML for visual fidelity
```rust
use pdf_oxide::PdfDocument;
use pdf_oxide::converters::ConversionOptions;
let mut doc = PdfDocument::open("brochure.pdf")?;
let options = ConversionOptions {
preserve_layout: true,
detect_headings: false, // layout mode uses exact positioning
include_images: true,
embed_images: true,
..Default::default()
};
let html = doc.to_html(0, &options)?;
std::fs::write("brochure.html", &html)?;
```
### Convert with external image files
```python
from pdf_oxide import PdfDocument
doc = PdfDocument("report.pdf")
html = doc.to_html_all(
detect_headings=True,
include_images=True,
embed_images=False,
image_output_dir="output/images",
)
with open("output/report.html", "w") as f:
f.write(html)
# Images saved as output/images/img_001.png, img_002.jpg, etc.
```
### Page-by-page conversion with custom wrappers
```rust
use pdf_oxide::PdfDocument;
use pdf_oxide::converters::ConversionOptions;
let mut doc = PdfDocument::open("book.pdf")?;
let options = ConversionOptions::default();
let page_count = doc.page_count()?;
let mut pages_html = Vec::new();
for i in 0..page_count {
let html = doc.to_html(i, &options)?;
pages_html.push(format!(
"\n{}\n",
i + 1, html
));
}
let full = pages_html.join("\n");
std::fs::write("output.html", &full)?;
```
---
## Related Pages
- [Markdown Conversion](/docs/extraction/markdown) -- Convert to Markdown instead of HTML
- [Text Extraction](/docs/extraction/text) -- Extract raw text without formatting
- [Image Extraction](/docs/extraction/images) -- Extract images separately
---
# Form Data Extraction
PDF Oxide extracts interactive form fields (AcroForms) from PDF documents, including text fields, checkboxes, radio buttons, choice fields, and signatures. Extracted form data can be exported to FDF or XFDF format for interchange. XFA forms (XML Forms Architecture) can be analyzed and converted as well.
Use `FormExtractor::extract_fields()` in Rust or `doc.page().form_fields` via the DOM API to read form field names, types, and current values.
## Quick Example
**Python**
```python
from pdf_oxide import PdfDocument
doc = PdfDocument("form.pdf")
page = doc.page(0)
for field in page.form_fields():
print(f"{field.name}: {field.value}")
```
**Rust**
```rust
use pdf_oxide::extractors::FormExtractor;
use pdf_oxide::PdfDocument;
let mut doc = PdfDocument::open("form.pdf")?;
let fields = FormExtractor::extract_fields(&mut doc)?;
for field in &fields {
println!("{}: {:?}", field.name, field.value);
}
```
---
## API Reference
### `FormExtractor::extract_fields(doc) -> Vec`
Extract all form fields from a PDF document's AcroForm dictionary (ISO 32000-1:2008, Section 12.7). Traverses the field hierarchy, resolving inherited attributes and computing full qualified names.
| Parameter | Type | Description |
|-----------|------|-------------|
| `doc` | `&mut PdfDocument` | The PDF document to extract fields from |
**Returns:** A vector of `FormField` objects.
#### FormField Fields
| Field | Type | Description |
|-------|------|-------------|
| `name` | `String` | Field name from `/T` key |
| `full_name` | `String` | Fully qualified name (dot-separated for hierarchical fields) |
| `field_type` | `FieldType` | Field type (Button, Text, Choice, Signature, Unknown) |
| `value` | `FieldValue` | Current field value |
| `tooltip` | `Option` | Tooltip/description from `/TU` key |
| `bounds` | `Option<[f64; 4]>` | Bounding box [x1, y1, x2, y2] from `/Rect` |
| `object_ref` | `Option` | Object reference for updates |
| `flags` | `Option` | Field flags (ReadOnly, Required, NoExport, etc.) |
| `default_value` | `Option` | Default value from `/DV` key |
| `max_length` | `Option` | Maximum length for text fields |
| `alignment` | `Option` | Text alignment (0=left, 1=center, 2=right) |
| `default_appearance` | `Option` | Default appearance string from `/DA` |
| `border_style` | `Option` | Border style from `/BS` |
| `appearance_chars` | `Option` | Appearance characteristics from `/MK` |
#### FieldType Variants
| Variant | Description |
|---------|-------------|
| `Button` | Checkbox, radio button, or push button (`/Btn`) |
| `Text` | Single or multi-line text field (`/Tx`) |
| `Choice` | List box or combo box (`/Ch`) |
| `Signature` | Digital signature field (`/Sig`) |
| `Unknown(String)` | Unrecognized field type |
#### FieldValue Variants
| Variant | Description |
|---------|-------------|
| `Text(String)` | Text string value |
| `Boolean(bool)` | Boolean value (checkboxes) |
| `Name(String)` | Name value (radio buttons, choice fields) |
| `Array(Vec)` | Multiple values (multi-select list boxes) |
| `None` | No value present |
---
### `FormExtractor::export_fdf(doc, fields) -> Result>`
Export form field data as FDF (Forms Data Format). FDF is a lightweight format for exchanging form data between applications (ISO 32000-1:2008, Section 12.7.8).
| Parameter | Type | Description |
|-----------|------|-------------|
| `doc` | `&mut PdfDocument` | Source PDF document |
| `fields` | `Vec` | Fields to export |
**Returns:** FDF file content as bytes.
**Rust**
```rust
use pdf_oxide::extractors::FormExtractor;
let mut doc = PdfDocument::open("form.pdf")?;
let fields = FormExtractor::extract_fields(&mut doc)?;
let fdf_bytes = FormExtractor::export_fdf(&mut doc, fields)?;
std::fs::write("form_data.fdf", &fdf_bytes)?;
```
---
### `FormExtractor::export_xfdf(doc, fields) -> Result`
Export form field data as XFDF (XML Forms Data Format). XFDF is the XML-based equivalent of FDF and is widely supported for form data interchange.
| Parameter | Type | Description |
|-----------|------|-------------|
| `doc` | `&mut PdfDocument` | Source PDF document |
| `fields` | `Vec` | Fields to export |
**Returns:** XFDF content as an XML string.
**Rust**
```rust
use pdf_oxide::extractors::FormExtractor;
let mut doc = PdfDocument::open("form.pdf")?;
let fields = FormExtractor::extract_fields(&mut doc)?;
let xfdf = FormExtractor::export_xfdf(&mut doc, fields)?;
std::fs::write("form_data.xfdf", &xfdf)?;
```
---
### `analyze_xfa_document(doc) -> Result`
Analyze XFA (XML Forms Architecture) form content. XFA forms use XML-based templates rather than AcroForm fields and are common in government and enterprise forms.
| Parameter | Type | Description |
|-----------|------|-------------|
| `doc` | `&mut PdfDocument` | The PDF document to analyze |
**Returns:** An `XfaAnalysis` struct with details about the XFA form structure.
**Rust**
```rust
use pdf_oxide::xfa::analyze_xfa_document;
let mut doc = PdfDocument::open("xfa-form.pdf")?;
let analysis = analyze_xfa_document(&mut doc)?;
println!("XFA form detected: {} fields", analysis.fields.len());
for field in &analysis.fields {
println!(" {} ({:?})", field.name, field.field_type);
}
```
---
### DocumentEditor Form API
The `DocumentEditor` provides higher-level form field access through `FormFieldWrapper`, which bridges the read-side `FormField` and write-side `FormFieldWidget` interfaces.
#### `editor.get_form_fields() -> Vec`
Get all form fields wrapped for reading and modification.
#### `editor.get_form_field_value(name) -> Option`
Get the current value of a specific field by name.
#### `editor.set_form_field_value(name, value)`
Set the value of an existing form field.
**Rust**
```rust
use pdf_oxide::editor::{DocumentEditor, EditableDocument, FormFieldValue};
let mut editor = DocumentEditor::open("form.pdf")?;
let fields = editor.get_form_fields()?;
for field in &fields {
println!("{}: {:?}", field.name(), field.value());
}
// Modify a field
editor.set_form_field_value("full_name", FormFieldValue::Text("Jane Doe".into()))?;
editor.set_form_field_value("agree", FormFieldValue::Boolean(true))?;
editor.save("filled_form.pdf")?;
```
---
## Advanced Examples
### Extract and display all form fields with metadata
```rust
use pdf_oxide::extractors::{FormExtractor, FieldType};
let mut doc = PdfDocument::open("application.pdf")?;
let fields = FormExtractor::extract_fields(&mut doc)?;
for field in &fields {
let type_str = match &field.field_type {
FieldType::Button => "Button",
FieldType::Text => "Text",
FieldType::Choice => "Choice",
FieldType::Signature => "Signature",
FieldType::Unknown(s) => s.as_str(),
};
println!("[{}] {} = {:?}", type_str, field.full_name, field.value);
if let Some(tooltip) = &field.tooltip {
println!(" Tooltip: {}", tooltip);
}
if let Some(bounds) = &field.bounds {
println!(" Bounds: [{:.1}, {:.1}, {:.1}, {:.1}]",
bounds[0], bounds[1], bounds[2], bounds[3]);
}
}
```
### Check required fields are filled
```rust
use pdf_oxide::extractors::{FormExtractor, FieldValue};
let mut doc = PdfDocument::open("form.pdf")?;
let fields = FormExtractor::extract_fields(&mut doc)?;
let required_empty: Vec<_> = fields.iter()
.filter(|f| {
// Bit 1 of flags = Required
f.flags.map_or(false, |flags| flags & 0x02 != 0)
&& matches!(f.value, FieldValue::None | FieldValue::Text(ref s) if s.is_empty())
})
.collect();
if !required_empty.is_empty() {
println!("Missing required fields:");
for f in &required_empty {
println!(" - {}", f.full_name);
}
}
```
---
## Related Pages
- [Annotation Extraction](/docs/extraction/annotations) -- Access annotations alongside form fields
- [Text Extraction](/docs/extraction/text) -- Extract text content from pages
- [Metadata & XMP](/docs/extraction/metadata) -- Read document-level properties
---
# Annotation Extraction
PDF Oxide provides access to all annotation types defined in the PDF specification (ISO 32000-1:2008, Section 12.5), including text notes, hyperlinks, highlights, stamps, ink annotations, and more. The document outline (bookmarks) is also accessible for building navigation structures.
Use `get_annotations()` on `PdfDocument` for raw annotation data, or the `PdfPage` DOM API for a unified `AnnotationWrapper` interface that supports both reading and writing.
## Quick Example
**Python**
```python
from pdf_oxide import PdfDocument
doc = PdfDocument("annotated.pdf")
page = doc.page(0)
for annot in page.annotations():
print(f"{annot.subtype}: {annot.contents}")
```
**Rust**
```rust
use pdf_oxide::PdfDocument;
let mut doc = PdfDocument::open("annotated.pdf")?;
let annotations = doc.get_annotations(0)?;
for annot in &annotations {
println!("{:?}: {:?}", annot.subtype_enum, annot.contents);
}
```
---
## API Reference
### `get_annotations(page_index) -> Vec`
Extract raw annotations from a specific page. Returns all annotation types present on the page.
| Parameter | Type | Description |
|-----------|------|-------------|
| `page_index` | `usize` | Zero-based page index |
**Returns:** A vector of `Annotation` objects.
#### Annotation Fields
| Field | Type | Description |
|-------|------|-------------|
| `annotation_type` | `String` | Always `"Annot"` |
| `subtype` | `Option` | Raw subtype string (e.g., `"Text"`, `"Highlight"`) |
| `subtype_enum` | `AnnotationSubtype` | Parsed subtype enum |
| `contents` | `Option` | Text contents of the annotation |
| `rect` | `Option<[f64; 4]>` | Bounding rectangle [x1, y1, x2, y2] |
| `author` | `Option` | Author/creator (`/T` entry) |
| `creation_date` | `Option` | Creation date |
| `modification_date` | `Option` | Last modification date |
| `subject` | `Option` | Subject of the annotation |
| `destination` | `Option` | Link destination (for Link annotations) |
| `action` | `Option` | Link action (for Link annotations) |
| `color` | `Option>` | Annotation color components |
| `flags` | `Option` | Annotation flags (invisible, hidden, print, etc.) |
#### AnnotationSubtype Variants
| Variant | Description |
|---------|-------------|
| `Text` | Sticky note annotation |
| `Link` | Hyperlink annotation |
| `FreeText` | Text box annotation |
| `Line` | Line shape annotation |
| `Square` | Rectangle shape annotation |
| `Circle` | Ellipse shape annotation |
| `Polygon` | Polygon shape annotation |
| `PolyLine` | Polyline shape annotation |
| `Highlight` | Text highlight markup |
| `Underline` | Text underline markup |
| `Squiggly` | Squiggly underline markup |
| `StrikeOut` | Strikethrough markup |
| `Stamp` | Rubber stamp annotation |
| `Ink` | Freehand drawing annotation |
| `Popup` | Pop-up note associated with another annotation |
| `FileAttachment` | Embedded file annotation |
| `Sound` | Sound annotation |
| `Movie` | Movie annotation |
| `Screen` | Screen annotation |
| `Widget` | Form field widget |
| `PrinterMark` | Printer's mark annotation |
| `TrapNet` | Trap network annotation |
| `Watermark` | Watermark annotation |
| `ThreeDimensional` | 3D annotation |
| `Redact` | Redaction annotation |
| `Caret` | Caret annotation (insertion point) |
| `RichMedia` | Rich media annotation |
| `Unknown` | Unrecognized annotation type |
---
### `get_outline() -> Option>`
Get the document outline (bookmarks) if present. Returns a hierarchical tree of outline items that can be used for document navigation.
**Returns:**
- `Some(Vec)` -- Bookmarks found and parsed
- `None` -- No bookmarks in the document
#### OutlineItem Fields
| Field | Type | Description |
|-------|------|-------------|
| `title` | `String` | Bookmark title text |
| `dest` | `Option` | Navigation destination |
| `children` | `Vec` | Nested child bookmarks |
#### Destination Variants
| Variant | Description |
|---------|-------------|
| `PageIndex(usize)` | Direct page reference (0-based index) |
| `Named(String)` | Named destination identifier |
**Rust**
```rust
let mut doc = PdfDocument::open("book.pdf")?;
if let Some(outline) = doc.get_outline()? {
for item in &outline {
println!(" {}", item.title);
for child in &item.children {
println!(" {}", child.title);
}
}
} else {
println!("No bookmarks found.");
}
```
---
### PdfPage Annotation API (DOM)
The `PdfPage` object from the `DocumentEditor` provides a higher-level `AnnotationWrapper` interface that supports both reading existing annotations and adding new ones.
#### `page.annotations() -> &[AnnotationWrapper]`
Get all annotations on the page as wrapped objects.
#### `page.find_annotations_by_type(subtype) -> Vec<&AnnotationWrapper>`
Find annotations of a specific type.
#### `page.add_annotation(annotation)`
Add a new annotation to the page.
#### `page.remove_annotation(index) -> Option`
Remove an annotation by index.
#### `page.find_annotations_in_region(rect) -> Vec<&AnnotationWrapper>`
Find annotations whose bounding boxes intersect a given region.
#### AnnotationWrapper Methods
| Method | Returns | Description |
|--------|---------|-------------|
| `id()` | `AnnotationId` | Unique session ID |
| `subtype()` | `AnnotationSubtype` | Annotation type |
| `rect()` | `Rect` | Bounding rectangle |
| `contents()` | `Option<&str>` | Text contents |
| `color()` | `Option<(f32, f32, f32)>` | RGB color (0.0--1.0) |
| `is_modified()` | `bool` | Whether annotation has been changed |
**Python**
```python
doc = PdfDocument("annotated.pdf")
page = doc.page(0)
# List all annotations
for annot in page.annotations():
print(f"[{annot.subtype}] {annot.contents} at {annot.rect}")
# Find highlights
highlights = [a for a in page.annotations() if a.subtype == "Highlight"]
print(f"Found {len(highlights)} highlights")
```
**Rust**
```rust
use pdf_oxide::editor::{DocumentEditor, EditableDocument};
use pdf_oxide::annotation_types::AnnotationSubtype;
let mut editor = DocumentEditor::open("annotated.pdf")?;
let page = editor.get_page(0)?;
// Find all highlight annotations
let highlights = page.find_annotations_by_type(AnnotationSubtype::Highlight);
for h in &highlights {
println!("Highlight at {:?}: {:?}", h.rect(), h.contents());
}
```
---
## Advanced Examples
### Build a table of contents from bookmarks
```rust
use pdf_oxide::PdfDocument;
use pdf_oxide::outline::Destination;
let mut doc = PdfDocument::open("book.pdf")?;
fn print_toc(items: &[pdf_oxide::outline::OutlineItem], depth: usize) {
for item in items {
let indent = " ".repeat(depth);
let page = match &item.dest {
Some(Destination::PageIndex(p)) => format!("page {}", p + 1),
Some(Destination::Named(n)) => format!("dest '{}'", n),
None => "no dest".to_string(),
};
println!("{}{} ({})", indent, item.title, page);
print_toc(&item.children, depth + 1);
}
}
if let Some(outline) = doc.get_outline()? {
println!("Table of Contents:");
print_toc(&outline, 0);
}
```
### Extract all comments (Text annotations)
```rust
use pdf_oxide::PdfDocument;
use pdf_oxide::annotation_types::AnnotationSubtype;
let mut doc = PdfDocument::open("reviewed.pdf")?;
let page_count = doc.page_count()?;
for page_idx in 0..page_count {
let annotations = doc.get_annotations(page_idx)?;
let comments: Vec<_> = annotations.iter()
.filter(|a| a.subtype_enum == AnnotationSubtype::Text)
.collect();
if !comments.is_empty() {
println!("Page {}:", page_idx + 1);
for c in &comments {
let author = c.author.as_deref().unwrap_or("Unknown");
let text = c.contents.as_deref().unwrap_or("");
println!(" [{}] {}", author, text);
}
}
}
```
### Extract all hyperlinks
```rust
use pdf_oxide::PdfDocument;
use pdf_oxide::annotation_types::AnnotationSubtype;
let mut doc = PdfDocument::open("report.pdf")?;
let annotations = doc.get_annotations(0)?;
let links: Vec<_> = annotations.iter()
.filter(|a| a.subtype_enum == AnnotationSubtype::Link)
.collect();
for link in &links {
if let Some(ref action) = link.action {
println!("Link: {:?}", action);
}
if let Some(ref dest) = link.destination {
println!("Internal link: {:?}", dest);
}
}
```
---
## Related Pages
- [Form Data Extraction](/docs/extraction/forms) -- Extract form fields (Widget annotations)
- [Text Extraction](/docs/extraction/text) -- Extract text content from pages
- [Metadata & XMP](/docs/extraction/metadata) -- Read document properties and bookmarks
---
# Metadata & XMP
PDF Oxide reads document-level metadata from multiple sources: the PDF header (version), the trailer and catalog dictionaries, XMP metadata streams (ISO 16684), and page label definitions. The `XmpExtractor` parses the Dublin Core, XMP Core, PDF, and XMP Rights namespaces, plus any custom properties.
Use `version()` and `catalog()` for basic document properties, `XmpExtractor::extract()` for rich metadata, and `PageLabelExtractor` for page numbering schemes.
## Quick Example
**Python**
```python
from pdf_oxide import PdfDocument
doc = PdfDocument("report.pdf")
major, minor = doc.version()
print(f"PDF {major}.{minor}, {doc.page_count()} pages")
```
**Rust**
```rust
use pdf_oxide::PdfDocument;
let mut doc = PdfDocument::open("report.pdf")?;
let (major, minor) = doc.version();
println!("PDF {}.{}", major, minor);
println!("Pages: {}", doc.page_count()?);
```
---
## API Reference
### `version() -> (u8, u8)`
Get the PDF version from the file header.
**Returns:** A tuple of (major, minor), e.g., `(1, 7)` for PDF 1.7 or `(2, 0)` for PDF 2.0.
---
### `catalog() -> Result