dom-to-pptx 1.0.7 → 1.0.8
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 +7 -0
- package/README.md +1 -1
- package/dist/dom-to-pptx.bundle.js +96 -10
- package/dist/dom-to-pptx.cjs +96 -10
- package/dist/dom-to-pptx.min.js +96 -10
- package/dist/dom-to-pptx.mjs +96 -10
- package/package.json +1 -1
- package/src/index.js +34 -10
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.0.8] - 2025-12-12 (Hot-Patch)
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- **Fixed SVGs not getting converted**: Seperated the logic to handle SVGs and Web Components/Icons.
|
|
10
|
+
|
|
11
|
+
|
|
5
12
|
## [1.0.7] - 2025-12-12
|
|
6
13
|
|
|
7
14
|
### Fixed
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# dom-to-pptx
|
|
2
2
|
|
|
3
|
-
**The High-Fidelity HTML to PowerPoint Converter (v1.0.
|
|
3
|
+
**The High-Fidelity HTML to PowerPoint Converter (v1.0.8).**
|
|
4
4
|
|
|
5
5
|
Most HTML-to-PPTX libraries fail when faced with modern web design. They break on gradients, misalign text, ignore rounded corners, or simply take a screenshot (which isn't editable).
|
|
6
6
|
|
|
@@ -17808,6 +17808,69 @@
|
|
|
17808
17808
|
return Math.round(Math.atan2(b, a) * (180 / Math.PI));
|
|
17809
17809
|
}
|
|
17810
17810
|
|
|
17811
|
+
function svgToPng(node) {
|
|
17812
|
+
return new Promise((resolve) => {
|
|
17813
|
+
const clone = node.cloneNode(true);
|
|
17814
|
+
const rect = node.getBoundingClientRect();
|
|
17815
|
+
const width = rect.width || 300;
|
|
17816
|
+
const height = rect.height || 150;
|
|
17817
|
+
|
|
17818
|
+
function inlineStyles(source, target) {
|
|
17819
|
+
const computed = window.getComputedStyle(source);
|
|
17820
|
+
const properties = [
|
|
17821
|
+
'fill',
|
|
17822
|
+
'stroke',
|
|
17823
|
+
'stroke-width',
|
|
17824
|
+
'stroke-linecap',
|
|
17825
|
+
'stroke-linejoin',
|
|
17826
|
+
'opacity',
|
|
17827
|
+
'font-family',
|
|
17828
|
+
'font-size',
|
|
17829
|
+
'font-weight',
|
|
17830
|
+
];
|
|
17831
|
+
|
|
17832
|
+
if (computed.fill === 'none') target.setAttribute('fill', 'none');
|
|
17833
|
+
else if (computed.fill) target.style.fill = computed.fill;
|
|
17834
|
+
|
|
17835
|
+
if (computed.stroke === 'none') target.setAttribute('stroke', 'none');
|
|
17836
|
+
else if (computed.stroke) target.style.stroke = computed.stroke;
|
|
17837
|
+
|
|
17838
|
+
properties.forEach((prop) => {
|
|
17839
|
+
if (prop !== 'fill' && prop !== 'stroke') {
|
|
17840
|
+
const val = computed[prop];
|
|
17841
|
+
if (val && val !== 'auto') target.style[prop] = val;
|
|
17842
|
+
}
|
|
17843
|
+
});
|
|
17844
|
+
|
|
17845
|
+
for (let i = 0; i < source.children.length; i++) {
|
|
17846
|
+
if (target.children[i]) inlineStyles(source.children[i], target.children[i]);
|
|
17847
|
+
}
|
|
17848
|
+
}
|
|
17849
|
+
|
|
17850
|
+
inlineStyles(node, clone);
|
|
17851
|
+
clone.setAttribute('width', width);
|
|
17852
|
+
clone.setAttribute('height', height);
|
|
17853
|
+
clone.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
|
|
17854
|
+
|
|
17855
|
+
const xml = new XMLSerializer().serializeToString(clone);
|
|
17856
|
+
const svgUrl = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(xml)}`;
|
|
17857
|
+
const img = new Image();
|
|
17858
|
+
img.crossOrigin = 'Anonymous';
|
|
17859
|
+
img.onload = () => {
|
|
17860
|
+
const canvas = document.createElement('canvas');
|
|
17861
|
+
const scale = 3;
|
|
17862
|
+
canvas.width = width * scale;
|
|
17863
|
+
canvas.height = height * scale;
|
|
17864
|
+
const ctx = canvas.getContext('2d');
|
|
17865
|
+
ctx.scale(scale, scale);
|
|
17866
|
+
ctx.drawImage(img, 0, 0, width, height);
|
|
17867
|
+
resolve(canvas.toDataURL('image/png'));
|
|
17868
|
+
};
|
|
17869
|
+
img.onerror = () => resolve(null);
|
|
17870
|
+
img.src = svgUrl;
|
|
17871
|
+
});
|
|
17872
|
+
}
|
|
17873
|
+
|
|
17811
17874
|
function getVisibleShadow(shadowStr, scale) {
|
|
17812
17875
|
if (!shadowStr || shadowStr === 'none') return null;
|
|
17813
17876
|
const shadows = shadowStr.split(/,(?![^()]*\))/);
|
|
@@ -18318,23 +18381,18 @@
|
|
|
18318
18381
|
|
|
18319
18382
|
const items = [];
|
|
18320
18383
|
|
|
18321
|
-
// --- ASYNC JOB:
|
|
18322
|
-
if (
|
|
18323
|
-
node.nodeName.toUpperCase() === 'SVG' ||
|
|
18324
|
-
node.tagName.includes('-') ||
|
|
18325
|
-
node.tagName === 'ION-ICON'
|
|
18326
|
-
) {
|
|
18384
|
+
// --- ASYNC JOB: SVG Tags ---
|
|
18385
|
+
if (node.nodeName.toUpperCase() === 'SVG') {
|
|
18327
18386
|
const item = {
|
|
18328
18387
|
type: 'image',
|
|
18329
18388
|
zIndex,
|
|
18330
18389
|
domOrder,
|
|
18331
|
-
options: { x, y, w, h, rotate: rotation
|
|
18390
|
+
options: { data: null, x, y, w, h, rotate: rotation },
|
|
18332
18391
|
};
|
|
18333
18392
|
|
|
18334
|
-
// Create Job
|
|
18335
18393
|
const job = async () => {
|
|
18336
|
-
const
|
|
18337
|
-
if (
|
|
18394
|
+
const processed = await svgToPng(node);
|
|
18395
|
+
if (processed) item.options.data = processed;
|
|
18338
18396
|
else item.skip = true;
|
|
18339
18397
|
};
|
|
18340
18398
|
|
|
@@ -18384,6 +18442,34 @@
|
|
|
18384
18442
|
return { items: [item], job, stopRecursion: true };
|
|
18385
18443
|
}
|
|
18386
18444
|
|
|
18445
|
+
// --- ASYNC JOB: Icons and Other Elements ---
|
|
18446
|
+
if (
|
|
18447
|
+
node.tagName.toUpperCase() === 'MATERIAL-ICON' ||
|
|
18448
|
+
node.tagName.toUpperCase() === 'ICONIFY-ICON' ||
|
|
18449
|
+
node.tagName.toUpperCase() === 'REMIX-ICON' ||
|
|
18450
|
+
node.tagName.toUpperCase() === 'ION-ICON' ||
|
|
18451
|
+
node.tagName.toUpperCase() === 'EVA-ICON' ||
|
|
18452
|
+
node.tagName.toUpperCase() === 'BOX-ICON' ||
|
|
18453
|
+
node.tagName.toUpperCase() === 'FA-ICON' ||
|
|
18454
|
+
node.tagName.includes('-')
|
|
18455
|
+
) {
|
|
18456
|
+
const item = {
|
|
18457
|
+
type: 'image',
|
|
18458
|
+
zIndex,
|
|
18459
|
+
domOrder,
|
|
18460
|
+
options: { x, y, w, h, rotate: rotation, data: null }, // Data null initially
|
|
18461
|
+
};
|
|
18462
|
+
|
|
18463
|
+
// Create Job
|
|
18464
|
+
const job = async () => {
|
|
18465
|
+
const pngData = await elementToCanvasImage(node, widthPx, heightPx);
|
|
18466
|
+
if (pngData) item.options.data = pngData;
|
|
18467
|
+
else item.skip = true;
|
|
18468
|
+
};
|
|
18469
|
+
|
|
18470
|
+
return { items: [item], job, stopRecursion: true };
|
|
18471
|
+
}
|
|
18472
|
+
|
|
18387
18473
|
// Radii logic
|
|
18388
18474
|
const borderRadiusValue = parseFloat(style.borderRadius) || 0;
|
|
18389
18475
|
const borderBottomLeftRadius = parseFloat(style.borderBottomLeftRadius) || 0;
|
package/dist/dom-to-pptx.cjs
CHANGED
|
@@ -8135,6 +8135,69 @@ function getRotation(transformStr) {
|
|
|
8135
8135
|
return Math.round(Math.atan2(b, a) * (180 / Math.PI));
|
|
8136
8136
|
}
|
|
8137
8137
|
|
|
8138
|
+
function svgToPng(node) {
|
|
8139
|
+
return new Promise((resolve) => {
|
|
8140
|
+
const clone = node.cloneNode(true);
|
|
8141
|
+
const rect = node.getBoundingClientRect();
|
|
8142
|
+
const width = rect.width || 300;
|
|
8143
|
+
const height = rect.height || 150;
|
|
8144
|
+
|
|
8145
|
+
function inlineStyles(source, target) {
|
|
8146
|
+
const computed = window.getComputedStyle(source);
|
|
8147
|
+
const properties = [
|
|
8148
|
+
'fill',
|
|
8149
|
+
'stroke',
|
|
8150
|
+
'stroke-width',
|
|
8151
|
+
'stroke-linecap',
|
|
8152
|
+
'stroke-linejoin',
|
|
8153
|
+
'opacity',
|
|
8154
|
+
'font-family',
|
|
8155
|
+
'font-size',
|
|
8156
|
+
'font-weight',
|
|
8157
|
+
];
|
|
8158
|
+
|
|
8159
|
+
if (computed.fill === 'none') target.setAttribute('fill', 'none');
|
|
8160
|
+
else if (computed.fill) target.style.fill = computed.fill;
|
|
8161
|
+
|
|
8162
|
+
if (computed.stroke === 'none') target.setAttribute('stroke', 'none');
|
|
8163
|
+
else if (computed.stroke) target.style.stroke = computed.stroke;
|
|
8164
|
+
|
|
8165
|
+
properties.forEach((prop) => {
|
|
8166
|
+
if (prop !== 'fill' && prop !== 'stroke') {
|
|
8167
|
+
const val = computed[prop];
|
|
8168
|
+
if (val && val !== 'auto') target.style[prop] = val;
|
|
8169
|
+
}
|
|
8170
|
+
});
|
|
8171
|
+
|
|
8172
|
+
for (let i = 0; i < source.children.length; i++) {
|
|
8173
|
+
if (target.children[i]) inlineStyles(source.children[i], target.children[i]);
|
|
8174
|
+
}
|
|
8175
|
+
}
|
|
8176
|
+
|
|
8177
|
+
inlineStyles(node, clone);
|
|
8178
|
+
clone.setAttribute('width', width);
|
|
8179
|
+
clone.setAttribute('height', height);
|
|
8180
|
+
clone.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
|
|
8181
|
+
|
|
8182
|
+
const xml = new XMLSerializer().serializeToString(clone);
|
|
8183
|
+
const svgUrl = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(xml)}`;
|
|
8184
|
+
const img = new Image();
|
|
8185
|
+
img.crossOrigin = 'Anonymous';
|
|
8186
|
+
img.onload = () => {
|
|
8187
|
+
const canvas = document.createElement('canvas');
|
|
8188
|
+
const scale = 3;
|
|
8189
|
+
canvas.width = width * scale;
|
|
8190
|
+
canvas.height = height * scale;
|
|
8191
|
+
const ctx = canvas.getContext('2d');
|
|
8192
|
+
ctx.scale(scale, scale);
|
|
8193
|
+
ctx.drawImage(img, 0, 0, width, height);
|
|
8194
|
+
resolve(canvas.toDataURL('image/png'));
|
|
8195
|
+
};
|
|
8196
|
+
img.onerror = () => resolve(null);
|
|
8197
|
+
img.src = svgUrl;
|
|
8198
|
+
});
|
|
8199
|
+
}
|
|
8200
|
+
|
|
8138
8201
|
function getVisibleShadow(shadowStr, scale) {
|
|
8139
8202
|
if (!shadowStr || shadowStr === 'none') return null;
|
|
8140
8203
|
const shadows = shadowStr.split(/,(?![^()]*\))/);
|
|
@@ -8645,23 +8708,18 @@ function prepareRenderItem(node, config, domOrder, pptx, effectiveZIndex, comput
|
|
|
8645
8708
|
|
|
8646
8709
|
const items = [];
|
|
8647
8710
|
|
|
8648
|
-
// --- ASYNC JOB:
|
|
8649
|
-
if (
|
|
8650
|
-
node.nodeName.toUpperCase() === 'SVG' ||
|
|
8651
|
-
node.tagName.includes('-') ||
|
|
8652
|
-
node.tagName === 'ION-ICON'
|
|
8653
|
-
) {
|
|
8711
|
+
// --- ASYNC JOB: SVG Tags ---
|
|
8712
|
+
if (node.nodeName.toUpperCase() === 'SVG') {
|
|
8654
8713
|
const item = {
|
|
8655
8714
|
type: 'image',
|
|
8656
8715
|
zIndex,
|
|
8657
8716
|
domOrder,
|
|
8658
|
-
options: { x, y, w, h, rotate: rotation
|
|
8717
|
+
options: { data: null, x, y, w, h, rotate: rotation },
|
|
8659
8718
|
};
|
|
8660
8719
|
|
|
8661
|
-
// Create Job
|
|
8662
8720
|
const job = async () => {
|
|
8663
|
-
const
|
|
8664
|
-
if (
|
|
8721
|
+
const processed = await svgToPng(node);
|
|
8722
|
+
if (processed) item.options.data = processed;
|
|
8665
8723
|
else item.skip = true;
|
|
8666
8724
|
};
|
|
8667
8725
|
|
|
@@ -8711,6 +8769,34 @@ function prepareRenderItem(node, config, domOrder, pptx, effectiveZIndex, comput
|
|
|
8711
8769
|
return { items: [item], job, stopRecursion: true };
|
|
8712
8770
|
}
|
|
8713
8771
|
|
|
8772
|
+
// --- ASYNC JOB: Icons and Other Elements ---
|
|
8773
|
+
if (
|
|
8774
|
+
node.tagName.toUpperCase() === 'MATERIAL-ICON' ||
|
|
8775
|
+
node.tagName.toUpperCase() === 'ICONIFY-ICON' ||
|
|
8776
|
+
node.tagName.toUpperCase() === 'REMIX-ICON' ||
|
|
8777
|
+
node.tagName.toUpperCase() === 'ION-ICON' ||
|
|
8778
|
+
node.tagName.toUpperCase() === 'EVA-ICON' ||
|
|
8779
|
+
node.tagName.toUpperCase() === 'BOX-ICON' ||
|
|
8780
|
+
node.tagName.toUpperCase() === 'FA-ICON' ||
|
|
8781
|
+
node.tagName.includes('-')
|
|
8782
|
+
) {
|
|
8783
|
+
const item = {
|
|
8784
|
+
type: 'image',
|
|
8785
|
+
zIndex,
|
|
8786
|
+
domOrder,
|
|
8787
|
+
options: { x, y, w, h, rotate: rotation, data: null }, // Data null initially
|
|
8788
|
+
};
|
|
8789
|
+
|
|
8790
|
+
// Create Job
|
|
8791
|
+
const job = async () => {
|
|
8792
|
+
const pngData = await elementToCanvasImage(node, widthPx, heightPx);
|
|
8793
|
+
if (pngData) item.options.data = pngData;
|
|
8794
|
+
else item.skip = true;
|
|
8795
|
+
};
|
|
8796
|
+
|
|
8797
|
+
return { items: [item], job, stopRecursion: true };
|
|
8798
|
+
}
|
|
8799
|
+
|
|
8714
8800
|
// Radii logic
|
|
8715
8801
|
const borderRadiusValue = parseFloat(style.borderRadius) || 0;
|
|
8716
8802
|
const borderBottomLeftRadius = parseFloat(style.borderBottomLeftRadius) || 0;
|
package/dist/dom-to-pptx.min.js
CHANGED
|
@@ -8135,6 +8135,69 @@
|
|
|
8135
8135
|
return Math.round(Math.atan2(b, a) * (180 / Math.PI));
|
|
8136
8136
|
}
|
|
8137
8137
|
|
|
8138
|
+
function svgToPng(node) {
|
|
8139
|
+
return new Promise((resolve) => {
|
|
8140
|
+
const clone = node.cloneNode(true);
|
|
8141
|
+
const rect = node.getBoundingClientRect();
|
|
8142
|
+
const width = rect.width || 300;
|
|
8143
|
+
const height = rect.height || 150;
|
|
8144
|
+
|
|
8145
|
+
function inlineStyles(source, target) {
|
|
8146
|
+
const computed = window.getComputedStyle(source);
|
|
8147
|
+
const properties = [
|
|
8148
|
+
'fill',
|
|
8149
|
+
'stroke',
|
|
8150
|
+
'stroke-width',
|
|
8151
|
+
'stroke-linecap',
|
|
8152
|
+
'stroke-linejoin',
|
|
8153
|
+
'opacity',
|
|
8154
|
+
'font-family',
|
|
8155
|
+
'font-size',
|
|
8156
|
+
'font-weight',
|
|
8157
|
+
];
|
|
8158
|
+
|
|
8159
|
+
if (computed.fill === 'none') target.setAttribute('fill', 'none');
|
|
8160
|
+
else if (computed.fill) target.style.fill = computed.fill;
|
|
8161
|
+
|
|
8162
|
+
if (computed.stroke === 'none') target.setAttribute('stroke', 'none');
|
|
8163
|
+
else if (computed.stroke) target.style.stroke = computed.stroke;
|
|
8164
|
+
|
|
8165
|
+
properties.forEach((prop) => {
|
|
8166
|
+
if (prop !== 'fill' && prop !== 'stroke') {
|
|
8167
|
+
const val = computed[prop];
|
|
8168
|
+
if (val && val !== 'auto') target.style[prop] = val;
|
|
8169
|
+
}
|
|
8170
|
+
});
|
|
8171
|
+
|
|
8172
|
+
for (let i = 0; i < source.children.length; i++) {
|
|
8173
|
+
if (target.children[i]) inlineStyles(source.children[i], target.children[i]);
|
|
8174
|
+
}
|
|
8175
|
+
}
|
|
8176
|
+
|
|
8177
|
+
inlineStyles(node, clone);
|
|
8178
|
+
clone.setAttribute('width', width);
|
|
8179
|
+
clone.setAttribute('height', height);
|
|
8180
|
+
clone.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
|
|
8181
|
+
|
|
8182
|
+
const xml = new XMLSerializer().serializeToString(clone);
|
|
8183
|
+
const svgUrl = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(xml)}`;
|
|
8184
|
+
const img = new Image();
|
|
8185
|
+
img.crossOrigin = 'Anonymous';
|
|
8186
|
+
img.onload = () => {
|
|
8187
|
+
const canvas = document.createElement('canvas');
|
|
8188
|
+
const scale = 3;
|
|
8189
|
+
canvas.width = width * scale;
|
|
8190
|
+
canvas.height = height * scale;
|
|
8191
|
+
const ctx = canvas.getContext('2d');
|
|
8192
|
+
ctx.scale(scale, scale);
|
|
8193
|
+
ctx.drawImage(img, 0, 0, width, height);
|
|
8194
|
+
resolve(canvas.toDataURL('image/png'));
|
|
8195
|
+
};
|
|
8196
|
+
img.onerror = () => resolve(null);
|
|
8197
|
+
img.src = svgUrl;
|
|
8198
|
+
});
|
|
8199
|
+
}
|
|
8200
|
+
|
|
8138
8201
|
function getVisibleShadow(shadowStr, scale) {
|
|
8139
8202
|
if (!shadowStr || shadowStr === 'none') return null;
|
|
8140
8203
|
const shadows = shadowStr.split(/,(?![^()]*\))/);
|
|
@@ -8645,23 +8708,18 @@
|
|
|
8645
8708
|
|
|
8646
8709
|
const items = [];
|
|
8647
8710
|
|
|
8648
|
-
// --- ASYNC JOB:
|
|
8649
|
-
if (
|
|
8650
|
-
node.nodeName.toUpperCase() === 'SVG' ||
|
|
8651
|
-
node.tagName.includes('-') ||
|
|
8652
|
-
node.tagName === 'ION-ICON'
|
|
8653
|
-
) {
|
|
8711
|
+
// --- ASYNC JOB: SVG Tags ---
|
|
8712
|
+
if (node.nodeName.toUpperCase() === 'SVG') {
|
|
8654
8713
|
const item = {
|
|
8655
8714
|
type: 'image',
|
|
8656
8715
|
zIndex,
|
|
8657
8716
|
domOrder,
|
|
8658
|
-
options: { x, y, w, h, rotate: rotation
|
|
8717
|
+
options: { data: null, x, y, w, h, rotate: rotation },
|
|
8659
8718
|
};
|
|
8660
8719
|
|
|
8661
|
-
// Create Job
|
|
8662
8720
|
const job = async () => {
|
|
8663
|
-
const
|
|
8664
|
-
if (
|
|
8721
|
+
const processed = await svgToPng(node);
|
|
8722
|
+
if (processed) item.options.data = processed;
|
|
8665
8723
|
else item.skip = true;
|
|
8666
8724
|
};
|
|
8667
8725
|
|
|
@@ -8711,6 +8769,34 @@
|
|
|
8711
8769
|
return { items: [item], job, stopRecursion: true };
|
|
8712
8770
|
}
|
|
8713
8771
|
|
|
8772
|
+
// --- ASYNC JOB: Icons and Other Elements ---
|
|
8773
|
+
if (
|
|
8774
|
+
node.tagName.toUpperCase() === 'MATERIAL-ICON' ||
|
|
8775
|
+
node.tagName.toUpperCase() === 'ICONIFY-ICON' ||
|
|
8776
|
+
node.tagName.toUpperCase() === 'REMIX-ICON' ||
|
|
8777
|
+
node.tagName.toUpperCase() === 'ION-ICON' ||
|
|
8778
|
+
node.tagName.toUpperCase() === 'EVA-ICON' ||
|
|
8779
|
+
node.tagName.toUpperCase() === 'BOX-ICON' ||
|
|
8780
|
+
node.tagName.toUpperCase() === 'FA-ICON' ||
|
|
8781
|
+
node.tagName.includes('-')
|
|
8782
|
+
) {
|
|
8783
|
+
const item = {
|
|
8784
|
+
type: 'image',
|
|
8785
|
+
zIndex,
|
|
8786
|
+
domOrder,
|
|
8787
|
+
options: { x, y, w, h, rotate: rotation, data: null }, // Data null initially
|
|
8788
|
+
};
|
|
8789
|
+
|
|
8790
|
+
// Create Job
|
|
8791
|
+
const job = async () => {
|
|
8792
|
+
const pngData = await elementToCanvasImage(node, widthPx, heightPx);
|
|
8793
|
+
if (pngData) item.options.data = pngData;
|
|
8794
|
+
else item.skip = true;
|
|
8795
|
+
};
|
|
8796
|
+
|
|
8797
|
+
return { items: [item], job, stopRecursion: true };
|
|
8798
|
+
}
|
|
8799
|
+
|
|
8714
8800
|
// Radii logic
|
|
8715
8801
|
const borderRadiusValue = parseFloat(style.borderRadius) || 0;
|
|
8716
8802
|
const borderBottomLeftRadius = parseFloat(style.borderBottomLeftRadius) || 0;
|
package/dist/dom-to-pptx.mjs
CHANGED
|
@@ -8111,6 +8111,69 @@ function getRotation(transformStr) {
|
|
|
8111
8111
|
return Math.round(Math.atan2(b, a) * (180 / Math.PI));
|
|
8112
8112
|
}
|
|
8113
8113
|
|
|
8114
|
+
function svgToPng(node) {
|
|
8115
|
+
return new Promise((resolve) => {
|
|
8116
|
+
const clone = node.cloneNode(true);
|
|
8117
|
+
const rect = node.getBoundingClientRect();
|
|
8118
|
+
const width = rect.width || 300;
|
|
8119
|
+
const height = rect.height || 150;
|
|
8120
|
+
|
|
8121
|
+
function inlineStyles(source, target) {
|
|
8122
|
+
const computed = window.getComputedStyle(source);
|
|
8123
|
+
const properties = [
|
|
8124
|
+
'fill',
|
|
8125
|
+
'stroke',
|
|
8126
|
+
'stroke-width',
|
|
8127
|
+
'stroke-linecap',
|
|
8128
|
+
'stroke-linejoin',
|
|
8129
|
+
'opacity',
|
|
8130
|
+
'font-family',
|
|
8131
|
+
'font-size',
|
|
8132
|
+
'font-weight',
|
|
8133
|
+
];
|
|
8134
|
+
|
|
8135
|
+
if (computed.fill === 'none') target.setAttribute('fill', 'none');
|
|
8136
|
+
else if (computed.fill) target.style.fill = computed.fill;
|
|
8137
|
+
|
|
8138
|
+
if (computed.stroke === 'none') target.setAttribute('stroke', 'none');
|
|
8139
|
+
else if (computed.stroke) target.style.stroke = computed.stroke;
|
|
8140
|
+
|
|
8141
|
+
properties.forEach((prop) => {
|
|
8142
|
+
if (prop !== 'fill' && prop !== 'stroke') {
|
|
8143
|
+
const val = computed[prop];
|
|
8144
|
+
if (val && val !== 'auto') target.style[prop] = val;
|
|
8145
|
+
}
|
|
8146
|
+
});
|
|
8147
|
+
|
|
8148
|
+
for (let i = 0; i < source.children.length; i++) {
|
|
8149
|
+
if (target.children[i]) inlineStyles(source.children[i], target.children[i]);
|
|
8150
|
+
}
|
|
8151
|
+
}
|
|
8152
|
+
|
|
8153
|
+
inlineStyles(node, clone);
|
|
8154
|
+
clone.setAttribute('width', width);
|
|
8155
|
+
clone.setAttribute('height', height);
|
|
8156
|
+
clone.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
|
|
8157
|
+
|
|
8158
|
+
const xml = new XMLSerializer().serializeToString(clone);
|
|
8159
|
+
const svgUrl = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(xml)}`;
|
|
8160
|
+
const img = new Image();
|
|
8161
|
+
img.crossOrigin = 'Anonymous';
|
|
8162
|
+
img.onload = () => {
|
|
8163
|
+
const canvas = document.createElement('canvas');
|
|
8164
|
+
const scale = 3;
|
|
8165
|
+
canvas.width = width * scale;
|
|
8166
|
+
canvas.height = height * scale;
|
|
8167
|
+
const ctx = canvas.getContext('2d');
|
|
8168
|
+
ctx.scale(scale, scale);
|
|
8169
|
+
ctx.drawImage(img, 0, 0, width, height);
|
|
8170
|
+
resolve(canvas.toDataURL('image/png'));
|
|
8171
|
+
};
|
|
8172
|
+
img.onerror = () => resolve(null);
|
|
8173
|
+
img.src = svgUrl;
|
|
8174
|
+
});
|
|
8175
|
+
}
|
|
8176
|
+
|
|
8114
8177
|
function getVisibleShadow(shadowStr, scale) {
|
|
8115
8178
|
if (!shadowStr || shadowStr === 'none') return null;
|
|
8116
8179
|
const shadows = shadowStr.split(/,(?![^()]*\))/);
|
|
@@ -8621,23 +8684,18 @@ function prepareRenderItem(node, config, domOrder, pptx, effectiveZIndex, comput
|
|
|
8621
8684
|
|
|
8622
8685
|
const items = [];
|
|
8623
8686
|
|
|
8624
|
-
// --- ASYNC JOB:
|
|
8625
|
-
if (
|
|
8626
|
-
node.nodeName.toUpperCase() === 'SVG' ||
|
|
8627
|
-
node.tagName.includes('-') ||
|
|
8628
|
-
node.tagName === 'ION-ICON'
|
|
8629
|
-
) {
|
|
8687
|
+
// --- ASYNC JOB: SVG Tags ---
|
|
8688
|
+
if (node.nodeName.toUpperCase() === 'SVG') {
|
|
8630
8689
|
const item = {
|
|
8631
8690
|
type: 'image',
|
|
8632
8691
|
zIndex,
|
|
8633
8692
|
domOrder,
|
|
8634
|
-
options: { x, y, w, h, rotate: rotation
|
|
8693
|
+
options: { data: null, x, y, w, h, rotate: rotation },
|
|
8635
8694
|
};
|
|
8636
8695
|
|
|
8637
|
-
// Create Job
|
|
8638
8696
|
const job = async () => {
|
|
8639
|
-
const
|
|
8640
|
-
if (
|
|
8697
|
+
const processed = await svgToPng(node);
|
|
8698
|
+
if (processed) item.options.data = processed;
|
|
8641
8699
|
else item.skip = true;
|
|
8642
8700
|
};
|
|
8643
8701
|
|
|
@@ -8687,6 +8745,34 @@ function prepareRenderItem(node, config, domOrder, pptx, effectiveZIndex, comput
|
|
|
8687
8745
|
return { items: [item], job, stopRecursion: true };
|
|
8688
8746
|
}
|
|
8689
8747
|
|
|
8748
|
+
// --- ASYNC JOB: Icons and Other Elements ---
|
|
8749
|
+
if (
|
|
8750
|
+
node.tagName.toUpperCase() === 'MATERIAL-ICON' ||
|
|
8751
|
+
node.tagName.toUpperCase() === 'ICONIFY-ICON' ||
|
|
8752
|
+
node.tagName.toUpperCase() === 'REMIX-ICON' ||
|
|
8753
|
+
node.tagName.toUpperCase() === 'ION-ICON' ||
|
|
8754
|
+
node.tagName.toUpperCase() === 'EVA-ICON' ||
|
|
8755
|
+
node.tagName.toUpperCase() === 'BOX-ICON' ||
|
|
8756
|
+
node.tagName.toUpperCase() === 'FA-ICON' ||
|
|
8757
|
+
node.tagName.includes('-')
|
|
8758
|
+
) {
|
|
8759
|
+
const item = {
|
|
8760
|
+
type: 'image',
|
|
8761
|
+
zIndex,
|
|
8762
|
+
domOrder,
|
|
8763
|
+
options: { x, y, w, h, rotate: rotation, data: null }, // Data null initially
|
|
8764
|
+
};
|
|
8765
|
+
|
|
8766
|
+
// Create Job
|
|
8767
|
+
const job = async () => {
|
|
8768
|
+
const pngData = await elementToCanvasImage(node, widthPx, heightPx);
|
|
8769
|
+
if (pngData) item.options.data = pngData;
|
|
8770
|
+
else item.skip = true;
|
|
8771
|
+
};
|
|
8772
|
+
|
|
8773
|
+
return { items: [item], job, stopRecursion: true };
|
|
8774
|
+
}
|
|
8775
|
+
|
|
8690
8776
|
// Radii logic
|
|
8691
8777
|
const borderRadiusValue = parseFloat(style.borderRadius) || 0;
|
|
8692
8778
|
const borderBottomLeftRadius = parseFloat(style.borderBottomLeftRadius) || 0;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dom-to-pptx",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.8",
|
|
4
4
|
"description": "A client-side library that converts any HTML element into a fully editable PowerPoint slide. **dom-to-pptx** transforms DOM structures into pixel-accurate `.pptx` content, preserving gradients, shadows, rounded images, and responsive layouts. It translates CSS Flexbox/Grid, linear-gradients, box-shadows, and typography into native PowerPoint shapes, enabling precise, design-faithful slide generation directly from the browser.",
|
|
5
5
|
"main": "dist/dom-to-pptx.cjs",
|
|
6
6
|
"module": "dist/dom-to-pptx.mjs",
|
package/src/index.js
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
getVisibleShadow,
|
|
13
13
|
generateGradientSVG,
|
|
14
14
|
getRotation,
|
|
15
|
+
svgToPng,
|
|
15
16
|
getPadding,
|
|
16
17
|
getSoftEdges,
|
|
17
18
|
generateBlurredSVG,
|
|
@@ -312,23 +313,18 @@ function prepareRenderItem(node, config, domOrder, pptx, effectiveZIndex, comput
|
|
|
312
313
|
|
|
313
314
|
const items = [];
|
|
314
315
|
|
|
315
|
-
// --- ASYNC JOB:
|
|
316
|
-
if (
|
|
317
|
-
node.nodeName.toUpperCase() === 'SVG' ||
|
|
318
|
-
node.tagName.includes('-') ||
|
|
319
|
-
node.tagName === 'ION-ICON'
|
|
320
|
-
) {
|
|
316
|
+
// --- ASYNC JOB: SVG Tags ---
|
|
317
|
+
if (node.nodeName.toUpperCase() === 'SVG') {
|
|
321
318
|
const item = {
|
|
322
319
|
type: 'image',
|
|
323
320
|
zIndex,
|
|
324
321
|
domOrder,
|
|
325
|
-
options: { x, y, w, h, rotate: rotation
|
|
322
|
+
options: { data: null, x, y, w, h, rotate: rotation },
|
|
326
323
|
};
|
|
327
324
|
|
|
328
|
-
// Create Job
|
|
329
325
|
const job = async () => {
|
|
330
|
-
const
|
|
331
|
-
if (
|
|
326
|
+
const processed = await svgToPng(node);
|
|
327
|
+
if (processed) item.options.data = processed;
|
|
332
328
|
else item.skip = true;
|
|
333
329
|
};
|
|
334
330
|
|
|
@@ -378,6 +374,34 @@ function prepareRenderItem(node, config, domOrder, pptx, effectiveZIndex, comput
|
|
|
378
374
|
return { items: [item], job, stopRecursion: true };
|
|
379
375
|
}
|
|
380
376
|
|
|
377
|
+
// --- ASYNC JOB: Icons and Other Elements ---
|
|
378
|
+
if (
|
|
379
|
+
node.tagName.toUpperCase() === 'MATERIAL-ICON' ||
|
|
380
|
+
node.tagName.toUpperCase() === 'ICONIFY-ICON' ||
|
|
381
|
+
node.tagName.toUpperCase() === 'REMIX-ICON' ||
|
|
382
|
+
node.tagName.toUpperCase() === 'ION-ICON' ||
|
|
383
|
+
node.tagName.toUpperCase() === 'EVA-ICON' ||
|
|
384
|
+
node.tagName.toUpperCase() === 'BOX-ICON' ||
|
|
385
|
+
node.tagName.toUpperCase() === 'FA-ICON' ||
|
|
386
|
+
node.tagName.includes('-')
|
|
387
|
+
) {
|
|
388
|
+
const item = {
|
|
389
|
+
type: 'image',
|
|
390
|
+
zIndex,
|
|
391
|
+
domOrder,
|
|
392
|
+
options: { x, y, w, h, rotate: rotation, data: null }, // Data null initially
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
// Create Job
|
|
396
|
+
const job = async () => {
|
|
397
|
+
const pngData = await elementToCanvasImage(node, widthPx, heightPx);
|
|
398
|
+
if (pngData) item.options.data = pngData;
|
|
399
|
+
else item.skip = true;
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
return { items: [item], job, stopRecursion: true };
|
|
403
|
+
}
|
|
404
|
+
|
|
381
405
|
// Radii logic
|
|
382
406
|
const borderRadiusValue = parseFloat(style.borderRadius) || 0;
|
|
383
407
|
const borderBottomLeftRadius = parseFloat(style.borderBottomLeftRadius) || 0;
|