Published 2026-09-06 · Markdown tools
md2pdf 2026: Markdown to a Typeset CJK-Ready PDF
Most Markdown-to-PDF pipelines quietly fall apart the moment you throw in Chinese, Japanese or Korean text: line spacing gets too tight, justified CJK opens ugly rivers, a wide table gets shoved onto its own page, and the font you picked on screen is not the font that ends up embedded in the file. On September 5, 2026 a tiny new open-source tool called md2pdf shipped specifically to fix that — and it treats CJK as a first-class citizen rather than an afterthought.
I spent the weekend pulling it apart so I could tell you exactly what it does, why the CJK angle actually matters for writers, and how it slots alongside md2rich in a write-once, publish-anywhere workflow. Here is the honest, verified picture.
Why the browser-engine approach fails on CJK
A lot of Markdown-to-PDF converters are really "print this HTML" tools: they render your Markdown to HTML and push it through a browser engine into a PDF. That is fine for pure Latin text. It falls apart on CJK for three concrete reasons, all confirmed in the project's own write-up:
- Leading is too tight. Browser defaults measure line height for Latin glyphs; CJK ideographs are taller and denser, so paragraphs look cramped.
- Justification opens rivers. Western text-justify logic on CJK punctuation creates ragged vertical gaps that read as streaks down the column.
- The font you see is not the font you get. On macOS the system font PingFang cannot be embedded by any third-party engine, because its glyphs live in Apple's proprietary
hvgltable that no third-party PDF engine reads. Your file opens elsewhere and silently swaps fonts.
md2pdf attacks all three with two rendering roads instead of one.
Two engines, one command
The default is the Typst engine, available on every platform. pandoc converts the Markdown to Typst, which compiles it with a template tuned for mixed-script text. The result is fast, small and deterministic, with every font subset-embedded so the file renders the same on any machine.
The second road is the macOS CoreText engine, switched on with --pingfang. Here pandoc converts the Markdown to HTML, and a small WebKit program renders it with the real system PingFang font, cutting the output into A4 pages at paragraph, list-item and table-row boundaries. This is the only way to get genuine PingFang into a PDF, because Typst and pandoc themselves cannot read hvgl glyphs.
A genuinely simple kickoff
# one Markdown file in, one clean A4 PDF out
md2pdf report.md
# table of contents, or landscape for a wide table
md2pdf report.md --toc
md2pdf report.md --landscape
# English hyphenation, region and a "Contents" heading
md2pdf report.md --lang en
# a serif, aesthetic or system-font body
md2pdf report.md --serif
md2pdf report.md --wenkai
md2pdf report.md --hiragino
md2pdf report.md --pingfangThe output path defaults to the input name with a .pdf extension, and --open hands the finished file to your default viewer. When you give several font options, the last one wins. pandoc's Markdown dialect is used with citations off and task lists on, so tables, footnotes, definition lists and checkbox items all survive.
Font presets you can actually use
The Typst engine reads fonts installed on your system. The following presets are baked in (verified against the project README):
| Preset | CJK font | Latin font | Install |
|---|---|---|---|
| noto (default) | Noto Sans CJK SC | Helvetica Neue | brew install --cask font-noto-sans-cjk-sc |
| hiragino | Hiragino Sans GB | Helvetica Neue | ships with macOS |
| songti | Songti SC | Libertinus Serif | ships with macOS |
| wenkai | LXGW WenKai | Libertinus Serif | brew install --cask font-lxgw-wenkai |
| pingfang | PingFang SC (CoreText engine) | PingFang SC | ships with macOS |
On Linux, install fonts-noto-cjk from your distribution and pass any other family you have with --font "Sarasa UI SC". Two honest caveats from the README: the CoreText engine compiles its renderer from source on first use, so it needs the Xcode Command Line Tools, and because it renders through WebKit it requires a logged-in graphical session — it will not work over plain SSH or in headless CI.
Front matter drives the title block
Your YAML front matter flows straight into the typeset title block, which is the piece most browser-based converters mangle:
---
title: Quarterly Report
subtitle: 2026 Q3
author: Jane Doe
date: 2026-09-05
---
# 客户续约率概览
本季度**续约率**回升至 92%……Environment variables MD2PDF_FONT, MD2PDF_TEMPLATE and MD2PDF_MARGIN_PT change the defaults for fonts, the template and the margin, so you can bake your team's style into a single alias instead of typing flags every time. Custom templates start from the bundled typst.typ, with the CLI passing mainfont, cjkfont, sansfont, cjksans and monofont over pandoc's standard ones.
Example: a Chinese-to-English FAQ document
Here is a working two-source example: I wrote the body once in Markdown with mixed Chinese and English, then produced both a clean document PDF and a rich-text post from the same source.
# FAQ 常见问题
**退款需要多久?**
退款通常在 2–3 个工作日到账。如遇周末会顺延。Contact us for help.
| 套餐 | 价格 | 有效期 |
|------|------|--------|
| Basic | $9 | 30 天 |
| Pro | $29 | 90 天 |Run md2pdf faq.md and the A4 PDF keeps the mixed Chinese/English line spacing comfortable, the table breaks cleanly, and the CJK font is embedded so the file survives email and print. Then convert the same source for a social post or a support portal:
From PDF to published — and the other way around
The beauty of keeping your source in Markdown is that the PDF is just one output. The day you want that same note as a LinkedIn article, an X post or a Notion page, you do not copy-paste the PDF — you run the Markdown through md2rich, the privacy-first (100% client-side, zero upload) converter that turns Markdown into clean rich text for LinkedIn, X, Medium and Notion.
The verdict: md2pdf and md2rich sit at opposite ends of the same writer's loop. md2pdf gives you a pixel-stable, font-embedded A4 PDF for reports, print and archives where CJK is properly set. md2rich gives you rich text that keeps its formatting when you paste into a live platform. Same Markdown file, two polished outputs — a document for the file drawer, a post for the feed.
Quick comparison for writers deciding which tool matches which task:
| Target | Reach for | Why |
|---|---|---|
| LinkedIn / X / Medium / Notion post | md2rich | Keeps headings, bold, lists, links into the platform's rich-text editor |
| Client report, handout, print | md2pdf | Typeset A4, font-embedded, identical on any machine, CJK proper |
| Archive a long doc | md2pdf | Stable snapshot independent of platform markup churn |
| Reuse a post as a report (or reverse) | Both, from one source | Write once in Markdown, render per destination |
How to install md2pdf today
# macOS (binaries first, then the package)
brew install pandoc typst
uv tool install md2pdf-cjk
# or: pipx install md2pdf-cjk
# Linux: pandoc >= 3.1.3 from its releases, typst-cli, and CJK fonts
sudo apt install fonts-noto-cjk
# run it
md2pdf report.mdThe package is md2pdf-cjk on PyPI (v0.1.0 as of this writing, uploaded 2026-09-05, requires Python 3.10+), MIT-licensed, with the source at github.com/openwhale-labs/md2pdf. It needs pandoc 3.1.3 or newer for the Typst engine — Linux distribution packages are often older, so grab the .deb or tarball from pandoc's releases instead. On Windows, winget install JohnMacFarlane.Pandoc and winget install Typst.Typst.
Frequently asked questions
Do I need a macOS to get CJK typesetting?
No. The default Typst engine runs on any platform and handles CJK as long as you have CJK fonts installed (Noto Sans CJK on Linux, or the macOS presets). The PingFang CoreText engine is macOS-only, because it is the only road to embedding Apple's system font; on Windows or Linux you use Noto or another installed CJK family via --font.
Is md2pdf private / does it upload my Markdown?
It runs entirely on your machine: pandoc and Typst compile locally, so your Markdown never leaves it unless you choose to paste it elsewhere. That matches the same privacy-first posture md2rich takes — md2rich converts 100% in the browser with zero upload, which is why it pairs so naturally with a local-first PDF tool.
Which is better, md2pdf or md-to-pdf?
The project's own note says it plainly: if you do not need CJK typesetting, md-to-pdf or mdxport-cli may fit you better. md2pdf's reason to exist is the CJK-first typesetting story — proper leading, justification and embedded fonts for Chinese, Japanese and Korean text. Pick md2pdf when your audience or documents are multilingual.
Does it work over SSH or in CI?
The Typst engine does — it is a pure CLI and needs no display. The CoreText PingFang engine does not, because WebKit needs a logged-in graphical session; the README is explicit that it will not work over plain SSH or headless CI. For automated server PDF generation, stick with the Typst engine.
Can I turn a PDF output back into a LinkedIn post?
Yes — but do not copy from the rendered PDF. Keep the Markdown source, and when you want the platform version run it through md2rich to get clean rich text that pastes with formatting intact. That is the whole write-once workflow: one file, a print document and a live post, no hand-reformatting.
Bottom line
md2pdf is one day old and tiny, but it solves a real gap that has annoyed anyone who has ever tried to produce a professional-looking PDF from Markdown containing CJK text. If your writing is English-only and your output is always a screen, it may be more tool than you need — point to md-to-pdf and keep going. But if you localize documents, work with Chinese, Japanese or Korean colleagues, or ship multilingual reports, md2pdf's pandoc-plus-Typst engine and its macOS PingFang road are worth an afternoon.
And whenever that same Markdown needs to become a LinkedIn article, an X post or a Notion page, Try md2rich — free, client-side, and it keeps your formatting through the paste.
Last verified against github.com/openwhale-labs/md2pdf and pypi.org/project/md2pdf-cjk on 2026-09-06. Launch date, engines, font presets, commands and install steps are taken from the project README and the PyPI index as of that date.