Weave markdown files into beautifully formatted PDFs and EPUBs.
mdweaver is a command-line tool that converts your Markdown documentation into professional-looking PDF and EPUB files. It handles syntax highlighting, recursive file discovery, common rendering issues, and sensible default exclusions automatically.
- Multiple Formats: Generate PDF, EPUB, or both simultaneously.
- Recursive Processing: Convert a single file or an entire directory of documentation.
- Default Exclusions: Skips common “noise” directories by default:
.git,.venv/venv,node_modules,__pycache__,.pytest_cache,output
- Syntax Highlighting: Includes Pygments support (Monokai theme) for code blocks.
- Smart Preprocessing: Automatically fixes common Markdown issues like:
- Escaping generic type parameters (e.g.,
Result<T, E>). - Correcting list spacing for proper rendering.
- Escaping generic type parameters (e.g.,
- Customization:
- Add watermarks to PDFs (e.g.,
"DRAFT"). - Add branded page headers (e.g.,
"belderbos.dev · Python · Rust · AI"). - Override the default styles with a custom CSS file.
- Start a new PDF page at each horizontal rule (
---) with--page-break-on-hr. - Set custom document titles and author metadata.
- Exclude additional paths via glob patterns.
- Add watermarks to PDFs (e.g.,
- Clean Typography: Uses optimized CSS for print (A4) and e-reader layouts.
For PDF generation, mdweaver relies on WeasyPrint, which requires system libraries (Pango, Cairo, etc.) to be installed. EPUB generation works without these dependencies.
macOS:
brew install pangoLinux (Debian/Ubuntu):
sudo apt install libpango-1.0-0 libpangoft2-1.0-0See the WeasyPrint documentation for other platforms: https://doc.courtbouillon.org/weasyprint/stable/first_steps.html
This project is managed with uv.
# Install dependencies
uv syncOr using pip:
pip install mdweaverIf import weasyprint fails with a libgobject-2.0-0 / gobject missing error on macOS, install the native deps with Homebrew and expose Homebrew’s lib directory to the dynamic loader:
brew install glib pango cairo gdk-pixbuf libffi
export DYLD_FALLBACK_LIBRARY_PATH="$(brew --prefix)/lib:${DYLD_FALLBACK_LIBRARY_PATH}"
uv run python -c "import weasyprint; print(weasyprint.__version__)"You can add the export DYLD_FALLBACK_LIBRARY_PATH=... line to your shell config (typically ~/.zshrc on macOS; or ~/.bashrc) to make it persistent across sessions.
The basic syntax is mdweaver <input> [options].
Convert a single file:
mdweaver document.mdConvert a directory (recursively), with defaults excluded:
mdweaver ./docsExclude additional paths (repeatable):
mdweaver ./docs --exclude "**/dist/**" --exclude "**/build/**"Generate EPUB with metadata:
mdweaver ./content -f epub -t "My Book" -a "Author Name"Generate PDF with a watermark:
mdweaver ./content -f pdf -w "DRAFT" -o ./buildGenerate PDF with a branded page header:
mdweaver document.md --header "belderbos.dev · Python · Rust · AI"Generate PDF with custom CSS:
mdweaver document.md --css my-theme.cssGenerate both formats:
mdweaver ./content -f bothinput: Markdown file or directory to process.-o, --output: Output directory (default:./output).-f, --format: Output format:pdf,epub, orboth(default:pdf).-t, --title: Document title (default: derived from filename/path).-a, --author: Author name (for EPUB metadata).-w, --watermark: Watermark text to display diagonally across PDF pages.--css: Path to a custom CSS file to use instead of the default styles.--header: Text to display in the page header on every PDF page.--page-break-on-hr: Start a new page at each horizontal rule (---) instead of drawing a line (PDF only).--exclude: Glob pattern to exclude (repeatable), e.g.--exclude "**/dist/**".
When scanning directories recursively, mdweaver excludes these patterns by default:
**/.git/****/.venv/****/venv/****/node_modules/****/__pycache__/****/.pytest_cache/****/output/**
You can add more exclusions using --exclude.
mdweaver/
├── src/mdweaver/
│ ├── __init__.py
│ └── generate_pdf.py # Core logic
├── tests/
│ ├── conftest.py
│ └── test_mdweaver.py
├── pyproject.toml # Dependencies and metadata
└── README.md
uv run pytestNote: You might see a skipped test for PDF generation if the system dependencies (WeasyPrint libs) are not installed.
