convert: a first-party pptx importer in the kernel - #351
Merged
Conversation
…arser dash could not reuse, and the fidelity contract The ZIP reader/writer moves verbatim from dash/src/zip.ts to kernel/src/convert/zip.ts, with dash re-exporting through a six-line facade (the charts pattern); both dash rigs pass unchanged (34/34, 133/133). The XML parser is new because dash's regex scanner is explicitly safe only while no element nests inside itself, and PresentationML's p:grpSp nests. Tokenizing, namespace-resolving, node-and-browser. DOCTYPE is refused outright, which kills the entity-expansion attack class by construction. mc:AlternateContent resolution lives here too, since an mc-blind reader double-imports. report.ts is the fidelity spine (carried/approximated/dropped with per-code folding, and provenance tallies for the silent-cascade problem); types.ts is the structural output contract, declared in the kernel rather than imported from slides, the same dependency inversion the charts engine uses.
Seven modules on the foundation commit: theme.ts (the colour/font resolver),
inherit.ts (the placeholder chain), media.ts, text.ts, shapes.ts, pptx.ts
(the orchestrator), and a seven-rig suite (378 checks) under
scripts/test-convert/.
The resolver IS the importer -- the spike measured 91% of colour and 71% of
run properties as indirect in real decks -- so that is where the correctness
budget went, and the traps it names are pinned by rigs whose negative
controls have each been SEEN to fail against a deliberately wrong
implementation:
- a:shade/a:tint compute in linear-light RGB while lumMod/lumOff/satOff
are HSL; mixing the spaces yields plausible wrong colours (rig recomputes
shade50(#808080) = #5D5D5D by hand, and pins that the sRGB multiply's
#404040 is NOT produced)
- clrMap applies forward (bg1 -> lt1), with raw slot names still resolving
directly, spec-correct
- a PRESENT-but-empty a:effectLst cancels an inherited effect; an absent
one defers up the chain
- group chOff/chExt scaling composes recursively (nested groups double-
scale; the rig pins a hand-computed leaf frame through two levels)
- buNone at a nearer level silences an inherited buChar; bullets resolve
as a unit, never merged across sources
- every emitted slide carries transition:'none' explicitly -- the model's
own constructors default to 'fade', and 71 of 79 census slides carry no
transition at all
- svg arrives as image + data:image/svg+xml, never an inline svg element
(url(#id) resolves document-globally; 386 icons sharing ids would all
paint as the first)
Fixtures are built in code with the kernel's own writeZip -- no binaries in
the repo -- and the gate that matters most is load.ts: every fixture's
emitted document must pass the REAL slides parseDoc and report zero
validateDoc errors. The engine emits documents the app itself would accept,
or the rig fails.
Unit arithmetic is pinned by hand-computed values (EMU/9525, centipoints,
60000ths of a degree), and provenance tallies survive into the report so a
reviewer checks the class ("1,700 fills came from the theme") rather than
the instance.
xml.ts loses its one parameter property: node's native type stripping
refuses non-erasable TS syntax, and it made every rig unrunnable.
Layouts are deliberately NOT imported (500 layout parts per 79 census
slides = 85% dead weight; flattening is the M0 answer), tables and charts
are reported honestly as not-yet rather than converted badly, and dash's
rigs (34/34, 133/133) confirm the zip lift disturbed nothing.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The first half of the convert engine: a first-party
.pptx→bento/slidesimporter inkernel/src/convert/, with no dependencies, running in both node and the browser.What this is
kernel/src/convert/zip.ts, lifted verbatim from dash, which now re-exports through a six-line facade — the charts pattern). Both dash rigs pass unchanged (34/34, 133/133).p:grpSpnests. DOCTYPE is refused outright, which kills the entity-expansion attack class by construction.Why the correctness budget went where it did
A structural census of six real business decks measured 91% of colour and 71% of run properties as indirect — resolved through a cascade where every hop fails silently. A backwards clrMap still renders, just dark-on-dark. So the traps are pinned by rigs whose negative controls have each been seen to fail against a deliberately wrong implementation:
a:shade/a:tintcompute in linear-light RGB whilelumMod/lumOffare HSL — the rig hand-computes shade50(#808080) =#5D5D5Dand pins that the naive sRGB multiply's#404040is not produceda:effectLstcancels an inherited effect; an absent one deferschOff/chExtscaling composes recursively (hand-computed leaf frame through two nesting levels)buNoneat a nearer level silences an inheritedbuChartransition:'none'explicitly — the model's own constructors default to'fade'image+data:image/svg+xml, never an inline svg element (url(#id)is document-global)The gate that matters
Fixtures are built in code with our own
writeZip(no binaries in the repo), andload.tsimports the real slidesparseDoc+validateDoc: every emitted document must parse and report zero errors. The engine emits documents the app itself accepts, or CI fails.7 rigs, 378 checks, wired into CI. Verified by an adversarial pass that mutated six implementations and confirmed every mutation caught.
What this deliberately does not do
Layouts are not imported (500 layout parts per 79 census slides — flattening is the M0 answer;
doc.layoutsarrives with the theme/layout work). Tables and charts are reported honestly as not-yet rather than converted badly. No UI, no host — the standalone app and tray hosts consume this next, and the export direction (PR #88, reworked) builds on the same foundation.