Skip to content

About

Recursively convert Markdown directory trees into sibling PDFs with polished tables, local math, and optional Mermaid and PlantUML rendering.

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

6 Commits

Folders and files

Repository files navigation

MarkTree PDF

mdtree2pdf.py recursively walks a directory tree and creates a sibling PDF with the same base name for every lower-case *.md file. Conversion is strictly local:

  • Markdown, tables, and PDF layout: pymd2pdf
  • Inline and block math: local Matplotlib Mathtext
  • Mermaid: optional local mmdc
  • PlantUML: optional local plantuml

If a diagram renderer is unavailable or reports an error, the PDF contains a visible source placeholder. Markdown and diagram contents are never sent to Kroki or another service. Diagrams containing http:// or https:// references are also rendered as placeholders so optional local renderers cannot download external content.

Requirements

  • Python 3.11 or newer
  • uv

All Python dependencies are declared directly in the script as PEP 723 metadata. A separate virtual environment or pip install step is not required. The checked-in mdtree2pdf.py.lock records the resolved dependency versions and is used automatically by uv when running the script. Keep it alongside the script when copying MarkTree to another directory.

Usage

# Process the current directory recursively
./mdtree2pdf.py

# Process another directory tree or a single file
./mdtree2pdf.py /path/to/documents
./mdtree2pdf.py report.md

# Preview planned actions
./mdtree2pdf.py --dry-run

# Rebuild every PDF
./mdtree2pdf.py --force

# Choose a page orientation, rebuilding existing PDFs
./mdtree2pdf.py --orientation portrait report.md
./mdtree2pdf.py --orientation landscape report.md
./mdtree2pdf.py --orientation auto /path/to/documents

# Exclude additional files or directories
./mdtree2pdf.py --exclude 'drafts/**' --exclude 'private.md'

Full command-line help:

./mdtree2pdf.py --help

The scanner excludes .git, .venv, venv, node_modules, and __pycache__ by default. Directory symlinks are not followed.

An existing PDF is normally replaced only when its Markdown source or the explicitly supplied configuration file is newer. Specifying --orientation or --force rebuilds existing PDFs. Use --force after changing embedded images, Markdown includes, or switching to an older configuration file. PDFs are written atomically, so an existing target remains intact if rendering fails.

Page orientation

Use --orientation portrait, --orientation landscape, or --orientation auto. The CLI option overrides orientation in the explicitly supplied --config file. If neither specifies an orientation, the default is auto. Existing configuration files that specify portrait or landscape keep that behavior.

Automatic selection chooses one orientation for the entire PDF, independently for each Markdown file. It measures parsed tables after preprocessing, using the renderer's column widths, cell padding, fonts, and text wrapping. Page size, theme settings, and explicit column width hints participate in the measurement. Table-like text inside code fences is ignored.

Portrait is preferred. Landscape is selected when it lets a tall row fit, reduces table width overflow, or reduces the width deficit for unbroken words by at least 25%, with at least one font-size unit of deficit in portrait. For tables with at least three columns and a row at least six text lines high, a reduction in total table height of at least 25% also selects landscape. Every table's tallest row and its repeated header must still fit within the shorter landscape page.

This is a table-readability heuristic, not a guarantee of optimal pagination. It does not select orientation based on images, diagrams, or code blocks, and does not mix portrait and landscape pages in one PDF. Very wide tables or oversized rows may still need a larger page size, different column widths, or source restructuring. Manual orientation always takes precedence.

Each conversion prints a LAYOUT line with the chosen orientation and reason. Preview the decisions for all files, including existing PDFs, without writing PDFs:

./mdtree2pdf.py --orientation auto --dry-run /path/to/documents

Use --orientation auto once to apply automatic layout to PDFs created by older MarkTree versions; otherwise up-to-date PDFs are skipped as usual.

Tables and layout

pymd2pdf splits long tables across pages, repeats the header row, and respects Markdown column alignment:

| Name | Count | Description |
|:-----|------:|:------------|
| Alpha | 12 | Longer text wraps cleanly inside the table cell. |

Column widths can be specified in the header:

| Name {width=25%} | Description {width=75%} |
|:-----------------|:------------------------|
| Alpha | Detailed description |

An md2pdf.toml file can also control the page size and orientation:

[md2pdf]
page_size = "A4"
orientation = "landscape"
toc = false

Set orientation = "auto" to use MarkTree's automatic selection with the configured page size and theme.

Pass the configuration explicitly:

./mdtree2pdf.py --config md2pdf.toml /path/to/documents

For safety and predictable output placement, the script always overrides the input_file, output_file, offline, and emoji settings.

Math

Common Markdown and LaTeX forms are supported:

Inline: $E = mc^2$

$$
\int_0^1 x^2\,dx = \frac{1}{3}
$$

Matplotlib supports common mathematical expressions but is not a complete TeX distribution. Unsupported constructs are shown as labeled source text.

Optional local diagrams

Mermaid fences are rendered automatically when mmdc is available in PATH:

npm install --global @mermaid-js/mermaid-cli

PlantUML fences are rendered automatically when plantuml is available in PATH. On macOS, Homebrew installs PlantUML and its runtime dependencies:

brew install plantuml

Examples:

```mermaid
flowchart LR
  A[Start] --> B[End]
```

```plantuml
@startuml
Alice -> Bob: Hello
@enduml
```

Successfully rendered diagrams are cached by content under ~/.cache/pymd2pdf/local-diagrams.

Development and testing

uv run \
  --with 'pymd2pdf[matplotlib]>=0.6.2,<0.7' \
  --with pytest \
  --with pypdf \
  pytest -q

The tests cover recursive discovery, exclusions, freshness checks, atomic writes, local diagram fallbacks, multi-page tables, offline operation, and manual/automatic orientation including page geometry, themes, width hints, includes, configuration precedence, and dry-run decisions.

Refresh the script's dependencies within the declared compatibility bounds:

uv lock --script mdtree2pdf.py --upgrade

Commit the updated lockfile together with any dependency declaration changes.

License

See LICENSE.

About

Recursively convert Markdown directory trees into sibling PDFs with polished tables, local math, and optional Mermaid and PlantUML rendering.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages