-
fix(importer): convert Equation.3 OLE formulas via MTEF v3, surface degrade telemetry (#1411)
发布于
2026-09-11 11:09:46 +00:00 - fix(importer): convert Equation.3 OLE formulas via MTEF v3, surface degrade telemetry
Legacy courseware stores formulas as Equation 3.0 / MathType OLE objects
whose only renderable form inside the .pptx is a WMF preview picture.
The importer cannot rasterize WMF, so those formulas degraded to a
hardcoded 1x1 "transparent" placeholder — actually a 50%-alpha red
pixel that rendered as a pink block once stretched over the formula
frame, with no way for callers to notice the content loss.- Convert
Equation.3/MathTypeOLE objects to LaTeX: detect the
progId, resolve the embedding, parse the OLE compound file (cfb) and
convert itsEquation NativeMTEF v3 stream (new utils/mtef.ts) —
fractions, radicals, scripts, fences, big operators with per-family
limit variations, embellishments, and Symbol-font local character
encodings. Slot order follows the rtf2latex2e reference
implementation and real MathType streams ([main, lower, upper]), not
the archived spec prose. Any failure falls back to the picture path. - Fix the placeholder constant to a truly transparent pixel; keep
recognizing the legacy red one (exported isPlaceholderDataUrl). - Surface degradation instead of failing silently: optional
ImportPptxOptions.onWarning receives machine-coded warnings
(media-unconvertible / formula-fallback-image / formula-degraded /
element-dropped) at every placeholder consumption point (image,
background, shape/text pattern fills, math fallback); a throwing
sink is isolated so telemetry can never fail an import. A formula
whose fallback picture is also a placeholder keeps its plain text as
a text element.
- fix(importer): address review — LSCRIPT base duplication, one-sided fences, Symbol table, depth caps
Review round on #1411 (thanks @wyuc — the fuzz safety-net validation and the
adversarial constructions found what the spec-conformance rounds could not):- tmLSCRIPT: stop re-emitting a script slot as the base group (isotopes
rendered as {}{6}^{12}{12}C and the output doubled per nesting level —
a 221-byte stream could OOM the worker); the base is the following
sibling, so the template emits only {}{sub}^{sup}, and a leading script
no longer steals the previous atom via the trailing-script lookahead.
tvLSUPER writers that emit a single slot now fall back to it. - One-sided fences: render the missing side as a null delimiter
(\left. / \right.) instead of an unmatched \left — piecewise-function
braces (tmBRACE var 1) produced KaTeX-invalid output that silently
degraded to flat text. - SYMBOL_FONT_LATEX corrected against URW StandardSymbolsPS AFM + Adobe
AGL: 0x3C/0x3E/0x5B/0x5D are literal < > [ ] (≤/≥ live at 0xA3/0xB3,
now mapped, along with the rest of the Symbol operator block);
0x22/0x24/0x5C are ∀/∃/∴; added Chi/vartheta/varsigma, the phi/varphi
split (0x66/0x6A), and 0x5E = \perp. Unmapped font-local bytes >= 0xA0
now flag degraded instead of passing silently. - tmLIM: variation roles were inverted — spec + rtf2latex2e eqn.c say
0 = upper limit, 1 = lower limit; single-limit writers keep their limit
via the same slot fallback as the big operators. - Hardening: MAX_DEPTH = 200 nesting cap and a 64 KiB LaTeX output cap,
both throwing MtefParseError (deep bombs now fail loud instead of
RangeError/OOM); video poster joined the placeholder warning points. - Tests: +6 — three REAL Equation Native stream fixtures (round-tripped
from a legacy deck, pinning the font-local encoding and big-op slot
order), tvLSUPER single-slot, tmLIM both roles, script-nesting perf
guard; fence tests now assert KaTeX renderability instead of pinning
broken strings. Suite: 85. - Version 0.1.5 -> 0.2.0 (new public option/type/export + Math.degraded).
Lockfile re-anchored on main with pnpm@10.28.0: only the cfb additions
remain, no unrelated churn.
- fix(importer): address review round 2 — tmLIM function slot, full Symbol high-half, bra/ket, cap tests
- tmLIM: emit the main slot FIRST followed by the limits and inject no
operator name (the reference39.1 = limit: lower, #1 #2puts the
function in the main slot — hardcoding \lim duplicated it and glued to
letter-leading main slots, crashing KaTeX with an undefined control
sequence). An empty main slot falls back to \lim as a neutral base. - SYMBOL_FONT_LATEX: completed the 0xA0–0xFF block from the URW AFM
(~70 positions: ∫ ∑ ∏ ⟨⟩ ∂ ∇ ⇒ ⇔ ⋅ ′ ∅ ⊆ ⊇ ∈ ∉ ∪ ∩ …). Unmapped
font-local codes now throw MtefParseError instead of passing through
as Latin-1 (0xF7 was an integral extender rendering as ÷ — plausible
but wrong math); radicalex (0x60) and C1 controls (0x80–0x9F) also
throw, taking the picture fallback. - tmDIRAC: var1 renders a bra
\left\langle L\right|, var2 a ket
\left| R\right\rangleper the reference (was wrapping both sides). - Embellishment records now count against the record budget (a 10 MB
embellishment-only stream no longer allocates 1 GB before the output
cap fires). - Tests: +6 — every cap now has its own assertion (output-length width
case at 15k sibling CHARs, record-count at 20k, in addition to the
existing depth test), previously-dangerous Symbol codes verified from
the AFM, unmapped-code rejection, embellishment budget, tmDIRAC
KaTeX-validity for all variations. Fixture claims corrected (no
big-op selector in the three real streams; the reading is pinned by
the spec-conformance tests). Suite: 91.
- test(importer): pin 0xD6/0xF3 Symbol glyphs through KaTeX, fix big-op test title
Follow-up to the round-3 review: the two glyph fixes landed without a
test, the BigOp test title still named the wrong slot order, and the
table comment claimed the high half was complete while unlisted
positions intentionally throw.Co-Authored-By: Claude Fable 5.1 noreply@anthropic.com
- docs(importer): correct Symbol table comments — 0xF7 is parenrightex not an integral extender, high-half coverage is ~50 of ~70 positions
Co-authored-by: Percy percy@PercydeMacBook-Pro.local
Co-authored-by: wyuc wang-yc24@mails.tsinghua.edu.cn
Co-authored-by: Claude Fable 5.1 noreply@anthropic.com下载附件