Convert Markdown to beautifully styled PDFs — and extract the source back out.
MDPDF is a fast, self-contained tool written in Rust that converts Markdown files into styled PDFs using headless Chrome for rendering. It works both as a CLI tool and as a desktop GUI application (powered by Tauri). It ships with 11 built-in visual templates and supports custom CSS, KaTeX math rendering, and PDF metadata embedding. Every PDF it produces carries the original Markdown source and the CSS template embedded inside it, so you can always recover both.
- Features
- Requirements
- Installation
- Usage
- Templates
- Custom Templates
- PDF Metadata
- Configuration
- Options Reference
- How It Works
- Building from Source
- Dependencies
- Dual interface: desktop GUI and full-featured CLI in a single binary
- Converts any Markdown file to a styled, print-ready PDF
- 11 built-in templates across mobile, tablet, widescreen, and A4 print layouts — each available in light and dark variants
- Custom CSS template support for full visual control — live CSS editor in the GUI
- Syntax-highlighted fenced code blocks powered by syntect — theme (dark or light) is chosen automatically from the
prebackground color declared in the active CSS template - KaTeX math rendering built in — no external CDN required
- Markdown extensions: tables, footnotes, and strikethrough
- Optional raw HTML passthrough — keep
<div>,<span>, custom tags, and inline styles intact in the output - Embedded source: the original
.mdfile and the CSS template are embedded inside the output PDF by default — disable with--no-embed-css(CLI) or the Embed CSS template in PDF checkbox (GUI) - Bidirectional workflow: recover the Markdown source and the CSS template from any MDPDF-generated PDF; older PDFs without an embedded CSS are handled gracefully
- Custom PDF metadata (Title, Author, Subject, Keywords, or any key) — GUI offers preset fields
- Adjustable PDF scale factor
- Single-page output mode: measures rendered content and sets the PDF page to fit it exactly — no clipping, no scrolling void
- Manual page break mode: suppress all automatic Chrome page breaks and break only on explicit CSS indicators in the content
- Optional intermediate HTML output for debugging your styles
- Zero-margin PDF output with full background color support
- Configuration persistence: auto-saves settings to platform config directory, plus manual import/export
- Works fully offline — all assets are bundled at compile time
- Google Chrome (or Chromium) must be installed and accessible on your system. MDPDF uses it in headless mode to render the PDF.
- Rust toolchain (only required if building from source)
Download the latest release for your platform from the Releases page and place the binary somewhere on your PATH.
# Example on Linux/macOS
chmod +x mdpdf
mv mdpdf /usr/local/bin/git clone https://github.com/alfoirazabal/mdpdf.git
cd mdpdf/mdpdf
cargo build --releaseThe compiled binary will be at target/release/mdpdf.
MDPDF automatically launches in GUI mode when run without arguments, or in CLI mode when invoked with a subcommand (render or extract).
On Windows, launching the GUI (e.g. by double-clicking the executable) does not spawn a console window.
Simply run mdpdf with no arguments to open the desktop application.
mdpdfThe GUI provides:
- Two-column layout — Left panel for Files, PDF Metadata, and Options; right panel for the live CSS editor
- Template picker — Select from 11 built-in templates; the CSS editor auto-populates with the template's styles for customization
- PDF Metadata editor — Dropdown with common preset fields (Title, Author, Subject, Keywords, Creator, Producer) plus custom key support
- Embed CSS option — "Embed CSS template in PDF" checkbox in the Options panel (checked by default); uncheck to omit the CSS from the embedded files in the output PDF
- Extract tab — Choose an input PDF, specify the output Markdown path, and optionally override the output CSS path (auto-filled as
<markdown_stem>.css); the log shows whether a CSS template was also recovered - Progress indicator — Always-visible bottom bar with real-time rendering/extraction progress and action buttons
- Configuration persistence — Settings auto-save to the platform config directory; Import/Export buttons allow sharing configs as JSON files
Output file paths: if you specify only a filename (without a directory), the output is placed in the same directory as the input file.
mdpdf render <INPUT> --output <OUTPUT> [OPTIONS]Minimal example:
mdpdf render document.md --output document.pdfThis reads document.md, applies the default A4 template, and writes document.pdf. The .pdf extension is added automatically if omitted from the output name.
With a built-in template:
mdpdf render document.md --output document.pdf --template widescreen-darkWith a custom CSS file:
mdpdf render document.md --output document.pdf --custom-template-path ./my-styles.cssWith custom metadata and a scale adjustment:
mdpdf render report.md --output report.pdf \
--template print-a4 \
--cm "Author=Jane Smith" \
--cm "Subject=Q3 Financial Report" \
--scale 0.9Keep the intermediate HTML file (useful for tweaking your template):
mdpdf render document.md --output document.pdf --ghtmlThis produces both document.pdf and document.pdf.html so you can inspect exactly what Chrome is rendering.
Disable CSS template embedding:
mdpdf render document.md --output document.pdf --no-embed-cssBy default, both the Markdown source and the CSS template are embedded inside the output PDF. Pass --no-embed-css to omit the CSS. The Markdown source is always embedded regardless of this flag. PDFs rendered with --no-embed-css will report "No CSS template was embedded in this PDF" when extracted.
Allow raw HTML elements in your Markdown source:
mdpdf render document.md --output document.pdf --allow-htmlBy default, comrak strips any HTML tags embedded in your Markdown — <div>, <span>, inline style attributes, custom elements, and so on. Passing --allow-html lets them through to the rendered output unchanged.
Note: Because of how comrak works internally, this flag also permits unsafe link schemes such as
javascript:anddata:— the two cannot be separated at the library level. Only use--allow-htmlwith Markdown source you control and trust.
Fit the entire document to a single PDF page:
mdpdf render document.md --output document.pdf --one-pageMDPDF reads the @page margin from the active template, measures the rendered content dimensions, and sets the PDF page to exactly fit the content plus its margins — no clipping, no empty space, no multiple pages. Wide tables push the page width out rather than getting cut off. The template's original margin and visual style are preserved unchanged.
--one-page and --scale are fully compatible: scale is applied first by Chrome, then dimensions are measured from the scaled result.
mdpdf render document.md --output document.pdf --one-page --scale 0.8Control page breaks manually:
mdpdf render document.md --output document.pdf --manual-breaksBy default, Chrome breaks pages automatically based on the template's @page size. With --manual-breaks, all automatic page breaks are suppressed. Pages break only where the Markdown source contains an explicit CSS break indicator, for example:
<div style="break-after: page"></div>The template @page size, margins, and all other visual styling are preserved exactly as normal. --allow-html is not required for this to work — the break indicator is just an inline style on an HTML element that can be embedded directly in the Markdown source.
--manual-breaks and --one-page are mutually exclusive. Passing both together exits with an error.
Any PDF produced by MDPDF contains the original Markdown source embedded inside it. PDFs produced with CSS embedding enabled (the default) also carry the CSS template. Use the extract command to recover them.
mdpdf extract <INPUT> --output <OUTPUT> [--css-output <CSS_OUTPUT>]Example:
mdpdf extract document.pdf --output recovered.mdThis writes the embedded Markdown source to recovered.md. The CSS template is simultaneously extracted to recovered.css (the default — same stem as --output, .css extension). The command reports what was extracted:
Extracted Markdown → recovered.md
Extracted CSS template → recovered.css
If the PDF was rendered with --no-embed-css, or was produced by an older version of MDPDF that did not embed the CSS, the Markdown is still extracted and the output notes:
Extracted Markdown → recovered.md
Note: No CSS template was embedded in this PDF.
Override the CSS output path:
mdpdf extract document.pdf --output recovered.md --css-output styles/custom.cssIf the PDF was not created by MDPDF the command exits with an error.
MDPDF ships 11 built-in templates. Pass the template name to --template using the kebab-case identifier below.
| Template name | Description |
|---|---|
mobile-dark |
Dark theme sized for mobile phone screens |
mobile-light |
Light theme sized for mobile phone screens |
tablet-dark |
Dark theme sized for tablet screens |
tablet-light |
Light theme sized for tablet screens |
widescreen-dark |
Dark theme for laptops and desktops |
widescreen-light |
Light theme for laptops and desktops |
widescreen-cut-dark |
Dark widescreen theme with cutout for browser and taskbar chrome |
widescreen-cut-light |
Light widescreen theme with cutout for browser and taskbar chrome |
print-a4-dark |
Dark theme formatted for A4 paper printing |
print-a4-light |
Light theme formatted for A4 paper printing |
print-a4 |
Clean white A4 theme for standard printing (default) |
When no --template flag is provided, print-a4 is used.
If the built-in templates do not fit your needs, you can supply your own CSS file with --custom-template-path (CLI) or edit the CSS directly in the GUI editor.
mdpdf render document.md --output document.pdf --custom-template-path ./custom.cssA good starting point is to export the intermediate HTML with --ghtml, inspect the DOM structure, and write your CSS against it.
When --custom-template-path is set, --template is ignored.
In the GUI, select any built-in template to populate the editor with its CSS, then modify it freely — the editor content is what gets rendered.
The syntax highlighting theme for fenced code blocks is selected automatically from the CSS template. MDPDF reads the background or background-color property of the first pre { } rule and resolves it to an RGB value. The following color formats are supported:
- Hex literals:
#rgb,#rrggbb,#rrggbbaa(alpha channel ignored) - RGB/RGBA functions:
rgb(r, g, b),rgba(r, g, b, a)(alpha ignored) - CSS variable references:
var(--any-name)— resolved one level deep to wherever the variable is defined elsewhere in the CSS
If the resolved background has a perceived luminance below 0.5 (dark), the base16-ocean.dark highlighting theme is applied. Otherwise InspiredGitHub (light) is used. If no color can be resolved, the light theme is the fallback.
The pre element itself is rendered without a forced background color — the CSS template has full control over it.
You can embed metadata into the output PDF using the --cm flag (CLI) or the PDF Metadata section (GUI). Each value must follow the format key=value. The flag can be repeated to set multiple fields.
mdpdf render paper.md --output paper.pdf \
--cm "Title=My Research Paper" \
--cm "Author=Jane Smith" \
--cm "Subject=Computer Science" \
--cm "Keywords=Rust, PDF, CLI"Standard PDF metadata keys are Title, Author, Subject, and Keywords, but any custom key is accepted. Values are stored as UTF-16 strings for full Unicode support.
In the GUI, a dropdown provides quick access to common metadata fields (Title, Author, Subject, Keywords, Creator, Producer) and also allows adding custom keys.
MDPDF also automatically sets the Creator metadata field to MDPDF v<version>.
MDPDF automatically saves your GUI settings (template, CSS, options, and metadata) to the platform-appropriate configuration directory:
| Platform | Config path |
|---|---|
| Linux | ~/.config/mdpdf/config.json |
| macOS | ~/Library/Application Support/mdpdf/config.json |
| Windows | %APPDATA%\mdpdf\config.json |
Settings are restored automatically when the application starts.
You can also manually export and import configurations as JSON files using the Import and Export buttons in the GUI header. This is useful for sharing rendering presets across machines or team members.
| Flag / Argument | Short | Description | Default |
|---|---|---|---|
<INPUT> |
Path to the input Markdown file | (required) | |
--output <OUTPUT> |
-o |
Path for the output PDF file (.pdf extension added if missing) |
(required) |
--template <TEMPLATE> |
-t |
Built-in template name (see Templates) | print-a4 |
--custom-template-path |
-c |
Path to a custom CSS file. Overrides --template when set |
|
--scale <SCALE> |
-s |
PDF scale factor. Must be between 0.1 and 2.0 |
1.0 |
--ghtml |
Keep the intermediate HTML file alongside the PDF output | false |
|
--allow-html |
Pass raw HTML elements in the Markdown source through to the output. Also permits unsafe link schemes (javascript:, data:). Use only with trusted input. |
false |
|
--one-page |
Fit the entire document into a single PDF page by measuring rendered content dimensions. Compatible with --scale. Mutually exclusive with --manual-breaks. |
false |
|
--manual-breaks |
Suppress automatic page breaks. Break only on explicit CSS indicators in the content (e.g. break-after: page). Mutually exclusive with --one-page. |
false |
|
--cm <KEY=VALUE> |
Add a custom metadata field. Repeatable | ||
--no-embed-css |
Omit the CSS template from the PDF's embedded files. The Markdown source is always embedded. | false |
| Flag / Argument | Short | Description | |
|---|---|---|---|
<INPUT> |
Path to an MDPDF-generated PDF file | (required) | |
--output <OUTPUT> |
-o |
Path for the recovered Markdown file | (required) |
--css-output <CSS_OUTPUT> |
Path for the recovered CSS file. Defaults to <output_stem>.css |
MDPDF processes a document through a well-defined pipeline:
- Read — The input
.mdfile is read from disk. - Parse — The Markdown is parsed to HTML using comrak with tables, footnotes, and strikethrough enabled. By default, raw HTML tags and unsafe link schemes embedded in the source are stripped. Passing
--allow-htmldisables that sanitisation and lets them through as-is. Fenced code blocks are syntax-highlighted using syntect. The highlighting theme is chosen automatically by inspecting thebackgroundorbackground-colorproperty of thepre { }block in the active CSS template: if the resolved color has a perceived luminance below 0.5 the dark theme (base16-ocean.dark) is used, otherwise the light theme (InspiredGitHub) is used. The resolver handles hex colors (#rgb,#rrggbb,#rrggbbaa),rgb()/rgba()functions, andvar(--name)references (resolved one level deep to wherever the variable is defined in the CSS). Token foreground colors are written as inline spans; no background color is forced on the<pre>element, so the CSS template has full control over it. - Template — The HTML body is wrapped in a full HTML document that includes the selected CSS template and the KaTeX math rendering library (CSS and JS are bundled into the binary at compile time, so no network access is needed).
- Render — A temporary HTML file is written to disk and opened in headless Chrome via headless_chrome. Chrome prints the page to PDF with zero margins, full background printing, and an auto-generated document outline. If
--one-pageis set, the pipeline first reads the@pagemargin values from the loaded template stylesheet via JavaScript, then measures the rendered content'sscrollWidthandscrollHeight, adds the margins to both dimensions, injects a@pagesize override to suppress page fragmentation, and passes the result aspaper_width/paper_heightto Chrome — producing a single page that fits the content exactly with the template's original spacing intact. If--manual-breaksis set, a style is injected that suppresses all automaticbreak-beforeandbreak-afterbehaviour on every element that does not carry an explicit inline break rule, leaving author-specified page breaks intact while preventing Chrome from inserting its own. - Embed — The original
.mdsource file is embedded into the PDF as an attached file using lopdf. The active CSS template is also embedded by default (pass--no-embed-cssto skip it). Custom metadata fields are written to the PDF's Info dictionary. - Clean up — The temporary HTML file is deleted (unless
--ghtmlwas passed).
The extract command reverses step 5: it reads the PDF's embedded file tree, locates the attached Markdown source and writes it to disk, then attempts to locate the embedded CSS template and writes it alongside (defaulting to <output_stem>.css). If the CSS was not embedded (older PDFs or PDFs rendered with --no-embed-css), the Markdown is still recovered and the output notes that no CSS was found.
Requires Rust 1.85 or later (edition 2024).
git clone https://github.com/alfoirazabal/mdpdf.git
cd mdpdf/mdpdf
cargo build --releaseThe release profile is optimized for minimal binary size: opt-level = "z", LTO enabled, single codegen unit, panic = abort, and symbols stripped.
Run the tests:
cargo test| Crate | Purpose |
|---|---|
comrak |
Markdown parsing and HTML generation |
syntect |
Syntax highlighting for fenced code blocks |
headless_chrome |
Headless Chrome control for HTML-to-PDF rendering |
lopdf |
PDF manipulation, file embedding, and metadata |
clap |
Command-line argument parsing |
tokio |
Async runtime for the Chrome rendering step |
anyhow |
Ergonomic error handling |
url |
File path to file:// URL conversion |
serde_json |
Parsing JS-evaluated content dimensions in one-page mode |
tauri |
Desktop GUI framework |
tauri-plugin-dialog |
Native file/save dialogs for the GUI |
dirs |
Platform-appropriate config directory resolution |
KaTeX (CSS and JS) is bundled directly into the binary at compile time via include_str!.
Alfonso Irazabal Levy
GPLv3. Attribution is appreciated but not required beyond what the license mandates.
See LICENSE for details.