148 lines
4.7 KiB
Python
148 lines
4.7 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
fix-doxygen-unicode.py — replace problematic Unicode characters in
|
||
Doxygen-generated LaTeX files with pdflatex-safe equivalents.
|
||
|
||
Run this on the doxygen/latex/ directory before calling pdflatex.
|
||
"""
|
||
import os, sys, re
|
||
|
||
REPLACEMENTS = [
|
||
# Math symbols
|
||
('≈', r'\ensuremath{\approx}'),
|
||
('≤', r'\ensuremath{\leq}'),
|
||
('≥', r'\ensuremath{\geq}'),
|
||
('≠', r'\ensuremath{\neq}'),
|
||
('±', r'\ensuremath{\pm}'),
|
||
('×', r'\ensuremath{\times}'),
|
||
('÷', r'\ensuremath{\div}'),
|
||
('∞', r'\ensuremath{\infty}'),
|
||
('∑', r'\ensuremath{\sum}'),
|
||
('∏', r'\ensuremath{\prod}'),
|
||
('∫', r'\ensuremath{\int}'),
|
||
('√', r'\ensuremath{\sqrt{\cdot}}'),
|
||
('∂', r'\ensuremath{\partial}'),
|
||
('∆', r'\ensuremath{\Delta}'),
|
||
('∇', r'\ensuremath{\nabla}'),
|
||
('∈', r'\ensuremath{\in}'),
|
||
('∉', r'\ensuremath{\notin}'),
|
||
('∩', r'\ensuremath{\cap}'),
|
||
('∪', r'\ensuremath{\cup}'),
|
||
('⊂', r'\ensuremath{\subset}'),
|
||
('⊃', r'\ensuremath{\supset}'),
|
||
('∀', r'\ensuremath{\forall}'),
|
||
('∃', r'\ensuremath{\exists}'),
|
||
('∧', r'\ensuremath{\wedge}'),
|
||
('∨', r'\ensuremath{\vee}'),
|
||
('¬', r'\ensuremath{\neg}'),
|
||
# Arrows
|
||
('←', r'\ensuremath{\leftarrow}'),
|
||
('→', r'\ensuremath{\rightarrow}'),
|
||
('↑', r'\ensuremath{\uparrow}'),
|
||
('↓', r'\ensuremath{\downarrow}'),
|
||
('↔', r'\ensuremath{\leftrightarrow}'),
|
||
('⇒', r'\ensuremath{\Rightarrow}'),
|
||
('⇐', r'\ensuremath{\Leftarrow}'),
|
||
('⇔', r'\ensuremath{\Leftrightarrow}'),
|
||
# Greek letters
|
||
('α', r'\ensuremath{\alpha}'),
|
||
('β', r'\ensuremath{\beta}'),
|
||
('γ', r'\ensuremath{\gamma}'),
|
||
('δ', r'\ensuremath{\delta}'),
|
||
('ε', r'\ensuremath{\varepsilon}'),
|
||
('θ', r'\ensuremath{\theta}'),
|
||
('λ', r'\ensuremath{\lambda}'),
|
||
('μ', r'\ensuremath{\mu}'),
|
||
('π', r'\ensuremath{\pi}'),
|
||
('σ', r'\ensuremath{\sigma}'),
|
||
('τ', r'\ensuremath{\tau}'),
|
||
('φ', r'\ensuremath{\phi}'),
|
||
('ω', r'\ensuremath{\omega}'),
|
||
('Δ', r'\ensuremath{\Delta}'),
|
||
('Σ', r'\ensuremath{\Sigma}'),
|
||
('Ω', r'\ensuremath{\Omega}'),
|
||
# Superscripts/subscripts
|
||
('⁻', r'\ensuremath{{}^{-}}'),
|
||
('⁺', r'\ensuremath{{}^{+}}'),
|
||
('²', r'\ensuremath{{}^{2}}'),
|
||
('³', r'\ensuremath{{}^{3}}'),
|
||
('⁰', r'\ensuremath{{}^{0}}'),
|
||
('¹', r'\ensuremath{{}^{1}}'),
|
||
# Box-drawing (from ASCII art in comments)
|
||
('├', r'\textbar{}'),
|
||
('│', r'\textbar{}'),
|
||
('└', r'`'),
|
||
('┌', r'.'),
|
||
('┐', r'.'),
|
||
('┘', r"'"),
|
||
('─', r'-'),
|
||
('═', r'='),
|
||
('║', r'\textbar{}'),
|
||
('╔', r'.'),
|
||
('╗', r'.'),
|
||
('╚', r'.'),
|
||
('╝', r'.'),
|
||
('╠', r'\textbar{}'),
|
||
('╣', r'\textbar{}'),
|
||
('╦', r'-'),
|
||
('╩', r'-'),
|
||
('╬', r'+'),
|
||
# Misc
|
||
('·', r'\ensuremath{\cdot}'),
|
||
('°', r'\ensuremath{{}^{\circ}}'),
|
||
('™', r'\texttrademark{}'),
|
||
('®', r'\textregistered{}'),
|
||
('©', r'\textcopyright{}'),
|
||
('…', r'\ldots{}'),
|
||
('–', r'--'),
|
||
('—', r'---'),
|
||
('‘', r'`'), # left single quote
|
||
('’', r"'"), # right single quote
|
||
('“', r'``'), # left double quote
|
||
('”', r"''"), # right double quote
|
||
]
|
||
|
||
def fix_line(line):
|
||
# Protect \doxynewunicodechar{CHAR}{...} and \newunicodechar{CHAR}{...}
|
||
# first arguments — these must stay as literal Unicode chars.
|
||
# Strategy: split on these patterns, fix only the non-protected parts.
|
||
import re
|
||
parts = re.split(r'(\\(?:doxy)?newunicodechar\{[^}]*\})', line)
|
||
result = []
|
||
for i, part in enumerate(parts):
|
||
if i % 2 == 1: # protected token — preserve as-is
|
||
result.append(part)
|
||
else:
|
||
for char, replacement in REPLACEMENTS:
|
||
part = part.replace(char, replacement)
|
||
result.append(part)
|
||
return ''.join(result)
|
||
|
||
def fix_file(path):
|
||
with open(path, encoding='utf-8', errors='replace') as f:
|
||
lines = f.readlines()
|
||
original = ''.join(lines)
|
||
fixed_lines = [fix_line(l) for l in lines]
|
||
content = ''.join(fixed_lines)
|
||
# Replace any remaining non-ASCII with ? placeholder
|
||
content = content.encode('ascii', errors='replace').decode('ascii')
|
||
if content != original:
|
||
with open(path, 'w', encoding='ascii') as f:
|
||
f.write(content)
|
||
return True
|
||
return False
|
||
|
||
def main():
|
||
latex_dir = sys.argv[1] if len(sys.argv) > 1 else '.'
|
||
fixed = 0
|
||
for root, _, files in os.walk(latex_dir):
|
||
for fname in files:
|
||
if fname.endswith('.tex') or fname.endswith('.sty'):
|
||
path = os.path.join(root, fname)
|
||
if fix_file(path):
|
||
fixed += 1
|
||
print(f"Fixed {fixed} files in {latex_dir}")
|
||
|
||
if __name__ == '__main__':
|
||
main()
|