Convert Markdown into beautiful, compilable LaTeX documents — no pandoc required.
md2latex reads CommonMark and GitHub-Flavored Markdown and produces a complete,
standalone .tex document that compiles with pdflatex (or XeLaTeX/LuaLaTeX when
custom fonts are configured). Headings become \section and friends, lists become
enumerate/itemize, GFM tables render with booktabs, fenced code blocks use
listings, and links, images, and footnotes map to their LaTeX equivalents. Raw
HTML is parsed and rendered (tables, CSS borders, inline tags, SVG) rather than
stripped. The output is a self-contained document — preamble through body — that
compiles with packages from a standard TeX Live distribution.
| Markdown | LaTeX |
|---|---|
| Headings (1–6) | \section* … \subparagraph* (configurable numbering) |
| Unordered / ordered lists | itemize / enumerate |
| Task lists | Checked/unchecked boxes via \textsquare / \textifsymbol |
| GFM tables | booktabs with per-side borders, colors, padding, and presets |
| Fenced code blocks | lstlisting with syntax highlighting (20+ languages) |
| Inline code | \texttt |
| Bold / italic / strikethrough | \textbf / \textit / \sout |
| Links & images | \href / \includegraphics |
| Footnotes | \footnote |
| Blockquotes | mdquote environment |
| Thematic breaks | \hrulefill |
| Page breaks | \newpage / \pagebreak |
| HTML blocks & inline tags | Parsed via justhtml — div, p, strong, em, a, img, table, td styling, CSS colors, SVG |
| YAML front matter | Stripped (never rendered) |
| Design tokens | TOML config for fonts, spacing, colors, tables, code blocks, headers, footers |
| Custom fonts | Google Fonts download via md2fonts (50+ families in catalog) |
| SVG images | Converted to PDF (requires svg2pdf-py) |
pip install md-to-latex
md2latex README.md -o README.texTo also compile to PDF:
md2latex README.md -o README.tex --pdfThat's it. pdflatex is auto-detected and runs twice (to resolve page counts).
Python 3.10 or later is required.
From PyPI:
pip install md-to-latexSVG support (optional — converts inline SVG and .svg images to PDF for inclusion):
pip install md-to-latex[svg]Development install (editable, with test and lint tooling):
python3 -m venv .venv && .venv/bin/pip install -e ".[dev]"This installs md2latex and md2fonts console scripts, plus pytest and ruff.
md2latex input.md -o output.texWithout -o, the LaTeX document is written to stdout:
md2latex input.mdRead from stdin via /dev/stdin:
echo '# Hello' | md2latex /dev/stdinmd2latex input.md -o output.tex --pdf # writes output.tex + output.pdf
md2latex input.md --pdf # .tex → stdout, PDF path on stderrPass a TOML config file to customize the document's appearance — margins, fonts, colors, table styling, code blocks, and more:
md2latex input.md -c design.toml -o output.tex --pdfEvery key is optional; anything omitted falls back to the built-in default. A
fully commented template is at config.toml.example.
See Configuration below for details.
md2latex input.md [-c TOML] [-o OUTPUT] [--pdf] [--title TITLE]
| Flag | Description | Default |
|---|---|---|
input_file |
Path to input Markdown file (use /dev/stdin for pipes) |
required |
-o, --output |
Write LaTeX to this file | stdout |
-c, --config |
Path to TOML design-tokens file | built-in defaults |
--pdf |
Compile .tex → PDF with pdflatex (runs twice) |
off |
--title |
Document title (\title{…}) |
input file stem |
--author |
Document author (\author{…}) |
(empty) |
--date |
Document date (\date{…}) |
\today |
Exit codes: 0 on success, 1 on I/O failure or pdflatex compile error
(including when pdflatex is not found). Warnings (raw HTML, unknown language
tags) go to stderr.
Note:
\title,\author, and\dateare set in the preamble for downstream packages that need them (e.g.hyperref). There is no\maketitleblock — documents start directly with the body.
Design tokens live in a TOML file passed with -c / --config. Copy
config.toml.example to design.toml, edit to taste,
and:
md2latex input.md -c design.toml -o output.texEvery key is optional — omitting a section or key falls back to the built-in
default. Setting only [spacing].parskip changes just that one value and leaves
everything else byte-identical to a no-config run.
| Section | Configures |
|---|---|
[document] |
document class (article, report, …) and base font size (10pt, 11pt, 12pt) |
[page] |
page geometry: margin and paper size |
[fonts] |
custom font families and paths (requires XeLaTeX/LuaLaTeX) |
[spacing] |
paragraph indent, paragraph skip, line stretch |
[colors] |
text color and hyperlink color |
[tables] |
borders, colors, padding, presets (plain, striped, colored-header, minimal), column gaps |
[code] |
code block font size, line numbers, frame, background color |
[images] |
default image width |
[footer] |
footer page numbers (on/off, position) |
[headings] |
heading numbering (true → numbered \section, false → \section*) |
A missing or unreadable config, or malformed TOML, prints an error to stderr and
exits 1. Unknown keys are warned and ignored rather than treated as fatal.
md2fonts is the sibling CLI bundled alongside md2latex. It downloads font
families from the public Google Fonts catalog and prints the fontspec block
needed in the [fonts] section of your config. No API key, no extra dependencies
— everything goes over the GitHub API.
# Download Inter and print its fontspec block to stdout
md2fonts add "Inter"
# List all 50+ available families
md2fonts list
# Filter by name
md2fonts list --query monoThe block md2fonts add prints can be pasted directly into a [fonts] TOML
section, or you can just set the family name:
[fonts]
main_font = "Inter"End to end:
md2fonts add "Inter"
md2latex input.md -c design.toml -o output.tex --pdfCustom fonts require XeLaTeX or LuaLaTeX. When
[fonts]keys are set, the preamble loadsfontspecand the engine auto-switches tolualatex.
| Config | Compiler | Notes |
|---|---|---|
No [fonts] keys |
pdflatex |
Default — all packages from TeX Live |
Any [fonts] key set |
lualatex |
Auto-detected; fontspec loaded |
SVG images ([svg] extra) |
pdflatex or lualatex |
Inline SVG → PDF via svg2pdf-py |
The generated document includes:
- A complete preamble with all required
\usepackagedeclarations - Right-aligned page numbers ("1 / total") in the footer via
lastpage - Full-width horizontal rules for thematic breaks
- Block-level
\paragraph/\subparagraphheadings (not inline) - Flush-left paragraphs with no first-line indent
- Indented lists (bullet and numbered)
- Task-list checkboxes using
amssymb
Documents start directly with the body. The \title, \author, and \date
commands are emitted in the preamble for downstream compatibility, but no
\maketitle or title block is rendered.
- No math support:
$is a literal character, escaped as\$. - No smart typography: straight quotes stay straight,
--stays--. - YAML front matter is stripped entirely and never rendered.
- Custom fonts require XeLaTeX/LuaLaTeX —
pdflatexcannot processfontspec.
# Clone and install
git clone https://github.com/mihok/md-to-latex.git
cd md-to-latex
python3 -m venv .venv && .venv/bin/pip install -e ".[dev]"
# Run tests
.venv/bin/pytest
# Lint
.venv/bin/ruff check .pytest runs golden fixture tests (compare output against committed .tex
fixtures) plus compile tests. The compile tests require pdflatex and are
skipped automatically when it's not on PATH.
Bug reports and pull requests are welcome. Before opening a PR:
- Run
pytestand ensure all tests pass (or are skipped due to missing tools). - Run
ruff check .to maintain style consistency. - Add or update golden fixtures if your change affects
.texoutput.
This project is licensed under the MIT License — see LICENSE for details.