ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

KaTeX 贡献实战指南:为开源数学排版引擎添加符号、函数与宏

KaTeX 贡献实战指南:为开源数学排版引擎添加符号、函数与宏 KaTeX 贡献实战指南为开源数学排版引擎添加符号、函数与宏【免费下载链接】KaTeXFast math typesetting for the web.项目地址: https://gitcode.com/GitHub_Trending/ka/KaTeX导读本文是 KaTeX面向 Web 的快速 TeX 数学排版引擎的贡献者实战指南基于仓库根目录的 CONTRIBUTING.md 展开并深入结合src/源码进行印证。KaTeX 目前仍有许多 LaTeX 符号与函数尚未支持通过阅读本文你将掌握在src/symbols.ts中添加单个符号、在src/functions/中通过defineFunction注册新函数、在src/macros.ts中用defineMacro定义宏的完整流程同时学会使用 Jest 单元测试、截图测试、构建与代码风格检查工具链最终以符合规范的 Pull Request 将改动合入主分支。说明本文提到的所有源码路径均以仓库根目录为基准。原贡献文档写作时引用的src/symbols.js、src/defineFunction.js等文件在当前仓库中已统一迁移为 TypeScript 版本.ts后缀下文一律使用仓库内实际存在的路径。一、从哪里开始先确认“值得贡献”的方向KaTeX 欢迎各种形式的 Pull Request。在动手之前建议先判断自己的改动属于哪一类新增符号symbol大量 LaTeX 单个字符或命令如\neq、\equiv尚未被支持改动量小、风险低是最佳入门选择。新增函数function像\phantom、\bigl这类带参数、带排版语义的命令需要同时提供解析器、HTML 与 MathML 构建逻辑。新增宏macro纯文本替换类的命令例如\hphantom、\ifstar直接在宏表中注册即可。仓库内有两份与支持范围直接相关的文档可用于核对“某个命令是否已被支持”docs/supported.mdKaTeX 已支持的功能列表docs/support_table.md同时列出已支持与不支持的功能对照表。此外static/index.html对应交互式演示页面本地开发时可通过pnpm start启动可以在真实环境中输入目标命令直观判断 KaTeX 是否支持、渲染效果如何。原文档还提示社区 wiki 中有“Examining-TeX”页面介绍如何分析 TeX 命令的排版规则这对理解字符分组group非常有帮助。二、添加单个符号从src/symbols.ts开始KaTeX 的符号表集中在 src/symbols.ts。该文件顶部注释明确说明了符号的三要素属性是否必填含义font必填该符号使用的字体取值为main主字体或amsAMS 字体group必填符号所属的 ParseNode 分组类型如textord、mathord、rel、bin、open、close、punct、inner等replace视情况该符号被替换成的字符例如\phi的replace值为\u03d5主字体中的 phi 字符符号表的最外层映射还按模式mode区分math数学模式与text文本模式同一命令在不同模式下的字体和分组可以不同。2.1 使用defineSymbol注册文件中的注册入口是defineSymbol函数src/symbols.tsexport function defineSymbol( mode: Mode, font: SymbolFont, group: Group, replace: string, name: string, acceptUnicodeChar?: boolean, ) { symbols[mode][name] {font, group, replace}; if (acceptUnicodeChar replace) { symbols[mode][replace] symbols[mode][name]; } }最后一个参数acceptUnicodeChar为true时会同时把replace对应的 Unicode 字符注册为同一符号方便用户直接输入 Unicode 字符而非 LaTeX 命令。典型的注册语句如defineSymbol(math, main, rel, \u2261, \\equiv, true); defineSymbol(math, main, punct, \u002e, \\ldotp);2.2 三步确定新符号的注册参数确定 Unicode 字符把目标命令放到 MathJax 等渲染器中跑一次观察其输出的 Unicode 码点以此作为replace值。确定分组group在 src/symbols.ts 的符号表中寻找同类符号。例如要添加\neq就去找所属的分组若找不到相似参考可以把新符号与不同类型的符号混排观察间距是否符合 TeX 的间距规则来推断其分组关系符、二元运算符、标点等在 TeX 中有不同的自动间距。渲染验证符号可渲染后打开浏览器 JavaScript 控制台确认没有No character metrics for _之类的警告。该警告表示当前字体度量数据中缺少该字符需要重新生成字体与度量文件——相关工具位于 dockers/fonts包含buildFonts.sh与buildMetrics.sh等脚本可参考 dockers/fonts/README.md。三、添加新函数defineFunction的完整生命周期比单个符号更复杂的是带参数、带排版逻辑的命令。这类命令统一放在 src/functions 目录下通过defineFunction注册。原文档特别指出这是 KaTeX 正在推行的“新式”定义方式——把过去分散在 src/functions.ts、src/buildHTML.ts、src/buildMathML.ts 三个文件中的函数名注册、HTML 构建、MathML 构建集中到单个文件内目标是让所有函数最终都迁移到这套系统。3.1defineFunction的规格字段src/defineFunction.ts 中定义了完整的FunctionSpec类型各字段及其默认值如下字段默认值说明type—必填唯一的字符串用于区分解析节点ParseNode也决定handler返回值类型names—必填函数名列表列表中的多个命令共享同一份实现numArgs—必填函数必选参数个数numOptionalArgs0可选参数个数找不到可选参数时以null传给 handlerargTypes无对应每个参数的类型数组长度为numOptionalArgs numArgs可选参数类型在前allowedInArgumentfalse是否展开为单个 token 或花括号包裹的一组 token若可被包裹就能作为\sqrt无可选参数形式或上/下标的参数allowedInTextfalse是否允许在文本模式中使用allowedInMathtrue是否允许在数学模式中使用infix未设置是否为中缀运算符必须显式置trueprimitive未设置是否为 TeX 原语handler—通常必填解析回调接收(context, args, optArgs)返回一个ParseNode构建器则通过可选的htmlBuilder与mathmlBuilder提供分别返回表示 DOM 结构的HtmlDomNode与 MathML 结构的MathDomNode二者不应修改传入的ParseNode。注册时defineFunction会把函数名写入_functions表供 Parser 查找把构建器写入_htmlGroupBuilders与_mathmlGroupBuilders供 HTML/MathML 构建阶段使用。3.2 经典示例一\phantomsrc/functions/phantom.ts 是原文档钦点的入门范例完整展示了“解析 HTML MathML”三段式结构defineFunction({ type: phantom, names: [\\phantom], numArgs: 1, allowedInText: true, handler: ({parser}, args) { const body args[0]; return { type: phantom, mode: parser.mode, body: ordargument(body), }; }, htmlBuilder: (group, options) { const elements html.buildExpression( group.body, options.withPhantom(), false ); return makeFragment(elements); }, mathmlBuilder: (group, options) { const inner mml.buildExpression(group.body, options); return new MathNode(mphantom, inner); }, });值得注意的细节handler中通过ordargument(body)将参数归一化为节点数组若参数是单元素ordgroup则直接取出见 src/defineFunction.ts 中的normalizeArgument/ordargument工具函数HTML 侧用options.withPhantom()渲染“隐形”内容即保留尺寸但不绘制MathML 侧输出mphantom节点同一文件中还定义了\vphantom只保留高度并在文件末尾用defineMacro(\\hphantom, \\smash{\\phantom{#1}})复用了宏机制。3.3 经典示例二多个相关函数共享一次注册src/functions/delimsizing.ts 演示了names列表的用法——把 16 个定界符尺寸命令\bigl、\Bigl、\biggl、\Biggl、\bigr、\bigm、\big等放在同一次defineFunction调用中注册通过context.funcName查表得到各自的尺寸与数学类别const delimiterSizes { \\bigl : {mclass: mopen, size: 1}, \\Bigl : {mclass: mopen, size: 2}, \\biggl: {mclass: mopen, size: 3}, \\Biggl: {mclass: mopen, size: 4}, // ... \bigr/\Bigr/\bigm/\Bigm/\big/\Big/\bigg/\Bigg 等 }; defineFunction({ type: delimsizing, names: [ \\bigl, \\Bigl, \\biggl, \\Biggl, \\bigr, \\Bigr, \\biggr, \\Biggr, \\bigm, \\Bigm, \\biggm, \\Biggm, \\big, \\Big, \\bigg, \\Bigg, ], numArgs: 1, argTypes: [primitive], handler: (context, args) { const delim checkDelimiter(args[0], context); return { type: delimsizing, mode: context.parser.mode, size: delimiterSizes[context.funcName].size, mclass: delimiterSizes[context.funcName].mclass, delim: delim.text, }; }, // htmlBuilder / mathmlBuilder ... });argTypes: [primitive]表示参数按“原语”方式解析checkDelimiter还会对照delimiters集合校验参数是否为合法定界符否则抛出ParseError。这个模式非常适合批量实现“同一语义、不同尺寸/类别”的命令族。四、定义宏defineMacro与“食道”gullet纯文本替换类命令不需要完整的函数实现直接在宏表中注册即可。宏的统一入口是 src/macros.ts通过defineMacro注册并在“gullet”即MacroExpander对应 src/MacroExpander.ts中完成展开。defineMacro既支持纯字符串形式的展开如defineMacro(\\ifstar, \\ifnextchar *{\\firstoftwo{#1}})也支持函数形式的动态展开——回调接收一个MacroContextInterface见 src/defineMacro.ts可调用popToken()、consumeArgs(n)、future()、consumeSpaces()、expandOnce()等接口操作 token 流返回{tokens, numArgs}。src/macros.ts 中现成实现了一批可直接借鉴的“宏工具”例如\noexpand让下一个 token 不再展开语义上等价于\relax\expandafter先展开目标 token 之后的内容再把原 token 放回\firstoftwo/\secondoftwo取两参数中的第一个/第二个\ifnextchar预读下一个非空格字符并据此选择分支\ifstar预读下一个符号是否为*实现星号变体语法。从源码结构看这些宏与 TeX/LaTeX 内核同名原语保持了一致的语义是编写复杂命令组合如带星号变体的新命令时的重要参考。五、本地开发环境启动交互式编辑器原文档给出的本地开发流程为corepack enable # 启用 corepack若尚未启用 pnpm install # 安装依赖 pnpm start # 启动 webpack-dev-serverpnpm start实际执行的是webpack serve --config webpack.dev.js见 package.json 的scripts字段。webpack.dev.js 中硬编码了端口7936并将static/目录作为静态资源根allowedHosts: all允许从任意主机访问——这便于在局域网或容器中调试。启动后访问http://localhost:7936/即可获得一个交互式 TeX 编辑器用于实时验证改动。调试 Jest 测试时也可以把测试用例直接粘贴进该编辑器反复运行其中的 permalink永久链接功能对重复跑同一用例非常实用。六、Jest 单元测试解析器与树构建的正确性保障JavaScript 解析器以及部分 HTML / MathML 树构建逻辑由 Jest 测试覆盖测试代码集中在 test 目录测试文件命名如katex-spec.ts、mathml-spec.ts、errors-spec.ts、dup-spec.ts等见 package.json 中 jest 配置的testMatch。常用命令均来自 package.json 的scripts命令对应脚本用途pnpm test:jestjest运行全部 Jest 测试pnpm test:jest:watchjest --watch监听模式运行pnpm test:jest:updatejest --updateSnapshot更新快照snapshotpnpm test:jest:coveragejest --coverage收集代码覆盖率报告位于coverage/lcov-report/index.html原文档的几条硬性要求值得牢记每次改动后都要跑 Jest 测试即使只是新增一个小符号CI 在提交 Pull Request 时也会自动运行这些测试作为兜底防线只要改动到Parsersrc/Parser.ts就必须补充对应的 Jest 测试部分测试通过**快照测试snapshot testing**验证输出树结构快照更新用pnpm test:jest:update。仓库中的快照样例见 test/snapshots/katex-spec.ts.snap 与 test/snapshots/mathml-spec.ts.snap覆盖率收集范围在 jest 配置中限定为src/**与contrib/**排除了unicodeSymbols与 mhchem 目录。七、截图测试像素级验证最终渲染效果单测验证的是“结构正确”但数学排版最终好不好看需要依赖截图测试。KaTeX 用浏览器对一组预定义的表达式截图并与基准图片逐字节比对。截图工具链封装在 dockers/screenshotter含 README.md 与screenshotter.sh入口脚本被测表达式的清单在 test/screenshotter/ss_data.yaml 中定义基准图片存放于 test/screenshotter/images比对新旧图片时差异是“不同即不同”的字节级比对若图片发生变化必须肉眼检查——要么确实没有可见变化可接受要么变化与你的新增一致需要在 PR 中解释原因与预期否则要查清变化来源并修复如果你新增的功能依赖最终视觉呈现请务必同时添加一条截图测试可以用 dockers/texcmp 工具把 KaTeX 的截图输出与真实 LaTeX 的输出做对比生成“视觉差异图visual diff”附在引入新功能的 Pull Request 中通常很有说服力。原文档还明确截图测试不会在 CI 中自动运行需要贡献者自觉执行——这是与 Jest 测试最大的不同点。凡改动超出“单个符号”规模都应主动跑一遍截图测试。八、跨浏览器与构建8.1 多浏览器验证KaTeX 支持所有主流浏览器Chrome、Safari、Firefox、Opera、Edge 等。由于单机难以覆盖全部浏览器原文档建议条件允许时尽量在尽可能多的浏览器中实测自己的改动。8.2 构建发布产物KaTeX 使用 webpack 构建配置文件为 webpack.config.js。执行pnpm buildpnpm build实际为rimraf dist/ mkdirp dist cp README.md dist rollup -c --failAfterWarnings webpack node update-sri.js package dist/README.md见 package.json即先清空并重建dist/再依次执行 Rollup生成 ESM 与 UMD 产物与 webpack 打包最后更新 SRISubresource Integrity哈希。原文档中有一条“清理 yarn 残留”的历史命令删除.pnp.cjs/.pnp.loader.mjs等文件。需要说明的是当前仓库已通过packageManager: pnpm11.4.0声明切换到 pnpm见 package.json依赖管理统一走corepack pnpm因此该清理步骤通常已不再需要仅在残留旧版 yarn 生成物时才有意义。九、代码风格指南与静态检查KaTeX 对代码风格有明确约定见 CONTRIBUTING.md 的 Style guide 一节规则约定缩进4 个空格行长不超过 80 个字符逗号放在行尾commas last变量声明声明在使用它的最外层作用域命名JavaScript 用 camelCasePython 用 snake_case总原则与周围代码风格保持一致提交前必须通过两轮静态检查pnpm test:lint # ESLintJavaScript/TypeScript stylelint样式表 pnpm test:ts # tsc --noEmit 类型检查test:lint展开为eslint .与stylelint src/styles/katex.scss static/main.css website/static/**/*.css见 package.json。这两项必须全部通过才能提交代码否则会阻塞合并。十、Pull Request 规范原文档对 PR 提出了明确要求这也是最终合入主分支的“入场券”标题与描述遵循 Angular Commit Message Conventions保证提交信息结构统一、可被工具解析尽可能关联原始 issue方便评审者追溯问题上下文新增命令必须同步更新 docs/support_table.md 与 docs/supported.md确保支持范围文档与代码保持一致合入前提交应 squash压缩保持主分支历史整洁大型 PR 应尽量拆分为多个小 PR或至少拆成多个逻辑内聚的提交降低评审负担。此外原文档提醒KaTeX 的贡献者还需要先签署贡献者许可协议CLA且项目采用 MIT 许可证见 LICENSE。十一、一张贡献流程图从符号到合并将上文各环节串起来一次典型的贡献旅程如下在交互式编辑器或支持表中确认目标命令确实缺失单个符号 → 在 src/symbols.ts 用defineSymbol注册函数 → 在 src/functions 新建文件并用defineFunction实现 handler 与两个 builder纯替换 → 在 src/macros.ts 用defineMacro注册打开控制台确认无No character metrics警告必要时借助 dockers/fonts 重新生成字体度量跑pnpm test:jest补充/验证单测改动 Parser 必须加测试改动较大时用 dockers/screenshotter 跑截图测试并用 dockers/texcmp 与 LaTeX 输出对比依次通过pnpm test:lint与pnpm test:ts更新 docs/supported.md 与 docs/support_table.md遵循 Angular Commit 规范提交 PR关联 issue等待 CI 与评审。结语KaTeX 的贡献门槛设计得相当清晰符号、函数、宏三条路径各自对应独立的注册机制defineSymbol/defineFunction/defineMacro配合 Jest 结构测试、截图视觉测试、lint 与类型检查四道质量闸门即使只改动几十行代码也能在合入前获得充分的正确性保障。希望本文能帮助你在为 KaTeX 补齐符号与函数的过程中同时深入理解一个成熟数学排版引擎的“解析—构建—渲染”分层架构。【免费下载链接】KaTeXFast math typesetting for the web.项目地址: https://gitcode.com/GitHub_Trending/ka/KaTeX创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表