{"id":51083066,"url":"https://github.com/hari31416/md2pdf","last_synced_at":"2026-06-23T20:01:31.641Z","repository":{"id":364352356,"uuid":"1267507360","full_name":"Hari31416/md2pdf","owner":"Hari31416","description":"Lightweight Markdown-to-PDF conversion in pure Python, built on ReportLab and mistletoe.","archived":false,"fork":false,"pushed_at":"2026-06-20T16:43:59.000Z","size":11667,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-20T18:21:56.680Z","etag":null,"topics":["markdown","markdown-to-pdf","pdf-generation","pure-python","python","typesetting"],"latest_commit_sha":null,"homepage":"https://hari31416.github.io/md2pdf/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Hari31416.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":"ROADMAP.md","authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-12T15:48:03.000Z","updated_at":"2026-06-20T16:51:14.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/Hari31416/md2pdf","commit_stats":null,"previous_names":["hari31416/md2pdf"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/Hari31416/md2pdf","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Hari31416%2Fmd2pdf","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Hari31416%2Fmd2pdf/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Hari31416%2Fmd2pdf/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Hari31416%2Fmd2pdf/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Hari31416","download_url":"https://codeload.github.com/Hari31416/md2pdf/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Hari31416%2Fmd2pdf/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34704748,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-23T02:00:07.161Z","response_time":65,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["markdown","markdown-to-pdf","pdf-generation","pure-python","python","typesetting"],"created_at":"2026-06-23T20:01:30.080Z","updated_at":"2026-06-23T20:01:31.634Z","avatar_url":"https://github.com/Hari31416.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# md2pdf — Markdown to PDF in Pure Python\n\n`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.\n\n[![PyPI version](https://img.shields.io/pypi/v/pymd2pdf)](https://pypi.org/project/pymd2pdf/)\n[![Downloads](https://pepy.tech/badge/pymd2pdf)](https://pepy.tech/projects/pymd2pdf)\n[![Python](https://img.shields.io/pypi/pyversions/pymd2pdf)](https://pypi.org/project/pymd2pdf/)\n[![License](https://img.shields.io/pypi/l/pymd2pdf)](https://github.com/Hari31416/md2pdf/blob/main/LICENSE)\n[![CI](https://github.com/Hari31416/md2pdf/actions/workflows/ci.yml/badge.svg)](https://github.com/Hari31416/md2pdf/actions/workflows/ci.yml)\n[![codecov](https://codecov.io/gh/Hari31416/md2pdf/graph/badge.svg)](https://codecov.io/gh/Hari31416/md2pdf)\n\n---\n\n## Why md2pdf?\n\nMost 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:\n\n| Tool                 | Requires Pandoc | Requires Chrome | Diagrams \u0026 Math                                            | Pure Python |\n| :------------------- | :-------------- | :-------------- | :--------------------------------------------------------- | :---------- |\n| Pandoc-based         | ✅ Yes           | ❌ No            | ⚠️ Extra packages (e.g. mermaid-filter, LaTeX distribution) | ❌ No        |\n| Puppeteer/Playwright | ❌ No            | ✅ Yes           | ⚠️ Depends on setup                                         | ❌ No        |\n| **md2pdf**           | ❌ **No**        | ❌ **No**        | ✅ **Built-in (via Kroki and matplotlib)**                  | ✅ **Yes**   |\n\nBeyond zero system dependencies, you also get:\n- **Diagrams \u0026 Math** — Mermaid and LaTeX via Kroki (with SHA-256 disk caching); matplotlib used for offline LaTeX rendering when configured\n- **PDF bookmarks** — every heading clickable in your PDF viewer's navigation panel\n- **Multi-page tables** — split cleanly across pages with repeated headers\n- **Colour emoji** — Twemoji-powered PNG emoji in your PDFs\n- **Extensible plugins** — custom handlers, themes, and preprocessors\n\n---\n\n## Performance\n\nAlthough 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.\n\n### Benchmarks (macOS-arm64, Apple Silicon)\n\nTo evaluate compilation speeds, we compared `md2pdf` against **Pandoc** (using various PDF engines) and **Playwright (Chromium)** across documents of different sizes:\n* **Cold Start**: Spawns a clean process to capture initialization costs (like browser startup for Playwright or module import overhead for `md2pdf`).\n* **Warm Start (Median)**: Measures rendering throughput by averaging subsequent runs in a warm environment.\n\n#### Warm Start Rendering Speed (Median)\n![Warm Start Performance](https://raw.githubusercontent.com/Hari31416/md2pdf/main/scripts/benchmark_outputs/benchmark_chart_warm.png)\n\n#### Cold Start Rendering Speed\n![Cold Start Performance](https://raw.githubusercontent.com/Hari31416/md2pdf/main/scripts/benchmark_outputs/benchmark_chart_cold.png)\n\n#### Summary Metrics (seconds, lower is better)\n\n| Document             | Engine                | Cold Start (s) | Warm Median (s) | Status  |\n| :------------------- | :-------------------- | :------------: | :-------------: | :------ |\n| **`simple.md`**      | **md2pdf (native)**   |   **0.236s**   |   **0.019s**    | Success |\n| *(Short document)*   | Playwright (Chromium) |     0.476s     |     0.039s      | Success |\n|                      | Pandoc (weasyprint)   |     0.624s     |     0.342s      | Success |\n|                      | Pandoc (pdflatex)     |     1.001s     |     0.737s      | Success |\n|                      | Pandoc (xelatex)      |     1.522s     |     1.428s      | Success |\n| **`medium.md`**      | Playwright (Chromium) |     0.388s     |   **0.043s**    | Success |\n| *(With math/code)*   | **md2pdf (native)**   |   **0.699s**   |   **0.065s**    | Success |\n|                      | Pandoc (weasyprint)   |     0.471s     |     0.462s      | Success |\n|                      | Pandoc (pdflatex)     |     0.846s     |     0.836s      | Success |\n|                      | Pandoc (xelatex)      |     1.502s     |     1.501s      | Success |\n| **`large.md`**       | Playwright (Chromium) |     0.395s     |   **0.047s**    | Success |\n| *(Multi-page guide)* | **md2pdf (native)**   |   **0.297s**   |   **0.070s**    | Success |\n|                      | Pandoc (weasyprint)   |     0.529s     |     0.518s      | Success |\n|                      | Pandoc (pdflatex)     |     0.817s     |     0.831s      | Success |\n|                      | Pandoc (xelatex)      |     1.498s     |     1.513s      | Success |\n\n*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.*\n\nTo run these benchmarks on your local system:\n```bash\nmake benchmark\n```\n\n---\n\n## Installation\n\nUsing `uv` (recommended):\n\n```bash\nuv tool install pymd2pdf\n```\n\nUsing `pip`:\n\n```bash\npip install pymd2pdf\n```\n\n**Heavy math documents** (physics, engineering)? Install the optional matplotlib backend for faster offline LaTeX rendering:\n\n```bash\npip install \"pymd2pdf[matplotlib]\"\n```\n\nor\n\n```bash\nuv tool install \"pymd2pdf[matplotlib]\"\n```\n\n\u003e **Note:** The PyPI package name is `pymd2pdf`, but the CLI command and Python import are both `md2pdf`.\n\n---\n\n## Quick Start\n\n```bash\n# Convert a Markdown file to PDF\nmd2pdf input.md -o output.pdf\n\n# Run validation checks without producing a PDF\nmd2pdf input.md --validate-only\n\n# Work offline (diagrams show source code placeholder instead)\nmd2pdf input.md -o output.pdf --offline\n```\n\nThat's it. For most use cases, the first command is all you need.\n\n---\n\n## Key Features\n\n- **Standard Elements**: Headings (H1–H6), paragraphs, lists, blockquotes, horizontal rules, and hyperlinks.\n- **Multi-page Tables**: Tables split cleanly across page boundaries with repeated column headers on each page.\n- **Diagrams \u0026 Math**: Mermaid diagrams and LaTeX math blocks rendered via the Kroki API, with transparent margin cropping, offline fallbacks, and SHA-256 disk caching.\n- **PDF Bookmarks \u0026 Outline Panel**: Every heading (H1–H6) becomes a clickable, correctly nested entry in your PDF viewer's navigation/bookmarks panel.\n- **Colour Emoji**: Twemoji-powered PNG emoji rendering in PDFs, with graceful network fallbacks.\n- **Admonitions \u0026 Callouts**: GitHub-style alerts (`\u003e [!NOTE]`, `\u003e [!WARNING]`) and MkDocs/Obsidian fenced containers (`:::note`).\n- **Broad Unicode Support**: Bundled DejaVu Sans fonts cover Latin Extended, Greek, Cyrillic, math operators, arrows, and box-drawing — no system font dependency needed.\n- **Custom Fonts \u0026 Themes**: Supply your own TTF fonts and TOML-based theme overrides.\n- **Extensible Plugin System**: Load custom element handlers, preprocessors, post-processors, and stylesheet/theme layers.\n- **Typesetting Safeguards**: Orphaned heading prevention, ghost page elimination, and widow/orphan protection.\n- **Pre-render Validation**: Catches nested tables, empty diagrams, and unsupported elements before committing to a render.\n\n---\n\n## CLI Options\n\n| Flag | Shortcut | Description |\n| :--- | :------- | :---------- |\n| `--output` | `-o` | Output PDF path (default: `\u003cinput\u003e.pdf`). |\n| `--config` | `-c` | Path to a custom `md2pdf.toml` config file. |\n| `--theme` | `-t` | Theme name to apply (default: `default`). |\n| `--offline` | | Skip Kroki API calls; use source code placeholders instead. |\n| `--validate-only` | | Run validation and exit without producing a PDF. |\n| `--verbose` | `-v` | Output debug-level logging to `stderr`. |\n| `--toc` | | Prepend a dynamically generated Table of Contents page. |\n| `--header` | | Running header template (supports `{title}` and `{section}`). |\n| `--header-on-first-page` | | Render the running header on the first page too. |\n| `--min-image-scale` | | Minimum image scale before moving to a new page (default: `0.8`). |\n| `--emoji` / `--no-emoji` | | Enable or disable Twemoji colour emoji (default: enabled). |\n| `--progress` / `--no-progress` | | Show stage-level progress output on `stderr` (default: enabled). |\n\n---\n\n## Python API\n\n```python\nfrom md2pdf import convert, Config, Pipeline\n\n# One-liner conversion\nconvert(\"input.md\", \"output.pdf\")\n\n# Advanced: custom config and pipeline\nconfig = Config(\n    offline=False,\n    cache_dir=\".md2pdf_cache\",\n    output_file=\"my_document.pdf\"\n)\npipeline = Pipeline(config)\n\n# Validate before rendering\nissues = pipeline.validate(\"# Hello World\")\nfor issue in issues:\n    print(f\"[{issue.severity}] {issue.code}: {issue.message}\")\n\n# Render\npipeline.run(raw_md=\"# Document Title\\n\\nSome body text.\")\n```\n\n---\n\n## Configuration\n\n`md2pdf` auto-discovers a `md2pdf.toml` config file in these locations (in order):\n\n1. `./md2pdf.toml` (project-local)\n2. `~/.config/md2pdf/md2pdf.toml`\n3. `~/.md2pdf.toml`\n\nYou can also pass one explicitly with `--config`. See the annotated [`md2pdf.toml.example`](md2pdf.toml.example) for all available options.\n\n---\n\n## Documentation \u0026 Examples\n\n| Resource                                     | Description                                        |\n| :------------------------------------------- | :------------------------------------------------- |\n| [User Guide](docs/user-guide.md)             | Comprehensive features reference                   |\n| [Themes \u0026 Styling](docs/themes.md)           | Custom themes, fonts, and stylesheet overrides     |\n| [Plugin Authoring](docs/plugin-authoring.md) | Writing custom handlers and preprocessors          |\n| [User Manual (PDF)](docs/user_manual.pdf)    | Consolidated print-ready manual                    |\n| [Examples](examples/)                        | Four production-grade templates with rendered PDFs |\n\n### Example Templates\n\n| Example                                           | Showcases                                                |\n| :------------------------------------------------ | :------------------------------------------------------- |\n| [`academic_paper/`](examples/academic_paper/)     | LaTeX math, citations, Mermaid flowcharts                |\n| [`business_invoice/`](examples/business_invoice/) | Tables, custom Corporate Blue theme                      |\n| [`project_roadmap/`](examples/project_roadmap/)   | Task lists, admonitions, Gantt charts, code highlighting |\n| [`simple_cv/`](examples/simple_cv/)               | Resume layout, structural tables, clean margins          |\n\n---\n\n## Tech Stack\n\n| Component                        | Library           | Description                                                       |\n| :------------------------------- | :---------------- | :---------------------------------------------------------------- |\n| **Core Language**                | Python \u003e= 3.11    | Strict type-hinting throughout                                    |\n| **PDF Generation**               | ReportLab \u003e= 4.0  | Low-level document layout engine                                  |\n| **Markdown Parsing**             | mistletoe \u003e= 1.3  | Fast, extensible Markdown AST parser                              |\n| **HTTP Requests**                | requests \u003e= 2.31  | Kroki API communication                                           |\n| **CLI Framework**                | typer \u003e= 0.12     | CLI builder with validation                                       |\n| **Image Processing**             | Pillow \u003e= 10.0    | Auto-cropping and dimension detection                             |\n| **LaTeX Rendering** *(optional)* | matplotlib \u003e= 3.8 | Offline LaTeX math rendering (`pip install \"pymd2pdf[matplotlib]\"`) |\n\n---\n\n## Development Setup\n\n```bash\n# Clone the repository\ngit clone https://github.com/Hari31416/md2pdf.git\ncd md2pdf\n\n# Create virtual environment and install all dependencies (including dev)\nuv sync\nsource .venv/bin/activate\n\n# Install pre-commit hooks\nuv run pre-commit install\n\n# Run the test suite\nuv run pytest\n```\n\nTo rebuild all documentation PDFs and example renders:\n\n```bash\nmake docs\n```\n\n---\n\n## Architecture Overview\n\n`md2pdf` runs as a four-stage pipeline: preprocessing → parsing/validation → element rendering → layout composition.\n\n```mermaid\ngraph TD\n    Input[Markdown File] --\u003e Pre[Preprocessors]\n    Pre --\u003e Parser[MarkdownParser]\n    Parser --\u003e Tokens[Token Stream]\n    Tokens --\u003e Val[DocumentValidator]\n    Tokens --\u003e Reg[Plugin/Handler Registry]\n    Reg --\u003e Handlers[Element Handlers]\n    Handlers --\u003e Kroki[Kroki API / Cache]\n    Handlers --\u003e Flowables[ReportLab Flowables]\n    Flowables --\u003e Layout[LayoutComposer]\n    Layout --\u003e Post[Postprocessors]\n    Post --\u003e Renderer[ReportLab PDF Engine]\n    Renderer --\u003e Output[Output PDF]\n```\n\nFor detailed sequence-level logic, see the [User Guide](docs/user-guide.md).\n\n---\n\n## Project Structure\n\n```txt\nmd2pdf/\n├── docs/                   # Documentation suite (Markdown \u0026 consolidated PDF)\n├── examples/               # Production-grade example templates with rendered PDFs\n├── md2pdf/                 # Core source package\n│   ├── assets/             # Kroki client, caching, and fallback elements\n│   ├── core/               # Engine pipeline, parser, validator, layout, registry\n│   ├── handlers/           # Element-specific flowable generators\n│   ├── styles/             # Default stylesheet and theme configs\n│   └── cli.py              # CLI entry point\n├── scripts/                # Helper scripts (e.g. build_docs.py)\n├── tests/                  # Automated test suite\n├── md2pdf.toml.example     # Annotated configuration reference\n└── pyproject.toml          # Build system and dependency declaration\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhari31416%2Fmd2pdf","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhari31416%2Fmd2pdf","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhari31416%2Fmd2pdf/lists"}