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, enabled by default
  • 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 (markdown-it and mistune parsers). 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}
$$

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.3.tar.gz (28.9 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.3-py3-none-any.whl (35.7 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: md2typst-0.3.3.tar.gz
  • Upload date:
  • Size: 28.9 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.3.tar.gz
Algorithm Hash digest
SHA256 9cb119bdd438f64bae3e416641f69e34e53f7eeee0dea41d378f89e1d16e267e
MD5 66dbda837f186f7a114ebc5c800252d3
BLAKE2b-256 cc527b8b34bee9726af54301710e04109cd2f5ba63cc4afd2bf5f07253435bf4

See more details on using hashes here.

File details

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

File metadata

  • Download URL: md2typst-0.3.3-py3-none-any.whl
  • Upload date:
  • Size: 35.7 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.3-py3-none-any.whl
Algorithm Hash digest
SHA256 268216b999615136e533aaaefb9161d6d21e6aafd7a8fa3496dcffbef8310d85
MD5 53c85777faa1a1f9464edb5040475417
BLAKE2b-256 754cbdab6fcdf06b48d992defb01cd024921fda5af0c3cf4c2594e2113998576

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