html-native-pptx 0.1.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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Davin Surya
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,124 @@
1
+ # html-native-pptx
2
+
3
+ Convert HTML/CSS layouts into native, fully-editable PowerPoint (`.pptx`) presentations via OpenXML DrawingML.
4
+
5
+ ## Overview
6
+
7
+ Unlike screenshot-based or wrapper libraries, `html-native-pptx` leverages headless Chromium to resolve modern CSS (Flexbox, CSS Grid, custom fonts, rounded corners), constructs a strongly-typed Abstract Syntax Tree (IR), and compiles directly into clean OpenXML PresentationML.
8
+
9
+ - **Fully Editable:** Texts remain real PowerPoint text runs, shapes remain vector shapes.
10
+ - **Accurate CSS:** Flexbox, Grid, absolute positions, padding, and borders are resolved natively by the browser engine.
11
+ - **High Performance:** Lightweight, zero external PowerPoint runtime dependencies (no LibreOffice required).
12
+
13
+ ## Key Features
14
+
15
+ - **Fully Editable & Native:** Output contains real PowerPoint shapes and text runs with authentic typography, alignments, and colors—never rasterized screenshots.
16
+ - **Accurate CSS Engine:** Headless Chromium accurately evaluates Flexbox, CSS Grid, absolute positioning, borders, and rounded corners.
17
+ - **Multi-Slide Harvesting:** Split presentation decks across multiple slides seamlessly via `slideSelector` (e.g. `.slide`).
18
+ - **Portable Custom Font Embedding:** Automatically parses `@font-face` rules or accepts custom `.ttf`/`.otf` files, converts them to Microsoft EOT (`.fntdata`), and embeds them directly inside the `.pptx` container so custom fonts render identically across all computers without requiring local OS installation!
19
+ - **Pure Node.js & Zero Office Dependencies:** Generates compliant OpenXML PresentationML archives directly with JSZip. No LibreOffice or MS Office installation required on the server.
20
+
21
+ ## Installation
22
+
23
+ ```bash
24
+ npm install html-native-pptx
25
+ ```
26
+
27
+ ## Quick Start
28
+
29
+ ### Basic HTML to PPTX Conversion
30
+
31
+ ```ts
32
+ import * as fs from 'node:fs/promises';
33
+ import { convertHtmlToPptx } from 'html-native-pptx';
34
+
35
+ const html = `
36
+ <div style="width: 100vw; height: 100vh; background: #0f172a; display: flex; flex-direction: column; justify-content: center; align-items: center; color: white; font-family: sans-serif;">
37
+ <h1 style="font-size: 48px; margin-bottom: 16px;">Hello from HTML</h1>
38
+ <p style="font-size: 24px; color: #94a3b8;">Rendered as native vector OpenXML PowerPoint shapes!</p>
39
+ </div>
40
+ `;
41
+
42
+ const pptxBuffer = await convertHtmlToPptx(html, {
43
+ aspect: '16:9',
44
+ viewport: { width: 1920, height: 1080 },
45
+ });
46
+
47
+ await fs.writeFile('presentation.pptx', pptxBuffer);
48
+ ```
49
+
50
+ ### Multi-Slide Presentation Deck with Embedded Custom Fonts
51
+
52
+ ```ts
53
+ import * as fs from 'node:fs/promises';
54
+ import * as path from 'node:path';
55
+ import { convertHtmlToPptx } from 'html-native-pptx';
56
+
57
+ const html = await fs.readFile('curatorial-deck.html', 'utf-8');
58
+
59
+ const pptxBuffer = await convertHtmlToPptx(html, {
60
+ slideSelector: '.slide', // Target each slide container
61
+ aspect: '16:9',
62
+ basePath: './fonts', // Auto-resolve @font-face local font files
63
+ autoEmbedFonts: true, // Automatically embed custom fonts into PPTX
64
+ });
65
+
66
+ await fs.writeFile('curatorial-deck.pptx', pptxBuffer);
67
+ ```
68
+
69
+ ### Explicit Font Embedding
70
+
71
+ You can also pass font files or buffers directly:
72
+
73
+ ```ts
74
+ const pptxBuffer = await convertHtmlToPptx(html, {
75
+ slideSelector: '.slide',
76
+ fonts: [
77
+ {
78
+ typeface: 'Telkomsel Batik Sans',
79
+ src: './fonts/Telkomsel-Batik-Sans-Reconstructed.ttf',
80
+ },
81
+ ],
82
+ });
83
+ ```
84
+
85
+ ## Pipeline Architecture
86
+
87
+ ```
88
+ [ HTML / CSS String or URL ]
89
+
90
+
91
+ Phase 1: Headless DOM Harvester (Puppeteer)
92
+ - Evaluates computed styles, bounding rects, and @font-face rules
93
+
94
+
95
+ Phase 2: Intermediate Representation (IR Normalizer)
96
+ - Normalizes coordinates into Inches/EMU and parses typography
97
+
98
+
99
+ Phase 3: OpenXML PresentationML Compiler (JSZip)
100
+ - Injects embedded fonts (.fntdata), layouts, themes, and DrawingML trees
101
+
102
+
103
+ [ Output .pptx Buffer ]
104
+ ```
105
+
106
+ ## Development
107
+
108
+ ```bash
109
+ # Install dependencies
110
+ npm install
111
+
112
+ # Typecheck
113
+ npm run typecheck
114
+
115
+ # Run test suites
116
+ npm test
117
+
118
+ # Build dual ESM / CJS bundle
119
+ npm run build
120
+ ```
121
+
122
+ ## License
123
+
124
+ MIT © Davin Surya
@@ -0,0 +1,337 @@
1
+ import { Browser } from 'puppeteer';
2
+
3
+ /**
4
+ * Intermediate Representation (IR) AST for html-native-pptx.
5
+ * Decouples the DOM crawler (Harvester) from the PresentationML generator (Compiler).
6
+ */
7
+ declare const UNITS: {
8
+ readonly DPI: 96;
9
+ readonly PT_PER_INCH: 72;
10
+ readonly EMU_PER_INCH: 914400;
11
+ readonly SLIDE_16_9_WIDTH_INCHES: 13.333333;
12
+ readonly SLIDE_16_9_HEIGHT_INCHES: 7.5;
13
+ readonly SLIDE_16_9_WIDTH_EMU: 12192000;
14
+ readonly SLIDE_16_9_HEIGHT_EMU: 6858000;
15
+ readonly SLIDE_4_3_WIDTH_INCHES: 10;
16
+ readonly SLIDE_4_3_HEIGHT_INCHES: 7.5;
17
+ readonly SLIDE_4_3_WIDTH_EMU: 9144000;
18
+ readonly SLIDE_4_3_HEIGHT_EMU: 6858000;
19
+ };
20
+ type ElementType = 'container' | 'text' | 'image' | 'table';
21
+ type TextAlign = 'left' | 'center' | 'right' | 'justify';
22
+ /**
23
+ * Normalized bounding box in Inches.
24
+ */
25
+ interface BoundingBox {
26
+ x: number;
27
+ y: number;
28
+ w: number;
29
+ h: number;
30
+ }
31
+ /**
32
+ * Visual styling for rectangular containers and shapes.
33
+ */
34
+ interface ShapeStyle {
35
+ fillColor?: string;
36
+ fillOpacity?: number;
37
+ borderColor?: string;
38
+ borderWidth?: number;
39
+ borderStyle?: 'solid' | 'dashed' | 'dotted';
40
+ radius?: number;
41
+ shadow?: {
42
+ color: string;
43
+ blur: number;
44
+ offsetX: number;
45
+ offsetY: number;
46
+ opacity?: number;
47
+ };
48
+ }
49
+ /**
50
+ * Individual formatted text run inside a paragraph (maps to OpenXML <a:r>).
51
+ */
52
+ interface TextRun {
53
+ content: string;
54
+ fontFamily?: string;
55
+ fontSize?: number;
56
+ color?: string;
57
+ bold?: boolean;
58
+ italic?: boolean;
59
+ underline?: boolean;
60
+ strikethrough?: boolean;
61
+ }
62
+ /**
63
+ * Paragraph containing one or more formatted text runs (maps to OpenXML <a:p>).
64
+ */
65
+ interface ParagraphIR {
66
+ align?: TextAlign;
67
+ lineHeight?: number;
68
+ spaceBefore?: number;
69
+ spaceAfter?: number;
70
+ runs: TextRun[];
71
+ }
72
+ /**
73
+ * Flat text style convenience interface (for simple, single-style text elements).
74
+ */
75
+ interface TextStyle {
76
+ fontFamily: string;
77
+ fontSize: number;
78
+ color: string;
79
+ bold?: boolean;
80
+ italic?: boolean;
81
+ underline?: boolean;
82
+ align?: TextAlign;
83
+ }
84
+ /**
85
+ * Primary Abstract Syntax Tree (AST) node.
86
+ */
87
+ interface IRNode {
88
+ id?: string | number;
89
+ name?: string;
90
+ type: ElementType;
91
+ box: BoundingBox;
92
+ zIndex?: number;
93
+ shapeStyle?: ShapeStyle;
94
+ content?: string;
95
+ textStyle?: TextStyle;
96
+ paragraphs?: ParagraphIR[];
97
+ children?: IRNode[];
98
+ }
99
+ /**
100
+ * Embedded font IR representing a portable font stream for OpenXML (.fntdata).
101
+ */
102
+ interface EmbeddedFontIR {
103
+ typeface: string;
104
+ fntData: Buffer;
105
+ }
106
+ /**
107
+ * Complete Intermediate Representation for a single slide.
108
+ */
109
+ interface SlideIR {
110
+ width: number;
111
+ height: number;
112
+ backgroundColor?: string;
113
+ elements: IRNode[];
114
+ fonts?: EmbeddedFontIR[];
115
+ }
116
+ /**
117
+ * Multi-slide presentation IR container with global presentation metadata.
118
+ */
119
+ interface PresentationIR {
120
+ slides: SlideIR[];
121
+ fonts?: EmbeddedFontIR[];
122
+ }
123
+
124
+ type SlideAspect = '16:9' | '4:3';
125
+ interface ViewportOptions {
126
+ width: number;
127
+ height: number;
128
+ deviceScaleFactor?: number;
129
+ }
130
+ interface EmbeddedFontOption {
131
+ /**
132
+ * Font family / typeface name (e.g. "Telkomsel Batik Sans").
133
+ */
134
+ typeface: string;
135
+ /**
136
+ * Source font file path (.ttf, .otf, .eot, .fntdata), HTTP(S) URL, data URI, or raw Buffer.
137
+ */
138
+ src: string | Buffer;
139
+ }
140
+ interface ConvertOptions {
141
+ /**
142
+ * Presentation aspect ratio. Defaults to '16:9'.
143
+ */
144
+ aspect?: SlideAspect;
145
+ /**
146
+ * Browser viewport dimensions for headless layout resolution.
147
+ * Defaults to 1920x1080 for 16:9, or 1024x768 for 4:3.
148
+ */
149
+ viewport?: ViewportOptions;
150
+ /**
151
+ * CSS selector for the root container element to capture as slide.
152
+ * Defaults to 'body'.
153
+ */
154
+ selector?: string;
155
+ /**
156
+ * Optional CSS selector to detect and extract multi-slide presentations.
157
+ * e.g., '.slide' or 'section.slide'. If matching elements are found,
158
+ * every slide will be extracted into a separate slide in the presentation.
159
+ */
160
+ slideSelector?: string;
161
+ /**
162
+ * Timeout in milliseconds for rendering/waiting.
163
+ * Defaults to 30000ms (30s).
164
+ */
165
+ timeout?: number;
166
+ /**
167
+ * Wait until specific condition before harvesting DOM.
168
+ * 'load' | 'domcontentloaded' | 'networkidle0' | 'networkidle2'
169
+ */
170
+ waitUntil?: 'load' | 'domcontentloaded' | 'networkidle0' | 'networkidle2';
171
+ /**
172
+ * Optional pre-existing Puppeteer browser instance.
173
+ * If supplied, html-native-pptx will reuse it rather than launching a new Chromium process.
174
+ */
175
+ browser?: Browser;
176
+ /**
177
+ * Optional WebSocket endpoint to connect to a remote Chromium (e.g. Browserless.io).
178
+ */
179
+ browserWSEndpoint?: string;
180
+ /**
181
+ * Optional list of custom fonts to embed into the PowerPoint presentation.
182
+ */
183
+ fonts?: EmbeddedFontOption[];
184
+ /**
185
+ * Whether to automatically detect @font-face rules in HTML/CSS and embed them.
186
+ * Defaults to true.
187
+ */
188
+ autoEmbedFonts?: boolean;
189
+ /**
190
+ * Base directory path used to resolve relative font paths and asset URLs.
191
+ * Defaults to process.cwd().
192
+ */
193
+ basePath?: string;
194
+ }
195
+
196
+ /**
197
+ * Convert pixels to inches given a DPI and optional coordinate scale factor.
198
+ */
199
+ declare function pxToInches(px: number, dpi?: number, scale?: number): number;
200
+ /**
201
+ * Convert inches to English Metric Units (EMU).
202
+ * 1 inch = 914,400 EMU.
203
+ */
204
+ declare function inchesToEmu(inches: number): number;
205
+ /**
206
+ * Direct conversion from pixels to EMU.
207
+ */
208
+ declare function pxToEmu(px: number, dpi?: number, scale?: number): number;
209
+ /**
210
+ * Convert pixel font size to points (pt).
211
+ * 1 inch = 72 pt = 96 px -> 1 px = 0.75 pt.
212
+ */
213
+ declare function pxToPt(px: number, dpi?: number): number;
214
+ /**
215
+ * Convert points to hundredths of a point (used by OpenXML sz attribute).
216
+ * e.g., 16pt -> 1600.
217
+ */
218
+ declare function ptToHundredthPt(pt: number): number;
219
+ /**
220
+ * Convert CSS border-radius (in pixels) to OpenXML rounded rectangle adjustment guide value (0 - 50000).
221
+ * In OpenXML, an adj value of 50000 represents a radius equal to half the shortest dimension.
222
+ */
223
+ declare function borderRadiusToGuide(radiusPx: number, widthPx: number, heightPx: number): number;
224
+
225
+ interface ColorResult {
226
+ hex: string;
227
+ opacity?: number;
228
+ }
229
+ /**
230
+ * Normalizes any CSS color string (rgb, rgba, hex, named) into a 6-character uppercase hex string.
231
+ * Returns undefined if transparent or invalid.
232
+ */
233
+ declare function normalizeHexColor(cssColor?: string): ColorResult | undefined;
234
+ /**
235
+ * Normalizes CSS font-weight into a boolean representing bold.
236
+ */
237
+ declare function normalizeFontWeight(fontWeight?: string | number): boolean;
238
+ /**
239
+ * Normalizes CSS font-style into a boolean representing italic.
240
+ */
241
+ declare function normalizeFontStyle(fontStyle?: string): boolean;
242
+ /**
243
+ * Normalizes CSS font-family string by extracting the primary font family without quotes.
244
+ */
245
+ declare function normalizeFontFamily(fontFamily?: string): string;
246
+ /**
247
+ * Normalizes CSS text-align to IR TextAlign.
248
+ */
249
+ declare function normalizeTextAlign(textAlign?: string): TextAlign;
250
+ /**
251
+ * Normalizes CSS border-style to supported IR border types.
252
+ */
253
+ declare function normalizeBorderStyle(borderStyle?: string): 'solid' | 'dashed' | 'dotted';
254
+
255
+ /**
256
+ * Normalizes a typeface name by stripping quotes and taking the primary family.
257
+ */
258
+ declare function cleanTypeface(name: string): string;
259
+ /**
260
+ * Checks if a buffer contains Microsoft Embedded OpenType (EOT) binary data.
261
+ */
262
+ declare function isEotBuffer(buf: Buffer): boolean;
263
+ /**
264
+ * Converts TrueType (TTF) or OpenType (OTF) font buffer into Microsoft EOT format.
265
+ */
266
+ declare function convertTtfToEot(buf: Buffer): Buffer;
267
+ /**
268
+ * Loads font data from a buffer, data URI, web URL, or local file path.
269
+ */
270
+ declare function loadFontBuffer(src: string | Buffer, basePath?: string): Promise<Buffer | null>;
271
+ /**
272
+ * Extracts @font-face rules from raw HTML string or CSS text.
273
+ */
274
+ declare function extractFontFacesFromHtml(html: string): Array<{
275
+ typeface: string;
276
+ src: string;
277
+ }>;
278
+ /**
279
+ * Resolves, deduplicates, and converts font options and discovered @font-face rules into EmbeddedFontIR items.
280
+ */
281
+ declare function resolveAndEmbedFonts(options: {
282
+ html?: string;
283
+ userFonts?: EmbeddedFontOption[];
284
+ autoEmbedFonts?: boolean;
285
+ basePath?: string;
286
+ discoveredFonts?: Array<{
287
+ typeface: string;
288
+ src: string;
289
+ }>;
290
+ }): Promise<EmbeddedFontIR[]>;
291
+
292
+ declare function harvestHtmlToIR(htmlOrUrl: string, options?: ConvertOptions): Promise<SlideIR | SlideIR[]>;
293
+
294
+ interface HarvestOptions {
295
+ selector?: string;
296
+ viewportWidth: number;
297
+ viewportHeight: number;
298
+ aspect?: '16:9' | '4:3';
299
+ }
300
+ /**
301
+ * In-browser evaluation function to harvest DOM tree and compile it to SlideIR.
302
+ * Designed to execute inside Puppeteer's page.evaluate().
303
+ */
304
+ declare function extractDomToSlideIR(options: HarvestOptions): SlideIR;
305
+
306
+ interface CompileOptions {
307
+ fonts?: EmbeddedFontIR[];
308
+ }
309
+ /**
310
+ * Compiles a single SlideIR, an array of SlideIRs, or a PresentationIR into a valid PowerPoint (.pptx) file Buffer using OpenXML and JSZip.
311
+ */
312
+ declare function compileSlideToPptx(slideOrSlidesOrPres: SlideIR | SlideIR[] | PresentationIR, options?: CompileOptions): Promise<Buffer>;
313
+
314
+ /**
315
+ * Compiles a container IRNode into OpenXML <p:sp> element.
316
+ */
317
+ declare function compileContainerShape(node: IRNode, id: number): string;
318
+
319
+ /**
320
+ * Compiles a text IRNode into an OpenXML <p:sp> textbox element with zero internal margins.
321
+ */
322
+ declare function compileTextShape(node: IRNode, id: number): string;
323
+
324
+ /**
325
+ * Main conversion API: Converts an HTML string or URL into a native, editable PowerPoint (.pptx) buffer.
326
+ *
327
+ * @param html - Raw HTML string or valid http(s) URL.
328
+ * @param options - Configuration options for viewport, aspect ratio, or browser instance.
329
+ * @returns Promise resolving to a Node.js Buffer representing the .pptx file.
330
+ */
331
+ declare function convertHtmlToPptx(html: string, options?: ConvertOptions): Promise<Buffer>;
332
+ /**
333
+ * Helper to construct a blank SlideIR with standard 16:9 or 4:3 dimensions.
334
+ */
335
+ declare function createSlideIR(aspect?: '16:9' | '4:3'): SlideIR;
336
+
337
+ export { type BoundingBox, type ColorResult, type ConvertOptions, type ElementType, type EmbeddedFontIR, type EmbeddedFontOption, type IRNode, type ParagraphIR, type PresentationIR, type ShapeStyle, type SlideAspect, type SlideIR, type TextAlign, type TextRun, type TextStyle, UNITS, type ViewportOptions, borderRadiusToGuide, cleanTypeface, compileContainerShape, compileSlideToPptx, compileTextShape, convertHtmlToPptx, convertTtfToEot, createSlideIR, extractDomToSlideIR, extractFontFacesFromHtml, harvestHtmlToIR, inchesToEmu, isEotBuffer, loadFontBuffer, normalizeBorderStyle, normalizeFontFamily, normalizeFontStyle, normalizeFontWeight, normalizeHexColor, normalizeTextAlign, ptToHundredthPt, pxToEmu, pxToInches, pxToPt, resolveAndEmbedFonts };