|
|
@@ -53,6 +53,9 @@ class KatexRenderer
|
|
|
return $html;
|
|
|
}
|
|
|
|
|
|
+ // 在渲染前修复公式中的实体与 cases 换行问题
|
|
|
+ $html = $this->sanitizeLatexInHtml($html);
|
|
|
+
|
|
|
// 尝试从缓存获取
|
|
|
$cacheKey = $this->getCacheKey($html);
|
|
|
if ($this->cacheEnabled && $cached = cache()->get($cacheKey)) {
|
|
|
@@ -63,6 +66,12 @@ class KatexRenderer
|
|
|
// 调用 Node.js 脚本渲染
|
|
|
$rendered = $this->callNodeScript($html);
|
|
|
|
|
|
+ if (strpos($rendered, 'katex-error') !== false) {
|
|
|
+ Log::warning('KatexRenderer: 发现未解析公式(katex-error)', [
|
|
|
+ 'sample' => $this->extractKatexErrorSnippet($rendered),
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+
|
|
|
// 缓存结果
|
|
|
if ($this->cacheEnabled && $rendered !== $html) {
|
|
|
cache()->put($cacheKey, $rendered, self::CACHE_TTL);
|
|
|
@@ -166,4 +175,63 @@ class KatexRenderer
|
|
|
// 注意:这个方法需要 Redis 或支持通配符删除的缓存驱动
|
|
|
Log::info('KatexRenderer: 缓存清除请求(需要手动清理或使用 Redis)');
|
|
|
}
|
|
|
+
|
|
|
+ private function sanitizeLatexInHtml(string $html): string
|
|
|
+ {
|
|
|
+ $sanitize = function (string $tex): string {
|
|
|
+ $decoded = html_entity_decode($tex, ENT_QUOTES, 'UTF-8');
|
|
|
+ while ($decoded !== $tex) {
|
|
|
+ $tex = $decoded;
|
|
|
+ $decoded = html_entity_decode($tex, ENT_QUOTES, 'UTF-8');
|
|
|
+ }
|
|
|
+
|
|
|
+ return $this->fixCasesLineBreaks($tex);
|
|
|
+ };
|
|
|
+
|
|
|
+ // $$...$$
|
|
|
+ $html = preg_replace_callback('/\$\$([\s\S]*?)\$\$/', function ($m) use ($sanitize) {
|
|
|
+ return '$$' . $sanitize($m[1]) . '$$';
|
|
|
+ }, $html);
|
|
|
+
|
|
|
+ // $...$ (avoid $$)
|
|
|
+ $html = preg_replace_callback('/(?<!\$)\$([^$\n]+?)\$(?!\$)/', function ($m) use ($sanitize) {
|
|
|
+ return '$' . $sanitize($m[1]) . '$';
|
|
|
+ }, $html);
|
|
|
+
|
|
|
+ // \(...\)
|
|
|
+ $html = preg_replace_callback('/\\\\\(([\s\S]*?)\\\\\)/', function ($m) use ($sanitize) {
|
|
|
+ return '\\(' . $sanitize($m[1]) . '\\)';
|
|
|
+ }, $html);
|
|
|
+
|
|
|
+ // \[...\]
|
|
|
+ $html = preg_replace_callback('/\\\\\[([\s\S]*?)\\\\\]/', function ($m) use ($sanitize) {
|
|
|
+ return '\\[' . $sanitize($m[1]) . '\\]';
|
|
|
+ }, $html);
|
|
|
+
|
|
|
+ return $html;
|
|
|
+ }
|
|
|
+
|
|
|
+ private function fixCasesLineBreaks(string $tex): string
|
|
|
+ {
|
|
|
+ return preg_replace_callback('/\\\\begin\{cases\}([\s\S]*?)\\\\end\{cases\}/', function ($m) {
|
|
|
+ $content = $m[1];
|
|
|
+ // 将 cases 中被转成单反斜杠的换行恢复为双反斜杠(仅处理紧跟 +/- 的情况)
|
|
|
+ $content = preg_replace('/(?<!\\\\)\\\\(?=[-+])/', '\\\\\\\\', $content);
|
|
|
+ return '\\begin{cases}' . $content . '\\end{cases}';
|
|
|
+ }, $tex);
|
|
|
+ }
|
|
|
+
|
|
|
+ private function extractKatexErrorSnippet(string $html): array
|
|
|
+ {
|
|
|
+ if (!preg_match('/<span class="katex-error"[^>]*>(.*?)<\/span>/is', $html, $match)) {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+
|
|
|
+ $text = trim(strip_tags($match[1]));
|
|
|
+ $text = preg_replace('/\s+/', ' ', $text);
|
|
|
+
|
|
|
+ return [
|
|
|
+ 'text' => mb_substr($text, 0, 200),
|
|
|
+ ];
|
|
|
+ }
|
|
|
}
|