skill
pptx-math
Native PowerPoint equations from LaTeX. Converts LaTeX to OMML and injects it into .pptx — editable, vector equations instead of images or Unicode text. A companion to the pptx skill / python-pptx.
Set up
Skills are installed by placing their folder where your agent loads skills.
The download comes from github.com/maenjh/pptx-math itself, not from our server, so what you get is exactly what its author published.
Install instructions come from the README
README
synced Oct 23, 2026pptx-math
한국어 문서는 README.ko.md
Put real PowerPoint equations into .pptx files from LaTeX — not pictures of equations.
add_native_equation(box.text_frame,
r"\Pr(y=1) = \sigma(\alpha + \beta_j \cdot z_j + \gamma \cdot \mathbf{x})")
The result is a native OMML equation: double-click it in PowerPoint and the equation editor opens. It is vector (never pixelates), it follows the theme color, and it is editable by whoever opens the deck.
No pandoc, no LaTeX distribution, no MathType. Standard library + python-pptx only.
Why this exists
python-pptx has no equation API. Every workaround is worse than it looks:
| Approach | Editable | Quality | Verdict |
|---|---|---|---|
| Native OMML (this repo) | Yes — equation editor | Vector, LaTeX-grade | ✅ |
| Rendered PNG (matplotlib) | No — it's an image | Rasterizes when zoomed | Fallback only |
Unicode text (σ(α + βⱼ zⱼ)) | Yes, but it's just text | Fractions, roots and ∑ collapse | ❌ |
The Unicode route is the one that gets used by default, and it silently destroys anything
non-trivial. \sigma(z) = \frac{1}{1+e^{-z}} becomes σ(z) = 11 + e⁻ᶻ.
Relationship to the pptx skill
This is a companion, not a replacement. The pptx skill (and any python-pptx
workflow) handles decks: layouts, text, tables, images, charts. It has no way to emit a
native equation. pptx-math fills exactly that one gap and nothing else.
Typical division of labour:
pptx skill / python-pptx → build the deck, place the text boxes
pptx-math → every place where math appears
When to use it
Use it whenever math reaches a slide — including a single italic variable in a sentence. Specifically:
- Equations, math variables (
x,r_A,β_j), Greek letters, sub/superscripts, fractions,∑,∫, matrices. - Generating a deck from a paper, PDF, or Markdown. Research slides almost always
carry math, and it is easy to flatten it to Unicode without noticing. Inline variables
inside prose (
p, r_A, r_B, y) count. - Upgrading an existing deck whose equations were typed as plain text
(
Pr(y = 1) = σ(α + βj zj),rA,τ).
Do not convert acronyms, labels, or plain numbers — AUC, Elo, Δwin-rate,
−31%p read worse in math italic.
Install
1. Claude Code plugin (recommended)
Two commands, nothing else to install:
/plugin marketplace add maenjh/pptx-math
/plugin install pptx-math
Update later with /plugin marketplace update pptx-math, remove with
/plugin uninstall pptx-math.
2. One-line script
No plugin system, no Node — copies the skill into ~/.claude/skills/pptx-math:
curl -fsSL https://raw.githubusercontent.com/maenjh/pptx-math/main/install.sh | bash
It backs up any existing install and tells you if python-pptx is missing.
3. Manual
git clone --depth 1 https://github.com/maenjh/pptx-math.git /tmp/pptx-math
cp -R /tmp/pptx-math/skills/pptx-math ~/.claude/skills/
4. As a plain Python module
The converter does not need Claude at all.
git clone https://github.com/maenjh/pptx-math.git
pip install python-pptx # matplotlib only for the image fallback
import sys; sys.path.insert(0, "pptx-math/skills/pptx-math/scripts")
from pptx_math import add_native_equation
Requirements
Python 3.9+ and python-pptx. Everything else is standard library. matplotlib is
optional and only used by add_equation_image().
Once installed, the skill triggers on requests like "put an equation in this PPT", "build slides from this PDF", or "turn these variables into proper math notation".
Quick start
from pptx import Presentation
from pptx.util import Inches, Pt
from pptx_math import add_native_equation, set_paragraph_segments, shape_census
prs = Presentation()
slide = prs.slides.add_slide(prs.slide_layouts[6])
box = slide.shapes.add_textbox(Inches(1), Inches(2), Inches(11), Inches(1.2))
# Standalone equation
add_native_equation(box.text_frame,
r"\Pr(y=1) = \sigma(\alpha + \beta_j \cdot z_j + \gamma \cdot \mathbf{x})",
size=Pt(32), align="center", color="1F3864")
# Several equations, one per line
for eq in [r"\sigma(z) = \frac{1}{1+e^{-z}}",
r"\hat{y} = \mathbf{w}^\top \mathbf{x} + b"]:
add_native_equation(box.text_frame, eq, size=Pt(24), align="center")
prs.save("out.pptx")
Inline variables inside a sentence
The most common real-world case: a body bullet that mentions p, rA, rB, y and should
read 𝑝, 𝑟_𝐴, 𝑟_𝐵, 𝑦. set_paragraph_segments() reuses the existing run's font, size,
colour and indentation, so the layout does not shift at all.
para = body_shape.text_frame.paragraphs[3]
set_paragraph_segments(para, [
("text", "The paper writes "), ("math", "p"), ("text", ", "), ("math", "r_A"),
("text", ", "), ("math", "r_B"), ("text", ", "), ("math", "y"),
("text", ", where "), ("math", "p"), ("text", " is the prompt."),
])
API
| Function | Purpose |
|---|---|
add_native_equation(tf, latex, size, align, paragraph, color, display) | Add a native equation. Pass paragraph= to append inline to an existing paragraph — paragraph properties are left untouched. |
set_paragraph_segments(para, segments) | Rebuild a paragraph from [("text", …), ("math", …)], preserving formatting. |
add_text_run(para, text, template_run=) | Append a plain run, cloning a run's formatting. |
math_node(latex, size, color) | Build just the <a14:m> lxml element, to place yourself. |
add_equation_image(slide, latex, left, top, …) | matplotlib mathtext PNG fallback. |
latex_to_unicode(latex) | Unicode approximation. Simple one-liners only. |
shape_census(path) | Per-slide shape inventory. Diff this before/after every save. |
latex2omml.latex_to_omml(latex) | The converter on its own. |
Supported LaTeX
Sub/superscripts · \frac \dfrac · \sqrt[n]{} · \left( \right) · all Greek letters ·
\sum \prod \int \oint with limits · \mathbf \mathrm \mathbb \mathcal \text \operatorname ·
\hat \bar \vec \tilde \dot \overline · function names (\log \sin \max \Pr — a thin space
is added automatically, and suppressed before ( or a subscript) · \begin{pmatrix} and
friends · operators (\cdot \times \leq \approx \to \infty \top …) · spacing (\quad \,).
Unknown commands are printed by name rather than silently swallowed, so mistakes are visible in the output instead of vanishing.
Pitfalls that will bite you
These are not hypothetical — every one of them was hit while building this.
- Verify the shape count after every save.
If you violate the PPTX schema ordering, PowerPoint "repairs" the file and deletes
whole shapes without warning. You will not find out until someone opens the deck.
before = shape_census(DECK); prs.save(DECK) assert before == shape_census(DECK)a:pchild order:pPr,(r|br|fld|a14:m)*,endParaRPra:pPrchild order:lnSpc, spcBef, spcAft, bu*, tabLst, defRPr, extLst
szis in hundredths of a point. PassingPt(36)writes the EMU value457200and the equation renders at a nonsense size._sz()handles this — don't hand-write XML.- Back up the user's file before editing it.
- Never open the original in PowerPoint to verify — open a copy. PowerPoint can write back to the original and persist its own repair.
- Don't change paragraph alignment when inserting inline.
aligndefaults to "leave as-is" for this reason. - Equations are not text runs, so they don't appear in
.textor in text search. Check whether speaker notes or post-processing scripts depend on those strings.
See references/verification.md for the full verification
procedure and a table of every bug encountered, with its cause.
Verification
LibreOffice renders OMML but ignores its font size (it imports the equation into its own formula engine), so it can confirm an equation exists but not how it looks. Size and alignment must be checked in real PowerPoint.
cp deck.pptx /tmp/verify.pptx # never open the original
osascript -e 'tell application "Microsoft PowerPoint"
open POSIX file "/tmp/verify.pptx"
delay 6
set d to presentation "verify.pptx"
save d in POSIX file "/tmp/v.pdf" as save as PDF
delay 4
close d saving no
end tell'
pdftoppm -png -r 110 -f 10 -l 10 /tmp/v.pdf /tmp/page
Use presentation "name", never active presentation — the latter grabs whatever
document the user happens to have in front, and close saving no will discard their
unsaved work.
Examples
examples/demo.py— builds a deck comparing all three approaches side by side.
License
MIT