Procházet zdrojové kódy

fix(pdf): unify parallel symbol rendering across formula and text

yemeishu před 1 měsícem
rodič
revize
c4efbdcceb

+ 95 - 3
app/Services/ExamPdfExportService.php

@@ -1704,11 +1704,11 @@ class ExamPdfExportService
             'has_katex_local' => $hasKatexLocal,
         ]);
 
-        // 如果既没有 CDN 也没有本地链接,跳过
+        // 如果既没有 CDN 也没有本地链接,仍尝试注入 KaTeX 关系符通用修复
         if (! $hasKatexCdn && ! $hasKatexLocal) {
             Log::warning('ExamPdfExportService: HTML 中没有 KaTeX 资源链接,跳过内联');
 
-            return $html;
+            return $this->applyKatexRelationGlyphFixes($html);
         }
 
         try {
@@ -1808,6 +1808,10 @@ class ExamPdfExportService
                     'after_length' => $afterLength,
                     'size_change' => $afterLength - $beforeLength,
                 ]);
+
+                // 通用修复:统一优化 KaTeX 关系运算符(mrel)字形
+                // 覆盖平行、垂直、等于、不等于等关系符,避免出现“平行符像 ||”的问题
+                $html = $this->applyKatexRelationGlyphFixes($html);
             } else {
                 Log::warning('ExamPdfExportService: KatexRenderer 未初始化,跳过预渲染');
             }
@@ -1823,7 +1827,95 @@ class ExamPdfExportService
             ]);
         }
 
-        return $html;
+        return $this->applyKatexRelationGlyphFixes($html);
+    }
+
+    /**
+     * 应用 KaTeX 关系符通用修复(先标记,再注入样式)
+     */
+    private function applyKatexRelationGlyphFixes(string $html): string
+    {
+        $html = $this->tagKatexParallelRelationSpans($html);
+
+        return $this->injectKatexRelationGlyphStyle($html);
+    }
+
+    /**
+     * 标记 KaTeX 中的平行关系符(∥),便于使用字体无关样式兜底
+     */
+    private function tagKatexParallelRelationSpans(string $html): string
+    {
+        if (strpos($html, '∥') === false || strpos($html, 'mrel') === false) {
+            return $html;
+        }
+
+        $pattern = '/<span(?P<attrs>[^>]*)class=(["\'])(?P<class>[^"\']*\bmrel\b[^"\']*)\2(?P<tail>[^>]*)>\s*∥\s*<\/span>/u';
+        $replaced = preg_replace_callback($pattern, static function (array $matches): string {
+            $attrs = $matches['attrs'] ?? '';
+            $quote = $matches[2] ?? '"';
+            $class = trim($matches['class'] ?? '');
+            $tail = $matches['tail'] ?? '';
+
+            if ($class === '') {
+                $class = 'mrel katex-rel-parallel';
+            } elseif (! preg_match('/\bkatex-rel-parallel\b/', $class)) {
+                $class .= ' katex-rel-parallel';
+            }
+
+            return '<span'.$attrs.'class='.$quote.$class.$quote.$tail.'>∥</span>';
+        }, $html);
+
+        return $replaced ?? $html;
+    }
+
+    /**
+     * 注入 KaTeX 关系运算符统一字形样式(全局、通用)
+     */
+    private function injectKatexRelationGlyphStyle(string $html): string
+    {
+        if (strpos($html, 'class="katex"') === false && strpos($html, 'class="katex-html"') === false) {
+            return $html;
+        }
+
+        if (strpos($html, 'id="katex-relation-glyph-style"') !== false) {
+            return $html;
+        }
+
+        $style = '<style id="katex-relation-glyph-style">'
+            .'.katex .mrel{'
+            .'font-family:"KaTeX_AMS","KaTeX_Main","NotoSerifCJKsc-Regular","NotoSerifCJKsc-Bold","NotoSans-Regular","NotoSans-Bold","NotoSansMonoCJKjp-Regular",serif !important;'
+            .'letter-spacing:.04em;'
+            .'}'
+            .'.katex .mrel .mord{font-family:inherit !important;}'
+            .'.katex .mrel.katex-rel-parallel{'
+            .'display:inline-block;'
+            .'position:relative;'
+            .'min-width:.68em;'
+            .'height:.92em;'
+            .'line-height:.92em;'
+            .'font-size:0 !important;'
+            .'vertical-align:-.06em;'
+            .'}'
+            .'.katex .mrel.katex-rel-parallel::before,.katex .mrel.katex-rel-parallel::after{'
+            .'content:"";'
+            .'position:absolute;'
+            .'top:.06em;'
+            .'bottom:.06em;'
+            .'width:.08em;'
+            .'border-radius:.02em;'
+            .'background:currentColor;'
+            .'transform:skewX(-18deg);'
+            .'transform-origin:center;'
+            .'}'
+            .'.katex .mrel.katex-rel-parallel::before{left:.22em;}'
+            .'.katex .mrel.katex-rel-parallel::after{left:.42em;}'
+            .'</style>';
+
+        if (stripos($html, '</head>') !== false) {
+            return preg_replace('/<\/head>/i', $style.'</head>', $html, 1) ?? ($html.$style);
+        }
+
+        return $html.$style;
     }
 
     /**

+ 13 - 0
app/Services/MathFormulaProcessor.php

@@ -32,6 +32,10 @@ class MathFormulaProcessor
         // 0.5 将自定义 <image> 标签转换为标准 <img> 标签
         $content = self::convertImageTags($content);
 
+        // 0.6 规范化几何平行符:AB||CD -> AB∥CD
+        // 仅处理大写点位字母场景,避免误伤通用竖线表达式
+        $content = self::normalizeParallelSymbol($content);
+
         // 1. 【关键修复】处理公式内的双反斜杠 -> 单反斜杠
         // 数据库存储时 \sqrt 变成 \\sqrt,需要还原
         $content = self::normalizeBackslashesInDelimiters($content);
@@ -82,6 +86,15 @@ class MathFormulaProcessor
         );
     }
 
+    /**
+     * 将几何文本中的 ASCII 平行符替换为数学平行符(∥)
+     * 例如:AB||CD、AB || CD -> AB∥CD
+     */
+    private static function normalizeParallelSymbol(string $content): string
+    {
+        return preg_replace('/(?<!\\\\)([A-Z]{1,4})\s*\|\|\s*([A-Z]{1,4})/', '$1∥$2', $content) ?? $content;
+    }
+
     /**
      * 【新增】将公式定界符内被JSON双重转义的LaTeX命令还原
      * 例如:\\sqrt -> \sqrt, \\frac -> \frac

+ 4 - 0
resources/views/pdf/exam-grading.blade.php

@@ -165,6 +165,10 @@
         .option-long { white-space: normal; word-break: break-word; }
         .option-compact { line-height: inherit; }
         .option p, .option div { margin: 0; display: inline; }
+        .parallel {
+            font-family: "NotoSansMonoCJKjp", monospace;
+            letter-spacing: 1px;
+        }
         .option .katex {
             font-size: 1em !important;
             vertical-align: 0;

+ 4 - 0
resources/views/pdf/exam-paper.blade.php

@@ -165,6 +165,10 @@
             orphans: 3;
             widows: 3;
         }
+        .parallel {
+            font-family: "NotoSansMonoCJKjp", monospace;
+            letter-spacing: 1px;
+        }
         /* 选项容器:不分页 */
         .options {
             display: grid;

+ 5 - 0
resources/views/pdf/partials/answer-detail-styles.blade.php

@@ -4,6 +4,11 @@
     padding: 0 12px;
 }
 
+.parallel {
+            font-family: "NotoSansMonoCJKjp", monospace;
+            letter-spacing: 1px;
+        }
+
 .answer-quick {
     border: 1px solid #d8d8d8;
     border-radius: 4px;