Add Unicode math formatter

This commit is contained in:
Edern Deneuville
2026-09-11 13:55:54 +02:00
parent 5e938fe456
commit 6cb34ead26
+134
View File
@@ -0,0 +1,134 @@
from __future__ import annotations
import re
GREEK_AND_SYMBOLS = {
r"\alpha": "α",
r"\beta": "β",
r"\gamma": "γ",
r"\delta": "δ",
r"\epsilon": "ε",
r"\varepsilon": "ε",
r"\zeta": "ζ",
r"\eta": "η",
r"\theta": "θ",
r"\vartheta": "ϑ",
r"\iota": "ι",
r"\kappa": "κ",
r"\lambda": "λ",
r"\mu": "μ",
r"\nu": "ν",
r"\xi": "ξ",
r"\pi": "π",
r"\rho": "ρ",
r"\sigma": "σ",
r"\tau": "τ",
r"\upsilon": "υ",
r"\phi": "φ",
r"\varphi": "φ",
r"\chi": "χ",
r"\psi": "ψ",
r"\omega": "ω",
r"\Gamma": "Γ",
r"\Delta": "Δ",
r"\Theta": "Θ",
r"\Lambda": "Λ",
r"\Xi": "Ξ",
r"\Pi": "Π",
r"\Sigma": "Σ",
r"\Phi": "Φ",
r"\Psi": "Ψ",
r"\Omega": "Ω",
r"\infty": "∞",
r"\leq": "≤",
r"\le": "≤",
r"\geq": "≥",
r"\ge": "≥",
r"\neq": "≠",
r"\ne": "≠",
r"\approx": "≈",
r"\times": "×",
r"\cdot": "·",
r"\pm": "±",
r"\mp": "∓",
r"\to": "→",
r"\rightarrow": "→",
r"\leftarrow": "←",
r"\Rightarrow": "⇒",
r"\Leftrightarrow": "⇔",
r"\forall": "∀",
r"\exists": "∃",
r"\in": "∈",
r"\notin": "∉",
r"\subset": "⊂",
r"\subseteq": "⊆",
r"\cup": "∪",
r"\cap": "∩",
r"\emptyset": "∅",
r"\mathbb{R}": "ℝ",
r"\mathbb{C}": "ℂ",
r"\mathbb{N}": "ℕ",
r"\mathbb{Z}": "ℤ",
r"\mathbb{Q}": "ℚ",
r"\int": "∫",
r"\sum": "∑",
r"\prod": "∏",
r"\partial": "∂",
r"\nabla": "∇",
}
SUPERSCRIPT = str.maketrans({
"0": "⁰", "1": "¹", "2": "²", "3": "³", "4": "⁴",
"5": "⁵", "6": "⁶", "7": "⁷", "8": "⁸", "9": "⁹",
"+": "⁺", "-": "⁻", "=": "⁼", "(": "⁽", ")": "⁾",
"n": "ⁿ", "i": "ⁱ",
})
SUBSCRIPT = str.maketrans({
"0": "₀", "1": "₁", "2": "₂", "3": "₃", "4": "₄",
"5": "₅", "6": "₆", "7": "₇", "8": "₈", "9": "₉",
"+": "₊", "-": "₋", "=": "₌", "(": "₍", ")": "₎",
"a": "ₐ", "e": "ₑ", "h": "ₕ", "i": "ᵢ", "j": "ⱼ", "k": "ₖ",
"l": "ₗ", "m": "ₘ", "n": "ₙ", "o": "ₒ", "p": "ₚ", "r": "ᵣ",
"s": "ₛ", "t": "ₜ", "u": "ᵤ", "v": "ᵥ", "x": "ₓ",
})
_GROUP_PATTERN = re.compile(r"([_^])\(([^()]+)\)|([_^])\{([^{}]+)\}|([_^])([A-Za-z0-9+\-=])")
def _translate_script(value: str, marker: str) -> str:
table = SUPERSCRIPT if marker == "^" else SUBSCRIPT
converted = value.translate(table)
return converted if converted != value else marker + value
def _replace_script(match: re.Match[str]) -> str:
marker = match.group(1) or match.group(3) or match.group(5)
value = match.group(2) or match.group(4) or match.group(6)
return _translate_script(value, marker)
def format_math_text(text: str, mode: str = "plain") -> str:
r"""Convert common UnicodeMath/LaTeX-like linear math to Unicode plain text.
This is meant for Word normal text insertion. It makes expressions such as
z_1, x^2, \alpha and \infty render as z₁, x², α and ∞ without requiring the
user to manually open Word's equation editor for every expression.
"""
if mode == "plain":
return text
if mode not in {"unicode", "unicode_math"}:
raise ValueError("math_text_format doit être 'plain' ou 'unicode'")
formatted = text
# Replace longer commands first so \subseteq wins before \subset.
for command, replacement in sorted(GREEK_AND_SYMBOLS.items(), key=lambda item: len(item[0]), reverse=True):
formatted = formatted.replace(command, replacement)
formatted = re.sub(r"\\vec\(([^()]+)\)", lambda match: match.group(1) + "⃗", formatted)
formatted = formatted.replace(r"\sqrt", "√")
previous = None
while previous != formatted:
previous = formatted
formatted = _GROUP_PATTERN.sub(_replace_script, formatted)
return formatted