#!/usr/bin/env node /** * KaTeX 服务端渲染脚本 * 用法: echo "HTML内容" | node katex-render.js * 或: node katex-render.js < input.html > output.html * * 将 HTML 中的 LaTeX 公式渲染为 KaTeX HTML */ // 尝试多个路径加载 KaTeX let katex; const possiblePaths = [ '/usr/local/lib/node_modules/katex', // npm -g 全局安装路径 '/usr/lib/node_modules/katex', // Alpine 系统路径 'katex', // 本地 node_modules ]; let loadError = null; for (const modulePath of possiblePaths) { try { katex = require(modulePath); // console.error('KaTeX loaded from:', modulePath); break; } catch (e) { loadError = e; // 继续尝试下一个路径 } } if (!katex) { console.error('Error: KaTeX module not found.'); console.error('Tried paths:', possiblePaths.join(', ')); if (loadError) console.error('Last error:', loadError.message); process.exit(1); } // 读取标准输入 let input = ''; process.stdin.setEncoding('utf8'); process.stdin.on('readable', () => { let chunk; while ((chunk = process.stdin.read()) !== null) { input += chunk; } }); process.stdin.on('end', () => { try { const output = renderMathInHtml(input); process.stdout.write(output); } catch (error) { console.error('KaTeX render error:', error.message); process.exit(1); } }); /** * 渲染 HTML 中的所有数学公式 */ function renderMathInHtml(html) { // 定界符配置(按优先级排序) const delimiters = [ { left: '$$', right: '$$', display: true }, { left: '\\[', right: '\\]', display: true }, { left: '\\(', right: '\\)', display: false }, { left: '$', right: '$', display: false }, ]; let result = html; // 按顺序处理每种定界符 for (const delimiter of delimiters) { result = processDelimiter(result, delimiter.left, delimiter.right, delimiter.display); } return result; } /** * 处理特定定界符的公式 */ function processDelimiter(html, left, right, displayMode) { // 转义正则特殊字符 const escapeRegex = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); const leftEscaped = escapeRegex(left); const rightEscaped = escapeRegex(right); // 构建正则表达式 // 对于 $ ... $,需要确保不匹配 $$ ... $$ let pattern; if (left === '$' && right === '$') { // 单个 $ 不能紧跟另一个 $ pattern = new RegExp(`(? { try { // 清理 LaTeX 内容 let cleanLatex = latex.trim(); // 跳过空内容 if (!cleanLatex) { return match; } // 渲染 KaTeX const rendered = katex.renderToString(cleanLatex, { displayMode: displayMode, throwOnError: false, strict: false, trust: true, output: 'html', // 使用 HTML 输出(比 mathml 兼容性更好) }); return rendered; } catch (error) { // 渲染失败时保留原始内容 console.error(`KaTeX error for "${latex.substring(0, 50)}...":`, error.message); return match; } }); }