em-dee-pdf

June 5, 2026 · View on GitHub

Rust CLI that converts Markdown files to beautiful PDFs using Typst as the rendering engine.

What it does

em-dee-pdf transforms Markdown documents into styled PDFs. The conversion process follows this architecture: Markdown -> comrak parser -> AST -> Typst transpiler -> Typst renderer -> PDF.

Features

  • 18 built-in themes — from minimal to corporate, with container layouts for structured documents
  • Syntax-highlighted code blocks — language-aware highlighting with monospace fonts across all themes
  • LaTeX math support — inline $E = mc^2$ and display-mode equations with full symbol coverage
  • Tables — with optional column sorting (numeric or string, ascending or descending)
  • Alerts & admonitions — GitHub-style [!NOTE], [!TIP], [!WARNING] callout blocks
  • Table of contents — auto-generated with configurable depth
  • Front matter — YAML metadata for title, author, date
  • Mermaid diagrams — optional rendering via mermaid-cli
  • PDF compression — reduce output size by ~30% with --compress
  • Stdin/stdout piping — composable with other CLI tools
  • Custom themes — pass any .typ file for full control over styling
  • Font auto-resolution — warns when a theme's font is missing and can fetch it from Google Fonts (with your OK)

Quickstart

Installation

Install the latest release (macOS, Apple Silicon or Intel):

curl --proto '=https' --tlsv1.2 -LsSf https://github.com/brendandebeasi/em-dee-pdf/releases/latest/download/em-dee-pdf-installer.sh | sh

Install from source (any platform with Rust):

cargo install --path crates/em-dee-pdf-cli

Or build the binary:

cargo build --release
# Binary at target/release/em-dee-pdf

Usage Examples

# Basic conversion
em-dee-pdf document.md

# Choose a theme
em-dee-pdf document.md --theme coral

# Multiple files
em-dee-pdf chapter1.md chapter2.md

# Pipe from stdin
cat document.md | em-dee-pdf - -o output.pdf

# See the generated Typst source
em-dee-pdf document.md --emit-typst

# Use a container theme with sections
em-dee-pdf document.md --theme cards --sections

# Sort a table before rendering
em-dee-pdf report.md --sort-table "0:2:desc:num"

# Print-friendly output (no background fills)
em-dee-pdf document.md --no-background

# Compress the output PDF
em-dee-pdf document.md --compress

CLI Reference

em-dee-pdf [OPTIONS] <INPUT>...

Arguments:
  <INPUT>...   Input Markdown file(s). Use '-' for stdin.

Options:
  -o, --output <OUTPUT>        Output file path. Defaults to input filename with .pdf extension.
  -t, --theme <THEME>          Theme name or path to custom .typ file [default: slate]
      --toc                    Generate table of contents
      --sections               Wrap H2 sections in visual containers (for container themes)
      --page-size <PAGE_SIZE>  Page size [default: us-letter]
      --emit-typst             Output Typst source instead of PDF
  -c, --config <CONFIG>        Configuration file path (TOML)
  -v, --verbose                Verbose output
  -q, --quiet                  Quiet mode
      --sort-table <SORT_SPEC> Sort table by column. Format: "table_index:column_index:asc|desc[:num|str]"
      --list-tables            List tables in the markdown (useful for finding indices)
      --mermaid                Enable mermaid diagram rendering (requires mermaid-cli)
      --no-background          Remove background fills for print-friendly output
      --compress               Compress the resulting PDF (disables tagging, deflate-compresses streams)
      --download-fonts         Download missing theme fonts from Google Fonts without prompting
      --no-download-fonts      Never download fonts; use only what's installed (alias: --offline)
  -h, --help                   Print help
  -V, --version                Print version

Themes

The project includes 18 built-in themes:

  • Special: corporate, tech, book, coral
  • Container: cards, panels, boxed (use with --sections)
  • Neutral: slate (default), zinc, stone
  • Colors: emerald, teal, sky, indigo, violet, rose, amber, orange

Container themes wrap H2 sections in styled boxes when the --sections flag is used. For custom themes, pass a path to a .typ file via the --theme option.

Slate — Table of Contents
Coral — Typography & Lists
Tech — Code & Tables
Book — Alerts & Math
Cards — Section Containers
Corporate — Code & Data

Generate these samples yourself:

em-dee-pdf examples/showcase.md --theme slate --toc -o examples/showcase-slate.pdf
em-dee-pdf examples/showcase.md --theme coral --toc -o examples/showcase-coral.pdf
em-dee-pdf examples/showcase.md --theme tech --toc -o examples/showcase-tech.pdf
em-dee-pdf examples/showcase.md --theme book --toc -o examples/showcase-book.pdf
em-dee-pdf examples/showcase.md --theme cards --toc --sections -o examples/showcase-cards.pdf
em-dee-pdf examples/showcase.md --theme corporate --toc -o examples/showcase-corporate.pdf

Fonts

Themes request fonts by name (e.g. the default slate theme uses Inter and JetBrains Mono). Typst renders with whatever font files it's given, so if a requested family isn't installed it silently falls back to a default face — which is how a document can come out in a spaced-out monospace font.

em-dee-pdf checks each theme's font stacks against your installed fonts and handles the gaps:

  • Installed → used directly.
  • Missing, on an interactive terminal → you're asked whether to download it from Google Fonts.
  • Missing, non-interactive (CI, pipes) → skipped with a warning telling you to pass --download-fonts.
  • Can't be downloaded (proprietary, or not on Google Fonts) → a warning naming the font and the fallback that will be used instead.

Downloaded fonts are cached under your OS cache dir (~/.cache/em-dee-pdf/fonts on Linux) and reused on later runs.

# Download any missing theme fonts without prompting
em-dee-pdf document.md --download-fonts

# Never touch the network; use only installed fonts (alias: --offline)
em-dee-pdf document.md --no-download-fonts

Set a default in config with download_policy = "prompt" | "always" | "never" under [fonts].

Configuration

Configure default behavior using a TOML file.

[theme]
name = "coral"

[output]
page_size = "us-letter"
toc = false
toc_depth = 3
page_numbers = true
no_background = false
compress = false
section_containers = false

[fonts]
search_paths = []
# default_family = "Arial"
# monospace_family = "Fira Code"

[extensions]
tables = true
task_lists = true
strikethrough = true
footnotes = true
math = true
autolinks = true
superscript = true
description_lists = true
front_matter = true
syntax_highlighting = true
mermaid = false

Cover page

Wrap any Markdown in <!-- cover --> / <!-- /cover --> (each on its own line) to render it as page 1, ahead of the table of contents:

<!-- cover align="top" -->
# Quarterly Report

**Q2 2026**

Prepared by Acme · 3 June 2026
<!-- /cover -->

## First Section
...
  • The cover's headings are kept out of the table of contents.
  • align="…" (optional) positions the content on the page using top/horizon/bottom and left/center/right, in any order — e.g. align="bottom right" or shorthand <!-- cover top -->. Default is vertically centered.
  • Themes can override the md-cover(body, alignment: …) function to style their own cover (rules, logos, background); the alignment is passed through.

Math & LaTeX

em-dee-pdf supports LaTeX math syntax via Typst's math engine. Enable math in your config (math = true, on by default) or just use dollar signs in your Markdown:

Inline math: Wrap expressions in single dollar signs.

The quadratic formula is $x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}$.

Display math: Wrap expressions in double dollar signs for centered, block-level equations.

$$
\int_0^\infty e^{-x^2} dx = \frac{\sqrt{\pi}}{2}
$$

Supported LaTeX features:

  • Fractions (\frac{a}{b}), square roots (\sqrt{x}), superscripts and subscripts
  • Greek letters (\alpha, \beta, \pi, \omega, etc.)
  • Operators and symbols (\pm, \times, \infty, \approx, \leq, \geq)
  • Integrals, summations, and limits with bounds
  • Matrices and aligned equations

Code Blocks

Fenced code blocks with language annotations get syntax highlighting across all themes:

```rust
fn main() {
    println!("Hello, world!");
}
```

All 18 themes include monospace font stacks that fall back gracefully across platforms (JetBrains Mono, Fira Code, SF Mono, Menlo, Courier New). Code blocks use non-justified text for proper character alignment.

Docker

Build the image:

./scripts/docker-build.sh

Run with Docker:

docker run --rm -v "$(pwd):/work" em-dee-pdf:latest input.md -o output.pdf

Or use the wrapper script:

./scripts/em-dee-pdf-docker input.md -o output.pdf

Library Usage

The core conversion logic is available as a Rust library in the em-dee-pdf-core crate.

use em_dee_pdf_core::{Converter, Config};

let config = Config::default();
let converter = Converter::new(config)?;
let pdf_bytes = converter.convert("# Hello World")?;
std::fs::write("output.pdf", pdf_bytes)?;

License

This project is licensed under either the MIT License or the Apache License, Version 2.0.