Skip to content

Извлечение по области — получение контента из конкретного региона

При обработке счетов, банковских выписок, налоговых форм или любых шаблонных документов вы, как правило, заранее знаете расположение нужных полей. Вместо того чтобы извлекать всю страницу и искать значения, укажите PDF Oxide точный прямоугольник — и получите только то, что находится в нём.

Fluent-API within(page, rect) возвращает область с заданной областью видимости, на которой можно цепочкой вызывать методы извлечения: extract_text(), extract_words(), extract_chars(), extract_tables().

Поддержка привязок. within(page, rect) доступен в Python, Rust и WASM. В Go и C# есть эквивалентные низкоуровневые вспомогательные функции (ExtractTextInRect, ExtractWordsInRect, ExtractImagesInRect) — см. ниже. Полное семейство in-rect (текст, слова, строки, таблицы, изображения) полностью реализовано в Rust, C ABI и обёртке Swift; подробнее о том, что поддерживает каждая привязка, см. в разделе Варианты in-rect извлечения.

Быстрый пример

rect — это (x, y, width, height) в пунктах PDF, с началом координат в нижнем левом углу страницы. Страница формата Letter имеет размер 612 × 792 пунктов.

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 (низкоуровневая вспомогательная функция, тот же эффект)

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# (низкоуровневая вспомогательная функция)

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 в форме углов (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)

Цепочечное извлечение из региона

Fluent-форма within() в Python / Rust / WASM позволяет вызывать любой метод извлечения на одном и том же регионе без повторного указания прямоугольника:

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++ (без цепочек — вызывайте каждую in-rect функцию для того же прямоугольника)

// 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)

Типичные сценарии использования

Извлечение полей счёта

Счёт-фактура обычно содержит адрес поставщика, номер счёта и таблицу позиций в фиксированных зонах. Определите прямоугольники один раз для каждого шаблона:

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"]))

Строки транзакций в банковской выписке

В большинстве выписок есть узкая полоса транзакций. Обрежьте её и вызовите extract_words() — получите каждую строку в порядке чтения с её ограничивающим прямоугольником:

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})")

Удаление верхних и нижних колонтитулов

Если нужно индексировать только основной контент, обрежьте верх и низ каждой страницы:

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` …
}

Обнаружение табличного региона

Если вы знаете, что на странице есть таблица и где именно, ограничьте область её прямоугольником и позвольте extract_tables() работать только в этой зоне:

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"]])

Какие варианты извлечения с прямоугольным ограничением существуют? {#what-rect-scoped-extraction-variants-exist}

Помимо extract_text(), extract_words() и extract_chars(), есть ещё два варианта с ограничением по прямоугольнику, возвращающих геометрически осведомлённые результаты из одного прямоугольника: строки в прямоугольнике и таблицы в прямоугольнике. Оба фильтруют полностраничное извлечение, оставляя только регионы, ограничивающий прямоугольник которых пересекает указанный прямоугольник, — координаты и порядок чтения при этом те же, что при полностраничном вызове, просто обрезанные.

Извлечение текстовых строк в регионе (extract_lines_in_rect)

Возвращает записи уровня строки (каждая содержит текст, ограничивающий прямоугольник и количество слов), попадающие в прямоугольник. Используйте, когда нужны целые строки в порядке чтения, а не отдельные слова — например, блоки адресов, многострочные итоги или отдельная строка выписки.

Авторитетная сигнатура C ABI:

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>> на 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 — fluent-регион предоставляет строки через 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:) возвращает [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) возвращает 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) возвращает 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: возвращает 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#. Точка входа extract_lines_in_rect в C существует, но обёртки Go и C# её пока не предоставляют. В этих языках извлекайте строки для всей страницы и фильтруйте по возвращённым ограничивающим прямоугольникам, либо используйте ExtractWordsInRect (Go) и группируйте слова в строки самостоятельно.

Извлечение таблиц в регионе (extract_tables_in_rect)

Ограничивает обнаружение таблиц одним прямоугольником — возвращаются только таблицы, ограничивающий прямоугольник которых пересекает указанный прямоугольник. Это геометрически осведомлённый аналог fluent-формы within(...).extract_tables(), описанной выше.

Сигнатура 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>> (вариант ..._with_config принимает пользовательский TableDetectionConfig):

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 — через fluent-регион:

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:) возвращает [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) возвращает 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) возвращает 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: возвращает 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#. Как и со строками, точка входа extract_tables_in_rect в C существует, но в Go или C# не обёрнута. Вызывайте ExtractTables(page) для всей страницы и оставляйте таблицы, ограничивающий прямоугольник которых попадает в нужный регион.

Как автоматически извлечь страницу, не выбирая между текстом и OCR?

Когда неизвестно, является ли страница цифровым текстом, сканом или их смесью, extract_page_auto берёт маршрутизацию на себя. Он запускает AutoExtractor — поррегионную маршрутизацию текст vs OCR с корректным нативным откатом (никаких непрозрачных ошибок OCR) — и возвращает JSON-объект PageExtraction: kind страницы, собранный text в порядке чтения, confidence, типизированный reason, флаг ocr_used и массив regions[], где каждый регион содержит bbox, kind, text, confidence, source и reason (bbox и reason присутствуют даже при пустом тексте региона, поэтому порядок чтения никогда не нарушается незаметно).

Принимает {}: передайте пустой / null JSON опций для настроек по умолчанию или укажите объект AutoExtractOptions. Распознаваемые поля (сериализуются в snake_case):

Поле Тип По умолчанию Значение
mode "text_only" | "auto" | "force_ocr" "auto" Стратегия маршрутизации текст vs OCR
reconstruct_image_tables bool true Восстанавливать чисто графические таблицы с помощью пространственного детектора по OCR-спанам
emit_placeholders bool true Вставлять позиционированные плейсхолдеры Figure/Table в поток текста
ocr_languages string[] [] Подсказки языка OCR (например, ["english","chinese"])
min_text_confidence float | null null Порог доверия для автоматического принятия решения
table_confidence float | null null Порог восстановления графических таблиц
force_ocr_pages int[] [] 0-based индексы страниц, для которых принудительно включить OCR

Флаг OCR. OCR реально запускается только если библиотека собрана с фичей ocr; в противном случае extract_page_auto откатывается к нативному текстовому слою (без ошибок). Автоматическая точка входа доступна в Python, Go, C#, Swift, WASM и C ABI. В Rust это библиотечный API AutoExtractor, а не однострочный метод PdfDocument — см. ниже.

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) (возвращает JSON; настройка через функциональные опции):

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 (по умолчанию "{}"):

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 — автоматический путь — это API библиотеки AutoExtractor. Создайте AutoExtractOptions (пресеты fast(), balanced(), high_fidelity() или fluent-билдер) и вызовите extract_page — вернётся типизированная PageExtraction (без 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 = "") возвращает 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]) возвращает 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) возвращает 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 = "{}") возвращает 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) возвращает JSON-байты:

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

Objective-CextractPageAuto:optionsJson:error: возвращает JSON-конверт:

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

Elixirextract_page_auto(doc, page, options_json \\ "") возвращает JSON:

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

Java — автоматический путь — это API AutoExtractor (extractPage → типизированный результат; extractTextForPage — для чистого текста):

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 — расширенный JSON-конверт доступен через 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) возвращает разобранный конверт в виде 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

Как получить структурированные типизированные регионы в виде JSON?

Для представления страницы в полностью структурированном виде — заголовки, блоки основного текста, колонтитулы, номера страниц, порядок колонок — используйте точку входа структурированного извлечения. Она возвращает StructuredPage: page_index, page_width, page_height и массив regions[], где каждый регион содержит kind (семантическая роль), text, bbox, spans и column_index (порядок чтения в многоколоночном макете). Типы kind включают блоки основного текста, структурные заголовки (H1H6), маргинальные метки, текущие колонтитулы, номера страниц и артефакты.

Большинство привязок возвращают это как JSON-строку (C ABI сериализует один раз, привязки десериализуют в нативные типы); Rust возвращает типизированную StructuredPage напрямую.

Сигнатура C ABI:

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

Pythonextract_structured(page) -> str (JSON; десериализовать через 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) (возвращает JSON-строку с ключами в 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> возвращает типизированные регионы напрямую (без JSON-туда-обратно). Вариант extract_structured_with_column_mode позволяет принудительно задать ColumnMode::Two/Single для сложных макетов:

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) возвращает JSON-строку:

auto json = doc.extract_structured_json(0);

DartextractStructuredJson(page) возвращает 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) возвращает JSON:

library(jsonlite)

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

Juliaextract_structured_json(doc, page) возвращает 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) возвращает JSON-байты:

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

Objective-CextractStructuredJson:error: возвращает JSON-строку:

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

Elixirextract_structured_json(doc, page) возвращает JSON:

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

JavaextractStructured(page) возвращает 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) возвращает 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) возвращает разобранный StructuredPage Hash:

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) возвращает десериализованный ассоциативный массив:

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

Справочник по координатам

PDF использует начало координат в нижнем левом углу, измерения — в пунктах (1 пт = 1/72 дюйма). Страница формата Letter — (0, 0, 612, 792). Чтобы выбрать верхнюю полосу шириной 1 дюйм, запишите:

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

Если вы привыкли к координатам изображений (начало в верхнем левом углу), инвертируйте y соответственно.

Чтобы получить фактический MediaBox страницы перед вычислениями:

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() возвращает 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++ — через редактор: 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# — вспомогательные функции in-rect

Go и C# пока не предоставляют fluent-цепочку within(), но низкоуровневые методы те же:

Метод Go C#
Текст в прямоугольнике doc.ExtractTextInRect(page, x, y, w, h) doc.ExtractTextInRect(page, x, y, w, h)
Слова в прямоугольнике doc.ExtractWordsInRect(page, x, y, w, h) (не обёрнуто)
Изображения в прямоугольнике doc.ExtractImagesInRect(page, x, y, w, h) (не обёрнуто)

Для шаблонов, требующих нескольких типов извлечения для одного прямоугольника в Go или C#, сохраните прямоугольник в переменных и вызывайте вспомогательные функции последовательно. Fluent-интерфейс появится после стабилизации API редактора.

Часто задаваемые вопросы

В чём разница между extract_words() и extract_lines_in_rect() в регионе? extract_words() возвращает одну запись на слово; extract_lines_in_rect() возвращает одну запись на строку (текст, ограничивающий прямоугольник и количество слов) для строк, чей прямоугольник пересекает указанный. Используйте строки, когда нужны целые ряды в порядке чтения — адресные блоки, строки выписок, многострочные итоги — без ручного группирования слов.

Всегда ли extract_page_auto запускает OCR? Нет. Маршрутизация идёт пореждонно. В режиме "auto" по умолчанию OCR включается только там, где нативный текстовый слой отсутствует или вызывает сомнения, и OCR реально запускается только если библиотека собрана с фичей ocr. Без этой фичи происходит откат к нативному текстовому слою без непрозрачных ошибок OCR.

Какие привязки предоставляют варианты lines-in-rect и tables-in-rect? Rust, C ABI и Swift предоставляют extract_lines_in_rect / extract_tables_in_rect напрямую. Python получает те же результаты через fluent-регион (within(...).extract_text_lines() и within(...).extract_tables()). Go и C# пока не оборачивают in-rect точки входа для строк/таблиц — извлекайте для всей страницы и фильтруйте по возвращённым ограничивающим прямоугольникам.

Насколько быстро работает извлечение по области? Ограничение области не добавляет измеримых накладных расходов к полностраничному извлечению — PDF Oxide извлекает со средним временем 0,8 мс (100% прохождения на тестовом корпусе), а in-rect вызов просто фильтрует тот же результат по ограничивающему прямоугольнику.

Связанные страницы