Skip to content

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:

  1. Extracción de texto – Extrae objetos TextSpan del flujo de contenido de la página, capturando texto, posición, fuente, tamaño, grosor y color.

  2. 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.

  3. 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.

  4. Detección de encabezados – Cuando detect_headings está 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 #, ##, ###.

  5. Formato – Aplica los marcadores de negrita (**text**) e itálica (*text*) según el grosor de la fuente y los metadatos de estilo.

  6. Detección de tablas – Identifica diseños tabulares mediante el análisis espacial de bloques de texto alineados y emite tablas Markdown estilo GFM.

  7. 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