How to Get X/Y Position Coordinates for Extracted Text in pdf-inspector

pdf-inspector exposes every text element's exact coordinates through TextItem structs returned by the extract_text_with_positions* family of functions, with x and y values measured in PDF points from the bottom-left origin.

The firecrawl/pdf-inspector repository is a Rust-based PDF parsing library that preserves spatial information during text extraction. Unlike simple text dumpers, it tracks where each glyph appears on the page—essential for layout analysis, OCR verification, and document reconstruction workflows.

Where Coordinates Are Stored: The TextItem Struct

Position data lives in src/types.rs, where the TextItem struct defines the schema for extracted text elements:

pub struct TextItem {
    pub text: String,
    pub x: f32,        // horizontal position in PDF points
    pub y: f32,        // vertical position in PDF points
    pub page: u32,     // 1-indexed page number
    pub font: Option<String>,
    pub size: Option<f32>,
    // ... additional metadata
}

Coordinates use the standard PDF coordinate system: origin at the bottom-left corner of the page, with Y increasing upward. One point equals 1/72 of an inch.

The Extraction Pipeline

The core logic resides in src/extractor/mod.rs. The pipeline follows this sequence:

  1. Document loading — load_document_from_path_with_password or load_document_from_mem parses the PDF structure
  2. Font mapping — FontCMaps::from_doc builds glyph-to-Unicode conversion tables
  3. Content stream processing — extract_positioned_text_impl walks text-showing operators (Tj, TJ)
  4. Matrix transformation — the current text matrix converts glyph positions to page-space coordinates
  5. Struct population — TextItem fields are filled and collected into Vec<TextItem>

The implementation automatically handles page rotation, scaling, and MediaBox offsets. You receive coordinates in normalized, upright space regardless of how the PDF was constructed.

Public APIs for Position-Aware Extraction

These functions are re-exported from src/lib.rs:

  • extract_text_with_positions(path) — all pages, file-based
  • extract_text_with_positions_pages(path, page_filter) — subset of 1-indexed pages
  • extract_text_with_positions_mem(buffer) — in-memory PDF
  • extract_text_with_positions_mem_pages(buffer, page_filter) — in-memory with page filter

Each returns Result<Vec<TextItem>, PdfError> containing the full position metadata.

Code Examples

Rust: Extracting Coordinates from File

use pdf_inspector::{extract_text_with_positions, TextItem};

fn main() -> Result<(), pdf_inspector::PdfError> {
    let items: Vec<TextItem> = extract_text_with_positions("invoice.pdf")?;
    
    for item in items {
        println!(
            "Page {} | ({:.2}, {:.2}) | \"{}\"",
            item.page, item.x, item.y, item.text
        );
    }
    Ok(())
}

Python: Using the PyO3 Bindings

The Python bindings in src/python.rs expose identical functionality through PyTextItem objects:

import pdf_inspector

items = pdf_inspector.extract_text_with_positions("invoice.pdf")

for itm in items:
    print(f"Page {itm.page} | ({itm.x:.2f}, {itm.y:.2f}) | \"{itm.text}\"")

Filtering by Specific Pages

For targeted extraction (pages 2 and 4 only):


# Python

items = pdf_inspector.extract_text_with_positions("document.pdf", pages=[2, 4])
// Rust
use std::collections::HashSet;

let mut pages = HashSet::new();
pages.insert(2);
pages.insert(4);

let items = extract_text_with_positions_pages("document.pdf", Some(&pages))?;

Working with PDF Coordinates

Unit Conversions

PDF points are the native unit. Convert as needed:

let inches = item.x / 72.0;
let centimeters = item.x / 72.0 * 2.54;

Coordinate Transformations

For top-down coordinate systems (common in UI frameworks), flip the Y axis:

// Assuming standard US Letter page (792 pt height)
let page_height = 792.0;
let y_from_top = page_height - item.y;

Relying on MediaBox dimensions from the PDF itself is more robust than hardcoded values.

Performance Characteristics

Position-aware extraction incurs minimal overhead. The coordinate calculations piggyback on matrix operations already performed during text rendering. Use the *_mem variants when processing PDFs from network streams or databases to eliminate redundant I/O.

Summary

  • Data structure: TextItem in src/types.rs holds x: f32 and y: f32 fields
  • Entry points: extract_text_with_positions* functions in src/extractor/mod.rs
  • Coordinate system: PDF points, bottom-left origin, pre-normalized for rotation
  • Language support: Native Rust API with equivalent Python bindings in src/python.rs
  • Page filtering: *_pages variants accept HashSet<u32> (Rust) or list[int] (Python)

Frequently Asked Questions

What units are the X and Y coordinates in?

PDF points, where 1 point = 1/72 inch ≈ 0.3528 mm. These are the native units of the PDF specification and require no transformation for accurate spatial calculations.

Do I need to handle page rotation manually?

No. The extraction pipeline in src/extractor/mod.rs applies the page's rotation matrix before populating TextItem. The coordinates you receive are always in upright, readable orientation regardless of how the PDF was authored.

Can I get coordinates for a specific region of a page?

pdf-inspector returns all text items with their positions. Apply bounding-box filtering in your consumer code:

items = pdf_inspector.extract_text_with_positions("doc.pdf")
region_items = [i for i in items if 100 < i.x < 300 and 500 < i.y < 700]

Built-in region filtering is not exposed in the current API surface.

Are the coordinates precise enough for exact glyph placement?

Yes. The values derive from the PDF content stream's text matrices, which define glyph positioning at the specification level. Sub-point precision is preserved through the f32 fields.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →