Skip to content

Markdown-Konvertierung

PDF Oxide wandelt PDF-Seiten in sauberes, lesbares Markdown um. Die Pipeline extrahiert Text-Spans, gruppiert sie zu Zeilen, konsultiert bei Tagged PDFs direkt /StructTreeRoot für Überschriften und Listenrollen, erkennt Zwischenräume zwischen Spalten und rückwärtsgerichtete Leserichtungs-Umbrüche, fasst Absätze zusammen und gibt schließlich Markdown aus.

Seit v0.3.36 liest der Konverter bei Tagged PDFs StructRole(Heading(1..6) | ListItem | ListItemLabel | ListItemBody) direkt aus /StructTreeRoot, statt Überschriftsebenen aus der Schriftgröße abzuleiten. Die Rolleninformation wird durch verschachtelte MCRs hindurch propagiert (H1 → Span → MCR, LI → LBody → Span → MCR). Für nicht getaggte Dokumente greift weiterhin der geometrische Fallback: Fettdruck plus 5 % mehr Größe stuft auf H4 hoch, und is_ordered_list_marker erkennt 1. / 12. / a) / iv. / A., lehnt Abbildungsüberschriften und Jahreszahlen aber ab.

Mehrspaltenverarbeitung: Spans mit derselben Grundlinie, die mehr als max(3 × font_size, 30 pt) voneinander entfernt stehen, gelten als spaltenübergreifend. Rückwärtsgerichtete Leserichtungs-Umbrüche (letzter Span in Spalte 1 → erster Span in Spalte 2 bei spaltenorientierter Reihenfolge) brechen Absätze, statt sie zu unsinnigen Tokens zusammenzukleben.

RTL: Bidi-Reordering ist standardmäßig aus. Das frühere bedingungslose visuell-zu-logisch-Umordnen brach bereits korrekt logisch geordnete PDFs (der hebräische Name בנימין wurde rückwärts geschrieben). Falsche **bold**-Marker rund um arabische kontextabhängige Glyphen werden entfernt. Wer visuell geordneten Input verarbeitet, kann text::bidi::reorder_visual_to_logical weiterhin manuell aufrufen (Rust).

Inline-Bilder sind auf eine Base64-Nutzlast von 200 KB begrenzt (neu in v0.3.36). Größere Bilder liefern einen HTML-Kommentar mit der Originalgröße als Platzhalter; mit image_output_dir schreiben Sie sie stattdessen auf die Platte.

Schnellbeispiel

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

API-Referenz

to_markdown(page_index, ...) -> str

Konvertiert eine einzelne Seite in Markdown.

Python-Signatur

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

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

Rust-Signatur

pub fn to_markdown(
    &mut self,
    page_index: usize,
    options: &ConversionOptions,
) -> Result<String>
Parameter Typ Standard Beschreibung
page_index int / usize / number Nullbasierter Seitenindex
preserve_layout bool false Visuelle Layoutpositionierung beibehalten
detect_headings bool true Überschriften anhand von Schriftgröße und -gewicht erkennen
include_images bool true Bilder in die Ausgabe aufnehmen
image_output_dir str / None None Verzeichnis zum Speichern extrahierter Bilder (nur Python/Rust). Vom 200-KB-Inline-Limit nicht betroffen.
embed_images bool true Bilder als base64-Daten-URIs einbetten (nur Python/Rust). Nutzlasten über 200 KB geben einen Platzhalter als HTML-Kommentar mit der Originalgröße aus (v0.3.36).
include_form_fields bool true Formularfeldwerte einbeziehen (Python/JS)

Rückgabe: Markdown-Zeichenkette für die Seite.


to_markdown_all(...) -> str

Konvertiert alle Seiten in Markdown, getrennt durch horizontale Linien (---).

Python-Signatur

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

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

Rust-Signatur

pub fn to_markdown_all(
    &mut self,
    options: &ConversionOptions,
) -> Result<String>
Parameter Typ Standard Beschreibung
preserve_layout bool false Visuelles Layout beibehalten
detect_headings bool true Überschriften erkennen
include_images bool true Bilder einbeziehen
image_output_dir str / None None Ausgabeverzeichnis für Bilder
embed_images bool true Bilder als base64 einbetten

Rückgabe: Markdown-Zeichenkette für alle Seiten, verbunden mit ----Trennern.


to_markdown_with_ocr(page_index, model_path, options) -> str

Konvertiert eine Seite in Markdown mit OCR-Fallback für gescannte Seiten. Wenn die Seite wenig oder keinen extrahierbaren Text enthält, wird OCR verwendet, um Text aus dem gerenderten Seitenbild zu erkennen. Erfordert das ocr-Feature.

Parameter Typ Beschreibung
page_index usize Nullbasierter Seitenindex
model_path &str Pfad zu den OCR-Modelldateien
options &ConversionOptions Konvertierungsoptionen

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

Die ConversionOptions-Struktur steuert das gesamte Konvertierungsverhalten.

Feld Typ Standard Beschreibung
preserve_layout bool false Visuelles Layout mit Positionierung beibehalten
detect_headings bool true Überschriften automatisch aus Schriftgrößen-Clustern erkennen
extract_tables bool false Tabellen extrahieren (experimentell)
include_images bool true Bilder in die Ausgabe aufnehmen
image_output_dir Option<String> None Bilder in dieses Verzeichnis speichern
embed_images bool true Bilder als base64-Daten-URIs einbetten
reading_order_mode ReadingOrderMode Auto Wie die Leserichtung bestimmt wird
bold_marker_behavior BoldMarkerBehavior Conservative Strategie für das Anwenden von Fettmarkierungen

Funktionsweise

Die Markdown-Konvertierungs-Pipeline arbeitet in mehreren Stufen:

  1. Textextraktion – Extrahiert TextSpan-Objekte aus dem Seiteninhaltsstrom und erfasst dabei Text, Position, Schriftart, Größe, Gewicht und Farbe.

  2. Zeichen-Clustering – Gruppiert Zeichen anhand der Zwischenräume zu Wörtern und Wörter anhand der vertikalen Nähe zu Zeilen.

  3. Leserichtung – Bestimmt die Leserichtung entweder über den Strukturbaum eines Tagged PDF (bevorzugt) oder über eine graphenbasierte räumliche Analyse der Textblockpositionen.

  4. Überschriftenerkennung – Wenn detect_headings aktiviert ist, werden die Schriftgrößen über die Seite hinweg geclustert, um Überschriftsebenen zu identifizieren. Größerer und fetterer Text wird auf #-, ##- und ###-Überschriften abgebildet.

  5. Formatierung – Wendet Fett- (**text**) und Kursivmarkierungen (*text*) anhand von Schriftgewicht und Stil-Metadaten an.

  6. Tabellenerkennung – Erkennt tabellarische Layouts über die räumliche Analyse ausgerichteter Textblöcke und gibt Markdown-Tabellen im GFM-Stil aus.

  7. Leerraumbereinigung – Normalisiert Abstände, entfernt überflüssige Leerzeilen und sorgt für konsistente Absatzumbrüche.


Erweiterte Beispiele

Gesamtes PDF in eine Markdown-Datei konvertieren

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

Konvertieren und Bilder in ein Verzeichnis speichern

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

Seitenweise Konvertierung mit Fortschrittsanzeige

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)

Überschriftenerkennung für Fließtext deaktivieren

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

Verwandte Seiten