dom-to-pptx 1.0.3 → 1.0.5
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 +18 -0
- package/Readme.md +249 -204
- package/SUPPORTED.md +50 -0
- package/dist/dom-to-pptx.bundle.js +31144 -0
- package/dist/dom-to-pptx.cjs +8568 -594
- package/dist/dom-to-pptx.min.js +8926 -952
- package/dist/dom-to-pptx.mjs +8555 -581
- package/package.json +7 -5
- package/rollup.config.js +53 -33
- package/src/image-processor.js +76 -48
- package/src/index.js +246 -91
- package/src/utils.js +452 -477
package/src/index.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
// src/index.js
|
|
2
2
|
import * as PptxGenJSImport from 'pptxgenjs';
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
import html2canvas from 'html2canvas';
|
|
4
|
+
|
|
5
|
+
// Normalize import
|
|
5
6
|
const PptxGenJS = PptxGenJSImport?.default ?? PptxGenJSImport;
|
|
7
|
+
|
|
6
8
|
import {
|
|
7
9
|
parseColor,
|
|
8
10
|
getTextStyle,
|
|
@@ -16,6 +18,8 @@ import {
|
|
|
16
18
|
generateBlurredSVG,
|
|
17
19
|
getBorderInfo,
|
|
18
20
|
generateCompositeBorderSVG,
|
|
21
|
+
isClippedByParent,
|
|
22
|
+
generateCustomShapeSVG,
|
|
19
23
|
} from './utils.js';
|
|
20
24
|
import { getProcessedImage } from './image-processor.js';
|
|
21
25
|
|
|
@@ -28,23 +32,21 @@ const PX_TO_INCH = 1 / PPI;
|
|
|
28
32
|
* @param {Object} options - { fileName: string }
|
|
29
33
|
*/
|
|
30
34
|
export async function exportToPptx(target, options = {}) {
|
|
31
|
-
// Resolve the actual constructor in case `pptxgenjs` was imported/required
|
|
32
|
-
// with different shapes (function, { default: fn }, or { PptxGenJS: fn }).
|
|
33
35
|
const resolvePptxConstructor = (pkg) => {
|
|
34
36
|
if (!pkg) return null;
|
|
35
37
|
if (typeof pkg === 'function') return pkg;
|
|
36
38
|
if (pkg && typeof pkg.default === 'function') return pkg.default;
|
|
37
39
|
if (pkg && typeof pkg.PptxGenJS === 'function') return pkg.PptxGenJS;
|
|
38
|
-
if (pkg && pkg.PptxGenJS && typeof pkg.PptxGenJS.default === 'function')
|
|
40
|
+
if (pkg && pkg.PptxGenJS && typeof pkg.PptxGenJS.default === 'function')
|
|
41
|
+
return pkg.PptxGenJS.default;
|
|
39
42
|
return null;
|
|
40
43
|
};
|
|
41
44
|
|
|
42
45
|
const PptxConstructor = resolvePptxConstructor(PptxGenJS);
|
|
43
|
-
if (!PptxConstructor) throw new Error('PptxGenJS constructor not found.
|
|
46
|
+
if (!PptxConstructor) throw new Error('PptxGenJS constructor not found.');
|
|
44
47
|
const pptx = new PptxConstructor();
|
|
45
48
|
pptx.layout = 'LAYOUT_16x9';
|
|
46
49
|
|
|
47
|
-
// Standardize input to an array, ensuring single or multiple elements are handled consistently
|
|
48
50
|
const elements = Array.isArray(target) ? target : [target];
|
|
49
51
|
|
|
50
52
|
for (const el of elements) {
|
|
@@ -53,7 +55,6 @@ export async function exportToPptx(target, options = {}) {
|
|
|
53
55
|
console.warn('Element not found, skipping slide:', el);
|
|
54
56
|
continue;
|
|
55
57
|
}
|
|
56
|
-
|
|
57
58
|
const slide = pptx.addSlide();
|
|
58
59
|
await processSlide(root, slide, pptx);
|
|
59
60
|
}
|
|
@@ -89,11 +90,11 @@ async function processSlide(root, slide, pptx) {
|
|
|
89
90
|
let domOrderCounter = 0;
|
|
90
91
|
|
|
91
92
|
async function collect(node) {
|
|
92
|
-
const order = domOrderCounter++;
|
|
93
|
-
const result = await createRenderItem(node, layoutConfig, order, pptx);
|
|
93
|
+
const order = domOrderCounter++;
|
|
94
|
+
const result = await createRenderItem(node, { ...layoutConfig, root }, order, pptx);
|
|
94
95
|
if (result) {
|
|
95
|
-
if (result.items) renderQueue.push(...result.items);
|
|
96
|
-
if (result.stopRecursion) return;
|
|
96
|
+
if (result.items) renderQueue.push(...result.items);
|
|
97
|
+
if (result.stopRecursion) return;
|
|
97
98
|
}
|
|
98
99
|
for (const child of node.children) await collect(child);
|
|
99
100
|
}
|
|
@@ -112,6 +113,79 @@ async function processSlide(root, slide, pptx) {
|
|
|
112
113
|
}
|
|
113
114
|
}
|
|
114
115
|
|
|
116
|
+
async function elementToCanvasImage(node, widthPx, heightPx, root) {
|
|
117
|
+
return new Promise((resolve) => {
|
|
118
|
+
const width = Math.ceil(widthPx);
|
|
119
|
+
const height = Math.ceil(heightPx);
|
|
120
|
+
|
|
121
|
+
if (width <= 0 || height <= 0) {
|
|
122
|
+
resolve(null);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const style = window.getComputedStyle(node);
|
|
127
|
+
|
|
128
|
+
html2canvas(root, {
|
|
129
|
+
width: root.scrollWidth,
|
|
130
|
+
height: root.scrollHeight,
|
|
131
|
+
useCORS: true,
|
|
132
|
+
allowTaint: true,
|
|
133
|
+
backgroundColor: null,
|
|
134
|
+
})
|
|
135
|
+
.then((canvas) => {
|
|
136
|
+
const rootCanvas = canvas;
|
|
137
|
+
const nodeRect = node.getBoundingClientRect();
|
|
138
|
+
const rootRect = root.getBoundingClientRect();
|
|
139
|
+
const sourceX = nodeRect.left - rootRect.left;
|
|
140
|
+
const sourceY = nodeRect.top - rootRect.top;
|
|
141
|
+
|
|
142
|
+
const destCanvas = document.createElement('canvas');
|
|
143
|
+
destCanvas.width = width;
|
|
144
|
+
destCanvas.height = height;
|
|
145
|
+
const ctx = destCanvas.getContext('2d');
|
|
146
|
+
|
|
147
|
+
ctx.drawImage(rootCanvas, sourceX, sourceY, width, height, 0, 0, width, height);
|
|
148
|
+
|
|
149
|
+
// Parse radii
|
|
150
|
+
let tl = parseFloat(style.borderTopLeftRadius) || 0;
|
|
151
|
+
let tr = parseFloat(style.borderTopRightRadius) || 0;
|
|
152
|
+
let br = parseFloat(style.borderBottomRightRadius) || 0;
|
|
153
|
+
let bl = parseFloat(style.borderBottomLeftRadius) || 0;
|
|
154
|
+
|
|
155
|
+
const f = Math.min(
|
|
156
|
+
width / (tl + tr) || Infinity,
|
|
157
|
+
height / (tr + br) || Infinity,
|
|
158
|
+
width / (br + bl) || Infinity,
|
|
159
|
+
height / (bl + tl) || Infinity
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
if (f < 1) {
|
|
163
|
+
tl *= f;
|
|
164
|
+
tr *= f;
|
|
165
|
+
br *= f;
|
|
166
|
+
bl *= f;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
ctx.globalCompositeOperation = 'destination-in';
|
|
170
|
+
ctx.beginPath();
|
|
171
|
+
ctx.moveTo(tl, 0);
|
|
172
|
+
ctx.lineTo(width - tr, 0);
|
|
173
|
+
ctx.arcTo(width, 0, width, tr, tr);
|
|
174
|
+
ctx.lineTo(width, height - br);
|
|
175
|
+
ctx.arcTo(width, height, width - br, height, br);
|
|
176
|
+
ctx.lineTo(bl, height);
|
|
177
|
+
ctx.arcTo(0, height, 0, height - bl, bl);
|
|
178
|
+
ctx.lineTo(0, tl);
|
|
179
|
+
ctx.arcTo(0, 0, tl, 0, tl);
|
|
180
|
+
ctx.closePath();
|
|
181
|
+
ctx.fill();
|
|
182
|
+
|
|
183
|
+
resolve(destCanvas.toDataURL('image/png'));
|
|
184
|
+
})
|
|
185
|
+
.catch(() => resolve(null));
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
115
189
|
async function createRenderItem(node, config, domOrder, pptx) {
|
|
116
190
|
if (node.nodeType !== 1) return null;
|
|
117
191
|
const style = window.getComputedStyle(node);
|
|
@@ -119,7 +193,7 @@ async function createRenderItem(node, config, domOrder, pptx) {
|
|
|
119
193
|
return null;
|
|
120
194
|
|
|
121
195
|
const rect = node.getBoundingClientRect();
|
|
122
|
-
if (rect.width
|
|
196
|
+
if (rect.width < 0.5 || rect.height < 0.5) return null;
|
|
123
197
|
|
|
124
198
|
const zIndex = style.zIndex !== 'auto' ? parseInt(style.zIndex) : 0;
|
|
125
199
|
const rotation = getRotation(style.transform);
|
|
@@ -139,7 +213,6 @@ async function createRenderItem(node, config, domOrder, pptx) {
|
|
|
139
213
|
|
|
140
214
|
const items = [];
|
|
141
215
|
|
|
142
|
-
// Image handling for SVG nodes directly
|
|
143
216
|
if (node.nodeName.toUpperCase() === 'SVG') {
|
|
144
217
|
const pngData = await svgToPng(node);
|
|
145
218
|
if (pngData)
|
|
@@ -151,15 +224,42 @@ async function createRenderItem(node, config, domOrder, pptx) {
|
|
|
151
224
|
});
|
|
152
225
|
return { items, stopRecursion: true };
|
|
153
226
|
}
|
|
154
|
-
|
|
227
|
+
|
|
228
|
+
// --- UPDATED IMG BLOCK START ---
|
|
155
229
|
if (node.tagName === 'IMG') {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
230
|
+
// Extract individual corner radii
|
|
231
|
+
let radii = {
|
|
232
|
+
tl: parseFloat(style.borderTopLeftRadius) || 0,
|
|
233
|
+
tr: parseFloat(style.borderTopRightRadius) || 0,
|
|
234
|
+
br: parseFloat(style.borderBottomRightRadius) || 0,
|
|
235
|
+
bl: parseFloat(style.borderBottomLeftRadius) || 0,
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
const hasAnyRadius = radii.tl > 0 || radii.tr > 0 || radii.br > 0 || radii.bl > 0;
|
|
239
|
+
|
|
240
|
+
// Fallback: Check parent if image has no specific radius but parent clips it
|
|
241
|
+
if (!hasAnyRadius) {
|
|
242
|
+
const parent = node.parentElement;
|
|
243
|
+
const parentStyle = window.getComputedStyle(parent);
|
|
244
|
+
if (parentStyle.overflow !== 'visible') {
|
|
245
|
+
const pRadii = {
|
|
246
|
+
tl: parseFloat(parentStyle.borderTopLeftRadius) || 0,
|
|
247
|
+
tr: parseFloat(parentStyle.borderTopRightRadius) || 0,
|
|
248
|
+
br: parseFloat(parentStyle.borderBottomRightRadius) || 0,
|
|
249
|
+
bl: parseFloat(parentStyle.borderBottomLeftRadius) || 0,
|
|
250
|
+
};
|
|
251
|
+
// Simple heuristic: If image takes up full size of parent, inherit radii.
|
|
252
|
+
// For complex grids (like slide-1), this blindly applies parent radius.
|
|
253
|
+
// In a perfect world, we'd calculate intersection, but for now we apply parent radius
|
|
254
|
+
// if the image is close to the parent's size, effectively masking it.
|
|
255
|
+
const pRect = parent.getBoundingClientRect();
|
|
256
|
+
if (Math.abs(pRect.width - rect.width) < 5 && Math.abs(pRect.height - rect.height) < 5) {
|
|
257
|
+
radii = pRadii;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
161
260
|
}
|
|
162
|
-
|
|
261
|
+
|
|
262
|
+
const processed = await getProcessedImage(node.src, widthPx, heightPx, radii);
|
|
163
263
|
if (processed)
|
|
164
264
|
items.push({
|
|
165
265
|
type: 'image',
|
|
@@ -169,6 +269,44 @@ async function createRenderItem(node, config, domOrder, pptx) {
|
|
|
169
269
|
});
|
|
170
270
|
return { items, stopRecursion: true };
|
|
171
271
|
}
|
|
272
|
+
// --- UPDATED IMG BLOCK END ---
|
|
273
|
+
|
|
274
|
+
// Radii processing for Divs/Shapes
|
|
275
|
+
const borderRadiusValue = parseFloat(style.borderRadius) || 0;
|
|
276
|
+
const borderBottomLeftRadius = parseFloat(style.borderBottomLeftRadius) || 0;
|
|
277
|
+
const borderBottomRightRadius = parseFloat(style.borderBottomRightRadius) || 0;
|
|
278
|
+
const borderTopLeftRadius = parseFloat(style.borderTopLeftRadius) || 0;
|
|
279
|
+
const borderTopRightRadius = parseFloat(style.borderTopRightRadius) || 0;
|
|
280
|
+
|
|
281
|
+
const hasPartialBorderRadius =
|
|
282
|
+
(borderBottomLeftRadius > 0 && borderBottomLeftRadius !== borderRadiusValue) ||
|
|
283
|
+
(borderBottomRightRadius > 0 && borderBottomRightRadius !== borderRadiusValue) ||
|
|
284
|
+
(borderTopLeftRadius > 0 && borderTopLeftRadius !== borderRadiusValue) ||
|
|
285
|
+
(borderTopRightRadius > 0 && borderTopRightRadius !== borderRadiusValue) ||
|
|
286
|
+
(borderRadiusValue === 0 &&
|
|
287
|
+
(borderBottomLeftRadius ||
|
|
288
|
+
borderBottomRightRadius ||
|
|
289
|
+
borderTopLeftRadius ||
|
|
290
|
+
borderTopRightRadius));
|
|
291
|
+
|
|
292
|
+
// Allow clipped elements to be rendered via canvas
|
|
293
|
+
if (hasPartialBorderRadius && isClippedByParent(node)) {
|
|
294
|
+
const marginLeft = parseFloat(style.marginLeft) || 0;
|
|
295
|
+
const marginTop = parseFloat(style.marginTop) || 0;
|
|
296
|
+
x += marginLeft * PX_TO_INCH * config.scale;
|
|
297
|
+
y += marginTop * PX_TO_INCH * config.scale;
|
|
298
|
+
|
|
299
|
+
const canvasImageData = await elementToCanvasImage(node, widthPx, heightPx, config.root);
|
|
300
|
+
if (canvasImageData) {
|
|
301
|
+
items.push({
|
|
302
|
+
type: 'image',
|
|
303
|
+
zIndex,
|
|
304
|
+
domOrder,
|
|
305
|
+
options: { data: canvasImageData, x, y, w, h, rotate: rotation },
|
|
306
|
+
});
|
|
307
|
+
return { items, stopRecursion: true };
|
|
308
|
+
}
|
|
309
|
+
}
|
|
172
310
|
|
|
173
311
|
const bgColorObj = parseColor(style.backgroundColor);
|
|
174
312
|
const bgClip = style.webkitBackgroundClip || style.backgroundClip;
|
|
@@ -186,7 +324,6 @@ async function createRenderItem(node, config, domOrder, pptx) {
|
|
|
186
324
|
|
|
187
325
|
const shadowStr = style.boxShadow;
|
|
188
326
|
const hasShadow = shadowStr && shadowStr !== 'none';
|
|
189
|
-
const borderRadius = parseFloat(style.borderRadius) || 0;
|
|
190
327
|
const softEdge = getSoftEdges(style.filter, config.scale);
|
|
191
328
|
|
|
192
329
|
let isImageWrapper = false;
|
|
@@ -211,7 +348,6 @@ async function createRenderItem(node, config, domOrder, pptx) {
|
|
|
211
348
|
textParts.push({
|
|
212
349
|
text: '• ',
|
|
213
350
|
options: {
|
|
214
|
-
// Default bullet point styling
|
|
215
351
|
color: parseColor(style.color).hex || '000000',
|
|
216
352
|
fontSize: fontSizePt,
|
|
217
353
|
},
|
|
@@ -219,7 +355,6 @@ async function createRenderItem(node, config, domOrder, pptx) {
|
|
|
219
355
|
}
|
|
220
356
|
|
|
221
357
|
node.childNodes.forEach((child, index) => {
|
|
222
|
-
// Process text content, sanitizing whitespace and applying text transformations
|
|
223
358
|
let textVal = child.nodeType === 3 ? child.nodeValue : child.textContent;
|
|
224
359
|
let nodeStyle = child.nodeType === 1 ? window.getComputedStyle(child) : style;
|
|
225
360
|
textVal = textVal.replace(/[\n\r\t]+/g, ' ').replace(/\s{2,}/g, ' ');
|
|
@@ -260,7 +395,13 @@ async function createRenderItem(node, config, domOrder, pptx) {
|
|
|
260
395
|
let bgData = null;
|
|
261
396
|
let padIn = 0;
|
|
262
397
|
if (softEdge) {
|
|
263
|
-
const svgInfo = generateBlurredSVG(
|
|
398
|
+
const svgInfo = generateBlurredSVG(
|
|
399
|
+
widthPx,
|
|
400
|
+
heightPx,
|
|
401
|
+
bgColorObj.hex,
|
|
402
|
+
borderRadiusValue,
|
|
403
|
+
softEdge
|
|
404
|
+
);
|
|
264
405
|
bgData = svgInfo.data;
|
|
265
406
|
padIn = svgInfo.padding * PX_TO_INCH * config.scale;
|
|
266
407
|
} else {
|
|
@@ -268,7 +409,7 @@ async function createRenderItem(node, config, domOrder, pptx) {
|
|
|
268
409
|
widthPx,
|
|
269
410
|
heightPx,
|
|
270
411
|
style.backgroundImage,
|
|
271
|
-
|
|
412
|
+
borderRadiusValue,
|
|
272
413
|
hasBorder ? { color: borderColorObj.hex, width: borderWidth } : null
|
|
273
414
|
);
|
|
274
415
|
}
|
|
@@ -333,91 +474,105 @@ async function createRenderItem(node, config, domOrder, pptx) {
|
|
|
333
474
|
) {
|
|
334
475
|
const finalAlpha = elementOpacity * bgColorObj.opacity;
|
|
335
476
|
const transparency = (1 - finalAlpha) * 100;
|
|
477
|
+
const useSolidFill = bgColorObj.hex && !isImageWrapper;
|
|
336
478
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
:
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
if (hasShadow) {
|
|
352
|
-
shapeOpts.shadow = getVisibleShadow(shadowStr, config.scale);
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
const borderRadius = parseFloat(style.borderRadius) || 0;
|
|
356
|
-
const widthPx = node.offsetWidth || rect.width;
|
|
357
|
-
const heightPx = node.offsetHeight || rect.height;
|
|
358
|
-
const isCircle =
|
|
359
|
-
borderRadius >= Math.min(widthPx, heightPx) / 2 - 1 && Math.abs(widthPx - heightPx) < 2;
|
|
360
|
-
|
|
361
|
-
let shapeType = pptx.ShapeType.rect;
|
|
362
|
-
if (isCircle) shapeType = pptx.ShapeType.ellipse;
|
|
363
|
-
else if (borderRadius > 0) {
|
|
364
|
-
shapeType = pptx.ShapeType.roundRect;
|
|
365
|
-
shapeOpts.rectRadius = Math.min(1, borderRadius / (Math.min(widthPx, heightPx) / 2));
|
|
366
|
-
}
|
|
479
|
+
if (hasPartialBorderRadius && useSolidFill && !textPayload) {
|
|
480
|
+
const shapeSvg = generateCustomShapeSVG(
|
|
481
|
+
widthPx,
|
|
482
|
+
heightPx,
|
|
483
|
+
bgColorObj.hex,
|
|
484
|
+
bgColorObj.opacity,
|
|
485
|
+
{
|
|
486
|
+
tl: parseFloat(style.borderTopLeftRadius) || 0,
|
|
487
|
+
tr: parseFloat(style.borderTopRightRadius) || 0,
|
|
488
|
+
br: parseFloat(style.borderBottomRightRadius) || 0,
|
|
489
|
+
bl: parseFloat(style.borderBottomLeftRadius) || 0,
|
|
490
|
+
}
|
|
491
|
+
);
|
|
367
492
|
|
|
368
|
-
// MERGE TEXT INTO SHAPE (if text exists)
|
|
369
|
-
if (textPayload) {
|
|
370
|
-
const textOptions = {
|
|
371
|
-
shape: shapeType,
|
|
372
|
-
...shapeOpts,
|
|
373
|
-
align: textPayload.align,
|
|
374
|
-
valign: textPayload.valign,
|
|
375
|
-
inset: textPayload.inset,
|
|
376
|
-
margin: 0,
|
|
377
|
-
wrap: true,
|
|
378
|
-
autoFit: false,
|
|
379
|
-
};
|
|
380
493
|
items.push({
|
|
381
|
-
type: '
|
|
494
|
+
type: 'image',
|
|
382
495
|
zIndex,
|
|
383
496
|
domOrder,
|
|
384
|
-
|
|
385
|
-
|
|
497
|
+
options: {
|
|
498
|
+
data: shapeSvg,
|
|
499
|
+
x,
|
|
500
|
+
y,
|
|
501
|
+
w,
|
|
502
|
+
h,
|
|
503
|
+
rotate: rotation,
|
|
504
|
+
},
|
|
386
505
|
});
|
|
387
|
-
// If no text, just draw the shape
|
|
388
506
|
} else {
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
507
|
+
const shapeOpts = {
|
|
508
|
+
x,
|
|
509
|
+
y,
|
|
510
|
+
w,
|
|
511
|
+
h,
|
|
512
|
+
rotate: rotation,
|
|
513
|
+
fill: useSolidFill
|
|
514
|
+
? { color: bgColorObj.hex, transparency: transparency }
|
|
515
|
+
: { type: 'none' },
|
|
516
|
+
line: hasUniformBorder ? borderInfo.options : null,
|
|
517
|
+
};
|
|
518
|
+
|
|
519
|
+
if (hasShadow) {
|
|
520
|
+
shapeOpts.shadow = getVisibleShadow(shadowStr, config.scale);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
const borderRadius = parseFloat(style.borderRadius) || 0;
|
|
524
|
+
const aspectRatio = Math.max(widthPx, heightPx) / Math.min(widthPx, heightPx);
|
|
525
|
+
const isCircle = aspectRatio < 1.1 && borderRadius >= Math.min(widthPx, heightPx) / 2 - 1;
|
|
526
|
+
|
|
527
|
+
let shapeType = pptx.ShapeType.rect;
|
|
528
|
+
if (isCircle) shapeType = pptx.ShapeType.ellipse;
|
|
529
|
+
else if (borderRadius > 0) {
|
|
530
|
+
shapeType = pptx.ShapeType.roundRect;
|
|
531
|
+
shapeOpts.rectRadius = Math.min(0.5, borderRadius / Math.min(widthPx, heightPx));
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
if (textPayload) {
|
|
535
|
+
const textOptions = {
|
|
536
|
+
shape: shapeType,
|
|
537
|
+
...shapeOpts,
|
|
538
|
+
align: textPayload.align,
|
|
539
|
+
valign: textPayload.valign,
|
|
540
|
+
inset: textPayload.inset,
|
|
541
|
+
margin: 0,
|
|
542
|
+
wrap: true,
|
|
543
|
+
autoFit: false,
|
|
544
|
+
};
|
|
545
|
+
items.push({
|
|
546
|
+
type: 'text',
|
|
547
|
+
zIndex,
|
|
548
|
+
domOrder,
|
|
549
|
+
textParts: textPayload.text,
|
|
550
|
+
options: textOptions,
|
|
551
|
+
});
|
|
552
|
+
} else if (!hasPartialBorderRadius) {
|
|
553
|
+
items.push({
|
|
554
|
+
type: 'shape',
|
|
555
|
+
zIndex,
|
|
556
|
+
domOrder,
|
|
557
|
+
shapeType,
|
|
558
|
+
options: shapeOpts,
|
|
559
|
+
});
|
|
560
|
+
}
|
|
396
561
|
}
|
|
397
562
|
|
|
398
|
-
// ADD COMPOSITE BORDERS (if they exist)
|
|
399
563
|
if (hasCompositeBorder) {
|
|
400
|
-
// Generate a single SVG image that contains all the rounded border sides
|
|
401
564
|
const borderSvgData = generateCompositeBorderSVG(
|
|
402
565
|
widthPx,
|
|
403
566
|
heightPx,
|
|
404
|
-
|
|
567
|
+
borderRadiusValue,
|
|
405
568
|
borderInfo.sides
|
|
406
569
|
);
|
|
407
|
-
|
|
408
570
|
if (borderSvgData) {
|
|
409
571
|
items.push({
|
|
410
572
|
type: 'image',
|
|
411
573
|
zIndex: zIndex + 1,
|
|
412
574
|
domOrder,
|
|
413
|
-
options: {
|
|
414
|
-
data: borderSvgData,
|
|
415
|
-
x: x,
|
|
416
|
-
y: y,
|
|
417
|
-
w: w,
|
|
418
|
-
h: h,
|
|
419
|
-
rotate: rotation,
|
|
420
|
-
},
|
|
575
|
+
options: { data: borderSvgData, x, y, w, h, rotate: rotation },
|
|
421
576
|
});
|
|
422
577
|
}
|
|
423
578
|
}
|