PDF to Markdown
PDF to Markdown, three ways.
pdfboss turns a PDF's page layout into Markdown: headings, lists and pipe tables inferred from the geometry and from drawn borders, columns read in order, emphasis carried over from styled spans. The same Rust engine answers from Python, from Rust and from the command line, and reads a 200-document benchmark corpus in 0.15 seconds.
Command line
One command, local or remote.
cargo install pdfboss-cli puts the pdfboss binary on your path. md writes Markdown to stdout and takes a local path or an http(s) URL.
$ pdfboss md report.pdf > report.md
$ pdfboss md https://example.com/report.pdf > report.md
# URLs are fetched in byte ranges, never downloaded wholePython
One method on the document.
pip install pdfboss installs prebuilt abi3 wheels for CPython 3.12 and later, no C toolchain involved. extract_markdown() runs the layout analysis and spreads pages across cores; extract_text() is its plain-text sibling.
import pdfboss
doc = pdfboss.Document("report.pdf")
markdown = doc.extract_markdown() # whole document
page_md = doc[0].extract_markdown() # one page, 0-basedRust
Two crates from the same workspace.
cargo add pdfboss-core pdfboss-output is all it takes: pdfboss-core parses the document and pdfboss-output runs the layout analysis that produces text and Markdown.
use pdfboss_core::Document;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let doc = Document::open("report.pdf")?;
let markdown = pdfboss_output::extract_markdown(&doc)?;
println!("{markdown}");
Ok(())
}What survives the trip
What the Markdown preserves.
- Headings, lists and emphasis, inferred from the page layout and the styled spans behind it.
- Tables as pipe tables, detected from column gaps and from drawn borders, so bordered grids and boxed lists without column gaps are found too.
- Reading order: two-column layouts are read column-major, and justified text keeps its word spacing.
- Repeated page headers are recognized by the layout analysis, and ligatures and small-caps variants decode through the full Adobe Glyph List conventions.
Measured
Scored on a public benchmark.
Reading order on opendataloader-bench, against pymupdf4llm’s 0.886.
To read all 200 corpus documents to Markdown, ~3× faster than the fastest competing Markdown engine.
The benchmark’s combined metric: reading order 0.883, headings and lists 0.709, table structure 0.494.
Quality rows come from the benchmark’s own evaluator over all 200 documents. Method and full tables →
The other direction
Markdown back into PDF.
The same toolkit composes CommonMark+GFM into CSS-themed, paginated PDFs: pdfboss.md.to_pdf in Python, pdfboss create md on the command line, and pdfboss_markdown::to_pdf in Rust, all with deterministic output.
Questions
- How do I convert a PDF to Markdown in Python?
- pip install pdfboss, then pdfboss.Document("report.pdf").extract_markdown() returns the whole document as Markdown. Each page has the same method, and the CLI equivalent is pdfboss md report.pdf.
- How good is the output?
- On opendataloader-bench, the 200-PDF corpus PDF-to-Markdown engines use for their published comparisons, pdfboss scores 0.883 on reading order (NID, mid-field of the engines measured) and 0.810 on the combined metric, and reads the whole corpus in 0.15 seconds, about 3 times faster than the fastest competing Markdown engine.
- Does it handle tables?
- Yes, as pipe tables. Tables are detected from column gaps and from drawn borders, so bordered grids and boxed lists without column gaps are found too. On the benchmark's table-structure metric it scores 0.494; if you need structured rows rather than Markdown, a dedicated table extractor such as pdfplumber is the stronger tool.
- Does it work on scanned PDFs?
- No. pdfboss does no OCR, and a scanned page carries no text operators to read. Render the page to PNG with pdfboss and run an OCR engine on the image instead.
- Can it read the PDF from a URL?
- Yes. The CLI accepts an http(s) URL for anything it reads and fetches only the byte ranges it needs, and Python's AsyncDocument opens URLs the same way.