Skip to content

Початок роботи з PDF Oxide (Zig)

PDF Oxide — найшвидша PDF-бібліотека із вбудованим видобуванням тексту: 0,8 мс у середньому, 100 % успіху на 3 830 PDF. Прив’язка для Zig — це ідіоматичний Zig поверх pdf_oxide C ABI через @cImport, без прошарку-shim, повноцінна взаємодія з C. Хендли — це структури з методом deinit, а повернуті C-рядки та буфери копіюються в аллокатор, який передає викликач.

Зафіксовано на Zig 0.15.1 — у передрелізній (до 1.0) збірці API імпорту C змінюється від релізу до релізу, тож CI використовує ту саму версію.

Встановлення

Прив’язка лінкується з cdylib типового набору можливостей (а не з Python-колесом wheel). Зберіть нативну бібліотеку, після чого вкажіть Zig на заголовковий файл і cdylib:

# 1. збираємо нативну бібліотеку (набір можливостей прив'язки)
cargo build --release --lib \
  --features ocr,rendering,signatures,barcodes,tsa-client,system-fonts

# 2. тестуємо й запускаємо приклад
cd zig
LD_LIBRARY_PATH="$PWD/../target/release" \
  zig build test \
    -DPDF_OXIDE_INCLUDE_DIR="$PWD/../include" \
    -DPDF_OXIDE_LIB_DIR="$PWD/../target/release"

LD_LIBRARY_PATH="$PWD/../target/release" \
  zig build example \
    -DPDF_OXIDE_INCLUDE_DIR="$PWD/../include" \
    -DPDF_OXIDE_LIB_DIR="$PWD/../target/release"

У власному коді достатньо імпортувати модуль — і можна працювати:

const pdf_oxide = @import("pdf_oxide");

Відкриття PDF

Відкрийте файл через Document.open (або Document.openFromBytes для даних у пам’яті) і перегляньте його метадані. Кожен хендл володіє C-ресурсами, тож одразу додавайте до нього defer doc.deinit().

const std = @import("std");
const pdf_oxide = @import("pdf_oxide");

pub fn main() !void {
    const a = std.heap.page_allocator;

    var doc = try pdf_oxide.Document.open("research-paper.pdf");
    defer doc.deinit();

    std.debug.print("pages:   {d}\n", .{try doc.pageCount()});
    const v = doc.version();
    std.debug.print("version: {d}.{d}\n", .{ v.major, v.minor });
    std.debug.print("encrypted: {}\n", .{doc.isEncrypted()});
}

Видобування тексту

extractText повертає текст однієї сторінки (нумерація з 0). Результатом володіє аллокатор, який ви передали, тож звільніть його, коли він більше не потрібен.

const a = std.heap.page_allocator;

var doc = try pdf_oxide.Document.open("report.pdf");
defer doc.deinit();

const text = try doc.extractText(a, 0);
defer a.free(text);
std.debug.print("{s}\n", .{text});

Варіанти для всього документа видобувають усі сторінки одразу:

const all_text = try doc.toPlainTextAll(a);
defer a.free(all_text);
std.debug.print("{s}\n", .{all_text});

Перетворення на Markdown і HTML

Перетворіть окрему сторінку або весь документ на Markdown чи HTML. Кожен виклик повертає зріз, яким володіє аллокатор.

const md = try doc.toMarkdown(a, 0);
defer a.free(md);
std.debug.print("{s}\n", .{md});

const md_all = try doc.toMarkdownAll(a);
defer a.free(md_all);

const html = try doc.toHtml(a, 0);
defer a.free(html);

Видобування на рівні слів

extractWords повертає зріз структур Word із текстом, обмежувальним прямокутником, шрифтом і прапорцем жирності. Звільняйте весь зріз відповідним помічником freeWords — він вивільняє рядки кожного слова та сам базовий зріз.

const words = try doc.extractWords(a, 0);
defer pdf_oxide.Document.freeWords(a, words);

for (words) |w| {
    std.debug.print("'{s}' at ({d:.1}, {d:.1}) font={s} size={d:.1} bold={}\n", .{
        w.text, w.bbox.x, w.bbox.y, w.fontName, w.fontSize, w.bold,
    });
}

Поля Word:

Поле Тип Опис
text []u8 Текст слова (належить аллокатору)
bbox Bbox { x, y, width, height } у пунктах
fontName []u8 Назва шрифту в PostScript
fontSize f32 Розмір шрифту в пунктах
bold bool Чи є фрагмент жирним

Той самий підхід дає доступ до символів і рядків:

const chars = try doc.extractChars(a, 0);
defer pdf_oxide.Document.freeChars(a, chars);

const lines = try doc.extractTextLines(a, 0);
defer pdf_oxide.Document.freeTextLines(a, lines);

Пошук

search шукає в межах однієї сторінки; searchAll сканує всі сторінки. Обидва приймають NUL-термінований термін і прапорець case_sensitive, а повертають зріз SearchResult.

const hits = try doc.searchAll(a, "configuration", false);
defer pdf_oxide.Document.freeSearchResults(a, hits);

for (hits) |hit| {
    std.debug.print("page {d}: '{s}' at ({d:.0}, {d:.0})\n", .{
        hit.page, hit.text, hit.bbox.x, hit.bbox.y,
    });
}

Щоб обмежити пошук однією сторінкою, скористайтеся search з індексом сторінки:

const page_hits = try doc.search(a, 0, "Alpha", false);
defer pdf_oxide.Document.freeSearchResults(a, page_hits);

Створення PDF

Тип Pdf будує документи з Markdown, HTML або звичайного тексту. toBytes серіалізує в пам’ять; save записує на диск.

const a = std.heap.page_allocator;

var pdf = try pdf_oxide.Pdf.fromMarkdown("# Hello\n\nThis is a **Zig** PDF.\n");
defer pdf.deinit();

// Серіалізуємо в пам'ять...
const bytes = try pdf.toBytes(a);
defer a.free(bytes);

// ...або записуємо одразу на диск.
try pdf.save("output.pdf");

Щойно створений PDF можна одразу пропустити назад крізь екстрактор:

var pdf = try pdf_oxide.Pdf.fromHtml("<h1>Invoice</h1><p>Amount: $42</p>");
defer pdf.deinit();

const bytes = try pdf.toBytes(a);
defer a.free(bytes);

var doc = try pdf_oxide.Document.openFromBytes(bytes);
defer doc.deinit();

const text = try doc.extractText(a, 0);
defer a.free(text);
std.debug.print("{s}\n", .{text});

Обробка помилок

Виклики, що можуть зазнати невдачі, повертають Error!T, де Error — це error{ PdfOxide, OutOfMemory }. Оскільки значення помилок Zig не можуть нести корисне навантаження, базовий код C-ABI доступний через lastErrorCode() — зчитуйте його одразу після перехоплення error.PdfOxide.

const text = doc.extractText(a, 99) catch |err| switch (err) {
    error.PdfOxide => {
        std.debug.print("pdf_oxide error code: {d}\n", .{pdf_oxide.lastErrorCode()});
        return;
    },
    error.OutOfMemory => return err,
};
defer a.free(text);

Наступні кроки