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:
-
Textextraktion – Extrahiert
TextSpan-Objekte aus dem Seiteninhaltsstrom und erfasst dabei Text, Position, Schriftart, Größe, Gewicht und Farbe. -
Zeichen-Clustering – Gruppiert Zeichen anhand der Zwischenräume zu Wörtern und Wörter anhand der vertikalen Nähe zu Zeilen.
-
Leserichtung – Bestimmt die Leserichtung entweder über den Strukturbaum eines Tagged PDF (bevorzugt) oder über eine graphenbasierte räumliche Analyse der Textblockpositionen.
-
Überschriftenerkennung – Wenn
detect_headingsaktiviert ist, werden die Schriftgrößen über die Seite hinweg geclustert, um Überschriftsebenen zu identifizieren. Größerer und fetterer Text wird auf#-,##- und###-Überschriften abgebildet. -
Formatierung – Wendet Fett- (
**text**) und Kursivmarkierungen (*text*) anhand von Schriftgewicht und Stil-Metadaten an. -
Tabellenerkennung – Erkennt tabellarische Layouts über die räumliche Analyse ausgerichteter Textblöcke und gibt Markdown-Tabellen im GFM-Stil aus.
-
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
- Textextraktion – Rohtext- und Span-Extraktion
- HTML-Konvertierung – In HTML statt Markdown konvertieren
- Bildextraktion – Bilder separat extrahieren