7.7 KiB
Executable File
7.7 KiB
Executable File
Math Formulas — LaTeX → docx-js Mapping
Design Philosophy
GLM uses LaTeX as the formula input syntax, internally converting to docx-js Math objects.
Why not write OMML directly?
- Models are naturally proficient in LaTeX (abundant in training data)
- LaTeX is semantically clear and highly readable
- Conversion layer is encapsulated internally, transparent to the user
Quick Start
const { Math: OoxmlMath, MathRun, MathFraction, MathSuperScript,
MathSubScript, MathRadical, MathSum, MathSubSuperScript } = require("docx");
// Embed formula in paragraph
new Paragraph({
alignment: AlignmentType.CENTER,
children: [
new OoxmlMath({
children: [/* Math components */]
})
]
})
LaTeX → docx-js Conversion Table
Basic Operations
| LaTeX | Meaning | docx-js Implementation |
|---|---|---|
x + y |
Addition | new MathRun("x + y") |
x - y |
Subtraction | new MathRun("x − y") (use Unicode minus −) |
x \times y |
Multiplication | new MathRun("x × y") |
x \div y |
Division | new MathRun("x ÷ y") |
x \pm y |
Plus-minus | new MathRun("x ± y") |
x \neq y |
Not equal | new MathRun("x ≠ y") |
x \leq y |
Less or equal | new MathRun("x ≤ y") |
x \geq y |
Greater or equal | new MathRun("x ≥ y") |
Fractions
| LaTeX | docx-js |
|---|---|
\frac{a}{b} |
new MathFraction({ numerator: [new MathRun("a")], denominator: [new MathRun("b")] }) |
\frac{x+1}{x-1} |
new MathFraction({ numerator: [new MathRun("x+1")], denominator: [new MathRun("x−1")] }) |
Superscripts & Subscripts
| LaTeX | docx-js |
|---|---|
x^2 |
new MathSuperScript({ children: [new MathRun("x")], superScript: [new MathRun("2")] }) |
x_i |
new MathSubScript({ children: [new MathRun("x")], subScript: [new MathRun("i")] }) |
x_i^2 |
new MathSubSuperScript({ children: [new MathRun("x")], subScript: [new MathRun("i")], superScript: [new MathRun("2")] }) |
Radicals
| LaTeX | docx-js |
|---|---|
\sqrt{x} |
new MathRadical({ children: [new MathRun("x")] }) |
\sqrt[3]{x} |
new MathRadical({ children: [new MathRun("x")], degree: [new MathRun("3")] }) |
Summation & Integrals
| LaTeX | docx-js |
|---|---|
\sum_{i=1}^{n} |
new MathSum({ subScript: [new MathRun("i=1")], superScript: [new MathRun("n")], children: [new MathRun("aᵢ")] }) |
Greek Letters
Use Unicode characters directly:
// LaTeX → Unicode mapping
const GREEK = {
"\\alpha": "α", "\\beta": "β", "\\gamma": "γ", "\\delta": "δ",
"\\epsilon": "ε", "\\zeta": "ζ", "\\eta": "η", "\\theta": "θ",
"\\iota": "ι", "\\kappa": "κ", "\\lambda": "λ", "\\mu": "μ",
"\\nu": "ν", "\\xi": "ξ", "\\pi": "π", "\\rho": "ρ",
"\\sigma": "σ", "\\tau": "τ", "\\phi": "φ", "\\chi": "χ",
"\\psi": "ψ", "\\omega": "ω",
"\\Alpha": "Α", "\\Beta": "Β", "\\Gamma": "Γ", "\\Delta": "Δ",
"\\Theta": "Θ", "\\Lambda": "Λ", "\\Pi": "Π", "\\Sigma": "Σ",
"\\Phi": "Φ", "\\Psi": "Ψ", "\\Omega": "Ω",
};
Complete Formula Examples
Quadratic Formula
LaTeX: x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}
new OoxmlMath({
children: [
new MathRun("x = "),
new MathFraction({
numerator: [
new MathRun("−b ± "),
new MathRadical({
children: [
new MathSuperScript({
children: [new MathRun("b")],
superScript: [new MathRun("2")],
}),
new MathRun(" − 4ac"),
],
}),
],
denominator: [new MathRun("2a")],
}),
],
})
Pythagorean Theorem
LaTeX: a^2 + b^2 = c^2
new OoxmlMath({
children: [
new MathSuperScript({ children: [new MathRun("a")], superScript: [new MathRun("2")] }),
new MathRun(" + "),
new MathSuperScript({ children: [new MathRun("b")], superScript: [new MathRun("2")] }),
new MathRun(" = "),
new MathSuperScript({ children: [new MathRun("c")], superScript: [new MathRun("2")] }),
],
})
Trigonometric Identity
LaTeX: \sin^2\theta + \cos^2\theta = 1
new OoxmlMath({
children: [
new MathSuperScript({ children: [new MathRun("sin")], superScript: [new MathRun("2")] }),
new MathRun("θ + "),
new MathSuperScript({ children: [new MathRun("cos")], superScript: [new MathRun("2")] }),
new MathRun("θ = 1"),
],
})
Common Exam Formula Templates
Middle School Math
// Quadratic discriminant
const discriminant = new OoxmlMath({
children: [
new MathRun("Δ = "),
new MathSuperScript({ children: [new MathRun("b")], superScript: [new MathRun("2")] }),
new MathRun(" − 4ac"),
],
});
// Circle area
const circleArea = new OoxmlMath({
children: [
new MathRun("S = π"),
new MathSuperScript({ children: [new MathRun("r")], superScript: [new MathRun("2")] }),
],
});
High School Math
// Logarithm change of base
const logChange = new OoxmlMath({
children: [
new MathSubScript({ children: [new MathRun("log")], subScript: [new MathRun("a")] }),
new MathRun("b = "),
new MathFraction({
numerator: [new MathRun("ln b")],
denominator: [new MathRun("ln a")],
}),
],
});
// Arithmetic series sum
const arithmeticSum = new OoxmlMath({
children: [
new MathSubScript({ children: [new MathRun("S")], subScript: [new MathRun("n")] }),
new MathRun(" = "),
new MathFraction({
numerator: [
new MathRun("n("),
new MathSubScript({ children: [new MathRun("a")], subScript: [new MathRun("1")] }),
new MathRun(" + "),
new MathSubScript({ children: [new MathRun("a")], subScript: [new MathRun("n")] }),
new MathRun(")"),
],
denominator: [new MathRun("2")],
}),
],
});
Physics
// Newton's second law
const newton2 = new OoxmlMath({
children: [new MathRun("F = ma")],
});
// Kinetic energy
const kineticEnergy = new OoxmlMath({
children: [
new MathSubScript({ children: [new MathRun("E")], subScript: [new MathRun("k")] }),
new MathRun(" = "),
new MathFraction({
numerator: [new MathRun("1")],
denominator: [new MathRun("2")],
}),
new MathRun("m"),
new MathSuperScript({ children: [new MathRun("v")], superScript: [new MathRun("2")] }),
],
});
Complexity Fallback Strategy
When formulas are too complex (nesting >3 levels) for docx-js Math, fall back to matplotlib PNG rendering:
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
def latex_to_png(latex_str: str, output_path: str, fontsize: int = 14, dpi: int = 200):
"""Render LaTeX formula as PNG image"""
fig, ax = plt.subplots(figsize=(0.1, 0.1))
ax.axis("off")
text = ax.text(0, 0.5, f"${latex_str}$", fontsize=fontsize,
transform=ax.transAxes, verticalalignment="center")
fig.canvas.draw()
bbox = text.get_window_extent(fig.canvas.get_renderer())
fig.set_size_inches(bbox.width / dpi + 0.2, bbox.height / dpi + 0.2)
plt.savefig(output_path, dpi=dpi, bbox_inches="tight",
pad_inches=0.05, transparent=True)
plt.close()
return output_path
Then embed the PNG in the document:
const formulaImg = fs.readFileSync("formula.png");
new Paragraph({
alignment: AlignmentType.CENTER,
children: [new ImageRun({
data: formulaImg,
transformation: { width: 300, height: 40 }, // adjust based on actual size
type: "png",
})],
})
Fallback rules:
- Nested fractions >2 levels → fallback
- Matrices/determinants → fallback
- Complex integrals (multiple integrals + limits + integrand) → fallback
- Piecewise functions → fallback
- All other cases → prefer docx-js Math