@office-kit/pptx 0.16.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,4 +1,53 @@
1
- # pptx-kit
1
+ # @office-kit/pptx
2
+
3
+ ## 0.17.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 71deec5: feat: author every chart type — scatter, bubble, radar, stock, surface, the 3-D variants and pie-of-pie
8
+
9
+ `addSlideChart` / `setChartSpec` now write all sixteen plot types of ECMA-376, and `getShapeChartSpec` reads every one of them back.
10
+
11
+ - `kind: 'scatter' | 'bubble' | 'radar'` are authorable (they used to throw "read-only"). Scatter and bubble series carry their own `xValues` (and `bubbleSizes`); the embedded workbook lays them out per series so "Edit data" opens onto the right cells.
12
+ - New kinds `'stock'` (3 series: high, low, close; or 4 with open first, drawn as candlesticks) and `'surface'` (`surfaceContour` for the top-down contour plot, `surfaceWireframe` for the mesh).
13
+ - 3-D is a modifier: `view3D: { rotX, rotY, rightAngleAxes, perspective, depthPercent, heightPercent }` on `bar` / `column` / `line` / `area` / `pie` selects the 3-D element, with `bar3DShape` (cylinder, cone, pyramid, …), `gapDepthPct` and `seriesAxis`. `bubble3D` shades bubbles as spheres.
14
+ - `ofPie` turns a pie into a pie-of-pie / bar-of-pie chart.
15
+ - Per series: `errorBars` / `xErrorBars` (fixed, percentage, standard deviation, standard error, custom), `fillOpacity`.
16
+ - Per chart: `dataTable`, `upDownBars`, `categoryAxisDate` (date axis with time units), `categoryGroupLevels` (multi-level category labels), `categoryAxisScaling` (the x axis of scatter / bubble charts, or a date axis' range), `plotAreaLayout` / `titleLayout` / `legend.layout` (manual placement), `valueAxisLineHidden` / `categoryAxisLineHidden`, `valueAxis.displayUnitsLabel`.
17
+ - Data labels gain `showBubbleSize`, `showLegendKey`, `fillColor`, and per-point `text` (a literal label, e.g. naming one scatter point).
18
+ - A spec whose fields contradict each other (`view3D` on a scatter chart, error bars on a pie, a date axis with non-numeric categories, …) throws with a message naming the field, instead of writing a chart PowerPoint would repair.
19
+
20
+ Behavior changes when reading existing decks:
21
+
22
+ - A stock chart now reads as `kind: 'stock'` (was `'line'`) and a surface chart as `kind: 'surface'` (was `'column'`). `getPresentationChartKindCounts` has the two new keys.
23
+ - 3-D charts still read as their flat kind, now with `view3D` set, so `setChartSpec(chart, getShapeChartSpec(shape))` no longer flattens a 3-D chart.
24
+ - fix: for scatter / bubble charts `valueAxis` (and the other `valueAxis*` fields) described the **x** axis. They now describe the y axis; the x axis reads into `categoryAxis*` and `categoryAxisScaling`.
25
+ - fix: charts with more than 25 series wrote workbook references past column `Z` as invalid cell addresses.
26
+
27
+ `@office-kit/pptx-preview` draws stock charts as lines and surface charts as columns (as before), and honors `categoryAxisScaling` on scatter / bubble charts.
28
+
29
+ - 71deec5: feat: embed video, audio and online video with `addSlideMedia`, and read them back with `getShapeMedia`
30
+
31
+ - `addSlideMedia(slide, { kind: 'video' | 'audio', data, ... })` embeds a clip from bytes; `{ kind: 'online', url }` links an online video. YouTube watch / `youtu.be` / shorts URLs are rewritten to the embed URL; any other `http(s)` URL is stored as given, and anything else throws.
32
+ - The container is detected from the bytes (mp4, m4v, mov, webm, avi, wmv, mp3, wav, m4a, ogg, wma); pass `format` to override. An undetectable clip, an unreadable `poster`, or a bad URL throws before anything is added to the package.
33
+ - The new shape is a picture showing the poster frame: `setShapeImage` / `getShapeImageBytes` replace and read it. Without `poster`, a small built-in play-button image is used.
34
+ - The slide gets the `<p:video>` / `<p:audio>` time node PowerPoint writes itself, which is what makes the play controls appear in the slide show. Identical clip bytes are stored once per deck.
35
+ - `getShapeMedia(shape)` returns `{ kind: 'video' | 'audio', partName, contentType, bytes }` or `{ kind: 'online', url }`, also for media authored by PowerPoint, PptxGenJS or python-pptx. `findShapesWithMedia(slide)` lists a slide's clips.
36
+ - `@office-kit/pptx-dsl` gains a `Media` element with the same `kind` / `data` / `url` / `poster` props. It now requires `@office-kit/pptx` >= 0.17.0.
37
+ - `copyShape` gives the copied clip its own time node, `removeShape` / `clearSlideShapes` remove it, and `importSlide` now carries video / audio parts (copied once per clip) and online-video links across decks — previously an imported slide with media was left with dangling relationships.
38
+ - `setShapeAnimation` now works on a slide that holds a clip but no animation yet (it used to throw), and `clearSlideAnimations` keeps clips' time nodes instead of removing their play controls.
39
+ - fix: `duplicateSlide` copied a slide's video / audio bytes for every duplicate, because the library's video / audio / media relationship-type constants did not match the URIs PowerPoint writes. Clips are now shared between the original and the duplicate, as documented.
40
+ - `validatePresentation` reports a video / audio relationship whose part is missing, and a media time node whose shape is no longer on the slide.
41
+
42
+ ## 0.16.1
43
+
44
+ ### Patch Changes
45
+
46
+ - b8adc13: Duplicated slides now have independent charts, embedded workbooks and notes.
47
+ Editing a duplicate no longer changes the original's chart or notes. Unknown
48
+ owned dependency parts are copied too, including cyclic relationships. Layouts,
49
+ masters, themes and media remain shared. Invalid missing dependencies fail before
50
+ parts are added.
2
51
 
3
52
  ## 0.16.0
4
53
 
package/README.md CHANGED
@@ -28,6 +28,16 @@ const out: Uint8Array = await savePresentation(pres);
28
28
 
29
29
  ## Why this library
30
30
 
31
+ For declarative TSX authoring, use the companion
32
+ [`@office-kit/pptx-dsl`](packages/dsl) and
33
+ [`@office-kit/pptx-dev`](packages/dev) packages. They provide typed elements,
34
+ template editing, a local watch preview and PPTX export, with no React or Vue
35
+ runtime. The dev package initializes a project with VSCode tasks and a
36
+ `CLAUDE.md` authoring guide. Start with the
37
+ [authoring guide](https://office-kit.github.io/pptx/docs/authoring): install the
38
+ Claude Code skill and describe your presentation. The agent handles project
39
+ setup, preview and export using the published npm packages.
40
+
31
41
  - **It reads as well as it writes.** Open a deck made in PowerPoint, Keynote,
32
42
  or Google Slides, change it, and save it. Every setter has a getter, and
33
43
  there are deck-wide queries (find every hyperlink, every comment by an
@@ -40,9 +50,8 @@ const out: Uint8Array = await savePresentation(pres);
40
50
  breaks Keynote is treated as a bug.
41
51
  - **One ESM bundle for Node and the browser.** No `fs`, `Buffer`, or `zlib` on
42
52
  the hot path, and one runtime dependency ([fflate](https://github.com/101arrowz/fflate), for ZIP).
43
- - **You ship only what you import.** The API is 451 side-effect-free functions.
44
- A `load → save` bundle is about 56 KB unminified; the entire API is about
45
- 142 KB. CI fails if either grows past its cap (`test/tree-shake.test.ts`).
53
+ - **You ship only what you import.** The API uses side-effect-free functions.
54
+ CI checks tree-shaking and bundle-size limits (`test/tree-shake.test.ts`).
46
55
  - **Types follow the spec.** The model mirrors ECMA-376 Part 1 §19
47
56
  (PresentationML). Positions are branded `Emu` numbers, so inches and points
48
57
  cannot be mixed up by accident.
@@ -57,26 +66,25 @@ PptxGenJS cannot open it.
57
66
 
58
67
  Compared against PptxGenJS 4.0.1:
59
68
 
60
- | | `@office-kit/pptx` | PptxGenJS |
61
- | --------------------------------- | -------------------------------------------------------------------------------------------- | --------------------------------------------------------- |
62
- | Open and edit an existing `.pptx` | ✅ Load, change, save; unknown parts are preserved | ❌ Creates new files only |
63
- | Templates | Any `.pptx` a designer made in PowerPoint | Slide masters defined in code (`defineSlideMaster`) |
64
- | Read back what is in a deck | ✅ Every setter has a getter, plus deck-wide queries | ❌ The API is write-only |
65
- | API shape | Tree-shakeable functions (`addSlideChart(slide, …)`) | One class with methods (`slide.addChart(…)`) |
66
- | Module formats | ESM only | ESM, CommonJS, and a script-tag bundle |
67
- | Runtime dependencies | 1 (`fflate`) | 4 (`jszip`, `image-size`, `https`, `@types/node`) |
68
- | Slide transitions | ✅ | ❌ |
69
- | Animations | ✅ Four entrance / exit presets | ❌ |
70
- | Comments | ✅ | ❌ (speaker notes only) |
71
- | Chart types you can author | Bar, column, line, pie, doughnut, area; combos, secondary axis, trendlines, per-point labels | Those, plus scatter, bubble, radar, and 3D bar |
72
- | Audio, video, YouTube embeds | ❌ Not yet (media already in a deck is preserved) | ✅ |
73
- | HTML `<table>` to slides | ❌ | ✅ With automatic paging |
74
- | Render a slide to an image | ✅ SVG and PNG, via [`@office-kit/pptx-preview`](packages/preview) | ❌ |
75
- | How output is checked | Open XML SDK validator and ECMA-376 XSDs, in CI | Manual runs in PowerPoint and other apps before a release |
76
-
77
- **Pick PptxGenJS** if you only ever generate new decks and need video,
78
- scatter / bubble / radar charts, HTML-table import, CommonJS, or a
79
- `<script>`-tag build.
69
+ | | `@office-kit/pptx` | PptxGenJS |
70
+ | --------------------------------- | --------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
71
+ | Open and edit an existing `.pptx` | ✅ Load, change, save; unknown parts are preserved | ❌ Creates new files only |
72
+ | Templates | Any `.pptx` a designer made in PowerPoint | Slide masters defined in code (`defineSlideMaster`) |
73
+ | Read back what is in a deck | ✅ Every setter has a getter, plus deck-wide queries | ❌ The API is write-only |
74
+ | API shape | Tree-shakeable functions (`addSlideChart(slide, …)`) | One class with methods (`slide.addChart(…)`) |
75
+ | Module formats | ESM only | ESM, CommonJS, and a script-tag bundle |
76
+ | Runtime dependencies | 1 (`fflate`) | 4 (`jszip`, `image-size`, `https`, `@types/node`) |
77
+ | Slide transitions | ✅ | ❌ |
78
+ | Animations | ✅ Four entrance / exit presets | ❌ |
79
+ | Comments | ✅ | ❌ (speaker notes only) |
80
+ | Chart types you can author | ✅ All 16 ECMA-376 plot types, incl. 3-D, stock, surface, pie-of-pie; combos, error bars, data table, date axis | Bar, line, area, pie, doughnut, scatter, bubble, radar, 3-D bar / bubble; combos |
81
+ | Audio, video, YouTube embeds | ✅ Embedded from bytes (Node and browser), with read-back (`getShapeMedia`); clips de-duplicated per deck | ✅ From a path or base64; write-only |
82
+ | HTML `<table>` to slides | ❌ | ✅ With automatic paging |
83
+ | Render a slide to an image | ✅ SVG and PNG, via [`@office-kit/pptx-preview`](packages/preview) | ❌ |
84
+ | How output is checked | Open XML SDK validator and ECMA-376 XSDs, in CI | Manual runs in PowerPoint and other apps before a release |
85
+
86
+ **Pick PptxGenJS** if you only ever generate new decks and need HTML-table
87
+ import, CommonJS, or a `<script>`-tag build.
80
88
 
81
89
  **Pick `@office-kit/pptx`** if a template, an existing deck, or a validation
82
90
  requirement is involved, or if you need to read a deck as well as write one.
@@ -85,13 +93,14 @@ The two also work together. Decks written by PptxGenJS are part of this
85
93
  library's test fixtures (`test/pptxgenjs-compat.test.ts`), so you can generate
86
94
  with PptxGenJS and post-process the result here.
87
95
 
88
- ### The same slide in both
96
+ ### The same slide with PptxGenJS, the core API, and TSX
89
97
 
90
98
  ```ts
91
99
  // PptxGenJS
92
100
  import pptxgen from 'pptxgenjs';
93
101
 
94
102
  const pptx = new pptxgen();
103
+ pptx.layout = 'LAYOUT_WIDE';
95
104
  const slide = pptx.addSlide();
96
105
  slide.addText('Q3 Review', { x: 1, y: 0.5, w: 8, h: 1, fontSize: 28, bold: true });
97
106
  slide.addChart(
@@ -109,28 +118,21 @@ await pptx.writeFile({ fileName: 'out.pptx' });
109
118
  ```
110
119
 
111
120
  ```ts
112
- // @office-kit/pptx
113
- import {
114
- addBlankSlide,
115
- addSlideChart,
116
- addSlideTextBox,
117
- createPresentation,
118
- inches,
119
- setShapeTextFormat,
120
- } from '@office-kit/pptx';
121
- import { savePresentationToFile } from '@office-kit/pptx/node';
121
+ // @office-kit/pptx: the Node barrel includes the core API and file helpers.
122
+ import * as pptx from '@office-kit/pptx/node';
122
123
 
123
- const pres = createPresentation();
124
- const slide = addBlankSlide(pres);
125
- const title = addSlideTextBox(slide, {
124
+ const { inches } = pptx;
125
+ const pres = pptx.createPresentation();
126
+ const slide = pptx.addBlankSlide(pres);
127
+ const title = pptx.addSlideTextBox(slide, {
126
128
  x: inches(1),
127
129
  y: inches(0.5),
128
130
  w: inches(8),
129
131
  h: inches(1),
130
132
  text: 'Q3 Review',
131
133
  });
132
- setShapeTextFormat(title, { size: 28, bold: true });
133
- addSlideChart(slide, {
134
+ pptx.setShapeTextFormat(title, { size: 28, bold: true });
135
+ pptx.addSlideChart(slide, {
134
136
  x: inches(1),
135
137
  y: inches(1.5),
136
138
  w: inches(8),
@@ -141,13 +143,42 @@ addSlideChart(slide, {
141
143
  series: [{ name: 'Revenue', values: [120, 180] }],
142
144
  },
143
145
  });
144
- await savePresentationToFile(pres, 'out.pptx');
146
+ await pptx.savePresentationToFile(pres, 'out.pptx');
145
147
  ```
146
148
 
147
- Two differences show up straight away. Positions are explicit units
148
- (`inches(1)`, `cm(2.5)`, `pt(12)`) rather than bare numbers, and everything is
149
- a function that takes the thing it changes, which is what lets a bundler drop
150
- the ones you do not call.
149
+ ```tsx
150
+ // @office-kit/pptx-dsl — deck.tsx
151
+ import { Presentation, Slide, Text, Chart } from '@office-kit/pptx-dsl';
152
+
153
+ export default (
154
+ <Presentation>
155
+ <Slide>
156
+ <Text x={1} y={0.5} width={8} height={1} size={28} bold>
157
+ Q3 Review
158
+ </Text>
159
+ <Chart
160
+ x={1}
161
+ y={1.5}
162
+ width={8}
163
+ height={4}
164
+ spec={{
165
+ kind: 'column',
166
+ categories: ['Q1', 'Q2'],
167
+ series: [{ name: 'Revenue', values: [120, 180] }],
168
+ }}
169
+ />
170
+ </Slide>
171
+ </Presentation>
172
+ );
173
+ ```
174
+
175
+ Save the TSX example as `deck.tsx` in an
176
+ [initialized authoring project](https://office-kit.github.io/pptx/docs/tsx), then
177
+ run `npx --no-install office-pptx build deck.tsx --out out.pptx`.
178
+
179
+ The core API uses explicit units (`inches(1)`, `cm(2.5)`, `pt(12)`). The DSL uses
180
+ inches for numeric geometry and points for font sizes, and compiles to the same
181
+ core model. Both produce editable PowerPoint text and charts.
151
182
 
152
183
  ## How output is checked
153
184
 
@@ -178,11 +209,10 @@ of level 4. Items marked "post-1.0" are not implemented yet:
178
209
  | L3 | Authoring on top of existing themes / masters / layouts | ✅ |
179
210
  | L3 | Rebranding a deck: theme colors and theme fonts | ✅ |
180
211
  | L3 | Constructing new themes / masters / layouts from scratch | ❌ post-1.0 |
181
- | L3 | Charts: bar, column, line, pie, doughnut, area, and combos | ✅ |
182
- | L3 | Charts: scatter, radar, bubble | Read + preview only |
212
+ | L3 | Charts: all 16 plot types (incl. 3-D, stock, surface), and combos | ✅ |
183
213
  | L4 | Notes, comments, transitions | ✅ |
184
214
  | L4 | Simple animations (entrance / exit presets) | ✅ |
185
- | L4 | Audio / video authoring | ❌ post-1.0 (read pass-through) |
215
+ | L4 | Audio / video / online video: embed, link and read back | ✅ |
186
216
  | L4 | SmartArt authoring | ❌ post-1.0 (read pass-through) |
187
217
  | L4 | Complex animation timing trees | ❌ post-1.0 |
188
218
  | L4 | OLE / ActiveX authoring | ❌ post-1.0 (read pass-through) |
@@ -239,13 +269,31 @@ const out = await savePresentation(pres);
239
269
 
240
270
  ## Driving @office-kit/pptx from an AI agent
241
271
 
242
- [`skill/SKILL.md`](skill/SKILL.md) is a self-contained guide for an LLM agent
243
- authoring presentations with this library: the canonical call for each
244
- capability, the design rules that keep output from looking template-generated,
245
- the handful of API footguns worth memorizing, and a QA loop to run before
246
- declaring a deck done. Its [worked example](skill/examples/business-deck.md) is
247
- exercised by the test suite, so the code there is known to produce a
248
- schema-valid deck.
272
+ Install the Claude Code plugin once from the shared [Office Kit marketplace](https://github.com/office-kit/skills):
273
+
274
+ ```sh
275
+ claude plugin marketplace add office-kit/skills
276
+ claude plugin install pptx@office-kit
277
+ ```
278
+
279
+ In Claude Code, open `/plugin` → **Marketplaces** → **office-kit** and enable
280
+ **auto-update** once. Third-party marketplaces do not enable it by default.
281
+ Updates load after `/reload-plugins` or a new session.
282
+
283
+ Then ask Claude Code:
284
+
285
+ ```text
286
+ /pptx:office-kit-pptx Create a quarterly business review with a revenue chart
287
+ and a next-actions table. Use labelled sample data where needed.
288
+ ```
289
+
290
+ The [skill](skill/SKILL.md) creates a TSX project, starts the view-only preview,
291
+ checks the source and exports an editable PPTX. Ask for changes in the same
292
+ conversation; the preview updates as the agent edits TSX. You need Node.js 22.18+
293
+ and Git alongside Claude Code. See the
294
+ [authoring guide](https://office-kit.github.io/pptx/docs/authoring) for template
295
+ editing and manual setup. The bundled [core reference](skill/references/core-api.md)
296
+ and [tested example](skill/examples/business-deck.md) cover direct API use.
249
297
 
250
298
  ## Usage
251
299
 
@@ -319,7 +367,7 @@ setShapeText(findSlidePlaceholder(slide, 'ctrTitle')!, 'Authored with @office-ki
319
367
  const out: Uint8Array = await savePresentation(pres);
320
368
  ```
321
369
 
322
- ### Build a deck from a blank template
370
+ ### Add slides to your own template
323
371
 
324
372
  ```ts
325
373
  import {
@@ -336,7 +384,8 @@ import {
336
384
  setShapeText,
337
385
  } from '@office-kit/pptx';
338
386
 
339
- const pres = await loadPresentation(await fetch('/blank.pptx').then((r) => r.arrayBuffer()));
387
+ // Layout names come from the template; list them with getSlideLayouts(pres).
388
+ const pres = await loadPresentation(await fetch('/template.pptx').then((r) => r.arrayBuffer()));
340
389
 
341
390
  const titleLayout = findSlideLayout(pres, 'Title Slide')!;
342
391
  const slide1 = addSlide(pres, { layout: titleLayout });
@@ -360,6 +409,49 @@ moveSlide(pres, dup, 0);
360
409
  const out: Uint8Array = await savePresentation(pres);
361
410
  ```
362
411
 
412
+ ### Add video, audio or an online video
413
+
414
+ ```ts
415
+ import { addSlideMedia, getShapeMedia, inches } from '@office-kit/pptx';
416
+
417
+ const box = { x: inches(1), y: inches(1.5), w: inches(8), h: inches(4.5) };
418
+
419
+ // Embedded video / audio, from bytes. The container is detected from the bytes.
420
+ const clip = addSlideMedia(slide, {
421
+ kind: 'video',
422
+ data: mp4Bytes,
423
+ poster: posterPngBytes,
424
+ ...box,
425
+ });
426
+ addSlideMedia(slide, {
427
+ kind: 'audio',
428
+ data: mp3Bytes,
429
+ x: inches(0.5),
430
+ y: inches(6.5),
431
+ w: inches(0.6),
432
+ h: inches(0.6),
433
+ });
434
+
435
+ // Online video. YouTube watch / youtu.be / shorts URLs become the embed URL.
436
+ addSlideMedia(slide2, {
437
+ kind: 'online',
438
+ url: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
439
+ ...box,
440
+ });
441
+
442
+ const media = getShapeMedia(clip);
443
+ // { kind: 'video', partName: '/ppt/media/media1.mp4', contentType: 'video/mp4', bytes }
444
+ // an online video reads back as { kind: 'online', url }
445
+ ```
446
+
447
+ The shape is an ordinary picture showing the poster frame, so `setShapeImage`
448
+ replaces the poster and `getShapeImageBytes` reads it. Without `poster` a neutral
449
+ play-button image is used. Containers detected from the bytes: mp4, m4v, mov,
450
+ webm, avi, wmv, mp3, wav, m4a, ogg, wma — pass `format` when the signature
451
+ check cannot tell which of those a file is. Identical clip bytes are stored once
452
+ per deck, and the clip survives `copyShape`, `duplicateSlide` and `importSlide`.
453
+ Whether a clip plays depends on the codecs of the application showing the deck.
454
+
363
455
  ### Replace an image in place
364
456
 
365
457
  ```ts
@@ -497,7 +589,7 @@ shown together.
497
589
  | Transitions | `getSlideTransition` / `setSlideTransition` / `clearSlideTransition` |
498
590
  | Animations | `getShapeAnimation` / `setShapeAnimation` (`fadeIn` / `fadeOut` / `appear` / `disappear`), `clearSlideAnimations` |
499
591
  | Comments | `addSlideComment`, `getSlideComments`, `removeSlideComment`, `getCommentAuthors`, `getCommentText` / `getCommentAuthor` / `getCommentPosition` |
500
- | Shape authoring | `addSlideTextBox`, `addSlideShape`, `addSlideLine`, `addSlideTable`, `addSlideImage`, `addSlideChart` |
592
+ | Shape authoring | `addSlideTextBox`, `addSlideShape`, `addSlideLine`, `addSlideTable`, `addSlideImage`, `addSlideMedia`, `addSlideChart` |
501
593
  | Shape lookup | `findShapeByName`, `findShapesByName`, `findShapesByKind`, `findShapeInPresentation`, `getAllShapes`, `getSlideShapes` |
502
594
  | Shape text | `setShapeText`, `setShapeParagraphs`, `setShapeBullets`, `setShapeAlignment`, `setShapeTextFormat`, `setShapeHyperlink` / `getShapeHyperlink` |
503
595
  | Per-paragraph | `setParagraphAlignment` / `getParagraphAlignment`, `setParagraphLevel` / `getParagraphLevel`, `setParagraphBullet` / `getParagraphBullet`, `setParagraphSpacing` / `getParagraphSpacing`, `setParagraphLineSpacing` / `getParagraphLineSpacing`, `getParagraphEndFormat` |