@s8fy/pptx-parser 1.0.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/LICENSE ADDED
@@ -0,0 +1,36 @@
1
+ Default license for this package's original code and modifications:
2
+
3
+ PolyForm Noncommercial License 1.0.0
4
+ https://polyformproject.org/licenses/noncommercial/1.0.0
5
+
6
+ Required Notice: Copyright (c) 2025-PRESENT hustcer
7
+
8
+ This package is source-available, not open source. No commercial use is granted
9
+ to third parties for the licensor's original code and modifications unless
10
+ separately licensed.
11
+
12
+ Third-party notice:
13
+ This package includes material derived from `pptxtojson` by `pipipi-pikachu`.
14
+ The preserved upstream MIT notice for those portions is reproduced below.
15
+
16
+ MIT License
17
+
18
+ Copyright (c) 2020-PRESENT pipipi-pikachu
19
+
20
+ Permission is hereby granted, free of charge, to any person obtaining a copy
21
+ of this software and associated documentation files (the "Software"), to deal
22
+ in the Software without restriction, including without limitation the rights
23
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
24
+ copies of the Software, and to permit persons to whom the Software is
25
+ furnished to do so, subject to the following conditions:
26
+
27
+ The above copyright notice and this permission notice shall be included in all
28
+ copies or substantial portions of the Software.
29
+
30
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
31
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
32
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
33
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
34
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
35
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
36
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,336 @@
1
+ # @s8fy/pptx-parser
2
+
3
+ A browser-first TypeScript library for parsing `.pptx` files into structured data with JavaScript or WebAssembly, and converting original PPTX bytes to PDF. Node.js **22.12+** is also supported through ESM and CommonJS entrypoints.
4
+
5
+ ### Architecture
6
+
7
+ - **Structured text output** — text content outputs `TextParagraph[]` JSON arrays with full formatting metadata, replacing raw HTML strings and eliminating `dangerouslySetInnerHTML`
8
+ - **Pure data output** — parser only outputs raw parsed data; model adaptation lives in `@s8fy/pptx-core` and DOM/SVG rendering lives in `@s8fy/pptx-renderer`
9
+ - **Full TypeScript rewrite** — all source files converted from JavaScript to TypeScript with strict mode
10
+
11
+ ### New Features
12
+
13
+ - **Embedded font extraction** — extracts font binary data (EOT/ODTTF/TTF/OTF/WOFF) from PPTX embedded font list; opt-in via `extractFonts` option (default `false` to avoid loading large CJK fonts)
14
+ - **Arrow endpoints** — parses `a:headEnd`/`a:tailEnd` on lines with support for arrow, stealth, diamond, dot marker types and sm/med/lg sizing
15
+ - **Chart type mapping** — OOXML chart types mapped to simplified types (`bar`, `column`, `line`, `pie`, `ring`, `area`, `radar`, `scatter`) directly in the parser
16
+
17
+ ## Install
18
+
19
+ ```sh
20
+ pnpm add @s8fy/pptx-parser
21
+ # or
22
+ npm install @s8fy/pptx-parser
23
+ ```
24
+
25
+ ## Usage
26
+
27
+ ### Browser
28
+
29
+ ```html
30
+ <input
31
+ type="file"
32
+ accept="application/vnd.openxmlformats-officedocument.presentationml.presentation"
33
+ />
34
+ ```
35
+
36
+ ```javascript
37
+ import { parsePptx, revokeBlobUrls } from '@s8fy/pptx-parser'
38
+
39
+ const input = document.querySelector('input[type="file"]')
40
+ if (!(input instanceof HTMLInputElement)) throw new Error('Missing file input')
41
+ input.addEventListener('change', async () => {
42
+ const file = input.files?.[0]
43
+ if (!file) return
44
+ try {
45
+ const result = await parsePptx(await file.arrayBuffer(), { parser: 'wasm' })
46
+ console.log(result)
47
+ // Keep result while its media is used; see cleanup below.
48
+ revokeBlobUrls(result)
49
+ } catch (error) {
50
+ console.error(error)
51
+ }
52
+ })
53
+ ```
54
+
55
+ ### Node.js
56
+
57
+ ```javascript
58
+ const { parsePptx } = require('@s8fy/pptx-parser')
59
+ const { readFileSync } = require('node:fs')
60
+
61
+ async function main() {
62
+ const bytes = readFileSync('test.pptx')
63
+ const buffer = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength)
64
+ const result = await parsePptx(buffer)
65
+ console.log(result)
66
+ }
67
+
68
+ main().catch(console.error)
69
+ ```
70
+
71
+ ## API
72
+
73
+ ### `parsePptx(file: ArrayBuffer, options?: ParseOptions): Promise<ParseResult>`
74
+
75
+ #### Options
76
+
77
+ | Option | Type | Default | Description |
78
+ | -------------- | ---------------- | ---------------- | --------------------------------------------------------------- |
79
+ | `extractFonts` | `boolean` | `false` | Whether to extract embedded font binary data |
80
+ | `parser` | `'js' \| 'wasm'` | `'js'` | Parser engine for this low-level package |
81
+ | `unzipMode` | `'js' \| 'wasm'` | `'wasm'` | ZIP engine when `parser: 'wasm'` |
82
+ | `wasmUrl` | `string \| URL` | Package-relative | Override the parser WASM URL for CDN or self-hosted deployments |
83
+ | `timing` | `boolean` | `false` | Include per-phase timing in the optional `_timing` result field |
84
+ | `signal` | `AbortSignal` | None | Cancel this parse request without cancelling other callers |
85
+
86
+ `@s8fy/pptx-core`, the renderer, and the viewer facade default to the WASM parser. The low-level parser defaults to JavaScript for backward compatibility.
87
+
88
+ The package ships package-relative JS/WASM/PDF workers and both WASM binaries. Modern bundlers such as Vite copy these assets from the static runtime references. The UMD entry resolves adjacent assets from the current `<script>` URL, so keep the published `dist/` files together when loading it directly. CommonJS is intended for Node.js/SSR and can load the packaged WASM files directly when `parser: 'wasm'` is requested. Browser applications should prefer the ESM entry. Both the parser and PDF APIs accept explicit `wasmUrl` overrides; the PDF API additionally accepts `workerUrl`.
89
+
90
+ ### Parser WASM deployment
91
+
92
+ Automatic package-relative resolution is the default. For a CDN, offline bundle, or private deployment, copy `node_modules/@s8fy/pptx-parser/dist/mbt/main.wasm` into the application's static assets and pass its deployed URL:
93
+
94
+ ```ts
95
+ await parsePptx(buffer, {
96
+ parser: 'wasm',
97
+ wasmUrl: '/vendor/s8fy/main.wasm',
98
+ })
99
+ ```
100
+
101
+ - The package-relative Worker/WASM paths are covered by the repository's Vite consumer smoke. For other bundlers, verify the deployed assets; use the bundler's public/copy-asset facility when supplying an override.
102
+ - Next.js and Nuxt serve the copied file from `public/`; call the SDK from client-side code and pass a root-relative URL.
103
+ - SvelteKit serves it from `static/`; pass the matching root-relative URL.
104
+ - Angular CLI can add the source file as an `assets` glob in `angular.json`, then pass the configured output URL.
105
+
106
+ The same-origin server should return the file without HTML fallback content and preferably with `Content-Type: application/wasm`. Cross-origin URLs must allow the application origin through CORS.
107
+
108
+ ### PDF and resource helpers
109
+
110
+ - `pptxToPdf(file, options?)` returns PDF bytes as `Uint8Array`.
111
+ - `pptxToPdfBlob(file, options?)` returns a browser `Blob`.
112
+ - `revokeBlobUrls(data)` releases Blob URLs created while parsing media.
113
+ - `transferBlobUrls(source, target)` moves cleanup ownership to a custom adapted object, without copying bytes. After transfer, clean up `target` instead of `source`.
114
+ - `ResourcePolicyError` and `RESOURCE_POLICY_CONTRACT` expose the parser's deterministic input/resource-limit contract.
115
+
116
+ PDF conversion accepts explicit `wasmUrl`, `wasmBytes`, and `workerUrl` overrides, font/emoji fallback sources, watermarks, and signed-license runtime data. Prefer the higher-level `@s8fy/pptx-core` facade unless the raw parser result is required.
117
+
118
+ ```ts
119
+ import { pptxToPdf } from '@s8fy/pptx-parser'
120
+
121
+ export async function convertFile(file: File): Promise<Uint8Array> {
122
+ return pptxToPdf(await file.arrayBuffer(), {
123
+ unicodeFallbackFont: { url: '/fonts/NotoSansSC-Regular.ttf' },
124
+ })
125
+ }
126
+ ```
127
+
128
+ Serve that font URL yourself or omit it for documents that do not need a Unicode fallback. The PDF API always uses its PDF WASM engine; choosing JS for parsing does not make PDF conversion JavaScript-only. PDF `wasmUrl` points to `pdf-converter.wasm`, not parser `main.wasm`. Browser-window conversion requires a working PDF Worker and does not silently retry on the main thread if the Worker fails. Node conversion runs on the current thread.
129
+
130
+ Both PDF functions accept `signal` for cancellation. Font options include `regularFonts` (with regular/bold/italic/boldItalic styles), `unicodeFallbackFont`, `mathFallbackFont`, `emojiFallbackFont`, and `emojiBitmapFallback`. Browser font CSS does not automatically supply PDF font bytes. The low-level `watermarks`/`licenseRuntime` options differ from core/viewer's `watermark`/`license` facade; do not interchange their option objects.
131
+
132
+ ### Cancellation, errors, and cleanup
133
+
134
+ Pass `new AbortController().signal` to parsing or PDF conversion, then call that controller's `abort()` when the request is obsolete. Cancellation rejects the request; synchronous work on the same JavaScript thread cannot be interrupted until control returns.
135
+
136
+ When a parsed result is no longer displayed or otherwise used, call `revokeBlobUrls(result)` on that same object. Media URLs may be `blob:` URLs rather than base64 strings and are not portable across sessions. A JSON clone does not retain the original cleanup ownership. Use `transferBlobUrls` only when intentionally replacing the owner with an adapted model.
137
+
138
+ `ResourcePolicyError.details` contains `reason`, `stage`, `subject`, `limit`, `observed`, `unit`, and `policyVersion`. These engineering limits are independent of commercial licenses (`commercialUpgradeApplicable` is false); catch and inspect the error rather than retrying the same oversized input. Other parse/conversion failures can be ordinary `Error` instances.
139
+
140
+ #### ParseResult
141
+
142
+ ```typescript
143
+ {
144
+ slides: Slide[]
145
+ themeColors: string[]
146
+ hlinkColor: string | null
147
+ fonts: EmbeddedFont[]
148
+ size: {
149
+ width: number // px
150
+ height: number // px
151
+ }
152
+ }
153
+ ```
154
+
155
+ ## Output Example
156
+
157
+ ```javascript
158
+ {
159
+ slides: [
160
+ {
161
+ fill: { type: 'color', value: '#FFFFFF' },
162
+ elements: [
163
+ {
164
+ type: 'text',
165
+ left: 100,
166
+ top: 50,
167
+ width: 600,
168
+ height: 80,
169
+ content: [
170
+ {
171
+ align: 'center',
172
+ lineHeight: 1.2,
173
+ runs: [
174
+ {
175
+ text: 'Hello World',
176
+ fontSize: '24pt',
177
+ fontFamily: 'Calibri',
178
+ fontWeight: 'bold',
179
+ color: '#333333'
180
+ }
181
+ ]
182
+ }
183
+ ],
184
+ vAlign: 'mid',
185
+ name: 'Title 1',
186
+ order: 0,
187
+ // ...
188
+ },
189
+ // more elements...
190
+ ],
191
+ layoutElements: [/* master/layout elements */],
192
+ note: 'Speaker notes...',
193
+ transition: { type: 'fade', duration: 500, direction: null }
194
+ },
195
+ // more slides...
196
+ ],
197
+ themeColors: ['#4472C4', '#ED7D31', '#A5A5A5', '#FFC000', '#5B9BD5', '#70AD47'],
198
+ hlinkColor: null,
199
+ fonts: [/* embedded fonts if extractFonts: true */],
200
+ size: { width: 960, height: 540 }
201
+ }
202
+ ```
203
+
204
+ ## Element Types
205
+
206
+ ### Text (`type: 'text'`)
207
+
208
+ Text box with structured paragraphs. `content` is a `TextParagraph[]` array containing runs with font/color/decoration info, list/bullet info, and paragraph-level alignment/spacing.
209
+
210
+ ### Shape (`type: 'shape'`)
211
+
212
+ Predefined or custom shapes. Also uses `TextParagraph[]` for `content`. Includes `shapType`, optional `path` (SVG), `keypoints`, and arrow endpoints (`headEnd`/`tailEnd`).
213
+
214
+ ### Image (`type: 'image'`)
215
+
216
+ Image element with `src` (data URI or an owned Blob URL), optional `rect` (crop), `geom` (clip shape), and `filters` (sharpen, brightness, contrast, saturation, color temperature).
217
+
218
+ ### Table (`type: 'table'`)
219
+
220
+ Table with `data` (2D `TableCell[][]`), `rowHeights`, `colWidths`. Cells support `rowSpan`/`colSpan` merging, per-cell borders and fill.
221
+
222
+ ### Chart (`type: 'chart'`)
223
+
224
+ Chart data with `chartType` (`bar`, `column`, `line`, `pie`, `ring`, `area`, `radar`, `scatter`), `rawChartType` (original OOXML type), `colors`, and optional `barDir`, `marker`, `holeSize`, `grouping`, `style`.
225
+
226
+ ### Video (`type: 'video'`)
227
+
228
+ Video element with `blob` (Blob URL) or `src` (external URL), optional `poster` (thumbnail image from slide), `ext` (file extension), and `rotate`.
229
+
230
+ ### Audio (`type: 'audio'`)
231
+
232
+ Audio element with `blob` (Blob URL, optional if format unsupported), optional `ext` (file extension), and `rotate`.
233
+
234
+ ### Diagram (`type: 'diagram'`)
235
+
236
+ SmartArt with `elements` (Shape/Text sub-elements) and `textList` (fallback text content).
237
+
238
+ ### Math (`type: 'math'`)
239
+
240
+ Math formula with `latex` (LaTeX expression), `picBase64` (fallback image), and optional `text` (mixed text/formula content).
241
+
242
+ ### Group (`type: 'group'`)
243
+
244
+ Group container with nested `elements` array.
245
+
246
+ ## Structured Text Format
247
+
248
+ Text content uses structured `TextParagraph[]` instead of HTML strings:
249
+
250
+ ```typescript
251
+ interface TextParagraph {
252
+ align: string // 'left' | 'center' | 'right' | 'justify'
253
+ lineHeight?: number // CSS line-height value
254
+ spaceBefore?: string // e.g. '12pt'
255
+ spaceAfter?: string // e.g. '6pt'
256
+ list?: {
257
+ type: 'ul' | 'ol'
258
+ level: number
259
+ bullet?: BulletInfo
260
+ }
261
+ runs: (TextRun | TextLink | TextBreak)[]
262
+ }
263
+
264
+ interface TextRun {
265
+ text: string
266
+ color?: string | GradientColor
267
+ fontSize?: string // e.g. '18pt'
268
+ fontFamily?: string
269
+ fontWeight?: string // e.g. 'bold'
270
+ fontStyle?: string // e.g. 'italic'
271
+ textDecoration?: string // e.g. 'underline'
272
+ textDecorationLine?: string // e.g. 'line-through'
273
+ letterSpacing?: string
274
+ verticalAlign?: string // 'super' | 'sub'
275
+ textShadow?: string
276
+ highlightColor?: string
277
+ }
278
+
279
+ interface TextLink extends TextRun {
280
+ linkURL: string
281
+ linkColor?: string
282
+ }
283
+
284
+ interface TextBreak {
285
+ type: 'br'
286
+ }
287
+ ```
288
+
289
+ ## Fill Types
290
+
291
+ All fill-capable elements support four fill types:
292
+
293
+ - **Color** (`type: 'color'`) — solid color value
294
+ - **Image** (`type: 'image'`) — base64 image with opacity
295
+ - **Gradient** (`type: 'gradient'`) — linear/circle/rect/shape gradient with color stops
296
+ - **Pattern** (`type: 'pattern'`) — pattern type with foreground/background colors
297
+
298
+ ## Embedded Fonts
299
+
300
+ When `extractFonts: true` is passed:
301
+
302
+ ```typescript
303
+ interface EmbeddedFont {
304
+ typeface: string
305
+ styles: {
306
+ regular?: { path: string; data: ArrayBuffer }
307
+ bold?: { path: string; data: ArrayBuffer }
308
+ italic?: { path: string; data: ArrayBuffer }
309
+ boldItalic?: { path: string; data: ArrayBuffer }
310
+ }
311
+ }
312
+ ```
313
+
314
+ ## Build Outputs
315
+
316
+ | Format | File | Usage |
317
+ | ------ | ------------------- | --------------------------------------------- |
318
+ | ESM | `dist/index.js` | Modern bundlers (Vite, Webpack 5+) |
319
+ | CJS | `dist/index.cjs` | Node.js `require()` |
320
+ | UMD | `dist/index.umd.js` | Browser `<script>` tag (global: `pptxParser`) |
321
+
322
+ ## Type Definitions
323
+
324
+ The package root exports the complete bundled TypeScript declarations through its `types` and conditional `exports` fields. No deep import is required.
325
+
326
+ WASM parsing/PDF conversion requires WebAssembly GC support. Workers may be unavailable in some hosts: parsing has a main-thread fallback, while browser-window PDF conversion requires its Worker. The parsed model is a subset of PowerPoint behavior; output presence does not imply pixel-identical rendering or support for every animation/chart effect.
327
+
328
+ ## Credits
329
+
330
+ This library is based on [pptxtojson](https://github.com/pipipi-pikachu/pptxtojson) by pipipi-pikachu.
331
+
332
+ ## License
333
+
334
+ Default license for original contributions: `PolyForm-Noncommercial-1.0.0`
335
+
336
+ Commercial use of those contributions requires a separate commercial license.