Skip to content

Commit e56ddd2

Browse files
feat(pptx): applyEdits — lossless surgical-edit API (#95)
Add applyEdits(source, plan, options?): a patch on the original .pptx bytes rather than a full re-serialize. Everything not named by an edit comes out byte-identical to the source (masters, layouts, theme, fonts, tags, notes, embeddings, untouched elements); the result opens in PowerPoint with no repair. - New elementLocationRegistry in the parser (getElementLocation) maps every element id -> its verbatim source XML block, with no placeholder filtering. - Ops: setText/clearText, setChartData (in-place chart fill keeping type/colour + a regenerated, consistent embedded xlsx so Edit-Data works), setTableData, setImage, removeElement, addChart, addDiagram, per-slide background, title. - Slide subset/reorder/repeat from the template; repeats deep-clone from a pristine view so edits never bleed across copies. - Removed slides + their exclusive parts reclaimed by a reachability sweep, then reconcileDanglingRels + content-type pruning. - Unresolved ids / unsupported layout-instantiation surface via onWarning. Tests on a synthetic 4-slide template incl. the acceptance test (untouched slide byte-identical, chart workbook updated, zero dangling rels).
1 parent 017ce51 commit e56ddd2

7 files changed

Lines changed: 2014 additions & 1 deletion

File tree

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
---
2+
"@textcortex/slidewise": minor
3+
---
4+
5+
feat(pptx): `applyEdits` — lossless surgical-edit API
6+
7+
Add `applyEdits(source, plan, options?)`: a patch on the original `.pptx` bytes
8+
rather than a full re-serialize. The create flow can now emit an `EditPlan`
9+
(subset/reorder/repeat of template slides, each with edits) and get back a valid
10+
package where everything not named by an edit is byte-identical to the source —
11+
masters, layouts, theme, embedded fonts, `ppt/tags/*`, notes, embeddings, and
12+
any untouched element. This removes the lossy round-trip that produced the
13+
`custGeom`/SVG-fallback/dangling-rel fidelity bugs and lets hosts drop their
14+
defensive cleanup. `serializeDeck` stays for the live editor / from-scratch decks.
15+
16+
Edits address elements by the same stable ids `parsePptx` returns; slides by
17+
1-based template index. Supported ops: `setText`/`clearText` (preserve the
18+
template box + run styling, or rebuild from supplied runs), `setChartData`
19+
(repopulate a native chart in place — type/colours kept, caches **and** the
20+
embedded `xlsx` workbook updated so Edit-Data still works), `setTableData`,
21+
`setImage`, `removeElement`, `addChart`, `addDiagram`, plus per-slide
22+
`background` and deck `title`. Removed slides and any parts that become
23+
exclusive to them are reclaimed by a package-wide reachability sweep, then
24+
dangling relationships and content-types are reconciled. Unresolved element ids
25+
and unsupported layout-instantiation are surfaced via `onWarning` instead of
26+
throwing.

README.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,57 @@ editor only sees current-shape decks. It throws if the input was written by a
114114
newer Slidewise than the host has installed — pin the version range you can
115115
support.
116116

117+
### Lossless surgical edits with `applyEdits`
118+
119+
When you start from a branded template and only need to change a few things
120+
(swap some text, fill a chart, drop a sample element), a full
121+
`serializeDeck` round-trip is overkill — and re-rendering unedited elements is
122+
where fidelity bugs come from. `applyEdits(source, plan)` instead **patches the
123+
original bytes**: everything not named by an edit comes out byte-identical to
124+
the source (masters, layouts, theme, embedded fonts, `ppt/tags/*`, notes,
125+
embeddings, and any untouched element), and the result opens in PowerPoint with
126+
no repair.
127+
128+
```ts
129+
import { parsePptx, applyEdits, type EditPlan } from "@textcortex/slidewise";
130+
131+
const deck = await parsePptx(source); // address elements by deck ids
132+
const plan: EditPlan = {
133+
title: "Q3 Results",
134+
// Output order = this list. Slides are the source's 1-based template index;
135+
// a source slide may repeat for controlled reuse.
136+
slides: [
137+
{
138+
source: { slideIndex: 1 },
139+
edits: [{ op: "setText", elementId: titleId, text: "Q3 Results" }],
140+
},
141+
{
142+
source: { slideIndex: 3 },
143+
edits: [
144+
// Repopulate a native chart in place — type/colours and the embedded
145+
// workbook are preserved, so PowerPoint's Edit-Data still works.
146+
{ op: "setChartData", elementId: chartId, categories: ["Jan", "Feb", "Mar"], series: [{ name: "Revenue", values: [10, 20, 30] }] },
147+
{ op: "removeElement", elementId: sampleChartId },
148+
],
149+
},
150+
{ source: { slideIndex: 4 }, edits: [] }, // kept byte-identical
151+
],
152+
};
153+
154+
const out: Uint8Array = await applyEdits(source, plan, {
155+
onWarning: (w) => notifyHost(w.message), // unresolved id / unsupported op
156+
});
157+
```
158+
159+
Ops: `setText` / `clearText`, `setChartData`, `setTableData`, `setImage`,
160+
`removeElement`, `addChart`, `addDiagram`, plus per-slide `background` and the
161+
deck `title`. Elements are addressed by the same stable ids `parsePptx` returns,
162+
so call `applyEdits` in the same process as the `parsePptx` that produced the
163+
plan. Removed slides and any parts exclusive to them are reclaimed
164+
automatically. `serializeDeck` remains the path for the live editor and
165+
from-scratch decks; `applyEdits` is the lossless path for template-derived
166+
output.
167+
117168
### Generating slides from the template's layouts
118169

119170
`parsePptx` exposes the source template's master layouts on `deck.layouts`.

packages/slidewise/src/index.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,11 +88,18 @@ export {
8888
type SlideRailItemContextValue,
8989
} from "./compound";
9090

91-
export { parsePptx, isPptxTemplate, serializeDeck } from "./lib/pptx";
91+
export { parsePptx, isPptxTemplate, serializeDeck, applyEdits } from "./lib/pptx";
9292
export type {
9393
SerializeOptions,
9494
SerializeWarning,
9595
SvgRasterizer,
96+
EditPlan,
97+
PlannedSlide,
98+
Edit,
99+
Run,
100+
Series,
101+
Rect,
102+
ApplyEditsOptions,
96103
} from "./lib/pptx";
97104
export type { ParseDiagnostics, ParseResult } from "./lib/pptx/types";
98105

0 commit comments

Comments
 (0)