Introduction
MkDocs is an open source Python static site generator focused for Markdown, and Material for MkDocs is a popular theme. This ecosystem is very popular, e.g., arXiv help pages uses it.
It is possible write LaTeX math in *.md
, and let MathJax/KaTeX render formulae in the browser. See Arithmatex - PyMdown Extensions Documentation for more info.
However, once you have written $cal(T): {e_n}_n -> CC$
, it is hard to write $\mathcal{T}: \{e_n\}_n \to \mathbb{C}$
again…
Result
-
Render typst math at the build time.
<!-- Source markdown --> 用压缩映射原理分析关于 $phi$ 的积分方程 $$ phi (x) equiv f(x) + integral K(x,y) phi(y) dif y $$ 的解的存在性,其中 $abs(K)$ 有上界 $M$。
-
Integrate into the MkDocs site: dark theme, site search, permalink, admonitions, back-to-top button, …
-
Coexist with LaTeX math. You can write typst math on some pages and write LaTeX math on other pages.
-
Faster loading time than MathJax.
-
Minimum hack.
The most hacky part is the CSS style to make formulae copiable, but that’s quite common in the front-end world.
However, I just finished it yesterday. Use it at your own risk.
I will iterate and test, and publish it to PyPI under MIT License when appropirate.
How to render typst math in MkDocs
-
Edit
mkdocs.yml
and add the following.markdown_extensions: - pymdownx.arithmatex: generic: true hooks: - hooks/typst_math.py extra_css: - stylesheets/extra.css
-
Save the following to
docs/stylesheets/extra.css
..typst-math > svg { overflow: visible; } div.typst-math { display: flex; justify-content: center; } /* https://v3.tailwindcss.com/docs/screen-readers will interrupt the selection, thus unusable */ .sr-only { position: absolute; left: -9999px; }
-
Save the following to
hooks/typst_math.py
"""Render math with typst ## Usage 1. Install the markdown extensions pymdownx.arithmatex. 2. Add `math: typst` to pages' metadata. ## Requirements - typst """ from __future__ import annotations import html import re from functools import cache from subprocess import CalledProcessError, run from typing import TYPE_CHECKING if TYPE_CHECKING: from mkdocs.config.defaults import MkDocsConfig from mkdocs.structure.files import Files from mkdocs.structure.pages import Page def should_render(page: Page) -> bool: return page.meta.get("math") == "typst" def on_page_markdown( markdown: str, page: Page, config: MkDocsConfig, files: Files ) -> str | None: if should_render(page): assert "pymdownx.arithmatex" in config.markdown_extensions, ( "Missing pymdownx.arithmatex in config.markdown_extensions. " "Setting `math: typst` requires it to parse markdown." ) def on_post_page(output: str, page: Page, config: MkDocsConfig) -> str | None: if should_render(page): output = re.sub( r'<span class="arithmatex">(.+?)</span>', render_inline_math, output ) output = re.sub( r'<div class="arithmatex">(.+?)</div>', render_block_math, output, flags=re.MULTILINE | re.DOTALL, ) return output def render_inline_math(match: re.Match[str]) -> str: src = html.unescape(match.group(1)).removeprefix(R"\(").removesuffix(R"\)").strip() typ = f"${src}$" return ( '<span class="typst-math">' + fix_svg(typst_compile(typ)) + for_screen_reader(typ) + "</span>" ) def render_block_math(match: re.Match[str]) -> str: src = html.unescape(match.group(1)).removeprefix(R"\[").removesuffix(R"\]").strip() typ = f"$ {src} $" return ( '<div class="typst-math">' + fix_svg(typst_compile(typ)) + for_screen_reader(typ) + "</div>" ) def for_screen_reader(typ: str) -> str: return f'<span class="sr-only">{html.escape(typ)}</span>' def fix_svg(svg: bytes) -> str: """Fix the compiled SVG to be embedded in HTML - Strip trailing spaces - Support dark theme """ return re.sub( r' (fill|stroke)="#000000"', r' \1="var(--md-typeset-color)"', svg.decode().strip(), ) @cache def typst_compile( typ: str, *, prelude="#set page(width: auto, height: auto, margin: 0pt, fill: none)\n", format="svg", ) -> bytes: """Compile a Typst document https://github.com/marimo-team/marimo/discussions/2441 """ try: return run( ["typst", "compile", "-", "-", "--format", format], input=(prelude + typ).encode(), check=True, capture_output=True, ).stdout except CalledProcessError as err: raise RuntimeError( f""" Failed to render a typst math: ```typst {typ} ``` {err.stderr.decode()} """.strip() )
-
Start writing.
--- math: typst --- # Heading Inline math: $phi$. Block math: $$ phi (x) equiv f(x) + integral K(x,y) phi(y) dif y $$