Skip to main content

Lightweight, model-free PDF-to-Markdown conversion

Project description

Dumb PDF2MD

简体中文

dumb-pdf2md is a lightweight, typed Python library that converts text-based PDF documents into structured, LLM-friendly Markdown. It reconstructs document structure from the PDF text layer, geometry, and typography without layout models, OCR models, GPU resources, or external services.

Installation

With pip:

python -m pip install dumb-pdf2md

With uv:

uv add dumb-pdf2md

Requires Python 3.10 or later.

Benchmarks

READoc selectable-text holdout (12 real-world PDFs, 161 pages):

Metric dumb-pdf2md MarkItDown 0.1.6 Docling 2.115.0 PyMuPDF4LLM 1.28.0 pdftext 0.7.1 pdfplumber 0.11.10
Text token F1 0.852 0.756 0.899 0.863 0.893 0.691
Reading-order bigram F1 0.734 0.627 0.831 0.793 0.818 0.562
Table-content F1 (6-PDF subset) 0.452 0.326 0.890 0.685 N/A N/A
Conversion time 188.7 ms/page 53.5 ms/page 917.1 ms/page 143.7 ms/page 24.2 ms/page 53.6 ms/page
Peak RSS 248.5 MiB 115.1 MiB 4.44 GiB 667.7 MiB 122.6 MiB 202.6 MiB
Uses OCR model No No Yes No No No
Uses layout model No No Yes Yes No No
Installed size with dependencies 112.5 MiB 168.8 MiB 5.25 GiB 233.2 MiB 70.0 MiB 50.2 MiB
License Apache-2.0 MIT MIT AGPL-3.0 + PolyForm NC / commercial Apache-2.0 MIT

pdftext and pdfplumber are the two base extraction libraries used by dumb-pdf2md, not end-to-end Markdown converters. Their columns score native text output without dumb-pdf2md's structure analysis or Markdown rendering, so table-content F1 is not applicable to those baseline modes. pdfplumber does provide table-finding APIs; dumb-pdf2md combines their candidates with pdftext geometry, custom aligned-table detection, cell normalization, deduplication, and Markdown rendering.

Model rows describe the exact benchmark configuration. Docling ran on CPU with EasyOCR and its layout/table models enabled, without forcing full-page OCR on pages that already contain text. PyMuPDF4LLM uses its installed pymupdf-layout dependency. MarkItDown's optional OCR plugin and cloud integrations were not enabled. The PyMuPDF4LLM license row includes the license of its layout dependency.

Quality metrics are higher-is-better; time and memory are lower-is-better. Performance uses three isolated process runs per tool, excludes import time, and reports median conversion time and maximum peak RSS.

Installed size is the site-packages footprint in a clean Python 3.13 environment after installing the package and all transitive dependencies. It excludes Python itself, package caches, and models downloaded at runtime.

Performance host: Intel Xeon Platinum 8488C, 16 vCPUs (8 cores/16 threads), 64 GB RAM, Ubuntu 24.04.4 LTS (x86_64), and CPython 3.13.3. Results are directly comparable only on the same environment.

READoc was not used for parser tuning. See benchmark reproduction, the base-engine results, the MarkItDown results, the Docling results, the performance results, and the evaluation methodology.

Quality-resource map

quadrantChart
    title Overall extraction quality versus runtime memory
    x-axis Higher peak RSS --> Lower peak RSS
    y-axis Lower composite F1 --> Higher composite F1
    quadrant-1 Best quality-resource balance
    quadrant-2 High quality, high resource use
    quadrant-3 High resource use, weaker structure
    quadrant-4 Lightweight, weaker structure
    "MarkItDown": [0.96, 0.570]
    "pdfplumber": [0.82, 0.418]
    "★ Dumb PDF2MD": [0.77, 0.679]
    "PyMuPDF4LLM": [0.51, 0.781]
    "Docling": [0.02, 0.873]

Licenses:

  • ★ Dumb PDF2MD: Apache-2.0
  • MarkItDown: MIT
  • pdfplumber: MIT
  • PyMuPDF4LLM: AGPL-3.0 + PolyForm Noncommercial / commercial
  • Docling: MIT

The horizontal coordinates reverse and normalize log10(peak RSS), so lower memory use moves a tool to the right while Docling's 4.44 GiB result does not compress the other tools at the origin. They show relative resource tiers, not raw memory values; the exact measurements are in the table above. Composite F1 is the equal-weight mean of text-token, reading-order-bigram, and table-content F1; the table component uses the six-PDF subset with ground-truth tables. For this visualization only, pdfplumber receives zero for the table component because its benchmark mode does not produce Markdown tables; the source table keeps this result as N/A because no table-quality measurement was made.

Positioning

dumb-pdf2md fills the gap between raw text extraction and model-based document understanding:

  • It adds headings, lists, multi-column reading order, table reconstruction, deduplication, and Markdown rendering on top of lightweight PDF engines.
  • It has higher text, reading-order, and table F1 than MarkItDown on this holdout, while remaining fully local and model-free.
  • It uses substantially less memory and disk space than Docling, and less memory than PyMuPDF4LLM.
  • Its Apache-2.0 runtime has no model-weight license, GPU requirement, model download, or external service dependency.

The tradeoff is deliberate: Docling is more accurate on complex structures and supports scanned PDFs, while raw extractors are faster when structure is not needed. dumb-pdf2md targets selectable-text PDFs where useful Markdown structure matters but OCR/model deployment cost does not make sense.

Features

  • Accepts file paths, bytes-like objects, and readable binary streams.
  • Runs locally on CPU without downloading layout or OCR models.
  • Preserves headings, paragraphs, lists, footers, and reading order.
  • Converts ruled and structurally aligned tables to Markdown tables.
  • Handles common multi-column layouts and CJK text.
  • Produces clean Markdown suitable for LLM context, indexing, and RAG pipelines.
  • Returns page counts and non-fatal diagnostics alongside Markdown.
  • Provides immutable result models and a documented exception hierarchy.
  • Supports concurrent callers without taking ownership of caller-provided streams.

dumb-pdf2md is designed for PDFs that contain extractable text. Scanned or image-only PDFs require OCR before conversion. The library does not currently extract images, preserve inline text styling, or expose page-level bounding boxes and metadata.

Usage

Use parse when you need conversion metadata:

from dumb_pdf2md import parse

result = parse("report.pdf")

print(result.markdown)
print(f"Pages: {result.page_count}")

for diagnostic in result.diagnostics:
    print(diagnostic.code, diagnostic.page, diagnostic.message)

Use to_markdown when you only need the generated Markdown:

from dumb_pdf2md import to_markdown

markdown = to_markdown("report.pdf")

Both functions also accept PDF data and readable binary streams:

from io import BytesIO

from dumb_pdf2md import parse

with open("report.pdf", "rb") as file:
    pdf_data = file.read()

from_bytes = parse(pdf_data)

stream = BytesIO(pdf_data)
from_stream = parse(stream)
assert not stream.closed

Caller-owned streams remain open after conversion.

Parser Options

Document heuristics are enabled by default and can be controlled with ParserOptions:

from dumb_pdf2md import ParserOptions, parse

result = parse(
    "report.pdf",
    options=ParserOptions(
        promote_bold_headings=False,
        detect_columns=False,
    ),
)
  • promote_bold_headings promotes qualifying short bold lines to headings.
  • detect_columns detects common column layouts and restores reading order.

Error Handling

All public conversion errors inherit from PdfParserError:

from dumb_pdf2md import PdfInputError, PdfParserError, parse

try:
    result = parse("report.pdf")
except PdfInputError as error:
    print(f"Invalid PDF input: {error}")
except PdfParserError as error:
    print(f"PDF conversion failed: {error}")

PdfInputError reports invalid, empty, missing, or unreadable input. PdfConversionError reports extraction failures or an empty conversion result. Wrapped failures retain the original exception in __cause__.

Development

Run the complete release checkpoint:

scripts/release-check.sh

Or run individual checks:

uv run ruff check .
uv run mypy
uv run pytest
uv build

The test suite covers the public API, parsing algorithms, resource cleanup, concurrent use, distribution artifacts, and a reproducible golden PDF corpus.

Documentation

License

Licensed under the Apache License 2.0.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

dumb_pdf2md-0.1.1.tar.gz (288.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

dumb_pdf2md-0.1.1-py3-none-any.whl (26.4 kB view details)

Uploaded Python 3

File details

Details for the file dumb_pdf2md-0.1.1.tar.gz.

File metadata

  • Download URL: dumb_pdf2md-0.1.1.tar.gz
  • Upload date:
  • Size: 288.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.7.2

File hashes

Hashes for dumb_pdf2md-0.1.1.tar.gz
Algorithm Hash digest
SHA256 c62df682df94dab15e5d53ce0cac4b9cd50f920cb8464358627538fc360289c6
MD5 6ebf9408a7433f4cc1f087ce36a23d67
BLAKE2b-256 050aef077ffcbc47913445eb14ee8e712ff66f6207bc469bfeeaf4e7fa52e9dc

See more details on using hashes here.

File details

Details for the file dumb_pdf2md-0.1.1-py3-none-any.whl.

File metadata

File hashes

Hashes for dumb_pdf2md-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 3c4004eef4fcccfd2b9bab7a75023f3bc87fc162f80ce848f74ae7882ec7f019
MD5 5b080f26674d1b769d4ae69249a0bdf9
BLAKE2b-256 e121f3adc5e50b6930caf82362d649a90861b5d08fbfb7f7d65f333e28857b03

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page