Skip to content

Conversão para Markdown

O PDF Oxide converte páginas PDF em Markdown limpo e legível. O pipeline de conversão extrai spans de texto, os agrupa em linhas, consulta /StructTreeRoot para obter títulos e papéis de lista em PDFs marcados, detecta gutters de múltiplas colunas e quebras com ordem de leitura reversa em x, agrupa parágrafos e emite a sintaxe Markdown.

A partir da v0.3.36, para PDFs marcados o conversor lê StructRole(Heading(1..6) | ListItem | ListItemLabel | ListItemBody) diretamente de /StructTreeRoot em vez de rederivar os níveis de título a partir do tamanho da fonte. A informação de papel é propagada através de MCRs aninhadas (H1 → Span → MCR, LI → LBody → Span → MCR). Para documentos sem tags, vale o fallback geométrico: negrito + aumento de 5 % no tamanho promove a H4, e is_ordered_list_marker reconhece 1. / 12. / a) / iv. / A. ao mesmo tempo em que rejeita legendas de figura e anos.

Múltiplas colunas: spans na mesma baseline separados por > max(3 × font_size, 30 pt) são tratados como cross-column. Quebras de ordem de leitura com x decrescente (último span da col 1 → primeiro span da col 2) quebram parágrafos em vez de unir tudo em tokens sem sentido.

RTL: o reorder bidi fica desligado por padrão — o reorder incondicional visual→lógico anterior quebrava PDFs já em ordem lógica (o nome hebraico בנימין estava sendo invertido). Marcadores **bold** espúrios ao redor de glifos árabes contextuais são removidos. Chamadores cuja entrada esteja em ordem visual podem invocar text::bidi::reorder_visual_to_logical manualmente (Rust).

Imagens inline têm payload base64 limitado a 200 KB (adicionado na v0.3.36). Imagens acima do limite emitem um comentário HTML informando o tamanho original; use image_output_dir para gravá-las em disco.

Exemplo rápido

Python

from pdf_oxide import PdfDocument

doc = PdfDocument("paper.pdf")
md = doc.to_markdown(0, detect_headings=True)
print(md)

Node.js

const { PdfDocument } = require("pdf-oxide");

const doc = new PdfDocument("paper.pdf");
const md = doc.toMarkdown(0, { detectHeadings: true });
console.log(md);
doc.close();

Go

import pdfoxide "github.com/yfedoseev/pdf_oxide/go"

doc, _ := pdfoxide.Open("paper.pdf")
defer doc.Close()
md, _ := doc.ToMarkdown(0)
fmt.Println(md)

C#

using PdfOxide.Core;

using var doc = PdfDocument.Open("paper.pdf");
var md = doc.ToMarkdown(0);
Console.WriteLine(md);

WASM

const doc = new WasmPdfDocument(bytes);
const md = doc.toMarkdown(0, true);
console.log(md);

Rust

use pdf_oxide::PdfDocument;
use pdf_oxide::converters::ConversionOptions;

let mut doc = PdfDocument::open("paper.pdf")?;
let options = ConversionOptions { detect_headings: true, ..Default::default() };
let md = doc.to_markdown(0, &options)?;
println!("{}", md);

Referência de API

to_markdown(page_index, ...) -> str

Converte uma única página para Markdown.

Assinatura Python

doc.to_markdown(
    page: int,
    preserve_layout: bool = False,
    detect_headings: bool = True,
    include_images: bool = True,
    image_output_dir: str | None = None,
    embed_images: bool = True,
) -> str

Assinatura JavaScript

doc.toMarkdown(pageIndex, detectHeadings?, includeImages?, includeFormFields?) -> string

Assinatura Rust

pub fn to_markdown(
    &mut self,
    page_index: usize,
    options: &ConversionOptions,
) -> Result<String>
Parâmetro Tipo Padrão Descrição
page_index int / usize / number Índice de página com base zero
preserve_layout bool false Preserva o posicionamento do layout visual
detect_headings bool true Detecta títulos com base no tamanho e peso da fonte
include_images bool true Inclui imagens na saída
image_output_dir str / None None Diretório onde salvar as imagens extraídas (apenas Python/Rust). Não é afetado pelo limite inline de 200 KB.
embed_images bool true Embute as imagens como data URIs base64 (apenas Python/Rust). Payloads acima de 200 KB emitem um comentário HTML de placeholder informando o tamanho original (v0.3.36).
include_form_fields bool true Inclui os valores dos campos de formulário (Python/JS)

Retorna: a string Markdown da página.


to_markdown_all(...) -> str

Converte todas as páginas para Markdown, separadas por linhas horizontais (---).

Assinatura Python

doc.to_markdown_all(
    preserve_layout: bool = False,
    detect_headings: bool = True,
    include_images: bool = True,
    image_output_dir: str | None = None,
    embed_images: bool = True,
) -> str

Assinatura JavaScript

doc.toMarkdownAll(detectHeadings?, includeImages?, includeFormFields?) -> string

Assinatura Rust

pub fn to_markdown_all(
    &mut self,
    options: &ConversionOptions,
) -> Result<String>
Parâmetro Tipo Padrão Descrição
preserve_layout bool false Preserva o layout visual
detect_headings bool true Detecta títulos
include_images bool true Inclui imagens
image_output_dir str / None None Diretório de saída das imagens
embed_images bool true Embute as imagens como base64

Retorna: a string Markdown de todas as páginas unidas por separadores ---.


to_markdown_with_ocr(page_index, model_path, options) -> str

Converte uma página para Markdown com fallback de OCR para páginas digitalizadas. Quando a página tem pouco ou nenhum texto extraível, o OCR é usado para reconhecer o texto a partir da imagem renderizada da página. Requer a feature ocr.

Parâmetro Tipo Descrição
page_index usize Índice de página com base zero
model_path &str Caminho para os arquivos do modelo de OCR
options &ConversionOptions Opções de conversão

Rust

let mut doc = PdfDocument::open("scanned.pdf")?;
let options = ConversionOptions { detect_headings: true, ..Default::default() };
let md = doc.to_markdown_with_ocr(0, "/path/to/models", &options)?;
println!("{}", md);

ConversionOptions

A struct ConversionOptions controla todo o comportamento da conversão.

Campo Tipo Padrão Descrição
preserve_layout bool false Preserva o layout visual com posicionamento
detect_headings bool true Detecta títulos automaticamente a partir de clusters de tamanho de fonte
extract_tables bool false Extrai tabelas (experimental)
include_images bool true Inclui imagens na saída
image_output_dir Option<String> None Salva as imagens neste diretório
embed_images bool true Embute as imagens como data URIs base64
reading_order_mode ReadingOrderMode Auto Como determinar a ordem de leitura
bold_marker_behavior BoldMarkerBehavior Conservative Estratégia de aplicação de marcadores de negrito

Como funciona

O pipeline de conversão para Markdown opera em vários estágios:

  1. Extração de texto – Extrai objetos TextSpan do fluxo de conteúdo da página, capturando texto, posição, fonte, tamanho, peso e cor.

  2. Clustering de caracteres – Agrupa caracteres em palavras com base nos espaços entre caracteres e, em seguida, agrupa as palavras em linhas com base na proximidade vertical.

  3. Ordem de leitura – Determina a ordem de leitura usando a árvore de estrutura do Tagged PDF (preferencial) ou uma análise espacial baseada em grafos das posições dos blocos de texto.

  4. Detecção de títulos – Quando detect_headings está habilitado, agrupa os tamanhos de fonte ao longo da página para identificar os níveis de título. Texto maior e mais negrito é mapeado para títulos #, ##, ###.

  5. Formatação – Aplica marcadores de negrito (**texto**) e itálico (*texto*) com base nos metadados de peso e estilo da fonte.

  6. Detecção de tabelas – Identifica layouts tabulares usando análise espacial de blocos de texto alinhados e emite tabelas Markdown no estilo GFM.

  7. Limpeza de espaços em branco – Normaliza o espaçamento, remove linhas em branco redundantes e garante quebras de parágrafo consistentes.


Exemplos avançados

Converter um PDF inteiro em um arquivo Markdown

Python

from pdf_oxide import PdfDocument

doc = PdfDocument("book.pdf")
md = doc.to_markdown_all(detect_headings=True)

with open("book.md", "w", encoding="utf-8") as f:
    f.write(md)

Node.js

const fs = require("node:fs");

const doc = new PdfDocument("book.pdf");
const md = doc.toMarkdownAll();
fs.writeFileSync("book.md", md);
doc.close();

Go

doc, _ := pdfoxide.Open("book.pdf")
defer doc.Close()
md, _ := doc.ToMarkdownAll()
os.WriteFile("book.md", []byte(md), 0644)

C#

using var doc = PdfDocument.Open("book.pdf");
var md = doc.ToMarkdownAll();
File.WriteAllText("book.md", md);

WASM

const doc = new WasmPdfDocument(bytes);
const md = doc.toMarkdownAll(true);
writeFileSync("book.md", md);
doc.free();

Converter com as imagens salvas em um diretório

use pdf_oxide::PdfDocument;
use pdf_oxide::converters::ConversionOptions;

let mut doc = PdfDocument::open("report.pdf")?;
let options = ConversionOptions {
    detect_headings: true,
    include_images: true,
    embed_images: false,
    image_output_dir: Some("output/images".to_string()),
    ..Default::default()
};

let md = doc.to_markdown_all(&options)?;
std::fs::write("output/report.md", &md)?;

Conversão página a página com progresso

from pdf_oxide import PdfDocument

doc = PdfDocument("report.pdf")
pages = doc.page_count()

parts = []
for i in range(pages):
    md = doc.to_markdown(i, detect_headings=True)
    parts.append(md)
    print(f"Converted page {i + 1}/{pages}")

full_md = "\n\n---\n\n".join(parts)
with open("report.md", "w") as f:
    f.write(full_md)

Desabilitar a detecção de títulos para texto plano

doc = PdfDocument("form.pdf")
md = doc.to_markdown(0, detect_headings=False)
# All text rendered as paragraphs, no # headings

Páginas relacionadas