315 lines
8.6 KiB
Markdown
315 lines
8.6 KiB
Markdown
|
|
# LaTeX 字符渲染问题诊断与解决方案
|
|||
|
|
|
|||
|
|
## 问题描述
|
|||
|
|
|
|||
|
|
识别完成后,某些 LaTeX 字符(如 `\lambda`、`\vdots`)没有被成功渲染。
|
|||
|
|
|
|||
|
|
## 问题诊断
|
|||
|
|
|
|||
|
|
### 1. LaTeX 语法检查 ✅
|
|||
|
|
|
|||
|
|
`\lambda` 和 `\vdots` 都是标准的 LaTeX 命令,语法完全正确:
|
|||
|
|
- `\lambda` - 希腊字母 λ (Unicode: U+03BB)
|
|||
|
|
- `\vdots` - 垂直省略号 ⋮ (Unicode: U+22EE)
|
|||
|
|
|
|||
|
|
### 2. 后处理管道分析 ✅
|
|||
|
|
|
|||
|
|
经过代码审查,OCR 后处理管道(`app/services/ocr_service.py`)**不会**破坏这些字符:
|
|||
|
|
|
|||
|
|
#### Stage 0: 数字错误修复
|
|||
|
|
```python
|
|||
|
|
_fix_ocr_number_errors(expr)
|
|||
|
|
```
|
|||
|
|
- **影响范围**: 仅处理数字和小数点
|
|||
|
|
- **对 `\lambda` 和 `\vdots` 的影响**: ✅ 无影响
|
|||
|
|
|
|||
|
|
#### Stage 1: 粘连命令拆分
|
|||
|
|
```python
|
|||
|
|
_split_glued_command_token(token)
|
|||
|
|
```
|
|||
|
|
- **影响范围**: 仅处理 `_COMMANDS_NEED_SPACE` 白名单中的命令
|
|||
|
|
- **白名单内容**: `cdot`, `times`, `div`, `pm`, `mp`, `int`, `sum`, `sin`, `cos`, 等
|
|||
|
|
- **`\lambda` 和 `\vdots` 是否在白名单中**: ❌ 不在
|
|||
|
|
- **对 `\lambda` 和 `\vdots` 的影响**: ✅ 无影响(直接返回原始值)
|
|||
|
|
|
|||
|
|
#### Stage 2: 微分规范化
|
|||
|
|
```python
|
|||
|
|
_DIFFERENTIAL_UPPER_PATTERN.sub(r"\\mathrm{d} \1", expr)
|
|||
|
|
_DIFFERENTIAL_LOWER_PATTERN.sub(r"d \1", expr)
|
|||
|
|
```
|
|||
|
|
- **影响范围**: 匹配非转义的 `d` 字符(使用 `(?<!\\)` 负向后查找)
|
|||
|
|
- **对 `\lambda` 和 `\vdots` 的影响**: ✅ 无影响(都不包含非转义的 `d`)
|
|||
|
|
|
|||
|
|
**结论**: 后处理管道不会修改 `\lambda` 和 `\vdots`。
|
|||
|
|
|
|||
|
|
### 3. 可能的问题来源 ⚠️
|
|||
|
|
|
|||
|
|
既然后处理没有问题,问题可能出在以下环节:
|
|||
|
|
|
|||
|
|
#### A. Pandoc 转换问题
|
|||
|
|
|
|||
|
|
**位置**: `app/services/converter.py` → `_latex_to_mathml_cached()`
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
mathml_html = pypandoc.convert_text(
|
|||
|
|
f"${latex_formula}$",
|
|||
|
|
"html",
|
|||
|
|
format="markdown+tex_math_dollars",
|
|||
|
|
extra_args=["--mathml"],
|
|||
|
|
)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**可能的问题**:
|
|||
|
|
1. Pandoc 版本过低,不支持某些 Unicode 字符
|
|||
|
|
2. Pandoc 的 MathML 输出使用实体编码而非 Unicode 字符
|
|||
|
|
3. 字体映射表缺失
|
|||
|
|
|
|||
|
|
#### B. MathML 后处理问题
|
|||
|
|
|
|||
|
|
**位置**: `app/services/converter.py` → `_postprocess_mathml_for_word()`
|
|||
|
|
|
|||
|
|
这个函数对 MathML 进行了大量后处理,可能误删了某些内容:
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
# Step 1: Remove <semantics> and <annotation> wrappers
|
|||
|
|
# Step 2: Remove unnecessary attributes
|
|||
|
|
# Step 3: Remove redundant single <mrow> wrapper
|
|||
|
|
# Step 7: Decode common Unicode entities
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**问题点**: Step 7 的 Unicode 实体解码可能不完整:
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
unicode_map = {
|
|||
|
|
'+': '+',
|
|||
|
|
'-': '-',
|
|||
|
|
# ... more mappings
|
|||
|
|
'λ': 'λ', # lambda
|
|||
|
|
'μ': 'μ',
|
|||
|
|
# ...
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**发现**: 代码中已经包含了 `λ` (U+03BB) 的映射,但**没有** `⋮` (U+22EE, vdots) 的映射!
|
|||
|
|
|
|||
|
|
#### C. 前端渲染问题
|
|||
|
|
|
|||
|
|
如果后端返回的 LaTeX/MathML 是正确的,但前端显示不出来:
|
|||
|
|
|
|||
|
|
1. **MathJax/KaTeX 配置问题**
|
|||
|
|
- 可能使用的是旧版本
|
|||
|
|
- 宏定义缺失
|
|||
|
|
- 字体加载失败
|
|||
|
|
|
|||
|
|
2. **字体文件缺失**
|
|||
|
|
- 希腊字母需要数学字体支持
|
|||
|
|
- 可能缺少 STIX、Latin Modern Math 等字体
|
|||
|
|
|
|||
|
|
3. **前端二次处理**
|
|||
|
|
- 前端可能对特殊字符进行了转义或过滤
|
|||
|
|
- 可能使用了不当的正则表达式替换
|
|||
|
|
|
|||
|
|
## 解决方案
|
|||
|
|
|
|||
|
|
### 方案 1: 扩展 Unicode 实体映射(后端修复)
|
|||
|
|
|
|||
|
|
如果问题在于 MathML 后处理阶段,需要扩展 `unicode_map`:
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
# 在 app/services/converter.py 的 _postprocess_mathml_for_word() 中添加:
|
|||
|
|
unicode_map = {
|
|||
|
|
# ... 现有映射 ...
|
|||
|
|
|
|||
|
|
# 希腊字母(小写)
|
|||
|
|
'α': 'α', # alpha
|
|||
|
|
'β': 'β', # beta
|
|||
|
|
'γ': 'γ', # gamma
|
|||
|
|
'δ': 'δ', # delta
|
|||
|
|
'ε': 'ε', # epsilon
|
|||
|
|
'ζ': 'ζ', # zeta
|
|||
|
|
'η': 'η', # eta
|
|||
|
|
'θ': 'θ', # theta
|
|||
|
|
'ι': 'ι', # iota
|
|||
|
|
'κ': 'κ', # kappa
|
|||
|
|
'λ': 'λ', # lambda
|
|||
|
|
'μ': 'μ', # mu
|
|||
|
|
'ν': 'ν', # nu
|
|||
|
|
'ξ': 'ξ', # xi
|
|||
|
|
'ο': 'ο', # omicron
|
|||
|
|
'π': 'π', # pi
|
|||
|
|
'ρ': 'ρ', # rho
|
|||
|
|
'σ': 'σ', # sigma
|
|||
|
|
'τ': 'τ', # tau
|
|||
|
|
'υ': 'υ', # upsilon
|
|||
|
|
'φ': 'φ', # phi
|
|||
|
|
'χ': 'χ', # chi
|
|||
|
|
'ψ': 'ψ', # psi
|
|||
|
|
'ω': 'ω', # omega
|
|||
|
|
|
|||
|
|
# 希腊字母(大写)
|
|||
|
|
'Γ': 'Γ', # Gamma
|
|||
|
|
'Δ': 'Δ', # Delta
|
|||
|
|
'Θ': 'Θ', # Theta
|
|||
|
|
'Λ': 'Λ', # Lambda
|
|||
|
|
'Ξ': 'Ξ', # Xi
|
|||
|
|
'Π': 'Π', # Pi
|
|||
|
|
'Σ': 'Σ', # Sigma
|
|||
|
|
'Υ': 'Υ', # Upsilon
|
|||
|
|
'Φ': 'Φ', # Phi
|
|||
|
|
'Ψ': 'Ψ', # Psi
|
|||
|
|
'Ω': 'Ω', # Omega
|
|||
|
|
|
|||
|
|
# 数学符号
|
|||
|
|
'⋮': '⋮', # vdots (垂直省略号)
|
|||
|
|
'⋯': '⋯', # cdots (中间省略号)
|
|||
|
|
'⋰': '⋰', # addots (对角省略号)
|
|||
|
|
'⋱': '⋱', # ddots (对角省略号)
|
|||
|
|
'…': '…', # ldots (水平省略号)
|
|||
|
|
'∅': '∅', # emptyset
|
|||
|
|
'∈': '∈', # in
|
|||
|
|
'∉': '∉', # notin
|
|||
|
|
'∋': '∋', # ni
|
|||
|
|
'∑': '∑', # sum
|
|||
|
|
'∏': '∏', # prod
|
|||
|
|
'√': '√', # sqrt
|
|||
|
|
'∞': '∞', # infty
|
|||
|
|
'∩': '∩', # cap
|
|||
|
|
'∪': '∪', # cup
|
|||
|
|
'⊂': '⊂', # subset
|
|||
|
|
'⊃': '⊃', # supset
|
|||
|
|
'⊆': '⊆', # subseteq
|
|||
|
|
'⊇': '⊇', # supseteq
|
|||
|
|
'≤': '≤', # leq
|
|||
|
|
'≥': '≥', # geq
|
|||
|
|
'≠': '≠', # neq
|
|||
|
|
'≈': '≈', # approx
|
|||
|
|
'≡': '≡', # equiv
|
|||
|
|
'×': '×', # times
|
|||
|
|
'÷': '÷', # div
|
|||
|
|
'±': '±', # pm
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 方案 2: 检查前端渲染(前端修复)
|
|||
|
|
|
|||
|
|
如果后端返回正确,需要检查前端:
|
|||
|
|
|
|||
|
|
#### 步骤 1: 验证后端输出
|
|||
|
|
|
|||
|
|
使用诊断工具检查后端返回的内容:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
python diagnose_latex_rendering.py "$\lambda + \vdots$"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
或者直接调用 API 并检查响应:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
curl -X POST "http://localhost:8000/api/v1/image/ocr" \
|
|||
|
|
-H "Content-Type: application/json" \
|
|||
|
|
-d '{"image_url": "...", "model_name": "paddle"}' | jq
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
检查返回的 `latex`、`mathml`、`mml` 字段是否包含正确的字符。
|
|||
|
|
|
|||
|
|
#### 步骤 2: 检查前端配置
|
|||
|
|
|
|||
|
|
如果使用 MathJax:
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
MathJax = {
|
|||
|
|
tex: {
|
|||
|
|
inlineMath: [['$', '$'], ['\\(', '\\)']],
|
|||
|
|
displayMath: [['$$', '$$'], ['\\[', '\\]']],
|
|||
|
|
processEscapes: true,
|
|||
|
|
processEnvironments: true,
|
|||
|
|
},
|
|||
|
|
svg: {
|
|||
|
|
fontCache: 'global'
|
|||
|
|
},
|
|||
|
|
options: {
|
|||
|
|
enableMenu: false
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
如果使用 KaTeX:
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
renderMathInElement(document.body, {
|
|||
|
|
delimiters: [
|
|||
|
|
{left: '$$', right: '$$', display: true},
|
|||
|
|
{left: '$', right: '$', display: false},
|
|||
|
|
{left: '\\[', right: '\\]', display: true},
|
|||
|
|
{left: '\\(', right: '\\)', display: false}
|
|||
|
|
],
|
|||
|
|
throwOnError: false
|
|||
|
|
});
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 步骤 3: 检查字体加载
|
|||
|
|
|
|||
|
|
确保加载了数学字体:
|
|||
|
|
|
|||
|
|
```html
|
|||
|
|
<!-- MathJax -->
|
|||
|
|
<script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
|
|||
|
|
|
|||
|
|
<!-- 或 KaTeX -->
|
|||
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css">
|
|||
|
|
<script src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js"></script>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 方案 3: 禁用有问题的后处理(临时解决)
|
|||
|
|
|
|||
|
|
如果确认是 MathML 后处理导致的问题,可以临时禁用部分后处理:
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
# 在 app/services/converter.py 中
|
|||
|
|
@staticmethod
|
|||
|
|
def _postprocess_mathml_for_word(mathml: str) -> str:
|
|||
|
|
# 跳过所有后处理,直接返回原始 MathML
|
|||
|
|
return mathml
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 使用诊断工具
|
|||
|
|
|
|||
|
|
我已经创建了一个诊断工具 `diagnose_latex_rendering.py`,使用方法:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# 测试单个字符
|
|||
|
|
python diagnose_latex_rendering.py "$\lambda$"
|
|||
|
|
python diagnose_latex_rendering.py "$\vdots$"
|
|||
|
|
|
|||
|
|
# 测试组合
|
|||
|
|
python diagnose_latex_rendering.py "$$\lambda_1, \lambda_2, \vdots, \lambda_n$$"
|
|||
|
|
|
|||
|
|
# 测试矩阵
|
|||
|
|
python diagnose_latex_rendering.py "$\begin{pmatrix} a \\ \vdots \\ z \end{pmatrix}$"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
工具会输出:
|
|||
|
|
1. 字符检测结果
|
|||
|
|
2. 每个后处理阶段的变化
|
|||
|
|
3. 最终输出
|
|||
|
|
4. 问题定位建议
|
|||
|
|
|
|||
|
|
## 推荐的调试流程
|
|||
|
|
|
|||
|
|
1. **运行诊断工具**,确认后处理阶段是否修改了输入
|
|||
|
|
2. **检查 API 响应**,确认后端返回的内容是否正确
|
|||
|
|
3. **检查前端渲染**,使用浏览器开发者工具查看实际渲染的内容
|
|||
|
|
4. **根据问题位置**,应用相应的解决方案
|
|||
|
|
|
|||
|
|
## 总结
|
|||
|
|
|
|||
|
|
根据代码分析:
|
|||
|
|
- ✅ LaTeX 语法正确
|
|||
|
|
- ✅ OCR 后处理不会破坏这些字符
|
|||
|
|
- ⚠️ 可能的问题:
|
|||
|
|
- MathML Unicode 实体映射不完整(缺少 `\vdots` 等字符)
|
|||
|
|
- Pandoc 转换配置问题
|
|||
|
|
- 前端渲染或二次处理问题
|
|||
|
|
|
|||
|
|
建议先使用诊断工具确定问题位置,然后应用相应的解决方案。
|