Skip to content

Extracción por ámbito — Obtén contenido de una región específica

Cuando procesas facturas, extractos bancarios, declaraciones de impuestos o cualquier maquetación con plantilla, normalmente ya sabes dónde están los campos. En lugar de extraer la página completa y buscar el valor, apunta PDF Oxide al rectángulo exacto y obtén solo lo que hay ahí.

La API fluida within(page, rect) devuelve una región con ámbito acotado sobre la que puedes encadenar métodos de extracción: extract_text(), extract_words(), extract_chars(), extract_tables().

Cobertura de bindings. within(page, rect) está disponible en Python, Rust y WASM. Go y C# exponen los helpers de bajo nivel equivalentes (ExtractTextInRect, ExtractWordsInRect, ExtractImagesInRect) — ver más abajo. La familia in-rect completa (texto, palabras, líneas, tablas, imágenes) está disponible de extremo a extremo en Rust, el C ABI y el wrapper Swift; consulta Variantes de extracción in-rect para ver qué ofrece cada binding.

Ejemplo rápido

rect es (x, y, width, height) en puntos PDF, con el origen en la esquina inferior izquierda de la página. Las páginas Letter son de 612 × 792 puntos.

Python

from pdf_oxide import PdfDocument

doc = PdfDocument("invoice.pdf")

# Top 92 points of page 0 — typical header band
header = doc.within(0, (0, 700, 612, 92)).extract_text()
print(header)

Rust

use pdf_oxide::PdfDocument;
use pdf_oxide::geometry::Rect;

let mut doc = PdfDocument::open("invoice.pdf")?;
let header = doc.within(0, Rect::new(0.0, 700.0, 612.0, 92.0)).extract_text()?;
println!("{}", header);

JavaScript (WASM)

import { WasmPdfDocument } from "pdf-oxide-wasm";

const doc = new WasmPdfDocument(bytes);
const headerRegion = doc.within(0, [0, 700, 612, 92]);
console.log(headerRegion.extractText());
doc.free();

Go (helper de bajo nivel, mismo efecto)

package main

import (
    "fmt"
    "log"
    pdfoxide "github.com/yfedoseev/pdf_oxide/go"
)

func main() {
    doc, err := pdfoxide.Open("invoice.pdf")
    if err != nil { log.Fatal(err) }
    defer doc.Close()

    // ExtractTextInRect(pageIndex, x, y, width, height)
    header, _ := doc.ExtractTextInRect(0, 0, 700, 612, 92)
    fmt.Println(header)
}

C# (helper de bajo nivel)

using PdfOxide;

using var doc = PdfDocument.Open("invoice.pdf");
string header = doc.ExtractTextInRect(0, 0, 700, 612, 92);
Console.WriteLine(header);

Java (page.text(region); BBox en formato de esquinas (x0, y0, x1, y1))

import fyi.oxide.pdf.PdfDocument;
import fyi.oxide.pdf.geometry.BBox;

try (PdfDocument doc = PdfDocument.open(java.nio.file.Path.of("invoice.pdf"))) {
    // Top 92 points of page 0 → corners (0, 700) … (612, 792)
    String header = doc.page(0).text(new BBox(0, 700, 612, 792));
    System.out.println(header);
}

Kotlin

import fyi.oxide.pdf.PdfDocument
import fyi.oxide.pdf.geometry.BBox

PdfDocument.open(java.nio.file.Path.of("invoice.pdf")).use { doc ->
    val header = doc.page(0).text(BBox(0.0, 700.0, 612.0, 792.0))
    println(header)
}

Scala

import fyi.oxide.pdf.PdfDocument
import fyi.oxide.pdf.geometry.BBox
import scala.util.Using

Using.resource(PdfDocument.open("invoice.pdf")) { doc =>
  val header = doc.page(0).text(BBox(0, 700, 612, 792))
  println(header)
}

Clojure

(require '[pdf-oxide.core :as pdf])
(import '[fyi.oxide.pdf.geometry BBox])

(with-open [doc (pdf/open "invoice.pdf")]
  ;; Top 92 points of page 0 → corners (0 700) … (612 792)
  (println (pdf/page-text (pdf/page doc 0) (BBox. 0 700 612 792))))

C++

#include <pdf_oxide/pdf_oxide.hpp>

auto doc = pdf_oxide::Document::open("invoice.pdf");
// extract_text_in_rect(page, x, y, w, h)
auto header = doc.extract_text_in_rect(0, 0, 700, 612, 92);
std::cout << header << "\n";

Swift

import PdfOxide

let doc = try Document.open("invoice.pdf")
let header = try doc.extractTextInRect(0, x: 0, y: 700, w: 612, h: 92)
print(header)

Dart

import 'package:pdf_oxide/pdf_oxide.dart';

final doc = PdfDocument.open('invoice.pdf');
final header = doc.extractTextInRect(0, 0, 700, 612, 92);
print(header);
doc.close();

R

library(pdfoxide)

doc <- pdf_open("invoice.pdf")
# pdf_extract_text_in_rect(doc, page, x, y, width, height)
header <- pdf_extract_text_in_rect(doc, 0, 0, 700, 612, 92)
cat(header)

Julia

using PdfOxide

doc = open_document("invoice.pdf")
header = extract_text_in_rect(doc, 0, 0, 700, 612, 92)
println(header)

Zig

const pdf_oxide = @import("pdf_oxide");
const a = std.heap.page_allocator;

var doc = try pdf_oxide.Document.open("invoice.pdf");
const header = try doc.extractTextInRect(a, 0, 0, 700, 612, 92);  // free header
std.debug.print("{s}\n", .{header});

Objective-C

#import "POXPdfOxide.h"
NSError *err = nil;

POXDocument *doc = [POXDocument openPath:@"invoice.pdf" error:&err];
NSString *header = [doc extractTextInRect:0 x:0 y:700 w:612 h:92 error:&err];
NSLog(@"%@", header);

Elixir

{:ok, doc} = PdfOxide.open("invoice.pdf")
# extract_text_in_rect(doc, page, x, y, w, h)
{:ok, header} = PdfOxide.extract_text_in_rect(doc, 0, 0, 700, 612, 92)
IO.puts(header)

Extracción encadenada desde una región

La forma fluida within() en Python / Rust / WASM te permite llamar a cualquier método de extracción sobre la misma región acotada sin especificar de nuevo el rectángulo:

Python

doc = PdfDocument("invoice.pdf")
region = doc.within(0, (400, 100, 200, 200))   # bottom-right 200×200 box

total_text = region.extract_text()              # plain text
words      = region.extract_words()             # word-level records
chars      = region.extract_chars()             # character-level records

Rust

let region = doc.within(0, Rect::new(400.0, 100.0, 200.0, 200.0));
let text  = region.extract_text()?;
let words = region.extract_words()?;

C++ (sin encadenamiento fluido — llama a cada helper in-rect con el mismo rectángulo)

// bottom-right 200×200 box: x=400, y=100, w=200, h=200
auto text  = doc.extract_text_in_rect(0, 400, 100, 200, 200);
auto words = doc.extract_words_in_rect(0, 400, 100, 200, 200);
auto lines = doc.extract_lines_in_rect(0, 400, 100, 200, 200);

Swift

let text  = try doc.extractTextInRect(0, x: 400, y: 100, w: 200, h: 200)
let words = try doc.extractWordsInRect(0, x: 400, y: 100, w: 200, h: 200)

Dart

final text  = doc.extractTextInRect(0, 400, 100, 200, 200);
final words = doc.extractWordsInRect(0, 400, 100, 200, 200);

R

text  <- pdf_extract_text_in_rect(doc, 0, 400, 100, 200, 200)
words <- pdf_extract_words_in_rect(doc, 0, 400, 100, 200, 200)

Julia

text  = extract_text_in_rect(doc, 0, 400, 100, 200, 200)
words = extract_words_in_rect(doc, 0, 400, 100, 200, 200)

Zig

const text  = try doc.extractTextInRect(a, 0, 400, 100, 200, 200);
const words = try doc.extractWordsInRect(a, 0, 400, 100, 200, 200);  // freeWords

Objective-C

NSString *text = [doc extractTextInRect:0 x:400 y:100 w:200 h:200 error:&err];
NSArray<POXWord*> *words = [doc extractWordsInRect:0 x:400 y:100 w:200 h:200 error:&err];

Elixir

{:ok, text}  = PdfOxide.extract_text_in_rect(doc, 0, 400, 100, 200, 200)
{:ok, words} = PdfOxide.extract_words_in_rect(doc, 0, 400, 100, 200, 200)

Casos de uso habituales

Extracción de campos de factura

Una factura suele tener la dirección del proveedor, el número de factura y la tabla de líneas en zonas fijas. Define los rectángulos una vez por plantilla:

from pdf_oxide import PdfDocument

TEMPLATES = {
    "acme_v1": {
        "invoice_no":  (450, 720,  120,  20),
        "issue_date":  (450, 700,  120,  20),
        "vendor_name": ( 50, 740,  300,  40),
        "total":       (450, 100,  120,  24),
    },
}

def parse_invoice(path, template):
    doc = PdfDocument(path)
    out = {}
    for field, rect in template.items():
        out[field] = doc.within(0, rect).extract_text().strip()
    return out

print(parse_invoice("invoice-2025-04.pdf", TEMPLATES["acme_v1"]))

Líneas de movimientos de extracto bancario

La mayoría de los extractos tienen una franja estrecha de “transacciones”. Recorta esa franja y llama a extract_words() para obtener cada línea en orden de lectura con su bounding box:

doc = PdfDocument("statement.pdf")
for page in range(doc.page_count()):
    txn_region = doc.within(page, (36, 72, 540, 650))   # skip header + footer
    for w in txn_region.extract_words():
        print(f"page {page}: {w.text} at ({w.x0:.0f},{w.y0:.0f})")

Eliminación de encabezado y pie de página

Si solo indexas el contenido del cuerpo, recorta la parte superior e inferior de cada página:

Rust

let mut doc = PdfDocument::open("book.pdf")?;
for i in 0..doc.page_count()? {
    let body = doc.within(i, Rect::new(0.0, 100.0, 612.0, 600.0))
                  .extract_text()?;
    // index `body` …
}

Detección de región de tabla

Cuando ya sabes que una página contiene una tabla y dónde está, acota el rectángulo de la tabla y deja que extract_tables() se centre solo en esa región:

Python

tables = doc.within(0, (50, 200, 500, 400)).extract_tables()
for t in tables:
    for row in t["rows"]:
        print([c["text"] for c in row["cells"]])

¿Qué variantes de extracción acotada por rectángulo existen? {#what-rect-scoped-extraction-variants-exist}

Además de extract_text(), extract_words() y extract_chars(), hay dos variantes más con ámbito rectangular que devuelven resultados con conciencia geométrica desde un único rectángulo: líneas en el rectángulo y tablas en el rectángulo. Ambas filtran una extracción de página completa para quedarse solo con las regiones cuyo bounding box intersecta tu rectángulo, de modo que las coordenadas y el orden de lectura devueltos son los mismos que en una llamada de página completa — solo recortados.

Extraer líneas de texto en una región (extract_lines_in_rect)

Devuelve los registros a nivel de línea (cada uno con su texto, bounding box y recuento de palabras) que caen dentro del rectángulo. Úsalo cuando necesites líneas completas en orden de lectura en lugar de palabras individuales — por ejemplo, bloques de dirección, totales multilínea o una sola fila de extracto.

La firma del C ABI es la referencia autoritativa:

FfiTextLineList *pdf_document_extract_lines_in_rect(
    PdfDocument *handle,
    int32_t page_index,
    float x, float y, float w, float h,
    int32_t *error_code);

Rustextract_lines_in_rect(page_index, region) -> Result<Vec<PathContent>> en PdfDocument:

use pdf_oxide::PdfDocument;
use pdf_oxide::geometry::Rect;

let doc = PdfDocument::open("statement.pdf")?;

// Transactions band: skip the header (top 92pt) and footer (bottom 72pt)
let region = Rect::new(36.0, 72.0, 540.0, 628.0);
let lines = doc.extract_lines_in_rect(0, region)?;
for line in &lines {
    println!("{:?}", line.bbox);
}

Python — la región fluida expone líneas mediante extract_text_lines():

from pdf_oxide import PdfDocument

doc = PdfDocument("statement.pdf")

# Same band as the Rust example above
region = doc.within(0, (36, 72, 540, 628))
for line in region.extract_text_lines():
    print(line.text, line.bbox)

SwiftextractLinesInRect(_:x:y:w:h:) devuelve [TextLine]:

import PdfOxide

let doc = try PdfDocument(path: "statement.pdf")
let lines = try doc.extractLinesInRect(0, x: 36, y: 72, w: 540, h: 628)
for line in lines {
    print(line.text, line.bbox, line.wordCount)
}

C++extract_lines_in_rect(page, x, y, w, h) devuelve std::vector<TextLine>:

auto lines = doc.extract_lines_in_rect(0, 36, 72, 540, 628);
for (const auto& line : lines) {
    std::cout << line.text << "\n";
}

DartextractLinesInRect(page, x, y, w, h) devuelve List<TextLine>:

final lines = doc.extractLinesInRect(0, 36, 72, 540, 628);
for (final line in lines) {
    print('${line.text} ${line.bbox}');
}

Rpdf_extract_lines_in_rect(doc, page, x, y, width, height):

lines <- pdf_extract_lines_in_rect(doc, 0, 36, 72, 540, 628)

Juliaextract_lines_in_rect(doc, page, x, y, w, h):

lines = extract_lines_in_rect(doc, 0, 36, 72, 540, 628)
for line in lines
    println(line.text, " ", line.bbox)
end

ZigextractLinesInRect(allocator, page, x, y, w, h):

const lines = try doc.extractLinesInRect(a, 0, 36, 72, 540, 628);  // freeTextLines

Objective-CextractLinesInRect:x:y:w:h: devuelve NSArray<POXTextLine*>:

NSArray<POXTextLine*> *lines = [doc extractLinesInRect:0 x:36 y:72 w:540 h:628 error:&err];

Elixirextract_lines_in_rect(doc, page, x, y, w, h):

{:ok, lines} = PdfOxide.extract_lines_in_rect(doc, 0, 36, 72, 540, 628)

Go / C#. El punto de entrada C extract_lines_in_rect existe, pero los wrappers de Go y C# aún no lo exponen. En esos lenguajes, extrae las líneas de la página completa y filtra por los bounding boxes devueltos, o usa ExtractWordsInRect (Go) y agrupa las palabras en líneas tú mismo.

Extraer tablas en una región (extract_tables_in_rect)

Acota la detección de tablas a un único rectángulo — solo se devuelven las tablas cuyo bounding box intersecta el rectángulo. Esta es la contraparte con conciencia geométrica del fluido within(...).extract_tables() mostrado arriba.

Firma del C ABI:

FfiTableList *pdf_document_extract_tables_in_rect(
    PdfDocument *handle,
    int32_t page_index,
    float x, float y, float w, float h,
    int32_t *error_code);

Rustextract_tables_in_rect(page_index, region) -> Result<Vec<Table>> (la variante ..._with_config acepta una TableDetectionConfig personalizada):

use pdf_oxide::PdfDocument;
use pdf_oxide::geometry::Rect;

let doc = PdfDocument::open("invoice.pdf")?;
let region = Rect::new(50.0, 200.0, 500.0, 400.0);
let tables = doc.extract_tables_in_rect(0, region)?;
for table in &tables {
    println!("{} rows × {} cols", table.rows.len(), table.col_count);
}

Python — a través de la región fluida:

from pdf_oxide import PdfDocument

doc = PdfDocument("invoice.pdf")
tables = doc.within(0, (50, 200, 500, 400)).extract_tables()
for t in tables:
    for row in t["rows"]:
        print([c["text"] for c in row["cells"]])

SwiftextractTablesInRect(_:x:y:w:h:) devuelve [Table]:

let tables = try doc.extractTablesInRect(0, x: 50, y: 200, w: 500, h: 400)
for table in tables {
    print("\(table.rowCount) rows, header: \(table.hasHeader)")
}

C++extract_tables_in_rect(page, x, y, w, h) devuelve std::vector<Table>:

auto tables = doc.extract_tables_in_rect(0, 50, 200, 500, 400);
for (const auto& table : tables) {
    std::cout << table.rows.size() << " rows\n";
}

DartextractTablesInRect(page, x, y, w, h) devuelve List<Table>:

final tables = doc.extractTablesInRect(0, 50, 200, 500, 400);
for (final table in tables) {
    print('${table.rows.length} rows');
}

Rpdf_extract_tables_in_rect(doc, page, x, y, width, height):

tables <- pdf_extract_tables_in_rect(doc, 0, 50, 200, 500, 400)

Juliaextract_tables_in_rect(doc, page, x, y, w, h):

tables = extract_tables_in_rect(doc, 0, 50, 200, 500, 400)

ZigextractTablesInRect(allocator, page, x, y, w, h):

const tables = try doc.extractTablesInRect(a, 0, 50, 200, 500, 400);

Objective-CextractTablesInRect:x:y:w:h: devuelve NSArray<POXTable*>:

NSArray<POXTable*> *tables = [doc extractTablesInRect:0 x:50 y:200 w:500 h:400 error:&err];

Elixirextract_tables_in_rect(doc, page, x, y, w, h):

{:ok, tables} = PdfOxide.extract_tables_in_rect(doc, 0, 50, 200, 500, 400)

Go / C#. Al igual que con las líneas, el punto de entrada C extract_tables_in_rect existe pero aún no está envuelto en Go o C#. Llama a ExtractTables(page) para la página completa y conserva las tablas cuyo bounding box caiga dentro de tu rectángulo.

¿Cómo extraigo una página automáticamente sin elegir entre texto y OCR?

Cuando no sabes si una página tiene texto digital, es un escaneo o una mezcla, extract_page_auto hace el enrutamiento por ti. Ejecuta el AutoExtractor — enrutamiento texto vs. OCR por región, con retroceso nativo elegante (nunca expone un error opaco de OCR) — y devuelve un PageExtraction JSON: un kind de página, el text montado en orden de lectura, una confidence, un reason tipado, un flag ocr_used y un array regions[] donde cada región lleva bbox, kind, text, confidence, source y reason (el bbox y el reason están presentes incluso cuando el texto de una región está vacío, para que el orden de lectura nunca se corrompa silenciosamente).

Es tolerante a {}: pasa un JSON de opciones vacío/null para los valores por defecto, o proporciona un objeto AutoExtractOptions. Los campos reconocidos (serializados en snake_case) son:

Campo Tipo Por defecto Significado
mode "text_only" | "auto" | "force_ocr" "auto" Estrategia de enrutamiento texto vs. OCR
reconstruct_image_tables bool true Reconstruir tablas solo de imagen mediante el detector espacial sobre spans de OCR
emit_placeholders bool true Emitir marcadores de posición Figure/Table en el flujo de texto
ocr_languages string[] [] Pistas de idioma para OCR (ej.: ["english","chinese"])
min_text_confidence float | null null Umbral de confianza para la decisión automática
table_confidence float | null null Umbral de reconstrucción de tabla de imagen
force_ocr_pages int[] [] Índices de página (base 0) para forzar OCR

Activador de la función OCR. El OCR solo se ejecuta de verdad cuando la biblioteca se compila con la feature ocr; de lo contrario, extract_page_auto cae de vuelta a la capa de texto nativa (sin errores). El punto de entrada automático está disponible en Python, Go, C#, Swift, WASM y el C ABI. En Rust es la API de biblioteca AutoExtractor, no un método de una línea de PdfDocument — ver más abajo.

Pythonextract_page_auto(page, options_json=None) -> str (JSON):

import json
from pdf_oxide import PdfDocument

doc = PdfDocument("mixed-scan.pdf")

# Defaults (balanced preset)
page = json.loads(doc.extract_page_auto(0))
print(page["kind"], page["confidence"], page["ocr_used"])
for region in page["regions"]:
    print(region["kind"], region["bbox"], region["reason"])

# With options
opts = json.dumps({"mode": "auto", "reconstruct_image_tables": True,
                   "ocr_languages": ["english"]})
page = json.loads(doc.extract_page_auto(0, opts))

GoExtractPageAuto(pageIndex, opts ...AutoOption) (string, error) (devuelve JSON; configura mediante opciones funcionales):

package main

import (
    "encoding/json"
    "fmt"
    "log"
    pdfoxide "github.com/yfedoseev/pdf_oxide/go"
)

func main() {
    doc, err := pdfoxide.Open("mixed-scan.pdf")
    if err != nil { log.Fatal(err) }
    defer doc.Close()

    raw, err := doc.ExtractPageAuto(0)
    if err != nil { log.Fatal(err) }

    var page map[string]any
    json.Unmarshal([]byte(raw), &page)
    fmt.Println(page["kind"], page["confidence"], page["ocr_used"])
}

C#ExtractPageAuto(int pageIndex, string? optionsJson = null) -> string (JSON):

using System.Text.Json;
using PdfOxide.Core;

using var doc = PdfDocument.Open("mixed-scan.pdf");

// Defaults
string json = doc.ExtractPageAuto(0);
using var page = JsonDocument.Parse(json);
Console.WriteLine(page.RootElement.GetProperty("kind"));

// With options
string opts = """{"mode":"auto","ocr_languages":["english"]}""";
string json2 = doc.ExtractPageAuto(0, opts);

SwiftextractPageAuto(_:optionsJson:) -> String (por defecto "{}"):

let json = try doc.extractPageAuto(0, optionsJson: "{}")

JavaScript (WASM)extractPageAuto(pageIndex, optionsJson?):

import { WasmPdfDocument } from "pdf-oxide-wasm";

const doc = new WasmPdfDocument(bytes);
const page = JSON.parse(doc.extractPageAuto(0));
console.log(page.kind, page.confidence, page.ocr_used);
doc.free();

Rust — el camino automático es la API de biblioteca AutoExtractor. Construye AutoExtractOptions (presets fast(), balanced(), high_fidelity() o el builder fluido) y llama a extract_page, que devuelve un PageExtraction tipado (sin round-trip JSON):

use pdf_oxide::PdfDocument;
use pdf_oxide::extractors::auto::{AutoExtractor, AutoExtractOptions, ExtractMode};

let doc = PdfDocument::open("mixed-scan.pdf")?;

// Default (balanced) preset
let page = AutoExtractor::new().extract_page(&doc, 0)?;
println!("{:?} conf={} ocr={}", page.kind, page.confidence, page.ocr_used);

// Custom options via the builder
let opts = AutoExtractOptions::builder()
    .mode(ExtractMode::Auto)
    .reconstruct_image_tables(true)
    .ocr_languages(["english"])
    .build();
let page = AutoExtractor::with(opts).extract_page(&doc, 0)?;
for region in &page.regions {
    println!("{:?} {:?} {:?}", region.kind, region.bbox, region.reason);
}

C++extract_page_auto(page, options_json = "") devuelve el sobre JSON:

#include <pdf_oxide/pdf_oxide.hpp>

auto doc = pdf_oxide::Document::open("mixed-scan.pdf");
auto json = doc.extract_page_auto(0);                                    // defaults
auto json2 = doc.extract_page_auto(0, R"({"mode":"auto","ocr_languages":["english"]})");

DartextractPageAuto(page, [optionsJson]) devuelve el sobre JSON:

import 'dart:convert';
import 'package:pdf_oxide/pdf_oxide.dart';

final doc = PdfDocument.open('mixed-scan.pdf');
final page = jsonDecode(doc.extractPageAuto(0));
print('${page["kind"]} ${page["confidence"]} ${page["ocr_used"]}');
doc.close();

Rpdf_extract_page_auto(doc, page, options_json = NULL) devuelve JSON:

library(jsonlite)

doc  <- pdf_open("mixed-scan.pdf")
page <- fromJSON(pdf_extract_page_auto(doc, 0))
cat(page$kind, page$confidence, page$ocr_used, "\n")

Juliaextract_page_auto(doc, page, options = "{}") devuelve JSON:

using PdfOxide, JSON

doc  = open_document("mixed-scan.pdf")
page = JSON.parse(extract_page_auto(doc, 0))
println(page["kind"], " ", page["confidence"], " ", page["ocr_used"])

ZigextractPageAuto(allocator, page, options_json) devuelve bytes JSON:

const json = try doc.extractPageAuto(a, 0, null);  // free json

Objective-CextractPageAuto:optionsJson:error: devuelve el sobre JSON:

NSString *json = [doc extractPageAuto:0 optionsJson:@"{}" error:&err];

Elixirextract_page_auto(doc, page, options_json \\ "") devuelve JSON:

{:ok, json} = PdfOxide.extract_page_auto(doc, 0)
page = Jason.decode!(json)
IO.inspect({page["kind"], page["confidence"], page["ocr_used"]})

Java — el camino automático es la API AutoExtractor (extractPage → resultado tipado; extractTextForPage para texto plano):

import fyi.oxide.pdf.PdfDocument;
import fyi.oxide.pdf.AutoExtractor;

try (PdfDocument doc = PdfDocument.open(java.nio.file.Path.of("mixed-scan.pdf"))) {
    AutoExtractor ax = AutoExtractor.of(doc);             // or .fast/.balanced/.highFidelity
    String text = ax.extractTextForPage(0);               // graceful native/OCR routing
    System.out.println(text);
}

Kotlin

import fyi.oxide.pdf.PdfDocument
import fyi.oxide.pdf.AutoExtractor

PdfDocument.open(java.nio.file.Path.of("mixed-scan.pdf")).use { doc ->
    val ax = AutoExtractor.of(doc)
    println(ax.extractTextForPage(0))
}

Scala

import fyi.oxide.pdf.{PdfDocument, AutoExtractor}
import scala.util.Using

Using.resource(PdfDocument.open("mixed-scan.pdf")) { doc =>
  val ax = AutoExtractor.of(doc)
  println(ax.extractTextForPage(0))
}

PHP — el rico sobre JSON está disponible mediante AutoExtractor::extractPageJson:

use PdfOxide\PdfDocument;
use PdfOxide\AutoExtractor;

$doc = PdfDocument::open('mixed-scan.pdf');
$ax  = AutoExtractor::balanced($doc);
$page = json_decode($ax->extractPageJson(0), true);
echo $page['kind'], ' ', $page['confidence'], ' ', $page['ocr_used'];

Rubyauto_extractor.extract_page(page) devuelve el sobre analizado fusionado en un Hash:

require 'pdf_oxide'

PdfOxide::PdfDocument.open('mixed-scan.pdf') do |doc|
  result = doc.auto_extractor.extract_page(0)
  cls = result[:classification]            # full PageExtraction JSON as a Hash
  puts [cls['kind'], cls['confidence'], cls['ocr_used']].join(' ')
end

¿Cómo obtengo regiones tipadas estructuradas como JSON?

Para una vista estructurada de toda la página — encabezados, bloques de cuerpo, cabeceras/pies de página, números de página y orden de columnas — usa el punto de entrada de extracción estructurada. Devuelve un StructuredPage: page_index, page_width, page_height y un array regions[] donde cada región lleva kind (rol semántico), text, bbox, spans y column_index (para el orden de lectura en múltiples columnas). Los kind de región incluyen bloques de cuerpo, encabezados estructurales (H1H6), etiquetas marginales, cabeceras/pies de página recurrentes, números de página y artefactos.

La mayoría de los bindings devuelven esto como una cadena JSON (el C ABI serializa una vez y los bindings deserializan a tipos nativos); Rust devuelve el StructuredPage tipado directamente.

Firma del C ABI:

char *pdf_document_extract_structured_to_json(
    PdfDocument *handle,
    int32_t page_index,
    int32_t *error_code);

Pythonextract_structured(page) -> str (JSON; deserializa con json.loads):

import json
from pdf_oxide import PdfDocument

doc = PdfDocument("report.pdf")
page = json.loads(doc.extract_structured(0))

print(page["page_width"], page["page_height"])
for region in page["regions"]:
    print(region["kind"], region["column_index"], region["text"][:60])

GoExtractStructured(page) (string, error):

raw, err := doc.ExtractStructured(0)
if err != nil { log.Fatal(err) }

var page map[string]any
json.Unmarshal([]byte(raw), &page)
for _, r := range page["regions"].([]any) {
    region := r.(map[string]any)
    fmt.Println(region["kind"], region["text"])
}

C#ExtractStructured(int page) -> string:

using System.Text.Json;

string json = doc.ExtractStructured(0);
using var page = JsonDocument.Parse(json);
foreach (var region in page.RootElement.GetProperty("regions").EnumerateArray())
{
    Console.WriteLine(region.GetProperty("kind"));
}

SwiftextractStructuredJson(_:) -> String:

let json = try doc.extractStructuredJson(0)

JavaScript (WASM)extractStructured(pageIndex) (devuelve una cadena JSON con claves en camelCase):

const page = JSON.parse(doc.extractStructured(0));
for (const region of page.regions) {
    console.log(region.kind, region.columnIndex);
}

Rustextract_structured(page_index) -> Result<StructuredPage> devuelve regiones tipadas directamente (sin round-trip JSON). Una variante extract_structured_with_column_mode permite forzar ColumnMode::Two/Single para maquetaciones complicadas:

use pdf_oxide::PdfDocument;

let doc = PdfDocument::open("report.pdf")?;
let page = doc.extract_structured(0)?;
for region in &page.regions {
    println!("{:?} col={:?}: {}", region.kind, region.column_index, region.text);
}

C++extract_structured_json(page) devuelve la cadena JSON:

auto json = doc.extract_structured_json(0);

DartextractStructuredJson(page) devuelve la cadena JSON:

import 'dart:convert';

final page = jsonDecode(doc.extractStructuredJson(0));
for (final region in page['regions']) {
    print('${region["kind"]} ${region["column_index"]}');
}

Rpdf_extract_structured_json(doc, page) devuelve JSON:

library(jsonlite)

page <- fromJSON(pdf_extract_structured_json(doc, 0))
print(page$page_width)

Juliaextract_structured_json(doc, page) devuelve JSON:

using JSON
page = JSON.parse(extract_structured_json(doc, 0))
for region in page["regions"]
    println(region["kind"], " ", region["column_index"])
end

ZigextractStructuredJson(allocator, page) devuelve bytes JSON:

const json = try doc.extractStructuredJson(a, 0);  // free json

Objective-CextractStructuredJson:error: devuelve la cadena JSON:

NSString *json = [doc extractStructuredJson:0 error:&err];

Elixirextract_structured_json(doc, page) devuelve JSON:

{:ok, json} = PdfOxide.extract_structured_json(doc, 0)
page = Jason.decode!(json)

JavaextractStructured(page) devuelve la cadena JSON:

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

String json = doc.extractStructured(0);
JsonNode page = new ObjectMapper().readTree(json);
for (JsonNode region : page.get("regions")) {
    System.out.println(region.get("kind").asText());
}

Kotlin

val json = doc.extractStructured(0)   // JSON string; parse with your library of choice

Scala

val json = doc.extractStructured(0)   // JSON string

Clojure(pdf/extract-structured doc page) devuelve la cadena JSON:

(require '[clojure.data.json :as json])

(with-open [doc (pdf/open "report.pdf")]
  (let [page (json/read-str (pdf/extract-structured doc 0))]
    (doseq [region (get page "regions")]
      (println (get region "kind") (get region "column_index")))))

Rubyextract_structured(page) devuelve el Hash StructuredPage analizado:

PdfOxide::PdfDocument.open('report.pdf') do |doc|
  page = doc.extract_structured(0)
  page['regions'].each { |r| puts "#{r['kind']} #{r['column_index']}" }
end

PHPextractStructured($page) devuelve el array asociativo deserializado:

$doc = PdfOxide\PdfDocument::open('report.pdf');
$page = $doc->extractStructured(0);
foreach ($page['regions'] as $region) {
    echo $region['kind'], ' ', $region['column_index'], "\n";
}

Referencia de coordenadas

PDF usa un origen en la esquina inferior izquierda, medido en puntos (1 pt = 1/72 pulgada). Una página Letter es (0, 0, 612, 792). Para apuntar a la franja superior de 1 pulgada, escribe:

(x, y, w, h) = (0, 792 - 72, 612, 72)
             = (0, 720,      612, 72)

Si vienes de un sistema de coordenadas de imagen (origen arriba a la izquierda), invierte y en consecuencia.

Para obtener el MediaBox real de una página antes de calcular:

Python

doc = PdfDocument("doc.pdf")
mb = doc.page_media_box(0)       # (llx, lly, urx, ury)

Rust

let mb = editor.get_page_media_box(0)?;   // [f32; 4]

Javapage.mediaBox() devuelve un BBox (x0, y0, x1, y1):

import fyi.oxide.pdf.geometry.BBox;

BBox mb = doc.page(0).mediaBox();         // (x0, y0, x1, y1) in PDF user space
double w = mb.width(), h = mb.height();   // 612 × 792 for US Letter

Kotlin

val mb = doc.page(0).mediaBox()           // BBox(x0, y0, x1, y1)

Scala

val mb = doc.page(0).mediaBox             // BBox(x0, y0, x1, y1)

C++ — mediante el editor: get_page_media_box(page):

auto editor = pdf_oxide::DocumentEditor::open("doc.pdf");
auto mb = editor.get_page_media_box(0);   // Bbox{x, y, width, height}

Swift

let editor = try DocumentEditor.open("doc.pdf")
let mb = try editor.getPageMediaBox(0)    // Bbox(x, y, width, height)

Dart

final editor = DocumentEditor.open('doc.pdf');
final mb = editor.getPageMediaBox(0);     // Bbox(x, y, width, height)

R

editor <- pdf_editor_open("doc.pdf")
mb <- pdf_editor_get_page_media_box(editor, 0)   # list(x=, y=, width=, height=)

Julia

editor = open_editor("doc.pdf")
mb = get_page_media_box(editor, 0)        # Bbox

Zig

var editor = try pdf_oxide.DocumentEditor.openEditor("doc.pdf");
const mb = try editor.getPageMediaBox(0);  // Bbox{ x, y, width, height }

Objective-C

POXDocumentEditor *editor = [POXDocumentEditor openEditor:@"doc.pdf" error:&err];
POXBbox mb = [editor pageMediaBox:0 error:&err];   // {x, y, width, height}

Elixir

{:ok, editor} = PdfOxide.open_editor("doc.pdf")
{:ok, mb} = PdfOxide.get_page_media_box(editor, 0)   # %Bbox{}

Go / C# — helpers in-rect

Aunque Go y C# todavía no exponen la cadena fluida within(), los métodos de bajo nivel subyacentes son los mismos:

Método Go C#
Texto en el rectángulo doc.ExtractTextInRect(page, x, y, w, h) doc.ExtractTextInRect(page, x, y, w, h)
Palabras en el rectángulo doc.ExtractWordsInRect(page, x, y, w, h) (aún no envuelto)
Imágenes en el rectángulo doc.ExtractImagesInRect(page, x, y, w, h) (aún no envuelto)

Para patrones que necesitan múltiples tipos de extracción sobre el mismo rectángulo en Go o C#, guarda el rectángulo en variables y llama a los helpers de forma secuencial. La interfaz fluida llegará cuando la API del editor se estabilice.

Preguntas frecuentes

¿Cuál es la diferencia entre extract_words() y extract_lines_in_rect() en una región? extract_words() devuelve un registro por palabra; extract_lines_in_rect() devuelve un registro por línea (texto, bounding box y recuento de palabras) para las líneas cuyo bounding box intersecta el rectángulo. Usa líneas cuando necesites filas completas en orden de lectura — bloques de dirección, filas de extracto, totales multilínea — sin tener que reagrupar palabras tú mismo.

¿extract_page_auto siempre ejecuta OCR? No. El enrutamiento es por región. En el modo "auto" predeterminado, solo escala a OCR donde la capa de texto nativa está ausente o es sospechosa, y el OCR solo se ejecuta de verdad cuando la biblioteca se compila con la feature ocr. Sin esa feature, cae de vuelta a la capa de texto nativa sin generar un error opaco de OCR.

¿Qué bindings exponen las variantes lines-in-rect y tables-in-rect? Rust, el C ABI y Swift exponen extract_lines_in_rect / extract_tables_in_rect directamente. Python alcanza los mismos resultados a través de la región fluida (within(...).extract_text_lines() y within(...).extract_tables()). Go y C# todavía no envuelven los puntos de entrada in-rect de líneas/tablas — extrae para la página completa y filtra por los bounding boxes devueltos.

¿Qué tan rápida es la extracción por ámbito? El acotamiento no añade sobrecarga medible respecto a la extracción de página completa — PDF Oxide extrae con una media de 0,8 ms (tasa de aprobación del 100 % en el corpus de referencia), y una llamada in-rect filtra ese mismo resultado por bounding box.

Páginas relacionadas