npm package: @jxpeng98/markdown-it-typstex
Description: A markdown-it plugin that renders math with Typst, falls back to KaTeX, and hides on failure.
Author: Jiaxin Peng
Latest version: 3.1.0

# markdown-it-typstex

一个基于 `markdown-it` 的数学公式工具包，可以：

- 作为 **Typst + LaTeX 回退** 插件使用：优先 Typst，失败后回退到 KaTeX / MathJax；
- 也可以作为一个精简的 **纯 LaTeX 插件** 使用（仅 KaTeX / MathJax，不依赖 Typst）。

> Typst → KaTeX/MathJax → 隐藏  
> 一层层回退，保证页面在任何情况下都不会被一坨报错信息搞乱。

---

## ✨ 特性（Features）

- ✅ 支持常见数学语法：
  - 行内公式：`$ ... $`
  - 行间公式：`$$ ... $$`
  - fenced 代码块：<code>```math ... ```</code>
- ✅ 优先使用 **Typst** 渲染为 SVG，显示效果精细、可缩放
- ✅ Typst 渲染失败时自动回退到 **KaTeX（LaTeX）** 或 **MathJax v4（SVG）** 渲染（默认使用 KaTeX）
- ✅ LaTeX 引擎解析也失败时，**自动隐藏** 该公式，不破坏页面布局
- ✅ 可选支持在 HTML 块中解析 `$...$` / `$$...$$`
- ✅ 提供 `typstInjection` 选项，可以在每次 Typst 编译时注入全局配置（字体、`#show` 等）
- ✅ 只依赖 `markdown-it`，可用于 VitePress、VuePress 或任何使用 markdown-it 的渲染流程

---

## 📦 安装（Installation）

```bash
# 使用 npm
npm install @jxpeng98/markdown-it-typstex

# 使用 pnpm
pnpm add @jxpeng98/markdown-it-typstex

# 使用 yarn
yarn add @jxpeng98/markdown-it-typstex
```

本包将较重的渲染引擎声明为 peer 依赖，你需要在自己的项目中按需安装：

- 必需：
  - `markdown-it`
- 可选（按你的使用场景选择）：
  - `@myriaddreamin/typst-ts-node-compiler`（Typst 渲染，主要用于 Node/SSR）
  - `katex`（作为 LaTeX 回退引擎时需要）
  - `@mathjax/src` 和 `@mathjax/mathjax-modern-font`（使用 MathJax v4 作为 LaTeX 回退时需要）

如果某个引擎未安装，那么对应的渲染路径会不可用：  
例如未安装 Typst 编译器时，会直接走 LaTeX 回退；未安装 KaTeX/MathJax 时，相应的回退也无法工作。

由于这些是 peerDependencies，本包不会自动为你安装它们，需要你在自己的项目里显式添加到 `dependencies` 或 `devDependencies` 中。例如：

- 使用 Typst + KaTeX：

  ```bash
  npm install @jxpeng98/markdown-it-typstex markdown-it @myriaddreamin/typst-ts-node-compiler katex
  ```

- 使用 Typst + MathJax：

  ```bash
  npm install @jxpeng98/markdown-it-typstex markdown-it @myriaddreamin/typst-ts-node-compiler @mathjax/src @mathjax/mathjax-modern-font
  ```

---

## 🔧 基本用法：Typst + LaTeX 回退（plain markdown-it）

```ts
import MarkdownIt from 'markdown-it'
import typstex from '@jxpeng98/markdown-it-typstex'
import '@jxpeng98/markdown-it-typstex/styles.css'

const md = new MarkdownIt()

md.use(typstex, {
  enableFencedBlocks: true,
  enableMathInlineInHtml: true,
  enableMathBlockInHtml: true,
  // typstInjection: '#set text(font: "New Computer Modern")',
  // katexOptions: { macros: { '\\RR': '\\mathbb{R}' } },
  // latexEngine: 'mathjax', // 默认是 'katex'
})

const html = md.render(`
这是一个行内公式：$L = 1/2 rho v^2 S C_L$。

$$
\\int_a^b f(x)\\,dx
$$

\`\`\`math
L = 1/2 rho v^2 S C_L
\`\`\`
`)
```

---

## 🔧 Typst + 单一回退引擎（更小体积）

如果你希望减少 bundle 体积、避免同时打包 KaTeX 和 MathJax，可以使用专门的 **Typst + KaTeX** 或 **Typst + MathJax** 入口。每个入口只引入一个 LaTeX 引擎。

### 1. Typst + KaTeX 回退

```ts
import MarkdownIt from 'markdown-it'
import typstexKatex from '@jxpeng98/markdown-it-typstex/typst-katex'
import '@jxpeng98/markdown-it-typstex/styles.css'
import 'katex/dist/katex.min.css'

const md = new MarkdownIt()

md.use(typstexKatex, {
  enableFencedBlocks: true,
  enableMathInlineInHtml: true,
  enableMathBlockInHtml: true,
  // typstInjection: '#set text(size: 13pt)',
  // katexOptions: { throwOnError: false },
})
```

该插件：

- 优先使用 Typst 渲染；
- Typst 不可用或失败时回退到 KaTeX；
- **不会** 引入任何 MathJax 相关代码。

### 2. Typst + MathJax 回退

```ts
import MarkdownIt from 'markdown-it'
import typstexMathJax from '@jxpeng98/markdown-it-typstex/typst-mathjax'
import '@jxpeng98/markdown-it-typstex/styles.css'

const md = new MarkdownIt()

md.use(typstexMathJax, {
  enableFencedBlocks: true,
  enableMathInlineInHtml: true,
  enableMathBlockInHtml: true,
  // typstInjection: '#set text(size: 13pt)',
})
```

该插件：

- 优先使用 Typst 渲染；
- Typst 不可用或失败时回退到 **MathJax v4（SVG）**；
- **不会** 引入任何 KaTeX 相关代码。

---

## 📘 在 VitePress 中使用（Usage with VitePress）

> VitePress 内部使用的是 `markdown-it`，集成方式就是在 `markdown.config` 中挂插件。

`.vitepress/config.ts`：

```ts
import { defineConfig } from 'vitepress'
import typstex from '@jxpeng98/markdown-it-typstex'

export default defineConfig({
  markdown: {
    config(md) {
      // 兼容 CJS / ESM 的导出方式
      const plugin = (typstex as any).default ?? typstex

      md.use(plugin, {
        enableFencedBlocks: true,
        enableMathInlineInHtml: true,
        enableMathBlockInHtml: true,
      })
    },
  },
})
```

主题入口中需要引入插件样式。  
如果你使用 KaTeX 作为回退引擎（默认），还需要额外引入 KaTeX 的样式，例如 `.vitepress/theme/index.ts`：

```ts
import DefaultTheme from 'vitepress/theme'
import '@jxpeng98/markdown-it-typstex/styles.css'

// 可选：仅在使用 KaTeX 回退时需要
import 'katex/dist/katex.min.css'

export default {
  ...DefaultTheme,
}
```

---

## 🧩 支持的语法（Supported Syntax）

### 1. 行内公式（inline math）

```md
升力 $L$ 可以通过升力系数 $C_L$ 按如下公式计算。
```

渲染流程：

1. 尝试用 Typst 渲染 `$L$`；
2. Typst 失败 → 使用 KaTeX 渲染 `L`；
3. KaTeX 也失败 → 返回空字符串（该段公式被隐藏）。

---

### 2. 行间公式（display math）

```md
$$
L = 1/2 rho v^2 S C_L
$$
```

渲染为一个居中显示的块级公式，同样走 Typst → KaTeX → 隐藏 的回退链路。

---

### 3. fenced 代码块：```math

```md
```math
L = 1/2 rho v^2 S C_L
```
```

当 `enableFencedBlocks: true` 时，所有 language 为 `math` 的 fenced 块都会被当作数学公式处理，而不是普通代码块。

---

### 4. HTML 中的数学（math in HTML blocks）

如果你的内容中包含原始 HTML 块，并且希望在其中识别 `$...$` / `$$...$$`，可以开启：

```ts
md.use(typstex, {
  enableMathInlineInHtml: true, // 识别 HTML 中的 $...$
  enableMathBlockInHtml: true,  // 识别 HTML 中的 $$...$$
})
```

插件会在 `html_block` token 中拆分文本，将匹配到的部分转换成 `math_inline` / `math_block` 再参与渲染。

---

## ⚙️ 配置项（Options）

插件接受一个 `TypstMathFallbackOptions` 对象：

```ts
md.use(typstex, {
  enableMathBlockInHtml?: boolean
  enableMathInlineInHtml?: boolean
  enableFencedBlocks?: boolean
  typstInjection?: string
  katexOptions?: katex.KatexOptions
  latexEngine?: 'katex' | 'mathjax'
})
```

### `enableMathBlockInHtml`（默认：`false`）

是否在 HTML 块中解析 `$$...$$` 为行间公式。

示例：

```html
<div>
  $$ L = 1/2 rho v^2 S C_L $$
</div>
```

---

### `enableMathInlineInHtml`（默认：`false`）

是否在 HTML 块中解析 `$...$` 为行内公式。

示例：

```html
<p>升力 $L$ 可以通过 $C_L$ 计算</p>
```

---

### `enableFencedBlocks`（默认：`true`）

是否启用 fenced 代码块 <code>```math ... ```</code> 的处理。  
关闭后，<code>```math</code> 只会被当作普通代码块。

---

### `typstInjection`（默认：`undefined`）

每次调用 Typst 渲染时，会将这段 Typst 代码注入到文档顶部。  
可用于设置字体、全局 `#set`、`#show` 等配置，例如：

```ts
md.use(typstex, {
  typstInjection: `
    #set text(font: "New Computer Modern")
    #show math.equation: it => box(it, inset: 0.5em)
  `,
})
```

你也可以利用这一配置项来**整体放大或缩小 Typst 渲染的公式**，使其与 LaTeX 回退引擎（KaTeX / MathJax）或正文字号更匹配，例如：

```ts
md.use(typstex, {
  typstInjection: `
    #set text(size: 13pt)
  `,
})
```

---

### `katexOptions`（默认：`{ throwOnError: true }`）

传递给 `katex.renderToString` 的配置对象。  
可以用于定义 LaTeX 宏、控制错误行为等，例如：

```ts
md.use(typstex, {
  katexOptions: {
    macros: {
      '\\RR': '\\mathbb{R}',
    },
  },
})
```

---

### `latexEngine`（默认：`'katex'`）

用于 Typst 渲染失败时的 LaTeX 回退引擎：

- `'katex'`：使用 KaTeX 进行 LaTeX 回退（原有行为）。
- `'mathjax'`：使用 MathJax v3（SVG 输出）进行 LaTeX 回退。

说明：

- MathJax 通过 `mathjax-full` 在 Node 侧渲染，直接生成独立的 SVG 片段。
- 一般不需要额外的 MathJax CSS，但你仍然可以根据需要为外层容器添加样式。

---

## 🧠 设计思路（Plugin Idea）

这个插件的核心设计思路是：

1. **将 Typst 作为首选数学引擎**
   - 使用 `@myriaddreamin/typst-ts-node-compiler` 在 Node 环境中编译一段包含 `$ <code> $` 的 Typst 文档；
   - 使用 `NodeCompiler.svg(...)` 得到 SVG 输出；
   - 根据 Typst 生成的 `data-width` / `data-height` 将尺寸转换为 `em`，并为行内 / 行间公式设置合适的样式。

2. **Typst 失败时不把错误直接展示给用户**
   - Typst 语法不兼容、拼写错误、编译失败等情况，插件会捕获异常；
   - 而不是把 Typst 的错误文本渲染到 HTML 中。

3. **自动回退到 KaTeX（LaTeX 引擎）**
   - 当 Typst 渲染失败时，用 KaTeX 将同一段字符串作为 LaTeX 数学表达式渲染；
   - 对大部分“正常的 LaTeX 数学语法”来说，这一步可以平滑兜底。

4. **KaTeX 再失败则静默隐藏**
   - 如果 KaTeX 也抛错（语法错误、宏未定义且 `throwOnError: true` 等），插件返回空字符串；
   - 保证页面不会出现任何“渲染错误文本”或奇怪的占位元素。

因此，这个插件特别适用于：

- 希望逐步从 LaTeX 数学迁移到 Typst，但暂时无法一次性重写所有公式；
- 文档中可能混有 Typst 不支持的写法，需要自动回退到 LaTeX；
- 对页面整体观感有较高要求，希望即使公式渲染失败也不要破坏排版。

---

## ⚠️ 限制（Limitations）

- **需要 Node 环境**  
  Typst 渲染依赖 `@myriaddreamin/typst-ts-node-compiler`，目前主要用于 Node 侧（构建阶段 / SSR），无法直接在纯浏览器环境中运行。

- **Typst / LaTeX 语法差异**  
  - 大部分基础数学语法在 Typst 和 LaTeX 之间是兼容的；
  - 但 Typst 专有特性和 LaTeX 宏包的高级特性并不完全一致，可能触发回退逻辑。

- **性能注意**  
  Typst 渲染公式需要一定的计算资源，大量公式时建议结合缓存或合理的构建策略使用。

---

## 📄 License

MIT