@microlee666/dom-to-pptx 1.1.4

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.
@@ -0,0 +1,96 @@
1
+ // rollup.config.js
2
+ import resolve from '@rollup/plugin-node-resolve';
3
+ import commonjs from '@rollup/plugin-commonjs';
4
+ import polyfillNode from 'rollup-plugin-polyfill-node';
5
+ import json from '@rollup/plugin-json';
6
+
7
+ const input = 'src/index.js';
8
+
9
+ // Helper to suppress circular dependency warnings from specific libraries
10
+ const onwarn = (warning, warn) => {
11
+ if (warning.code === 'CIRCULAR_DEPENDENCY') {
12
+ // Ignore circular dependencies in these known packages
13
+ if (
14
+ warning.message.includes('node_modules/readable-stream') ||
15
+ warning.message.includes('node_modules/jszip') ||
16
+ warning.message.includes('node_modules/semver')
17
+ ) {
18
+ return;
19
+ }
20
+ }
21
+ warn(warning);
22
+ };
23
+
24
+ // --- CONFIG A: Library (NPM) ---
25
+ // Does NOT include dependencies. Consumers (Webpack/Vite) will bundle them.
26
+ const configLibrary = {
27
+ input,
28
+ output: [
29
+ {
30
+ file: 'dist/dom-to-pptx.mjs',
31
+ format: 'es',
32
+ sourcemap: true,
33
+ },
34
+ {
35
+ file: 'dist/dom-to-pptx.cjs',
36
+ format: 'cjs',
37
+ sourcemap: true,
38
+ exports: 'named',
39
+ },
40
+ ],
41
+ plugins: [
42
+ resolve({ preferBuiltins: true }), // Allow node resolution
43
+ commonjs(),
44
+ json(),
45
+ ],
46
+ // Mark all dependencies as external so they aren't bundled into the .mjs/.cjs files
47
+ external: ['pptxgenjs', 'html2canvas', 'jszip', 'fonteditor-core', 'opentype.js', 'pako'],
48
+ onwarn,
49
+ };
50
+
51
+ // --- CONFIG B: Browser Bundle (CDN) ---
52
+ // Includes EVERYTHING (Polyfills + Dependencies). Standalone file.
53
+ const configBundle = {
54
+ input,
55
+ output: {
56
+ file: 'dist/dom-to-pptx.bundle.js',
57
+ format: 'umd',
58
+ name: 'domToPptx',
59
+ esModule: false,
60
+ sourcemap: false,
61
+ // Inject global variables for browser compatibility
62
+ intro: `
63
+ var global = typeof self !== "undefined" ? self : this;
64
+ var process = { env: { NODE_ENV: "production" } };
65
+ `,
66
+ globals: {
67
+ // If you want users to load PptxGenJS separately via script tag, keep this.
68
+ // If you want to bundle PptxGenJS inside, remove it from external/globals.
69
+ // Usually for "bundle.js", we bundle everything except maybe very large libs.
70
+ // Based on your previous config, we are bundling everything.
71
+ },
72
+ },
73
+ plugins: [
74
+ // 1. JSON plugin (needed for some deps)
75
+ json(),
76
+
77
+ // 2. Resolve browser versions of modules
78
+ resolve({
79
+ browser: true,
80
+ preferBuiltins: false, // Force use of browser polyfills
81
+ }),
82
+
83
+ // 3. Convert CJS to ESM
84
+ commonjs({
85
+ transformMixedEsModules: true,
86
+ }),
87
+
88
+ // 4. Inject Node.js Polyfills (Buffer, Stream, etc.)
89
+ polyfillNode(),
90
+ ],
91
+ // Empty external list means "Bundle everything"
92
+ external: [],
93
+ onwarn,
94
+ };
95
+
96
+ export default [configLibrary, configBundle];
@@ -0,0 +1,163 @@
1
+ // src/font-embedder.js
2
+ import opentype from 'opentype.js';
3
+ import { fontToEot } from './font-utils.js';
4
+
5
+ const START_RID = 201314;
6
+
7
+ export class PPTXEmbedFonts {
8
+ constructor() {
9
+ this.zip = null;
10
+ this.rId = START_RID;
11
+ this.fonts = []; // { name, data, rid }
12
+ }
13
+
14
+ async loadZip(zip) {
15
+ this.zip = zip;
16
+ }
17
+
18
+ /**
19
+ * Reads the font name from the buffer using opentype.js
20
+ */
21
+ getFontInfo(fontBuffer) {
22
+ try {
23
+ const font = opentype.parse(fontBuffer);
24
+ const names = font.names;
25
+ // Prefer English name, fallback to others
26
+ const fontFamily = names.fontFamily.en || Object.values(names.fontFamily)[0];
27
+ return { name: fontFamily };
28
+ } catch (e) {
29
+ console.warn('Could not parse font info', e);
30
+ return { name: 'Unknown' };
31
+ }
32
+ }
33
+
34
+ async addFont(fontFace, fontBuffer, type) {
35
+ // Convert to EOT/fntdata for PPTX compatibility
36
+ const eotData = await fontToEot(type, fontBuffer);
37
+ const rid = this.rId++;
38
+ this.fonts.push({ name: fontFace, data: eotData, rid });
39
+ }
40
+
41
+ async updateFiles() {
42
+ await this.updateContentTypesXML();
43
+ await this.updatePresentationXML();
44
+ await this.updateRelsPresentationXML();
45
+ this.updateFontFiles();
46
+ }
47
+
48
+ async generateBlob() {
49
+ if (!this.zip) throw new Error('Zip not loaded');
50
+ return this.zip.generateAsync({
51
+ type: 'blob',
52
+ compression: 'DEFLATE',
53
+ compressionOptions: { level: 6 },
54
+ });
55
+ }
56
+
57
+ // --- XML Manipulation Methods ---
58
+
59
+ async updateContentTypesXML() {
60
+ const file = this.zip.file('[Content_Types].xml');
61
+ if (!file) throw new Error('[Content_Types].xml not found');
62
+
63
+ const xmlStr = await file.async('string');
64
+ const parser = new DOMParser();
65
+ const doc = parser.parseFromString(xmlStr, 'text/xml');
66
+
67
+ const types = doc.getElementsByTagName('Types')[0];
68
+ const defaults = Array.from(doc.getElementsByTagName('Default'));
69
+
70
+ const hasFntData = defaults.some((el) => el.getAttribute('Extension') === 'fntdata');
71
+
72
+ if (!hasFntData) {
73
+ const el = doc.createElement('Default');
74
+ el.setAttribute('Extension', 'fntdata');
75
+ el.setAttribute('ContentType', 'application/x-fontdata');
76
+ types.insertBefore(el, types.firstChild);
77
+ }
78
+
79
+ this.zip.file('[Content_Types].xml', new XMLSerializer().serializeToString(doc));
80
+ }
81
+
82
+ async updatePresentationXML() {
83
+ const file = this.zip.file('ppt/presentation.xml');
84
+ if (!file) throw new Error('ppt/presentation.xml not found');
85
+
86
+ const xmlStr = await file.async('string');
87
+ const parser = new DOMParser();
88
+ const doc = parser.parseFromString(xmlStr, 'text/xml');
89
+ const presentation = doc.getElementsByTagName('p:presentation')[0];
90
+
91
+ // Enable embedding flags
92
+ presentation.setAttribute('saveSubsetFonts', 'true');
93
+ presentation.setAttribute('embedTrueTypeFonts', 'true');
94
+
95
+ // Find or create embeddedFontLst
96
+ let embeddedFontLst = presentation.getElementsByTagName('p:embeddedFontLst')[0];
97
+
98
+ if (!embeddedFontLst) {
99
+ embeddedFontLst = doc.createElement('p:embeddedFontLst');
100
+
101
+ // Insert before defaultTextStyle or at end
102
+ const defaultTextStyle = presentation.getElementsByTagName('p:defaultTextStyle')[0];
103
+ if (defaultTextStyle) {
104
+ presentation.insertBefore(embeddedFontLst, defaultTextStyle);
105
+ } else {
106
+ presentation.appendChild(embeddedFontLst);
107
+ }
108
+ }
109
+
110
+ // Add font references
111
+ this.fonts.forEach((font) => {
112
+ // Check if already exists
113
+ const existing = Array.from(embeddedFontLst.getElementsByTagName('p:font')).find(
114
+ (node) => node.getAttribute('typeface') === font.name
115
+ );
116
+
117
+ if (!existing) {
118
+ const embedFont = doc.createElement('p:embeddedFont');
119
+
120
+ const fontNode = doc.createElement('p:font');
121
+ fontNode.setAttribute('typeface', font.name);
122
+ embedFont.appendChild(fontNode);
123
+
124
+ const regular = doc.createElement('p:regular');
125
+ regular.setAttribute('r:id', `rId${font.rid}`);
126
+ embedFont.appendChild(regular);
127
+
128
+ embeddedFontLst.appendChild(embedFont);
129
+ }
130
+ });
131
+
132
+ this.zip.file('ppt/presentation.xml', new XMLSerializer().serializeToString(doc));
133
+ }
134
+
135
+ async updateRelsPresentationXML() {
136
+ const file = this.zip.file('ppt/_rels/presentation.xml.rels');
137
+ if (!file) throw new Error('presentation.xml.rels not found');
138
+
139
+ const xmlStr = await file.async('string');
140
+ const parser = new DOMParser();
141
+ const doc = parser.parseFromString(xmlStr, 'text/xml');
142
+ const relationships = doc.getElementsByTagName('Relationships')[0];
143
+
144
+ this.fonts.forEach((font) => {
145
+ const rel = doc.createElement('Relationship');
146
+ rel.setAttribute('Id', `rId${font.rid}`);
147
+ rel.setAttribute('Target', `fonts/${font.rid}.fntdata`);
148
+ rel.setAttribute(
149
+ 'Type',
150
+ 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/font'
151
+ );
152
+ relationships.appendChild(rel);
153
+ });
154
+
155
+ this.zip.file('ppt/_rels/presentation.xml.rels', new XMLSerializer().serializeToString(doc));
156
+ }
157
+
158
+ updateFontFiles() {
159
+ this.fonts.forEach((font) => {
160
+ this.zip.file(`ppt/fonts/${font.rid}.fntdata`, font.data);
161
+ });
162
+ }
163
+ }
@@ -0,0 +1,32 @@
1
+ // src/font-utils.js
2
+ import { Font } from 'fonteditor-core';
3
+ import pako from 'pako';
4
+
5
+ /**
6
+ * Converts various font formats to EOT (Embedded OpenType),
7
+ * which is highly compatible with PowerPoint embedding.
8
+ * @param {string} type - 'ttf', 'woff', or 'otf'
9
+ * @param {ArrayBuffer} fontBuffer - The raw font data
10
+ */
11
+ export async function fontToEot(type, fontBuffer) {
12
+ const options = {
13
+ type,
14
+ hinting: true,
15
+ // inflate is required for WOFF decoding
16
+ inflate: type === 'woff' ? pako.inflate : undefined,
17
+ };
18
+
19
+ const font = Font.create(fontBuffer, options);
20
+
21
+ const eotBuffer = font.write({
22
+ type: 'eot',
23
+ toBuffer: true,
24
+ });
25
+
26
+ if (eotBuffer instanceof ArrayBuffer) {
27
+ return eotBuffer;
28
+ }
29
+
30
+ // Ensure we return an ArrayBuffer
31
+ return eotBuffer.buffer.slice(eotBuffer.byteOffset, eotBuffer.byteOffset + eotBuffer.byteLength);
32
+ }
@@ -0,0 +1,118 @@
1
+ // src/image-processor.js
2
+
3
+ export async function getProcessedImage(
4
+ src,
5
+ targetW,
6
+ targetH,
7
+ radius,
8
+ objectFit = 'fill',
9
+ objectPosition = '50% 50%'
10
+ ) {
11
+ return new Promise((resolve) => {
12
+ const img = new Image();
13
+ img.crossOrigin = 'Anonymous';
14
+
15
+ img.onload = () => {
16
+ const canvas = document.createElement('canvas');
17
+ const scale = 2; // Double resolution
18
+ canvas.width = targetW * scale;
19
+ canvas.height = targetH * scale;
20
+ const ctx = canvas.getContext('2d');
21
+ ctx.scale(scale, scale);
22
+
23
+ // Normalize radius
24
+ let r = { tl: 0, tr: 0, br: 0, bl: 0 };
25
+ if (typeof radius === 'number') {
26
+ r = { tl: radius, tr: radius, br: radius, bl: radius };
27
+ } else if (typeof radius === 'object' && radius !== null) {
28
+ r = { ...r, ...radius };
29
+ }
30
+
31
+ // 1. Draw Mask
32
+ ctx.beginPath();
33
+ // ... (radius clamping logic remains the same) ...
34
+ const factor = Math.min(
35
+ targetW / (r.tl + r.tr) || Infinity,
36
+ targetH / (r.tr + r.br) || Infinity,
37
+ targetW / (r.br + r.bl) || Infinity,
38
+ targetH / (r.bl + r.tl) || Infinity
39
+ );
40
+
41
+ if (factor < 1) {
42
+ r.tl *= factor;
43
+ r.tr *= factor;
44
+ r.br *= factor;
45
+ r.bl *= factor;
46
+ }
47
+
48
+ ctx.moveTo(r.tl, 0);
49
+ ctx.lineTo(targetW - r.tr, 0);
50
+ ctx.arcTo(targetW, 0, targetW, r.tr, r.tr);
51
+ ctx.lineTo(targetW, targetH - r.br);
52
+ ctx.arcTo(targetW, targetH, targetW - r.br, targetH, r.br);
53
+ ctx.lineTo(r.bl, targetH);
54
+ ctx.arcTo(0, targetH, 0, targetH - r.bl, r.bl);
55
+ ctx.lineTo(0, r.tl);
56
+ ctx.arcTo(0, 0, r.tl, 0, r.tl);
57
+ ctx.closePath();
58
+ ctx.fillStyle = '#000';
59
+ ctx.fill();
60
+
61
+ // 2. Composite Source-In
62
+ ctx.globalCompositeOperation = 'source-in';
63
+
64
+ // 3. Draw Image with Object Fit logic
65
+ const wRatio = targetW / img.width;
66
+ const hRatio = targetH / img.height;
67
+ let renderW, renderH;
68
+
69
+ if (objectFit === 'contain') {
70
+ const fitScale = Math.min(wRatio, hRatio);
71
+ renderW = img.width * fitScale;
72
+ renderH = img.height * fitScale;
73
+ } else if (objectFit === 'cover') {
74
+ const coverScale = Math.max(wRatio, hRatio);
75
+ renderW = img.width * coverScale;
76
+ renderH = img.height * coverScale;
77
+ } else if (objectFit === 'none') {
78
+ renderW = img.width;
79
+ renderH = img.height;
80
+ } else if (objectFit === 'scale-down') {
81
+ const scaleDown = Math.min(1, Math.min(wRatio, hRatio));
82
+ renderW = img.width * scaleDown;
83
+ renderH = img.height * scaleDown;
84
+ } else {
85
+ // 'fill' (default)
86
+ renderW = targetW;
87
+ renderH = targetH;
88
+ }
89
+
90
+ // Handle Object Position (simplified parsing for "x% y%" or keywords)
91
+ let posX = 0.5; // Default center
92
+ let posY = 0.5;
93
+
94
+ const posParts = objectPosition.split(' ');
95
+ if (posParts.length > 0) {
96
+ const parsePos = (val) => {
97
+ if (val === 'left' || val === 'top') return 0;
98
+ if (val === 'center') return 0.5;
99
+ if (val === 'right' || val === 'bottom') return 1;
100
+ if (val.includes('%')) return parseFloat(val) / 100;
101
+ return 0.5; // fallback
102
+ };
103
+ posX = parsePos(posParts[0]);
104
+ posY = posParts.length > 1 ? parsePos(posParts[1]) : 0.5;
105
+ }
106
+
107
+ const renderX = (targetW - renderW) * posX;
108
+ const renderY = (targetH - renderH) * posY;
109
+
110
+ ctx.drawImage(img, renderX, renderY, renderW, renderH);
111
+
112
+ resolve(canvas.toDataURL('image/png'));
113
+ };
114
+
115
+ img.onerror = () => resolve(null);
116
+ img.src = src;
117
+ });
118
+ }