Skip to main content

Markdown to Typst converter with multiple parser support

Project description

md2typst

A robust Markdown to Typst converter in Python with support for multiple Markdown parsers.

Features

  • Multiple parser backends: Choose from markdown-it-py, mistune, or marko at runtime
  • GFM support: Tables, strikethrough, footnotes, and other GitHub Flavored Markdown extensions
  • Math support: $...$ and $$...$$ rendered via mitex (currency-safe: $5 to $10 stays literal)
  • Mermaid diagrams: ```mermaid code blocks rendered via mmdr
  • Auto-imports: Required Typst packages are automatically imported based on content
  • Direct PDF output: md2pdf command converts Markdown to PDF in one step (requires typst CLI)
  • Document classes: article, report, and book presets (like LaTeX), selectable via front matter, CLI, or config
  • Title blocks: Automatic title page/block from front matter (title, author, date, version, publisher)
  • [TOC] support: Table of contents via [TOC] marker
  • Diagram blocks: ```diagram keeps ASCII-art diagrams from breaking across pages
  • Configurable: User config, project config, YAML front matter, and CLI options with clear cascade
  • Extensible: Plugin support for parser-specific extensions
  • Well-tested: Comprehensive test suite with TCK validation against CommonMark

Installation

# Using pip
pip install md2typst

# Using uv
uv add md2typst

Quick Start

Command Line

# Convert a file (writes input.typ by default)
md2typst input.md

# Explicit output path
md2typst input.md -o output.typ

# Output to stdout
md2typst input.md -o -

# Convert from stdin (outputs to stdout)
echo "# Hello **World**" | md2typst

# Use a specific parser
md2typst --parser mistune input.md

# Convert multiple files at once
md2typst *.md
md2pdf *.md

# Convert directly to PDF (requires typst CLI)
md2pdf input.md
md2pdf input.md -o custom-output.pdf

# Use a document class
md2pdf --class report input.md

# List available parsers
md2typst --list-parsers

Python API

from md2typst import convert

# Simple conversion
typst = convert("# Hello **World**")
print(typst)
# Output: = Hello *World*

# With specific parser
typst = convert("~~deleted~~", parser="mistune")
print(typst)
# Output: #strike[deleted]

# With configuration
from md2typst import convert_with_config
from md2typst.config import Config

config = Config(parser="marko", plugins=["gfm"])
typst = convert_with_config("| A | B |\n|---|---|\n| 1 | 2 |", config)

Supported Parsers

Parser CLI Name Description
markdown-it-py markdown-it Default. CommonMark compliant, extensible
mistune mistune Fast, pure Python
marko marko CommonMark compliant, extensible

All parsers have GFM extensions (tables, strikethrough, footnotes) enabled by default.

Markdown to Typst Mapping

Markdown Typst
# Heading = Heading
## Heading 2 == Heading 2
*italic* _italic_
**bold** *bold*
~~strike~~ #strike[strike]
`code` `code`
[text](url) #link("url")[text]
![alt](url) #image("url", alt: "alt")
> quote #block(...)[quote]
$E=mc^2$ #mi("E=mc^2")
$$...\int...$$ #mitex(\...`)`
--- #line(length: 100%)
GFM tables #table(...)
text[^1] / [^1]: note text#footnote[note]
```mermaid #mermaid("...")
```diagram #block(breakable: false)[...]
[TOC] #outline(indent: auto, depth: 4)

Configuration

Configuration is loaded from multiple sources (highest priority first):

  1. CLI arguments (--parser, --plugin, ...)
  2. Front matter in the document
  3. Explicit config file (--config path/to/config.toml)
  4. md2typst.toml in the current or parent directories
  5. [tool.md2typst] section in pyproject.toml
  6. User config at ~/.config/md2typst/config.toml (platform-specific via platformdirs)
  7. Built-in defaults

Styling with [style]

The [style] section customizes Typst's default output. Set your preferred font, language, and page layout once in your user config — applied to every document you convert.

~/.config/md2typst/config.toml:

[style]
# Font as single string or a fallback list
font = ["Libertinus Serif", "New Computer Modern", "Times New Roman"]
font_size = "11pt"
language = "en"
paper = "a4"
margin = "2.5cm"

# Raw Typst code appended after structured fields
preamble = """
#set par(justify: true, first-line-indent: 1em)
#show heading.where(level: 1): it => { it; v(0.5em) }
"""

This generates at the top of every converted document:

#set text(font: ("Libertinus Serif", "New Computer Modern", "Times New Roman"), size: 11pt, lang: "en")
#set page(paper: "a4", margin: 2.5cm)

#set par(justify: true, first-line-indent: 1em)
#show heading.where(level: 1): it => { it; v(0.5em) }

Example Configuration

md2typst.toml (project-level, overrides user config):

parser = "mistune"
plugins = ["strikethrough", "table"]

[parser_options]
html = true

[style]
language = "fr"

pyproject.toml:

[tool.md2typst]
parser = "markdown-it"
plugins = ["gfm"]

Front Matter

Markdown files can include YAML front matter. Any field is exposed as a #let doc-<key> variable in Typst, except reserved keys with special handling:

  • preamble — raw Typst code, concatenated with config style.preamble
  • stylesheet / stylesheets — additional Typst modules to import
  • font, font_size, language, paper, margin — override the corresponding [style] fields at document level
---
title: My Document
author: Jane Doe
language: fr          # overrides config style.language
font: EB Garamond     # overrides config style.font
stylesheet: my-style
preamble: |
  #set par(justify: true)
  #show heading.where(level: 1): it => { it; v(0.5em) }
---

# Hello World

This generates:

#let doc-title = "My Document"
#let doc-author = "Jane Doe"

#import "my-style.typ": *

#set text(font: "EB Garamond", lang: "fr")

#set par(justify: true)
#show heading.where(level: 1): it => { it; v(0.5em) }

= Hello World

The output ordering is: variables, stylesheet imports, package imports, #set directives from [style], preamble, then content.

Math

Dollar-sign math syntax is enabled by default with the markdown-it parser. The mitex package import is added automatically when math is detected.

Inline: $E = mc^2$

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

Currency safety: markdown-it is configured with allow_digits=False, so text like $5 to $10 per month is treated as literal currency, not math. Math expressions still work as long as the opening $ is followed by a non-digit (letter, backslash, etc.).

The mistune parser does not enable math by default (its hardcoded regex would mis-parse currency); opt in with --plugin math if you need it. marko has no built-in math support.

Mermaid Diagrams

Fenced code blocks with language mermaid are converted to native Typst diagrams using the mmdr package. The import is added automatically.

```mermaid
graph LR
    A[Start] --> B{Decision}
    B -->|Yes| C[OK]
    B -->|No| D[End]
```

Diagram Blocks

Use ```diagram for ASCII-art or box-drawing diagrams that must not break across pages:

```diagram
┌──────────┐       ┌──────────┐
│  Client  │──────▶│  Server  │
└──────────┘       └──────────┘
```

The content is wrapped in #block(breakable: false) in the Typst output.

Document Classes

md2typst supports named document classes inspired by LaTeX (article, report, book). Each class provides a complete set of Typst styling rules.

Select a class via front matter, CLI, or config:

---
class: report
title: My Report
---
md2pdf --class book input.md
# In md2typst.toml or ~/.config/md2typst/config.toml
default_class = "article"
Class Title Sections Page breaks TOC
article Inline on first page Numbered, no breaks None Inline
report Centered with rule Numbered (skip h1), breaks before ## Before sections Own page
book Full title page "Chapter N" for h1, numbered Chapters on odd pages Odd page

Classes are defined in [classes.<name>] config sections. The class preamble replaces the base [style] preamble; scalar fields (font, paper, etc.) are inherited unless overridden.

Title Block

When title metadata is present in front matter, a title block is automatically generated. Each document class formats it differently.

---
title: My Document
subtitle: A Technical Overview
author: Jane Doe
date: April 2026
version: "1.0"
publisher: Acme Corp
---

Supported fields: title, subtitle, author, authors, date, version, publisher. Classes define a doc-make-title() function in their preamble to customize the title formatting.

Table of Contents

Place [TOC] on its own line to generate a table of contents:

[TOC]

## Introduction
...

CLI Options

md2typst --help

Options:
  -o, --output FILE      Output file (default: <input>.typ, or stdout for stdin)
  -p, --parser NAME      Parser to use (markdown-it, mistune, marko)
  --class NAME           Document class (article, report, book)
  --plugin NAME          Load parser plugin (can be repeated)
  --stylesheet NAME      Import Typst stylesheet (can be repeated)
  --config FILE          Path to configuration file
  --list-parsers         List available parsers
  --show-config          Show effective configuration
  --debug                Show debug info (config sources, generated Typst)

Development

Setup

git clone https://github.com/user/md2typst.git
cd md2typst
uv sync

Running Tests

# Run all tests (benchmarks skipped by default)
uv run pytest

# Run by category
uv run pytest -m unit          # Unit tests (fast)
uv run pytest -m integration   # Integration tests
uv run pytest -m e2e          # End-to-end tests
uv run pytest -m benchmark    # Benchmark tests

Test Structure

tests/
├── a_unit/           # Unit tests (AST, generator)
├── b_integration/    # Integration tests (parsers, config, TCK)
├── c_e2e/           # End-to-end tests
├── d_benchmark/     # Performance benchmarks
└── fixtures/        # Test fixtures (CommonMark, GFM)

Sample Documents

The samples/ directory contains example documents for each class with a local config and Makefile:

cd samples
make all    # Build article.pdf, report.pdf, book.pdf
make clean  # Remove generated files

Code Quality

# Type checking
uv run mypy src/

# Linting
uv run ruff check src/

# Formatting
uv run ruff format src/

Architecture

Markdown Input → Parser → AST → Generator → Typst Output

The converter uses a parser-agnostic AST (Abstract Syntax Tree) that decouples parsing from code generation. This allows:

  • Swapping parsers without changing the generator
  • Consistent output regardless of parser choice
  • Easy extension with new parsers

License

MIT

Contributing

Contributions are welcome! Please:

  1. Fork the repository
  2. Create a feature branch
  3. Add tests for new functionality
  4. Ensure all tests pass
  5. Submit a pull request

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

md2typst-0.3.4.tar.gz (29.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

md2typst-0.3.4-py3-none-any.whl (36.0 kB view details)

Uploaded Python 3

File details

Details for the file md2typst-0.3.4.tar.gz.

File metadata

  • Download URL: md2typst-0.3.4.tar.gz
  • Upload date:
  • Size: 29.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.1 {"installer":{"name":"uv","version":"0.11.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for md2typst-0.3.4.tar.gz
Algorithm Hash digest
SHA256 ab662fa288278266876dc5f18877978cf774bbb506a04e9c985edf8c36be52f8
MD5 9c4e252b230cd40dc8e9b5d5ee952858
BLAKE2b-256 34b5026efb6bce827cab95f51f10522e942b0a57bdc95f5fdd50eb19fc655c0d

See more details on using hashes here.

File details

Details for the file md2typst-0.3.4-py3-none-any.whl.

File metadata

  • Download URL: md2typst-0.3.4-py3-none-any.whl
  • Upload date:
  • Size: 36.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.1 {"installer":{"name":"uv","version":"0.11.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for md2typst-0.3.4-py3-none-any.whl
Algorithm Hash digest
SHA256 684e09da285eed47ed829ef6effdbb87d62d4fafbd01a8e6dd5e6a319af03258
MD5 c2717c2e7ab85fb7e67706cfd984a9dc
BLAKE2b-256 f83ab447abbec382f3c7f41da9898b003943076ed0de16a987ee3f2bdc2e55d1

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page