From 214546399a89a5e3ce50a424b017b2b4aad895f1 Mon Sep 17 00:00:00 2001 From: Hwang In Tak Date: Tue, 24 Sep 2024 16:58:15 +0900 Subject: [PATCH] fix: fix katex rendering --- src/lib/utils/marked/katex-extension.ts | 163 +++++++++++++----------- 1 file changed, 92 insertions(+), 71 deletions(-) diff --git a/src/lib/utils/marked/katex-extension.ts b/src/lib/utils/marked/katex-extension.ts index c90736462..2c5186dac 100644 --- a/src/lib/utils/marked/katex-extension.ts +++ b/src/lib/utils/marked/katex-extension.ts @@ -1,8 +1,8 @@ import katex from 'katex'; const DELIMITER_LIST = [ - { left: '$$', right: '$$', display: false }, { left: '$', right: '$', display: false }, + { left: '$$', right: '$$', display: true }, { left: '\\pu{', right: '}', display: false }, { left: '\\ce{', right: '}', display: false }, { left: '\\(', right: '\\)', display: false }, @@ -28,24 +28,24 @@ function escapeRegex(string) { function generateRegexRules(delimiters) { delimiters.forEach((delimiter) => { - const { left, right } = delimiter; + const { left, right, display } = delimiter; // Ensure regex-safe delimiters const escapedLeft = escapeRegex(left); const escapedRight = escapeRegex(right); - // Inline pattern - Capture group $1, token content, followed by end delimiter and normal punctuation marks. - // Example: $text$ - inlinePatterns.push( - `${escapedLeft}((?:\\\\.|[^\\\\\\n])*?(?:\\\\.|[^\\\\\\n${escapedRight}]))${escapedRight}` - ); - - // Block pattern - Starts and ends with the delimiter on new lines. Example: - // $$\ncontent here\n$$ - blockPatterns.push(`${escapedLeft}\n((?:\\\\[^]|[^\\\\])+?)\n${escapedRight}`); + if (!display) { + inlinePatterns.push( + `${escapedLeft}((?:\\\\.|[^\\\\\\n])*?(?:\\\\.|[^\\\\\\n${escapedRight}]))${escapedRight}` + ); + } else { + blockPatterns.push( + `${escapedLeft}((?:\\\\.|[^\\\\\\n])*?(?:\\\\.|[^\\\\\\n${escapedRight}]))${escapedRight}` + ); + } }); const inlineRule = new RegExp(`^(${inlinePatterns.join('|')})(?=[\\s?!.,:?!。,:]|$)`, 'u'); - const blockRule = new RegExp(`^(${blockPatterns.join('|')})(?:\n|$)`, 'u'); + const blockRule = new RegExp(`^(${blockPatterns.join('|')})(?=[\\s?!.,:?!。,:]|$)`, 'u'); return { inlineRule, blockRule }; } @@ -55,84 +55,105 @@ const { inlineRule, blockRule } = generateRegexRules(DELIMITER_LIST); export default function (options = {}) { return { extensions: [ - inlineKatex(options, createRenderer(options, false)), - blockKatex(options, createRenderer(options, true)) + inlineKatex(options), + blockKatex(options), ] }; } -function createRenderer(options, newlineAfter) { - return (token) => - katex.renderToString(token.text, { ...options, displayMode: token.displayMode }) + - (newlineAfter ? '\n' : ''); +function katexStart(src, displayMode: boolean) { + let ruleReg = displayMode ? blockRule : inlineRule; + + let indexSrc = src; + + while (indexSrc) { + let index = -1; + let startIndex = -1; + let startDelimiter = ''; + let endDelimiter = ''; + for (let delimiter of DELIMITER_LIST) { + if (delimiter.display !== displayMode) { + continue; + } + + startIndex = indexSrc.indexOf(delimiter.left); + if (startIndex === -1) { + continue; + } + + index = startIndex; + startDelimiter = delimiter.left; + endDelimiter = delimiter.right; + } + + if (index === -1) { + return; + } + + const f = index === 0 || indexSrc.charAt(index - 1) === ' '; + if (f) { + const possibleKatex = indexSrc.substring(index); + + if (possibleKatex.match(ruleReg)) { + return index; + } + } + + indexSrc = indexSrc.substring(index + startDelimiter.length).replace(endDelimiter, ''); + } } -function inlineKatex(options, renderer) { - const ruleReg = inlineRule; +function katexTokenizer(src, tokens, displayMode: boolean) { + let ruleReg = displayMode ? blockRule : inlineRule; + let type = displayMode ? 'blockKatex' : 'inlineKatex'; + + const match = src.match(ruleReg); + + if (match) { + const text = match + .slice(2) + .filter((item) => item) + .find((item) => item.trim()); + + if (displayMode) { + console.log("block matched", match[0]); + } else { + console.log("inline matched", match[0]); + } + + return { + type, + raw: match[0], + text: text, + displayMode, + }; + } +} + + + +function inlineKatex(options) { return { name: 'inlineKatex', level: 'inline', start(src) { - let index; - let indexSrc = src; - - while (indexSrc) { - index = indexSrc.indexOf('$'); - if (index === -1) { - return; - } - const f = index === 0 || indexSrc.charAt(index - 1) === ' '; - if (f) { - const possibleKatex = indexSrc.substring(index); - - if (possibleKatex.match(ruleReg)) { - return index; - } - } - - indexSrc = indexSrc.substring(index + 1).replace(/^\$+/, ''); - } + return katexStart(src, false); }, tokenizer(src, tokens) { - const match = src.match(ruleReg); - - if (match) { - const text = match - .slice(2) - .filter((item) => item) - .find((item) => item.trim()); - - return { - type: 'inlineKatex', - raw: match[0], - text: text - }; - } + return katexTokenizer(src, tokens, false); }, - renderer }; } -function blockKatex(options, renderer) { +function blockKatex(options) { return { name: 'blockKatex', level: 'block', - tokenizer(src, tokens) { - const match = src.match(blockRule); - - if (match) { - const text = match - .slice(2) - .filter((item) => item) - .find((item) => item.trim()); - - return { - type: 'blockKatex', - raw: match[0], - text: text - }; - } + start(src) { + return katexStart(src, true); + }, + tokenizer(src, tokens) { + return katexTokenizer(src, tokens, true); }, - renderer }; }