| 1 | #!/usr/bin/env python3 |
| 2 | r""" |
| 3 | PPT Master - Native Formula Compiler |
| 4 | |
| 5 | Compile the documented Microsoft 365 LaTeX input profile into editable block |
| 6 | or inline Office Math XML. |
| 7 | |
| 8 | See references/native-formula.md for the owning authoring contract. |
| 9 | |
| 10 | Usage: |
| 11 | Import compile_latex_to_omml() or compile_latex_to_inline_omml() from the |
| 12 | SVG-to-PPTX native-object pipeline. |
| 13 | |
| 14 | Examples: |
| 15 | compile_latex_to_omml(r"\frac{-b \pm \sqrt{b^2-4ac}}{2a}") |
| 16 | |
| 17 | Dependencies: |
| 18 | None (only uses standard library and local PPT Master modules) |
| 19 | """ |
| 20 | |
| 21 | from __future__ import annotations |
| 22 | |
| 23 | from .formula_ast import ( |
| 24 | FormulaVerticalExtent, |
| 25 | Sequence, |
| 26 | formula_node_count, |
| 27 | formula_vertical_extent, |
| 28 | ) |
| 29 | from .formula_omml import emit_omml, validate_omml_fragment |
| 30 | from .formula_parser import FormulaCompileError, parse_latex_formula |
| 31 | |
| 32 | |
| 33 | _MAX_LATEX_LENGTH = 65_536 |
| 34 | _MAX_AST_NODES = 100_000 |
| 35 | |
| 36 | |
| 37 | def _parse_checked_formula(latex: str, *, display: bool) -> Sequence: |
| 38 | if not isinstance(latex, str): |
| 39 | raise FormulaCompileError("LaTeX formula must be a string") |
| 40 | if len(latex) > _MAX_LATEX_LENGTH: |
| 41 | raise FormulaCompileError( |
| 42 | f"LaTeX formula exceeds the {_MAX_LATEX_LENGTH}-character limit" |
| 43 | ) |
| 44 | try: |
| 45 | expression = parse_latex_formula(latex, display=display) |
| 46 | try: |
| 47 | formula_node_count(expression, maximum=_MAX_AST_NODES) |
| 48 | except ValueError as exc: |
| 49 | raise FormulaCompileError(str(exc)) from exc |
| 50 | return expression |
| 51 | except RecursionError as exc: |
| 52 | raise FormulaCompileError( |
| 53 | "Formula nesting exceeds the supported limit" |
| 54 | ) from exc |
| 55 | |
| 56 | |
| 57 | def _compile(latex: str, *, display: bool) -> str: |
| 58 | expression = _parse_checked_formula(latex, display=display) |
| 59 | try: |
| 60 | return emit_omml(expression, display=display) |
| 61 | except RecursionError as exc: |
| 62 | raise FormulaCompileError( |
| 63 | "Formula nesting exceeds the supported limit" |
| 64 | ) from exc |
| 65 | |
| 66 | |
| 67 | def compile_latex_to_omml(latex: str) -> str: |
| 68 | """Compile one standalone block formula into canonical m:oMathPara XML.""" |
| 69 | return _compile(latex, display=True) |
| 70 | |
| 71 | |
| 72 | def compile_latex_to_inline_omml(latex: str) -> str: |
| 73 | """Compile one inline formula into canonical m:oMath XML.""" |
| 74 | return _compile(latex, display=False) |
| 75 | |
| 76 | |
| 77 | def estimate_inline_formula_vertical_extent( |
| 78 | latex: str, |
| 79 | ) -> FormulaVerticalExtent: |
| 80 | """Estimate inline native-math bounds relative to its text baseline.""" |
| 81 | expression = _parse_checked_formula(latex, display=False) |
| 82 | try: |
| 83 | return formula_vertical_extent(expression) |
| 84 | except RecursionError as exc: |
| 85 | raise FormulaCompileError( |
| 86 | "Formula nesting exceeds the supported limit" |
| 87 | ) from exc |
| 88 | |
| 89 | |
| 90 | __all__ = [ |
| 91 | "FormulaCompileError", |
| 92 | "compile_latex_to_inline_omml", |
| 93 | "compile_latex_to_omml", |
| 94 | "estimate_inline_formula_vertical_extent", |
| 95 | "validate_omml_fragment", |
| 96 | ] |
| 97 |