Conversión a Markdown
PDF Oxide convierte páginas PDF a Markdown limpio y legible. El pipeline extrae spans de texto, los agrupa en líneas, consulta /StructTreeRoot para recuperar encabezados y roles de lista en PDFs etiquetados, detecta separadores multicolumna y saltos de orden de lectura, agrupa párrafos y emite sintaxis Markdown.
Desde la v0.3.36, en PDFs etiquetados el convertidor lee StructRole(Heading(1..6) | ListItem | ListItemLabel | ListItemBody) directamente de /StructTreeRoot en lugar de deducir el nivel del encabezado por el tamaño de la fuente. La información de rol se propaga a través de MCRs anidados (H1 → Span → MCR, LI → LBody → Span → MCR). En documentos no etiquetados sigue vigente el fallback geométrico: negrita + 5 % más de tamaño promociona a H4, y is_ordered_list_marker reconoce 1. / 12. / a) / iv. / A. y descarta pies de figura y años.
Multicolumna: los spans con la misma baseline separados por > max(3 × font_size, 30 pt) se tratan como cruce de columna. Los saltos de orden de lectura hacia atrás en X (patrón column-major último→primer-span) ahora cortan párrafos en lugar de fusionarlos en tokens sin sentido.
RTL: el reorden bidi está desactivado por defecto — el reorden visual→lógico incondicional de borradores previos rompía PDFs ya en orden lógico (el nombre hebreo בנימין se invertía). Los marcadores **bold** espurios alrededor de glifos árabes contextuales se eliminan. Cuando la entrada venga en orden visual, invoca manualmente text::bidi::reorder_visual_to_logical (Rust).
Imágenes inline están limitadas a 200 KB de payload base64 (añadido en la v0.3.36). Las imágenes que superan el límite emiten un comentario HTML con el tamaño original; usa image_output_dir para volcarlas al disco.
Ejemplo 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);
Referencia de API
to_markdown(page_index, ...) -> str
Convierte una sola página a Markdown.
Python Signature
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
JavaScript Signature
doc.toMarkdown(pageIndex, detectHeadings?, includeImages?, includeFormFields?) -> string
Rust Signature
pub fn to_markdown(
&mut self,
page_index: usize,
options: &ConversionOptions,
) -> Result<String>
| Parámetro | Tipo | Por defecto | Descripción |
|---|---|---|---|
page_index |
int / usize / number |
– | Índice de página de base cero |
preserve_layout |
bool |
false |
Conservar el posicionamiento del diseño visual |
detect_headings |
bool |
true |
Detectar encabezados según el tamaño y el grosor de la fuente |
include_images |
bool |
true |
Incluir imágenes en la salida |
image_output_dir |
str / None |
None |
Directorio donde guardar las imágenes extraídas (solo Python/Rust). No se ve afectado por el límite inline de 200 KB. |
embed_images |
bool |
true |
Incrustar las imágenes como data URIs base64 (solo Python/Rust). Los payloads de más de 200 KB emiten un comentario HTML placeholder con el tamaño original (v0.3.36). |
include_form_fields |
bool |
true |
Incluir los valores de los campos de formulario (Python/JS) |
Devuelve: Cadena Markdown de la página.
to_markdown_all(...) -> str
Convierte todas las páginas a Markdown, separadas por reglas horizontales (---).
Python Signature
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
JavaScript Signature
doc.toMarkdownAll(detectHeadings?, includeImages?, includeFormFields?) -> string
Rust Signature
pub fn to_markdown_all(
&mut self,
options: &ConversionOptions,
) -> Result<String>
| Parámetro | Tipo | Por defecto | Descripción |
|---|---|---|---|
preserve_layout |
bool |
false |
Conservar el diseño visual |
detect_headings |
bool |
true |
Detectar encabezados |
include_images |
bool |
true |
Incluir imágenes |
image_output_dir |
str / None |
None |
Directorio de salida de las imágenes |
embed_images |
bool |
true |
Incrustar las imágenes como base64 |
Devuelve: Cadena Markdown con todas las páginas unidas mediante separadores ---.
to_markdown_with_ocr(page_index, model_path, options) -> str
Convierte una página a Markdown con OCR de respaldo para páginas escaneadas. Cuando la página tiene poco o ningún texto extraíble, se usa OCR para reconocer el texto a partir de la imagen renderizada de la página. Requiere la feature ocr.
| Parámetro | Tipo | Descripción |
|---|---|---|
page_index |
usize |
Índice de página de base cero |
model_path |
&str |
Ruta a los archivos del modelo OCR |
options |
&ConversionOptions |
Opciones de conversión |
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
La struct ConversionOptions controla todo el comportamiento de la conversión.
| Campo | Tipo | Por defecto | Descripción |
|---|---|---|---|
preserve_layout |
bool |
false |
Conservar el diseño visual con posicionamiento |
detect_headings |
bool |
true |
Detectar encabezados automáticamente a partir de los grupos de tamaños de fuente |
extract_tables |
bool |
false |
Extraer tablas (experimental) |
include_images |
bool |
true |
Incluir imágenes en la salida |
image_output_dir |
Option<String> |
None |
Guardar las imágenes en este directorio |
embed_images |
bool |
true |
Incrustar las imágenes como data URIs base64 |
reading_order_mode |
ReadingOrderMode |
Auto |
Cómo determinar el orden de lectura |
bold_marker_behavior |
BoldMarkerBehavior |
Conservative |
Estrategia de aplicación de los marcadores de negrita |
Cómo funciona
El pipeline de conversión a Markdown opera en varias etapas:
-
Extracción de texto – Extrae objetos
TextSpandel flujo de contenido de la página, capturando texto, posición, fuente, tamaño, grosor y color. -
Agrupación de caracteres – Agrupa los caracteres en palabras según los espacios entre caracteres y, después, las palabras en líneas según su proximidad vertical.
-
Orden de lectura – Determina el orden de lectura usando el árbol de estructura del PDF etiquetado (preferente) o un análisis espacial basado en grafos de las posiciones de los bloques de texto.
-
Detección de encabezados – Cuando
detect_headingsestá activado, agrupa los tamaños de fuente de toda la página para identificar los niveles de encabezado. El texto más grande y en negrita se asigna a encabezados#,##,###. -
Formato – Aplica los marcadores de negrita (
**text**) e itálica (*text*) según el grosor de la fuente y los metadatos de estilo. -
Detección de tablas – Identifica diseños tabulares mediante el análisis espacial de bloques de texto alineados y emite tablas Markdown estilo GFM.
-
Limpieza de espacios en blanco – Normaliza el espaciado, elimina las líneas en blanco redundantes y garantiza saltos de párrafo coherentes.
Ejemplos avanzados
Convertir un PDF completo a un archivo 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();
Convertir guardando las imágenes en un directorio
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)?;
Conversión página por página con progreso
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)
Desactivar la detección de encabezados 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
- Extracción de texto – Extracción de texto y spans sin procesar
- Conversión a HTML – Convierte a HTML en lugar de Markdown
- Extracción de imágenes – Extrae las imágenes por separado