Huli's Blog.

在博客中实现 LaTeX 公式支持

Huli
Huli

在搭建本博客时,考虑到后续会有关于ML和CG的内容发布,所以遇到了一个需求:实现数学公式的显示支持。

需求分析

我希望在博客中可以将数学公式以正确的形式显示,为了实现这一目标,我决定在博客中支持LaTeX的输入和渲染。

什么是LaTeX

LaTeX,是一种基于TeX的排版系统,由美国计算机科学家莱斯利·兰伯特在20世纪80年代初期开发,利用这种格式系统的处理,即使用户没有排版和程序设计的知识也可以充分发挥由TeX所提供的强大功能,不必一一亲自去设计或校对,能在几天,甚至几小时内生成很多具有书籍质量的印刷品生成复杂表格和数学公式。—— Wikipedia

详细步骤

其实在这套博客系统中,实现这个操作不是什么难事,因为sanity官方提供了一个名为LaTex input 的插件,对于我们而言,只需安装该插件到项目中并在配置文件中使用即可。

1. 安装插件:

BATCHFILE

npm install --save sanity-plugin-latex-input

2. 使用插件:

先在 sanity.config.ts 中添加以下内容。

sanity.config.ts

TYPESCRIPT

import { latexInput } from "sanity-plugin-latex-input";
/* ... */
export default defineConfig({
/* ... */
plugins: [
/* ... */
latexInput(),
],
});

接着便可以在 Schema 中使用类型名称,这里分别添加了两种组件,一种是公式块,一种是行内公式。

yourschemas.ts

TYPESCRIPT

import { mathIcon } from 'others/MathIcons'
import { mathInlineIcon } from 'others/MathIcons'
ult defineType({
name: 'post',
title: 'Post',
icon: BookIcon,
type: 'document',
fields: [
/* ... */
defineField({
name: 'content',
title: 'Content',
type: 'array',
of: [
{
type: 'block',
title: 'Block',
of: [
{ type: 'latex', icon: mathInlineIcon, title: 'Inline math' },
],
},
{ type: 'latex', icon: mathIcon, title: 'Math block' },
/* ... */
],
}),
/* ... */
],
/* ... */
})

为了LeTeX的icon能正常加载,我们在others目录下新建一个 MathIcons.tsx 文件。

MathIcons.tsx

TYPESCRIPT

import React from 'react'
const mathInlineIcon = () => (
<span>
<span style={{ fontWeight: 'bold' }}></span>b
</span>
)
const mathIcon = () => <span style={{ fontWeight: 'bold' }}></span>
export { mathIcon,mathInlineIcon };

3. 测试

随后,我们运行并打开我们的sanity studio,此时我们便可以在文件编辑器的头部中看到这两种输入模式了。

接下来让我们来试验一下:

可以看到,在预览模式下,公式能够正常显示,但发布页面后,却发现公式显示为空白。此外,控制台还出现了未知块类型的报错信息:

[@portabletext/react] Unknown block type "latex", specify a component for it in the `components.types` prop

这是因为我们当前仅实现了文档编辑器的预览功能,我们还需要编写一个用于在前端渲染 LaTeX 的组件,并在 components.types 属性中为其指定相应的组件。

4.编写渲染组件

在这里我们选择了KaTeX做为渲染工具,我们先需要在项目中安装 KaTeX 及其 React 组件库。

BATCHFILE

npm install katex
npm install react-katex

接着,在components目录下,新建一个LatexBlock.tsx文件,用于封装 LaTeX 公式的渲染逻辑。这里我们使用 InlineMath 组件,因为 BlockMath 组件会导致报错。

LatexBlock.tsx

TYPESCRIPT

import 'katex/dist/katex.min.css'
import React from 'react'
import { InlineMath } from 'react-katex'
interface PortableTextTypeComponentProps {
value: any // PortableText传递的值
}
const LatexBlock: React.FC<PortableTextTypeComponentProps> = ({ value }) => {
const cleanedLaTex = '\\large ' + value.body.replace(
/[\u200B-\u200D\uFEFF\u2028\u2029]/g,
'',
)
if (typeof value.body === 'string') {
// 如果value是字符串,直接渲染
return <InlineMath math={cleanedLaTex} errorColor={'#cc0000'}/>
}
}
export default LatexBlock

随后,在使用 PortableText 组件的地方,添加 LaTeX 的渲染配置,指定 LatexBlock 组件用于处理 latex 类型的块。

yourfile.tsx

TYPESCRIPT

const myPortableTextComponents: Partial<PortableTextReactComponents> = {
types: {
/* ... */
latex: LatexBlock,
},
};

完成上述配置后,重新运行项目。此时,就会发现公式被正确的渲染在页面上了。

G(x)=1(2π)kΣe12(xμ)TΣ1(xμ)\normalsize G(x)=\frac{1}{\sqrt{(2 \pi)^{k}|\Sigma|}} e^{-\frac{1}{2}(x-\mu)^{T} \Sigma^{-1}(x-\mu)}

其中μ\normalsize \mu表示均值,Σ\normalsize \Sigma表示协方差矩阵。

后记

其实不光是LaTeX公式的渲染,类似的方法也可以应用于其他类型的富文本内容,例如代码块。本文中多次出现的代码块正是使用 Sanity 的 codeInput 插件进行预览,然后编写自己的渲染组件。然而,代码块的渲染相较于 LaTeX 公式的渲染会更为复杂一些,因为还涉及到了代码高亮和复制按钮的部分。但总体来讲,这些内容的处理方法大同小异。


More Stories

MTR-Dev Webinar

NTE train JS discussion & sharing

Huli
Huli

为什么搭建个人博客

Huli的第一篇博客

Huli
Huli