Skip to content

Primeiros Passos com o PDF Oxide (Zig)

O PDF Oxide é a biblioteca de PDF mais rápida com extração de texto embutida — 0,8ms de média e 100% de aprovação em 3.830 PDFs. O binding Zig é Zig idiomático sobre a C ABI do pdf_oxide via @cImport — sem camada intermediária, interoperabilidade com C de primeira classe. Os handles são structs com deinit, e as strings/buffers C retornados são copiados para um allocator fornecido pelo chamador.

Fixado no Zig 0.15.1 — a build pré-1.0 e a API de import de C mudam entre as versões, então o CI usa a mesma versão.

Instalação

O binding faz o link com a cdylib de features padrão (não com o wheel do Python). Compile a biblioteca nativa e então aponte o Zig para o header e para a cdylib:

# 1. compile a biblioteca nativa (conjunto de features do binding distribuído)
cargo build --release --lib \
  --features ocr,rendering,signatures,barcodes,tsa-client,system-fonts

# 2. teste + rode o exemplo
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"

No seu próprio código, importe o módulo e está tudo pronto:

const pdf_oxide = @import("pdf_oxide");

Abrindo um PDF

Abra um arquivo com Document.open (ou Document.openFromBytes para dados em memória) e inspecione seus metadados. Cada handle é dono de recursos C, então combine-o com 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()});
}

Extração de Texto

extractText retorna o texto de uma única página (índice começando em 0). O resultado pertence ao allocator que você passa, então libere-o quando terminar.

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

As variantes para o documento inteiro extraem todas as páginas de uma vez:

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

Conversão para Markdown e HTML

Converta uma única página ou o documento inteiro para Markdown ou HTML. Cada chamada retorna uma slice pertencente ao allocator.

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

Extração no Nível de Palavras

extractWords retorna uma slice de structs Word com texto, bounding box, fonte e flag de negrito. Libere a slice inteira com o helper correspondente freeWords — ele libera as strings de cada palavra e a slice de apoio.

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,
    });
}

Campos de Word:

Campo Tipo Descrição
text []u8 Texto da palavra (pertence ao allocator)
bbox Bbox { x, y, width, height } em pontos
fontName []u8 Nome PostScript da fonte
fontSize f32 Tamanho da fonte em pontos
bold bool Se o trecho está em negrito

O mesmo padrão entrega caracteres e linhas:

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

Busca

search procura dentro de uma página; searchAll varre todas as páginas. Ambos recebem um termo terminado em NUL e uma flag case_sensitive, e retornam uma slice de 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,
    });
}

Para restringir a busca a uma única página, use search com o índice da página:

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

Criando um PDF

O tipo Pdf constrói documentos a partir de Markdown, HTML ou texto puro. toBytes serializa para a memória; save grava em disco.

const a = std.heap.page_allocator;

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

// Serialize para a memória...
const bytes = try pdf.toBytes(a);
defer a.free(bytes);

// ...ou grave direto no disco.
try pdf.save("output.pdf");

Você pode fazer um round-trip de um PDF recém-construído direto de volta pelo extrator:

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

Tratamento de Erros

Chamadas que podem falhar retornam Error!T, onde Error é error{ PdfOxide, OutOfMemory }. Como valores de erro em Zig não podem carregar um payload, o código da C ABI subjacente é exposto via lastErrorCode() — leia-o logo após capturar 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);

Próximos Passos