Browse Source

fix: fix katex rendering

Hwang In Tak 9 months ago
parent
commit
214546399a
1 changed files with 91 additions and 70 deletions
  1. 91 70
      src/lib/utils/marked/katex-extension.ts

+ 91 - 70
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 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, renderer) {
-	const ruleReg = inlineRule;
+
+
+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',
+		start(src) {
+			return katexStart(src, true);
+		},
 		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
-				};
-			}
+			return katexTokenizer(src, tokens, true);
 		},
-		renderer
 	};
 }