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:
-
Extração de texto – Extrai objetos
TextSpando fluxo de conteúdo da página, capturando texto, posição, fonte, tamanho, peso e cor. -
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.
-
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.
-
Detecção de títulos – Quando
detect_headingsestá 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#,##,###. -
Formatação – Aplica marcadores de negrito (
**texto**) e itálico (*texto*) com base nos metadados de peso e estilo da fonte. -
Detecção de tabelas – Identifica layouts tabulares usando análise espacial de blocos de texto alinhados e emite tabelas Markdown no estilo GFM.
-
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
- Extração de texto — extração de texto cru e spans
- Conversão para HTML — converter para HTML em vez de Markdown
- Extração de imagens — extrair imagens separadamente