@office-kit/pptx 0.15.0 → 0.16.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,5 +1,23 @@
1
1
  # pptx-kit
2
2
 
3
+ ## 0.16.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 22f1f30: feat: author and read the complex-script typeface (`<a:cs>`), and align the blank deck's font scheme with Office's
8
+
9
+ - `TextFormat.fontComplexScript` sets and reads `<a:cs typeface="…"/>`, the third typeface slot next to `font` (`<a:latin>`) and `fontEastAsian` (`<a:ea>`). It works everywhere the other two already did: `setShapeRunFormat` / `setShapeTextFormat`, `setShapeParagraphs` and `setTableCellParagraphs` (runs and `endFormat`), and it comes back from `getShapeRunFormat`, `getShapeRunFormatEffective` (including `+mj-cs` / `+mn-cs` and the theme's complex-script fallback), `getShapeParagraphElements`, `getParagraphEndFormat` and `getTableCellParagraphs`.
10
+ - `ChartTextStyle.fontComplexScript` writes `<a:cs>` on a chart title / axis / data-label / legend style, and the chart reader returns it. `font` keeps filling the Latin and East Asian slots only, so a chart can name a complex-script face on the legend alone.
11
+ - The three slots stay independent: setting one never changes the other two. Rewriting a slot writes its `typeface` attribute alone, so a `pitchFamily` / `charset` the source file carried on that element is dropped — as was already the case for `<a:latin>` and `<a:ea>`.
12
+ - `createPresentation`'s blank deck now carries the standard per-script font list (`<a:font script="Thai" typeface="Cordia New"/>` and its 46 siblings) in both `majorFont` and `minorFont`, the same list Office's own default theme ships. It is what resolves a face for text in a script no run names one for. Every deck `createPresentation()` produces therefore has a larger `theme1.xml` than before — its bytes change — and text in a script with no explicit typeface now resolves through that list.
13
+ - Chart titles and axis titles no longer carry a `lang="en-US"` language tag on their run. The chart-level tag is still `ChartSpec.language` (`<c:lang>`).
14
+
15
+ ## 0.15.1
16
+
17
+ ### Patch Changes
18
+
19
+ - d6036a5: Reject U+FFFE, U+FFFF, and unpaired surrogates in authored XML text and attributes, preventing malformed PPTX parts and silent replacement with U+FFFD. Valid supplementary characters, including emoji, remain unchanged.
20
+
3
21
  ## 0.15.0
4
22
 
5
23
  ### Minor Changes
package/README.md CHANGED
@@ -1,72 +1,208 @@
1
1
  # @office-kit/pptx
2
2
 
3
- Generate and edit `.pptx` (PowerPoint / Office Open XML Presentation) files
4
- from TypeScript — in **Node.js or the browser**, from a single ESM bundle.
5
-
6
- > **Status: 0.x — pre-1.0, public API still evolving.** The capabilities in
7
- > the table below are exercised against real PPTX fixtures, and emitted XML is
8
- > checked against the ECMA-376 schemas with `xmllint` where it is available on
9
- > the machine running the tests. Until the 1.0 release the public API is not
10
- > frozen — breaking changes can land in a minor (`0.x`) release, so pin a
3
+ [![npm](https://img.shields.io/npm/v/@office-kit/pptx)](https://www.npmjs.com/package/@office-kit/pptx)
4
+ [![CI](https://github.com/office-kit/pptx/actions/workflows/ci.yml/badge.svg)](https://github.com/office-kit/pptx/actions/workflows/ci.yml)
5
+ [![license](https://img.shields.io/npm/l/@office-kit/pptx)](./LICENSE)
6
+
7
+ Read, edit, and write `.pptx` (PowerPoint / Office Open XML Presentation) files
8
+ from TypeScript, in **Node.js and the browser**, from a single ESM bundle.
9
+
10
+ **[Documentation](https://office-kit.github.io/pptx/)** ·
11
+ **[Playground](https://office-kit.github.io/pptx/playground)** (inspect a deck in your browser) ·
12
+ **[REPL](https://office-kit.github.io/pptx/repl)** (write code, watch the deck redraw)
13
+
14
+ ```ts
15
+ import { loadPresentation, replaceTokensInPresentation, savePresentation } from '@office-kit/pptx';
16
+
17
+ // A designer makes template.pptx in PowerPoint. Your code fills it in.
18
+ const pres = await loadPresentation(templateBytes);
19
+ replaceTokensInPresentation(pres, { name: 'Alice', event: 'Re:Invent', date: '2026-12-01' });
20
+ const out: Uint8Array = await savePresentation(pres);
21
+ ```
22
+
23
+ > **Status: 0.x, pre-1.0.** The capabilities in the [scope table](#scope) are
24
+ > exercised against real PPTX fixtures and validated in CI (see
25
+ > [How output is checked](#how-output-is-checked)). Until 1.0 the public API is
26
+ > not frozen: a breaking change can land in a minor (`0.x`) release, so pin a
11
27
  > version or an exact range.
12
28
 
13
- ## Why
29
+ ## Why this library
30
+
31
+ - **It reads as well as it writes.** Open a deck made in PowerPoint, Keynote,
32
+ or Google Slides, change it, and save it. Every setter has a getter, and
33
+ there are deck-wide queries (find every hyperlink, every comment by an
34
+ author, every slide with an empty title).
35
+ - **Parts it does not model survive the round trip.** SmartArt, OLE objects,
36
+ video, modern threaded comments, and vendor extensions are carried through
37
+ untouched. The library never silently strips what it does not understand.
38
+ - **The output is valid, not "valid enough".** Microsoft's own
39
+ `OpenXmlValidator` gates every CI run. A file that opens in PowerPoint but
40
+ breaks Keynote is treated as a bug.
41
+ - **One ESM bundle for Node and the browser.** No `fs`, `Buffer`, or `zlib` on
42
+ 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`).
46
+ - **Types follow the spec.** The model mirrors ECMA-376 Part 1 §19
47
+ (PresentationML). Positions are branded `Emu` numbers, so inches and points
48
+ cannot be mixed up by accident.
49
+
50
+ ## How it differs from PptxGenJS
51
+
52
+ [PptxGenJS](https://github.com/gitbrent/PptxGenJS) is the established way to
53
+ generate a deck in JavaScript, and it is good at that job. The difference is
54
+ direction: **PptxGenJS writes new files; `@office-kit/pptx` reads, edits, and
55
+ writes.** If your deck starts from a template, or from a file a person made,
56
+ PptxGenJS cannot open it.
57
+
58
+ Compared against PptxGenJS 4.0.1:
59
+
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.
80
+
81
+ **Pick `@office-kit/pptx`** if a template, an existing deck, or a validation
82
+ requirement is involved, or if you need to read a deck as well as write one.
83
+
84
+ The two also work together. Decks written by PptxGenJS are part of this
85
+ library's test fixtures (`test/pptxgenjs-compat.test.ts`), so you can generate
86
+ with PptxGenJS and post-process the result here.
87
+
88
+ ### The same slide in both
14
89
 
15
- The JavaScript ecosystem has several PPTX libraries, but they typically pick
16
- one trade-off:
90
+ ```ts
91
+ // PptxGenJS
92
+ import pptxgen from 'pptxgenjs';
93
+
94
+ const pptx = new pptxgen();
95
+ const slide = pptx.addSlide();
96
+ slide.addText('Q3 Review', { x: 1, y: 0.5, w: 8, h: 1, fontSize: 28, bold: true });
97
+ slide.addChart(
98
+ pptx.ChartType.bar,
99
+ [{ name: 'Revenue', labels: ['Q1', 'Q2'], values: [120, 180] }],
100
+ {
101
+ x: 1,
102
+ y: 1.5,
103
+ w: 8,
104
+ h: 4,
105
+ barDir: 'col',
106
+ },
107
+ );
108
+ await pptx.writeFile({ fileName: 'out.pptx' });
109
+ ```
17
110
 
18
- - **Node-only** with a Buffer-shaped API → does not work in the browser.
19
- - **Browser-only** wrapping a fixed template → cannot author from scratch.
20
- - **Loose XML strings** that "usually open" → break in Keynote / Google Slides
21
- / the Open XML SDK validator.
111
+ ```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';
22
122
 
23
- `@office-kit/pptx` is built around a different stance:
123
+ const pres = createPresentation();
124
+ const slide = addBlankSlide(pres);
125
+ const title = addSlideTextBox(slide, {
126
+ x: inches(1),
127
+ y: inches(0.5),
128
+ w: inches(8),
129
+ h: inches(1),
130
+ text: 'Q3 Review',
131
+ });
132
+ setShapeTextFormat(title, { size: 28, bold: true });
133
+ addSlideChart(slide, {
134
+ x: inches(1),
135
+ y: inches(1.5),
136
+ w: inches(8),
137
+ h: inches(4),
138
+ spec: {
139
+ kind: 'column',
140
+ categories: ['Q1', 'Q2'],
141
+ series: [{ name: 'Revenue', values: [120, 180] }],
142
+ },
143
+ });
144
+ await savePresentationToFile(pres, 'out.pptx');
145
+ ```
24
146
 
25
- - One ESM bundle that runs in **Node and the browser**.
26
- - A typed object model that mirrors the **OOXML PresentationML** spec
27
- (ECMA-376 Part 1, §19). When the spec says something is a choice, our types
28
- say it is a discriminated union.
29
- - Output that passes Microsoft's
30
- [Open XML SDK Productivity Tool](https://github.com/dotnet/Open-XML-SDK)
31
- validator, not just PowerPoint's "open and pray."
32
- - Two complementary paths: **author from scratch** _or_ **edit a template**.
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.
151
+
152
+ ## How output is checked
153
+
154
+ - **Open XML SDK.** A CI job generates the sample decks and runs Microsoft's
155
+ `OpenXmlValidator` over them (`tools/ooxml-validate`). Any validation error
156
+ fails the build.
157
+ - **ECMA-376 XSDs.** Emitted XML is validated against the official schemas
158
+ with `xmllint` in the test suite (these tests skip on a machine without
159
+ `xmllint`).
160
+ - **Real files and a parity corpus.** Fixture tests round-trip decks written
161
+ by python-pptx and PptxGenJS, and `test/corpus` authors the same slide once
162
+ with PptxGenJS and once with this library, then diffs the two drawing trees.
163
+ The suite runs on Node 22, 24, and 26.
164
+ - **At runtime.** `validatePresentation(pres)` checks package invariants
165
+ (missing relationships, dangling slide ids, duplicate shape ids, layouts
166
+ without masters) in Node and the browser.
33
167
 
34
168
  ## Scope
35
169
 
36
- The work is split into four levels of completeness. The current `0.x` line
37
- covers levels 1-3 in full and level 4 in part; the table tracks where each
38
- capability stands today. Items marked "post-1.0" are not implemented yet:
170
+ The work is split into four levels. The `0.x` line covers levels 1–3 and part
171
+ of level 4. Items marked "post-1.0" are not implemented yet:
39
172
 
40
173
  | Level | Capability | 0.x |
41
174
  | ----- | ------------------------------------------------------------------- | ------------------------------- |
42
175
  | L1 | Read an existing PPTX, save it back without corruption | ✅ |
43
- | L2 | Template edit — text replacement, image swap, add slide from layout | ✅ |
44
- | L3 | Authoring — shapes, text, tables, fills, effects, transforms | ✅ |
176
+ | L2 | Template edit: text replacement, image swap, add slide from layout | ✅ |
177
+ | L3 | Authoring: shapes, text, tables, fills, effects, transforms, groups | ✅ |
45
178
  | L3 | Authoring on top of existing themes / masters / layouts | ✅ |
179
+ | L3 | Rebranding a deck: theme colors and theme fonts | ✅ |
46
180
  | L3 | Constructing new themes / masters / layouts from scratch | ❌ post-1.0 |
47
- | L3 | Charts (all common types) with embedded data | ✅ |
181
+ | L3 | Charts: bar, column, line, pie, doughnut, area, and combos | ✅ |
182
+ | L3 | Charts: scatter, radar, bubble | Read + preview only |
48
183
  | L4 | Notes, comments, transitions | ✅ |
49
- | L4 | Simple animations (entrance / exit / emphasis presets) | ✅ |
184
+ | L4 | Simple animations (entrance / exit presets) | ✅ |
185
+ | L4 | Audio / video authoring | ❌ post-1.0 (read pass-through) |
50
186
  | L4 | SmartArt authoring | ❌ post-1.0 (read pass-through) |
51
187
  | L4 | Complex animation timing trees | ❌ post-1.0 |
52
188
  | L4 | OLE / ActiveX authoring | ❌ post-1.0 (read pass-through) |
53
189
  | L4 | Document encryption (read + write) | ❌ post-1.0 |
54
190
 
55
- Out-of-scope content is still **preserved on round-trip** — `@office-kit/pptx` will
56
- never silently strip parts it doesn't model. That's the L1 contract.
191
+ Out-of-scope content is still **preserved on round-trip**. That is the L1
192
+ contract.
57
193
 
58
194
  When NOT to use this:
59
195
 
60
196
  - You need a **pixel-perfect** PPTX rendering (print, archival). The
61
197
  companion [`@office-kit/pptx-preview`](packages/preview) package renders slides to
62
- SVG in the browser and to PNG on the server — its closeness to LibreOffice
63
- is measured per slide and gated in CI (`site/fidelity`) — but it is a
198
+ SVG in the browser and to PNG on the server, and its closeness to LibreOffice
199
+ is measured per slide and gated in CI (`site/fidelity`). It is a
64
200
  high-fidelity preview, not a spec-complete paint engine. For
65
201
  pixel-authoritative output, use PowerPoint itself or LibreOffice headless.
66
- - You need a thin DSL for one-off "report" slides and do not care about
67
- schema validity. A simpler library will be lighter.
202
+ - You only generate new decks and need a feature in the PptxGenJS column
203
+ above. Use PptxGenJS.
68
204
  - You want to convert PPTX to another format (Keynote, ODP). Out of scope
69
- forever — that's a renderer's job.
205
+ forever; that is a renderer's job.
70
206
 
71
207
  ## Install
72
208
 
@@ -83,7 +219,8 @@ yarn add @office-kit/pptx
83
219
  @office-kit/pptx exposes a single tree-shakeable free-function API. Every
84
220
  capability is a named export — `loadPresentation`, `savePresentation`,
85
221
  `addSlideTextBox`, `setShapeFill`, etc. Bundlers drop every entry you
86
- don't import, so the minimal `load → save` bundle is **~60 KB**.
222
+ don't import, so the minimal `load → save` bundle is **about 56 KB**
223
+ unminified. CI enforces the bound in `test/tree-shake.test.ts`.
87
224
 
88
225
  ```ts
89
226
  import {
@@ -110,8 +247,6 @@ declaring a deck done. Its [worked example](skill/examples/business-deck.md) is
110
247
  exercised by the test suite, so the code there is known to produce a
111
248
  schema-valid deck.
112
249
 
113
- CI enforces the tree-shake bound in `test/tree-shake.test.ts`.
114
-
115
250
  ## Usage
116
251
 
117
252
  ### Edit a template
@@ -277,7 +412,7 @@ addSlideChart(slide!, {
277
412
  w: inches(8),
278
413
  h: inches(4.5),
279
414
  spec: {
280
- kind: 'column', // bar | column | line | pie | doughnut | area
415
+ kind: 'column', // bar | column | line | pie | doughnut | area (combine per series with `chartKind`)
281
416
  categories: ['Q1', 'Q2', 'Q3', 'Q4'],
282
417
  series: [
283
418
  { name: 'Revenue', values: [120, 180, 240, 300] },
@@ -378,26 +513,63 @@ shown together.
378
513
  | Shape removal | `removeShape` |
379
514
  | Tables | `getTableCell` / `getTableCells`, `setTableCellText` / `getTableCellText`, `setTableCellParagraphs` / `getTableCellParagraphs`, `setTableCellFill` / `clearTableCellFill`, `setTableCellAlignment`, `setTableCellTextFormat`, `insertTableRow` / `removeTableRow`, `insertTableColumn` / `removeTableColumn`, `mergeTableCells` / `getTableCellSpan` |
380
515
  | Charts | `addSlideChart`, `getSlideCharts`, `setChartSpec` — kinds: `bar`, `column`, `line`, `pie`, `doughnut`, `area`; axis tick labels via `categoryAxisTickLabelPos` / `valueAxisTickLabelPos` / `secondaryValueAxis.tickLabelPos`; axis line / gridline widths via `valueAxisLineWidthEmu`, `valueAxisMajorGridlineWidthEmu` and their category / secondary-axis mirrors; series `lineColor` / `markerColor` / `markerLineColor`; `dataLabels.showLeaderLines` |
381
- | Theme | `getPresentationTheme` — color scheme (`accent1`..`accent6`, `dark1`, `light1`, `hyperlink`, ...) |
516
+ | Theme | `getPresentationTheme` / `setPresentationTheme` — color scheme (`accent1`..`accent6`, `dark1`, `light1`, `hyperlink`, ...); `getPresentationFonts` / `setPresentationFonts` — major / minor Latin, East Asian, and complex-script faces |
517
+ | Groups | `groupShapes`, `ungroupShapes`, `getGroupChildren`, `getGroupTransform` |
518
+ | Text autofit | `setShapeTextAutoFit` / `getShapeTextAutoFit`, `getShapeTextAutoFitParams` |
519
+ | Preset adjustments | `setShapeAdjustValues`; `getShapeCustomGeometry` (custom geometry is read-only) |
520
+ | Document properties | `getCoreProperties` / `setCoreProperties`, `touchModified`, `getThumbnail` / `setThumbnail` / `removeThumbnail` |
521
+ | Package inspection | `listPackageParts`, `readPackagePart`, `getMediaParts`, `getOrphanMediaPartNames`, `getPackageSize`, `compactPackage` |
382
522
  | Validation | `validatePresentation(pres)` — invariant checks, returns `ValidationIssue[]` |
383
523
  | Units | `inches(n)`, `cm(n)`, `mm(n)`, `pt(n)`, `emu(n)` — return branded `Emu` numbers |
384
524
 
385
- Text formats (`TextFormat`, including a paragraph's `endFormat`) cover the Latin and East Asian typefaces (`font`, `fontEastAsian`). A complex-script typeface (`<a:cs>`) is neither read nor authored: it survives a load / save untouched, but is not carried over when paragraphs are rebuilt from what the getters return.
525
+ Text formats (`TextFormat`, including a paragraph's `endFormat`) cover the Latin, East Asian and complex-script typefaces (`font`, `fontEastAsian`, `fontComplexScript` — `<a:latin>`, `<a:ea>`, `<a:cs>`). Each is authored and read on its own; setting one leaves the others as they were. Chart labels carry the Latin / East Asian pair and the complex-script slot on `ChartTextStyle`: `font` fills `<a:latin>` and `<a:ea>`, `fontComplexScript` fills `<a:cs>`, and neither implies the other. Rebuilding a typeface writes the `typeface` attribute only, so any `pitchFamily` / `charset` the source file carried on that element is dropped.
526
+
527
+ Runs that name no face fall back to the theme's font scheme, which is also where a per-script list (`<a:font script="Thai" typeface="Cordia New"/>` and 46 siblings) lives. `createPresentation`'s blank deck carries Office's own list in both `majorFont` and `minorFont`.
528
+
529
+ Authored XML text and attribute values must contain only XML 1.0 characters. Illegal C0 controls (except tab, LF, and CR), U+FFFE, U+FFFF, and unpaired UTF-16 surrogates throw an error identifying the code point. Remove these characters before authoring; valid supplementary characters such as emoji are preserved.
530
+
531
+ ## Preview and text-overflow checks
532
+
533
+ [`@office-kit/pptx-preview`](packages/preview) is a companion package that
534
+ renders a slide to SVG (browser and Node) or to PNG (Node, via resvg, with no
535
+ headless Office). `auditTextLayout` reports text that overflows its box or
536
+ wraps unexpectedly, which is how an automated pipeline catches a broken slide
537
+ before a person sees it.
538
+
539
+ ```ts
540
+ import { getSlides } from '@office-kit/pptx';
541
+ import { renderSlideToSvg } from '@office-kit/pptx-preview';
542
+
543
+ const svg = renderSlideToSvg(pres, getSlides(pres)[0]!);
544
+ ```
545
+
546
+ ## Office Kit
547
+
548
+ `@office-kit/pptx` is one of three libraries built on the same rules: the
549
+ ECMA-376 spec is the source of truth, output has to validate, and one ESM
550
+ build has to run everywhere.
551
+
552
+ | Package | Files |
553
+ | -------------------------------------------------------- | ------------------ |
554
+ | [`@office-kit/pptx`](https://github.com/office-kit/pptx) | PowerPoint `.pptx` |
555
+ | [`@office-kit/xlsx`](https://github.com/office-kit/xlsx) | Excel `.xlsx` |
556
+ | [`@office-kit/docx`](https://github.com/office-kit/docx) | Word `.docx` |
386
557
 
387
558
  ## Compatibility
388
559
 
389
- - **Node**: >= 24.16.
560
+ - **Node**: >= 22.18 (CI runs 22, 24, and 26).
390
561
  - **Browsers**: current and current-1 of Chrome, Firefox, Safari, Edge.
391
562
  - **TypeScript**: >= 5.4 (for strict `satisfies` and `const` type parameters).
392
- - **Output**: PPTX files checked against the ECMA-376 schemas with `xmllint`
393
- where it is available, and smoke-tested against PowerPoint (current),
394
- Keynote (current), Google Slides, and LibreOffice Impress.
563
+ - **Output**: validated with the Open XML SDK and the ECMA-376 schemas (see
564
+ [How output is checked](#how-output-is-checked)), and smoke-tested against
565
+ PowerPoint (current), Keynote (current), Google Slides, and LibreOffice
566
+ Impress.
395
567
 
396
568
  ## Development
397
569
 
398
570
  ```sh
399
571
  git clone --recurse-submodules git@github.com:office-kit/pptx.git
400
- cd @office-kit/pptx
572
+ cd pptx
401
573
  pnpm install
402
574
  pnpm test
403
575
  ```