Skip to main content

md2pdf — Markdown to PDF in Pure Python

md2pdf converts your Markdown documents into beautiful, print-ready PDFs using pure Python — no Pandoc, Node.js, or headless Chrome needed. Powered by ReportLab and mistletoe.

PyPI version Downloads Python License CI codecov


Why md2pdf?

Most Markdown-to-PDF tools require heavy system dependencies — and even then, getting diagrams or math to render often means installing additional packages. md2pdf works out of the box:

Tool Requires Pandoc Requires Chrome Diagrams & Math Pure Python
Pandoc-based ✅ Yes ❌ No ⚠️ Extra packages (e.g. mermaid-filter, LaTeX distribution) ❌ No
Puppeteer/Playwright ❌ No ✅ Yes ⚠️ Depends on setup ❌ No
md2pdf No No Built-in (via Kroki and matplotlib) Yes

Beyond zero system dependencies, you also get:

  • Diagrams & Math — Mermaid and LaTeX via Kroki (with SHA-256 disk caching); matplotlib used for offline LaTeX rendering when configured
  • PDF bookmarks — every heading clickable in your PDF viewer's navigation panel
  • Multi-page tables — split cleanly across pages with repeated headers
  • Colour emoji — Twemoji-powered PNG emoji in your PDFs
  • Extensible plugins — custom handlers, themes, and preprocessors

Performance

Although performance was not a primary design goal, md2pdf is highly efficient. By parsing the Markdown AST and mapping it directly to ReportLab flowables, it naturally avoids browser launching, process forking, and intermediate HTML rendering overhead.

Benchmarks (macOS-arm64, Apple Silicon)

To evaluate compilation speeds, we compared md2pdf against Pandoc (using various PDF engines) and Playwright (Chromium) across documents of different sizes:

  • Cold Start: Spawns a clean process to capture initialization costs (like browser startup for Playwright or module import overhead for md2pdf).
  • Warm Start (Median): Measures rendering throughput by averaging subsequent runs in a warm environment.

Warm Start Rendering Speed (Median)

Warm Start Performance

Cold Start Rendering Speed

Cold Start Performance

Summary Metrics (seconds, lower is better)

Document Engine Cold Start (s) Warm Median (s) Status
simple.md md2pdf (native) 0.236s 0.019s Success
(Short document) Playwright (Chromium) 0.476s 0.039s Success
Pandoc (weasyprint) 0.624s 0.342s Success
Pandoc (pdflatex) 1.001s 0.737s Success
Pandoc (xelatex) 1.522s 1.428s Success
medium.md Playwright (Chromium) 0.388s 0.043s Success
(With math/code) md2pdf (native) 0.699s 0.065s Success
Pandoc (weasyprint) 0.471s 0.462s Success
Pandoc (pdflatex) 0.846s 0.836s Success
Pandoc (xelatex) 1.502s 1.501s Success
large.md Playwright (Chromium) 0.395s 0.047s Success
(Multi-page guide) md2pdf (native) 0.297s 0.070s Success
Pandoc (weasyprint) 0.529s 0.518s Success
Pandoc (pdflatex) 0.817s 0.831s Success
Pandoc (xelatex) 1.498s 1.513s Success

Note: For medium.md, md2pdf's cold start is higher because it contains mathematical formulas, which initializes the LaTeX math typesetting subsystem. For large.md (which has no math), md2pdf's cold start is much faster.

To run these benchmarks on your local system:

make benchmark

Installation

Using uv (recommended):

uv tool install pymd2pdf

Using pip:

pip install pymd2pdf

Heavy math documents (physics, engineering)? Install the optional matplotlib backend for faster offline LaTeX rendering:

pip install "pymd2pdf[matplotlib]"

or

uv tool install "pymd2pdf[matplotlib]"

Note: The PyPI package name is pymd2pdf, but the CLI command and Python import are both md2pdf.


Quick Start

# Convert a Markdown file to PDF
md2pdf input.md -o output.pdf

# Run validation checks without producing a PDF
md2pdf input.md --validate-only

# Work offline (diagrams show source code placeholder instead)
md2pdf input.md -o output.pdf --offline

That's it. For most use cases, the first command is all you need.


Key Features

  • Standard Elements: Headings (H1–H6), paragraphs, lists, blockquotes, horizontal rules, and hyperlinks.
  • Multi-page Tables: Tables split cleanly across page boundaries with repeated column headers on each page.
  • Diagrams & Math: Mermaid diagrams and LaTeX math blocks rendered via the Kroki API, with transparent margin cropping, offline fallbacks, and SHA-256 disk caching.
  • PDF Bookmarks & Outline Panel: Every heading (H1–H6) becomes a clickable, correctly nested entry in your PDF viewer's navigation/bookmarks panel.
  • Colour Emoji: Twemoji-powered PNG emoji rendering in PDFs, with graceful network fallbacks.
  • Admonitions & Callouts: GitHub-style alerts (> [!NOTE], > [!WARNING]) and MkDocs/Obsidian fenced containers (:::note).
  • Broad Unicode Support: Bundled DejaVu Sans fonts cover Latin Extended, Greek, Cyrillic, math operators, arrows, and box-drawing — no system font dependency needed.
  • Custom Fonts & Themes: Supply your own TTF fonts and TOML-based theme overrides.
  • Extensible Plugin System: Load custom element handlers, preprocessors, post-processors, and stylesheet/theme layers.
  • Typesetting Safeguards: Orphaned heading prevention, ghost page elimination, and widow/orphan protection.
  • Pre-render Validation: Catches nested tables, empty diagrams, and unsupported elements before committing to a render.

CLI Options

Flag Shortcut Description
--output -o Output PDF path (default: <input>.pdf).
--config -c Path to a custom md2pdf.toml config file.
--theme -t Theme name to apply (default: default).
--offline Skip Kroki API calls; use source code placeholders instead.
--validate-only Run validation and exit without producing a PDF.
--verbose -v Output debug-level logging to stderr.
--toc Prepend a dynamically generated Table of Contents page.
--header Running header template (supports {title} and {section}).
--header-on-first-page Render the running header on the first page too.
--min-image-scale Minimum image scale before moving to a new page (default: 0.8).
--emoji / --no-emoji Enable or disable Twemoji colour emoji (default: enabled).
--progress / --no-progress Show stage-level progress output on stderr (default: enabled).

Python API

from md2pdf import convert, Config, Pipeline

# One-liner conversion
convert("input.md", "output.pdf")

# Advanced: custom config and pipeline
config = Config(
    offline=False,
    cache_dir=".md2pdf_cache",
    output_file="my_document.pdf"
)
pipeline = Pipeline(config)

# Validate before rendering
issues = pipeline.validate("# Hello World")
for issue in issues:
    print(f"[{issue.severity}] {issue.code}: {issue.message}")

# Render
pipeline.run(raw_md="# Document Title\n\nSome body text.")

Configuration

md2pdf auto-discovers a md2pdf.toml config file in these locations (in order):

  1. ./md2pdf.toml (project-local)
  2. ~/.config/md2pdf/md2pdf.toml
  3. ~/.md2pdf.toml

You can also pass one explicitly with --config. See the annotated md2pdf.toml.example for all available options.


Documentation & Examples

Resource Description
User Guide Comprehensive features reference
Themes & Styling Custom themes, fonts, and stylesheet overrides
Plugin Authoring Writing custom handlers and preprocessors
User Manual (PDF) Consolidated print-ready manual
Examples Four production-grade templates with rendered PDFs

Example Templates

Example Showcases
academic_paper/ LaTeX math, citations, Mermaid flowcharts
business_invoice/ Tables, custom Corporate Blue theme
project_roadmap/ Task lists, admonitions, Gantt charts, code highlighting
simple_cv/ Resume layout, structural tables, clean margins

Tech Stack

Component Library Description
Core Language Python >= 3.11 Strict type-hinting throughout
PDF Generation ReportLab >= 4.0 Low-level document layout engine
Markdown Parsing mistletoe >= 1.3 Fast, extensible Markdown AST parser
HTTP Requests requests >= 2.31 Kroki API communication
CLI Framework typer >= 0.12 CLI builder with validation
Image Processing Pillow >= 10.0 Auto-cropping and dimension detection
LaTeX Rendering (optional) matplotlib >= 3.8 Offline LaTeX math rendering (pip install "pymd2pdf[matplotlib]")

Development Setup

# Clone the repository
git clone https://github.com/Hari31416/md2pdf.git
cd md2pdf

# Create virtual environment and install all dependencies (including dev)
uv sync
source .venv/bin/activate

# Install pre-commit hooks
uv run pre-commit install

# Run the test suite
uv run pytest

To rebuild all documentation PDFs and example renders:

make docs

Architecture Overview

md2pdf runs as a four-stage pipeline: preprocessing → parsing/validation → element rendering → layout composition.

graph TD
    Input[Markdown File] --> Pre[Preprocessors]
    Pre --> Parser[MarkdownParser]
    Parser --> Tokens[Token Stream]
    Tokens --> Val[DocumentValidator]
    Tokens --> Reg[Plugin/Handler Registry]
    Reg --> Handlers[Element Handlers]
    Handlers --> Kroki[Kroki API / Cache]
    Handlers --> Flowables[ReportLab Flowables]
    Flowables --> Layout[LayoutComposer]
    Layout --> Post[Postprocessors]
    Post --> Renderer[ReportLab PDF Engine]
    Renderer --> Output[Output PDF]

For detailed sequence-level logic, see the User Guide.


Project Structure

md2pdf/
├── docs/                   # Documentation suite (Markdown & consolidated PDF)
├── examples/               # Production-grade example templates with rendered PDFs
├── md2pdf/                 # Core source package
│   ├── assets/             # Kroki client, caching, and fallback elements
│   ├── core/               # Engine pipeline, parser, validator, layout, registry
│   ├── handlers/           # Element-specific flowable generators
│   ├── styles/             # Default stylesheet and theme configs
│   └── cli.py              # CLI entry point
├── scripts/                # Helper scripts (e.g. build_docs.py)
├── tests/                  # Automated test suite
├── md2pdf.toml.example     # Annotated configuration reference
└── pyproject.toml          # Build system and dependency declaration

Download files

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

Source Distribution

pymd2pdf-0.6.0.tar.gz (4.2 MB view details)

Uploaded Source

Built Distribution

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

pymd2pdf-0.6.0-py3-none-any.whl (1.7 MB view details)

Uploaded Python 3

File details

Details for the file pymd2pdf-0.6.0.tar.gz.

File metadata

  • Download URL: pymd2pdf-0.6.0.tar.gz
  • Upload date:
  • Size: 4.2 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for pymd2pdf-0.6.0.tar.gz
Algorithm Hash digest
SHA256 8ec2deb5aa510bffa0241233b55fe489218da56a53103c2a3b08f1b88f75af62
MD5 2a3895ca5411392688834e19e81d832d
BLAKE2b-256 a58d1c345632f9f60a40008ba32649eca7488930a4f78cc4816c3d8ae61b3cc8

See more details on using hashes here.

Provenance

The following attestation bundles were made for pymd2pdf-0.6.0.tar.gz:

Publisher: release.yml on Hari31416/md2pdf

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pymd2pdf-0.6.0-py3-none-any.whl.

File metadata

  • Download URL: pymd2pdf-0.6.0-py3-none-any.whl
  • Upload date:
  • Size: 1.7 MB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for pymd2pdf-0.6.0-py3-none-any.whl
Algorithm Hash digest
SHA256 963b5d81937b54f101e60982cf3a71bdd32c8b7afe942fd3e51ba8c912646ebf
MD5 7ca53c9f7c289019ebd4f72fd6a6bcd3
BLAKE2b-256 b513d796a9053728a417dceb28f39905af7dabbaef41a52697c3d833c00a2b8b

See more details on using hashes here.

Provenance

The following attestation bundles were made for pymd2pdf-0.6.0-py3-none-any.whl:

Publisher: release.yml on Hari31416/md2pdf

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.6.0 This release

2 files

0.5.3

2 files

0.5.2

2 files

0.5.1

2 files

0.5.0

2 files

0.4.3

2 files

0.4.2

2 files

0.4.1

2 files

0.3.0

2 files

0.2.0

2 files

0.1.4

2 files

0.1.3

2 files

0.1.2

2 files

0.1.1

2 files

0.1.0

2 files

Supported by

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