dom-to-pptx 1.0.5 → 1.0.7
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 +31 -18
- package/{Readme.md → README.md} +165 -85
- package/SUPPORTED.md +50 -50
- package/dist/dom-to-pptx.bundle.js +18565 -30968
- package/dist/dom-to-pptx.cjs +358 -304
- package/dist/dom-to-pptx.min.js +356 -304
- package/dist/dom-to-pptx.mjs +352 -301
- package/package.json +73 -73
- package/rollup.config.js +62 -53
- package/src/image-processor.js +79 -76
- package/src/index.js +222 -148
- package/src/utils.js +59 -23
package/dist/dom-to-pptx.mjs
CHANGED
|
@@ -53,7 +53,7 @@ function __awaiter(thisArg, _arguments, P, generator) {
|
|
|
53
53
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
54
54
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
55
55
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
56
|
-
step((generator = generator.apply(thisArg, [])).next());
|
|
56
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
57
57
|
});
|
|
58
58
|
}
|
|
59
59
|
|
|
@@ -86,7 +86,7 @@ function __generator(thisArg, body) {
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
function __spreadArray(to, from, pack) {
|
|
89
|
-
if (arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
|
|
89
|
+
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
|
|
90
90
|
if (ar || !(i in from)) {
|
|
91
91
|
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
|
|
92
92
|
ar[i] = from[i];
|
|
@@ -7955,17 +7955,20 @@ function generateCompositeBorderSVG(w, h, radius, sides) {
|
|
|
7955
7955
|
*/
|
|
7956
7956
|
function generateCustomShapeSVG(w, h, color, opacity, radii) {
|
|
7957
7957
|
let { tl, tr, br, bl } = radii;
|
|
7958
|
-
|
|
7958
|
+
|
|
7959
7959
|
// Clamp radii using CSS spec logic (avoid overlap)
|
|
7960
7960
|
const factor = Math.min(
|
|
7961
|
-
|
|
7962
|
-
|
|
7963
|
-
|
|
7964
|
-
|
|
7961
|
+
w / (tl + tr) || Infinity,
|
|
7962
|
+
h / (tr + br) || Infinity,
|
|
7963
|
+
w / (br + bl) || Infinity,
|
|
7964
|
+
h / (bl + tl) || Infinity
|
|
7965
7965
|
);
|
|
7966
|
-
|
|
7966
|
+
|
|
7967
7967
|
if (factor < 1) {
|
|
7968
|
-
tl *= factor;
|
|
7968
|
+
tl *= factor;
|
|
7969
|
+
tr *= factor;
|
|
7970
|
+
br *= factor;
|
|
7971
|
+
bl *= factor;
|
|
7969
7972
|
}
|
|
7970
7973
|
|
|
7971
7974
|
const path = `
|
|
@@ -7996,7 +7999,10 @@ function parseColor(str) {
|
|
|
7996
7999
|
if (str.startsWith('#')) {
|
|
7997
8000
|
let hex = str.slice(1);
|
|
7998
8001
|
if (hex.length === 3)
|
|
7999
|
-
hex = hex
|
|
8002
|
+
hex = hex
|
|
8003
|
+
.split('')
|
|
8004
|
+
.map((c) => c + c)
|
|
8005
|
+
.join('');
|
|
8000
8006
|
return { hex: hex.toUpperCase(), opacity: 1 };
|
|
8001
8007
|
}
|
|
8002
8008
|
const match = str.match(/[\d.]+/g);
|
|
@@ -8059,25 +8065,35 @@ function isTextContainer(node) {
|
|
|
8059
8065
|
|
|
8060
8066
|
// Check if children are purely inline text formatting or visual shapes
|
|
8061
8067
|
const isSafeInline = (el) => {
|
|
8068
|
+
// 1. Reject Web Components / Icons / Images
|
|
8069
|
+
if (el.tagName.includes('-')) return false;
|
|
8070
|
+
if (el.tagName === 'IMG' || el.tagName === 'SVG') return false;
|
|
8071
|
+
|
|
8062
8072
|
const style = window.getComputedStyle(el);
|
|
8063
8073
|
const display = style.display;
|
|
8064
|
-
|
|
8065
|
-
//
|
|
8066
|
-
const isInlineTag = ['SPAN', 'B', 'STRONG', 'EM', 'I', 'A', 'SMALL'].includes(el.tagName);
|
|
8074
|
+
|
|
8075
|
+
// 2. Initial check: Must be a standard inline tag OR display:inline
|
|
8076
|
+
const isInlineTag = ['SPAN', 'B', 'STRONG', 'EM', 'I', 'A', 'SMALL', 'MARK'].includes(el.tagName);
|
|
8067
8077
|
const isInlineDisplay = display.includes('inline');
|
|
8068
8078
|
|
|
8069
8079
|
if (!isInlineTag && !isInlineDisplay) return false;
|
|
8070
8080
|
|
|
8071
|
-
//
|
|
8072
|
-
//
|
|
8073
|
-
//
|
|
8074
|
-
|
|
8081
|
+
// 3. CRITICAL FIX: Check for Structural Styling
|
|
8082
|
+
// PPTX Text Runs (parts of a text line) CANNOT have backgrounds, borders, or padding.
|
|
8083
|
+
// If a child element has these, the parent is NOT a simple text container;
|
|
8084
|
+
// it is a layout container composed of styled blocks.
|
|
8075
8085
|
const bgColor = parseColor(style.backgroundColor);
|
|
8076
8086
|
const hasVisibleBg = bgColor.hex && bgColor.opacity > 0;
|
|
8077
8087
|
const hasBorder = parseFloat(style.borderWidth) > 0 && parseColor(style.borderColor).opacity > 0;
|
|
8078
8088
|
|
|
8089
|
+
if (hasVisibleBg || hasBorder) {
|
|
8090
|
+
return false;
|
|
8091
|
+
}
|
|
8092
|
+
|
|
8093
|
+
// 4. Check for empty shapes (visual objects without text, like dots)
|
|
8094
|
+
const hasContent = el.textContent.trim().length > 0;
|
|
8079
8095
|
if (!hasContent && (hasVisibleBg || hasBorder)) {
|
|
8080
|
-
return false;
|
|
8096
|
+
return false;
|
|
8081
8097
|
}
|
|
8082
8098
|
|
|
8083
8099
|
return true;
|
|
@@ -8095,62 +8111,6 @@ function getRotation(transformStr) {
|
|
|
8095
8111
|
return Math.round(Math.atan2(b, a) * (180 / Math.PI));
|
|
8096
8112
|
}
|
|
8097
8113
|
|
|
8098
|
-
function svgToPng(node) {
|
|
8099
|
-
return new Promise((resolve) => {
|
|
8100
|
-
const clone = node.cloneNode(true);
|
|
8101
|
-
const rect = node.getBoundingClientRect();
|
|
8102
|
-
const width = rect.width || 300;
|
|
8103
|
-
const height = rect.height || 150;
|
|
8104
|
-
|
|
8105
|
-
function inlineStyles(source, target) {
|
|
8106
|
-
const computed = window.getComputedStyle(source);
|
|
8107
|
-
const properties = [
|
|
8108
|
-
'fill', 'stroke', 'stroke-width', 'stroke-linecap',
|
|
8109
|
-
'stroke-linejoin', 'opacity', 'font-family', 'font-size', 'font-weight',
|
|
8110
|
-
];
|
|
8111
|
-
|
|
8112
|
-
if (computed.fill === 'none') target.setAttribute('fill', 'none');
|
|
8113
|
-
else if (computed.fill) target.style.fill = computed.fill;
|
|
8114
|
-
|
|
8115
|
-
if (computed.stroke === 'none') target.setAttribute('stroke', 'none');
|
|
8116
|
-
else if (computed.stroke) target.style.stroke = computed.stroke;
|
|
8117
|
-
|
|
8118
|
-
properties.forEach((prop) => {
|
|
8119
|
-
if (prop !== 'fill' && prop !== 'stroke') {
|
|
8120
|
-
const val = computed[prop];
|
|
8121
|
-
if (val && val !== 'auto') target.style[prop] = val;
|
|
8122
|
-
}
|
|
8123
|
-
});
|
|
8124
|
-
|
|
8125
|
-
for (let i = 0; i < source.children.length; i++) {
|
|
8126
|
-
if (target.children[i]) inlineStyles(source.children[i], target.children[i]);
|
|
8127
|
-
}
|
|
8128
|
-
}
|
|
8129
|
-
|
|
8130
|
-
inlineStyles(node, clone);
|
|
8131
|
-
clone.setAttribute('width', width);
|
|
8132
|
-
clone.setAttribute('height', height);
|
|
8133
|
-
clone.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
|
|
8134
|
-
|
|
8135
|
-
const xml = new XMLSerializer().serializeToString(clone);
|
|
8136
|
-
const svgUrl = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(xml)}`;
|
|
8137
|
-
const img = new Image();
|
|
8138
|
-
img.crossOrigin = 'Anonymous';
|
|
8139
|
-
img.onload = () => {
|
|
8140
|
-
const canvas = document.createElement('canvas');
|
|
8141
|
-
const scale = 3;
|
|
8142
|
-
canvas.width = width * scale;
|
|
8143
|
-
canvas.height = height * scale;
|
|
8144
|
-
const ctx = canvas.getContext('2d');
|
|
8145
|
-
ctx.scale(scale, scale);
|
|
8146
|
-
ctx.drawImage(img, 0, 0, width, height);
|
|
8147
|
-
resolve(canvas.toDataURL('image/png'));
|
|
8148
|
-
};
|
|
8149
|
-
img.onerror = () => resolve(null);
|
|
8150
|
-
img.src = svgUrl;
|
|
8151
|
-
});
|
|
8152
|
-
}
|
|
8153
|
-
|
|
8154
8114
|
function getVisibleShadow(shadowStr, scale) {
|
|
8155
8115
|
if (!shadowStr || shadowStr === 'none') return null;
|
|
8156
8116
|
const shadows = shadowStr.split(/,(?![^()]*\))/);
|
|
@@ -8189,16 +8149,29 @@ function generateGradientSVG(w, h, bgString, radius, border) {
|
|
|
8189
8149
|
const content = match[1];
|
|
8190
8150
|
const parts = content.split(/,(?![^()]*\))/).map((p) => p.trim());
|
|
8191
8151
|
|
|
8192
|
-
let x1 = '0%',
|
|
8152
|
+
let x1 = '0%',
|
|
8153
|
+
y1 = '0%',
|
|
8154
|
+
x2 = '0%',
|
|
8155
|
+
y2 = '100%';
|
|
8193
8156
|
let stopsStartIdx = 0;
|
|
8194
8157
|
if (parts[0].includes('to right')) {
|
|
8195
|
-
x1 = '0%';
|
|
8158
|
+
x1 = '0%';
|
|
8159
|
+
x2 = '100%';
|
|
8160
|
+
y2 = '0%';
|
|
8161
|
+
stopsStartIdx = 1;
|
|
8196
8162
|
} else if (parts[0].includes('to left')) {
|
|
8197
|
-
x1 = '100%';
|
|
8163
|
+
x1 = '100%';
|
|
8164
|
+
x2 = '0%';
|
|
8165
|
+
y2 = '0%';
|
|
8166
|
+
stopsStartIdx = 1;
|
|
8198
8167
|
} else if (parts[0].includes('to top')) {
|
|
8199
|
-
y1 = '100%';
|
|
8168
|
+
y1 = '100%';
|
|
8169
|
+
y2 = '0%';
|
|
8170
|
+
stopsStartIdx = 1;
|
|
8200
8171
|
} else if (parts[0].includes('to bottom')) {
|
|
8201
|
-
y1 = '0%';
|
|
8172
|
+
y1 = '0%';
|
|
8173
|
+
y2 = '100%';
|
|
8174
|
+
stopsStartIdx = 1;
|
|
8202
8175
|
}
|
|
8203
8176
|
|
|
8204
8177
|
let stopsXML = '';
|
|
@@ -8273,81 +8246,84 @@ function generateBlurredSVG(w, h, color, radius, blurPx) {
|
|
|
8273
8246
|
};
|
|
8274
8247
|
}
|
|
8275
8248
|
|
|
8276
|
-
// src/image-processor.js
|
|
8277
|
-
|
|
8278
|
-
async function getProcessedImage(src, targetW, targetH, radius) {
|
|
8279
|
-
return new Promise((resolve) => {
|
|
8280
|
-
const img = new Image();
|
|
8281
|
-
img.crossOrigin = 'Anonymous'; // Critical for canvas manipulation
|
|
8282
|
-
|
|
8283
|
-
img.onload = () => {
|
|
8284
|
-
const canvas = document.createElement('canvas');
|
|
8285
|
-
// Double resolution for better quality
|
|
8286
|
-
const scale = 2;
|
|
8287
|
-
canvas.width = targetW * scale;
|
|
8288
|
-
canvas.height = targetH * scale;
|
|
8289
|
-
const ctx = canvas.getContext('2d');
|
|
8290
|
-
ctx.scale(scale, scale);
|
|
8291
|
-
|
|
8292
|
-
// Normalize radius input to an object { tl, tr, br, bl }
|
|
8293
|
-
let r = { tl: 0, tr: 0, br: 0, bl: 0 };
|
|
8294
|
-
if (typeof radius === 'number') {
|
|
8295
|
-
r = { tl: radius, tr: radius, br: radius, bl: radius };
|
|
8296
|
-
} else if (typeof radius === 'object' && radius !== null) {
|
|
8297
|
-
r = { ...r, ...radius }; // Merge with defaults
|
|
8298
|
-
}
|
|
8299
|
-
|
|
8300
|
-
// 1. Draw the Mask (Custom Shape with specific corners)
|
|
8301
|
-
ctx.beginPath();
|
|
8302
|
-
|
|
8303
|
-
// Border Radius Clamping Logic (CSS Spec)
|
|
8304
|
-
// Prevents corners from overlapping if radii are too large for the container
|
|
8305
|
-
const factor = Math.min(
|
|
8306
|
-
|
|
8307
|
-
|
|
8308
|
-
|
|
8309
|
-
|
|
8310
|
-
);
|
|
8311
|
-
|
|
8312
|
-
if (factor < 1) {
|
|
8313
|
-
r.tl *= factor;
|
|
8314
|
-
|
|
8315
|
-
|
|
8316
|
-
|
|
8317
|
-
|
|
8318
|
-
|
|
8319
|
-
|
|
8320
|
-
ctx.
|
|
8321
|
-
ctx.
|
|
8322
|
-
ctx.
|
|
8323
|
-
ctx.
|
|
8324
|
-
ctx.
|
|
8325
|
-
ctx.
|
|
8326
|
-
|
|
8327
|
-
ctx.
|
|
8328
|
-
ctx.
|
|
8329
|
-
|
|
8330
|
-
|
|
8331
|
-
|
|
8332
|
-
ctx.
|
|
8333
|
-
|
|
8334
|
-
//
|
|
8335
|
-
|
|
8336
|
-
|
|
8337
|
-
|
|
8338
|
-
const
|
|
8339
|
-
const
|
|
8340
|
-
const
|
|
8341
|
-
const
|
|
8342
|
-
|
|
8343
|
-
|
|
8344
|
-
|
|
8345
|
-
|
|
8346
|
-
|
|
8347
|
-
|
|
8348
|
-
|
|
8349
|
-
|
|
8350
|
-
|
|
8249
|
+
// src/image-processor.js
|
|
8250
|
+
|
|
8251
|
+
async function getProcessedImage(src, targetW, targetH, radius) {
|
|
8252
|
+
return new Promise((resolve) => {
|
|
8253
|
+
const img = new Image();
|
|
8254
|
+
img.crossOrigin = 'Anonymous'; // Critical for canvas manipulation
|
|
8255
|
+
|
|
8256
|
+
img.onload = () => {
|
|
8257
|
+
const canvas = document.createElement('canvas');
|
|
8258
|
+
// Double resolution for better quality
|
|
8259
|
+
const scale = 2;
|
|
8260
|
+
canvas.width = targetW * scale;
|
|
8261
|
+
canvas.height = targetH * scale;
|
|
8262
|
+
const ctx = canvas.getContext('2d');
|
|
8263
|
+
ctx.scale(scale, scale);
|
|
8264
|
+
|
|
8265
|
+
// Normalize radius input to an object { tl, tr, br, bl }
|
|
8266
|
+
let r = { tl: 0, tr: 0, br: 0, bl: 0 };
|
|
8267
|
+
if (typeof radius === 'number') {
|
|
8268
|
+
r = { tl: radius, tr: radius, br: radius, bl: radius };
|
|
8269
|
+
} else if (typeof radius === 'object' && radius !== null) {
|
|
8270
|
+
r = { ...r, ...radius }; // Merge with defaults
|
|
8271
|
+
}
|
|
8272
|
+
|
|
8273
|
+
// 1. Draw the Mask (Custom Shape with specific corners)
|
|
8274
|
+
ctx.beginPath();
|
|
8275
|
+
|
|
8276
|
+
// Border Radius Clamping Logic (CSS Spec)
|
|
8277
|
+
// Prevents corners from overlapping if radii are too large for the container
|
|
8278
|
+
const factor = Math.min(
|
|
8279
|
+
targetW / (r.tl + r.tr) || Infinity,
|
|
8280
|
+
targetH / (r.tr + r.br) || Infinity,
|
|
8281
|
+
targetW / (r.br + r.bl) || Infinity,
|
|
8282
|
+
targetH / (r.bl + r.tl) || Infinity
|
|
8283
|
+
);
|
|
8284
|
+
|
|
8285
|
+
if (factor < 1) {
|
|
8286
|
+
r.tl *= factor;
|
|
8287
|
+
r.tr *= factor;
|
|
8288
|
+
r.br *= factor;
|
|
8289
|
+
r.bl *= factor;
|
|
8290
|
+
}
|
|
8291
|
+
|
|
8292
|
+
// Draw path: Top-Left -> Top-Right -> Bottom-Right -> Bottom-Left
|
|
8293
|
+
ctx.moveTo(r.tl, 0);
|
|
8294
|
+
ctx.lineTo(targetW - r.tr, 0);
|
|
8295
|
+
ctx.arcTo(targetW, 0, targetW, r.tr, r.tr);
|
|
8296
|
+
ctx.lineTo(targetW, targetH - r.br);
|
|
8297
|
+
ctx.arcTo(targetW, targetH, targetW - r.br, targetH, r.br);
|
|
8298
|
+
ctx.lineTo(r.bl, targetH);
|
|
8299
|
+
ctx.arcTo(0, targetH, 0, targetH - r.bl, r.bl);
|
|
8300
|
+
ctx.lineTo(0, r.tl);
|
|
8301
|
+
ctx.arcTo(0, 0, r.tl, 0, r.tl);
|
|
8302
|
+
|
|
8303
|
+
ctx.closePath();
|
|
8304
|
+
ctx.fillStyle = '#000';
|
|
8305
|
+
ctx.fill();
|
|
8306
|
+
|
|
8307
|
+
// 2. Composite Source-In (Crops the next image draw to the mask)
|
|
8308
|
+
ctx.globalCompositeOperation = 'source-in';
|
|
8309
|
+
|
|
8310
|
+
// 3. Draw Image (Object Cover Logic)
|
|
8311
|
+
const wRatio = targetW / img.width;
|
|
8312
|
+
const hRatio = targetH / img.height;
|
|
8313
|
+
const maxRatio = Math.max(wRatio, hRatio);
|
|
8314
|
+
const renderW = img.width * maxRatio;
|
|
8315
|
+
const renderH = img.height * maxRatio;
|
|
8316
|
+
const renderX = (targetW - renderW) / 2;
|
|
8317
|
+
const renderY = (targetH - renderH) / 2;
|
|
8318
|
+
|
|
8319
|
+
ctx.drawImage(img, renderX, renderY, renderW, renderH);
|
|
8320
|
+
|
|
8321
|
+
resolve(canvas.toDataURL('image/png'));
|
|
8322
|
+
};
|
|
8323
|
+
|
|
8324
|
+
img.onerror = () => resolve(null);
|
|
8325
|
+
img.src = src;
|
|
8326
|
+
});
|
|
8351
8327
|
}
|
|
8352
8328
|
|
|
8353
8329
|
// src/index.js
|
|
@@ -8419,66 +8395,115 @@ async function processSlide(root, slide, pptx) {
|
|
|
8419
8395
|
};
|
|
8420
8396
|
|
|
8421
8397
|
const renderQueue = [];
|
|
8398
|
+
const asyncTasks = []; // Queue for heavy operations (Images, Canvas)
|
|
8422
8399
|
let domOrderCounter = 0;
|
|
8423
8400
|
|
|
8424
|
-
|
|
8401
|
+
// Sync Traversal Function
|
|
8402
|
+
function collect(node, parentZIndex) {
|
|
8425
8403
|
const order = domOrderCounter++;
|
|
8426
|
-
|
|
8404
|
+
|
|
8405
|
+
let currentZ = parentZIndex;
|
|
8406
|
+
let nodeStyle = null;
|
|
8407
|
+
const nodeType = node.nodeType;
|
|
8408
|
+
|
|
8409
|
+
if (nodeType === 1) {
|
|
8410
|
+
nodeStyle = window.getComputedStyle(node);
|
|
8411
|
+
// Optimization: Skip completely hidden elements immediately
|
|
8412
|
+
if (
|
|
8413
|
+
nodeStyle.display === 'none' ||
|
|
8414
|
+
nodeStyle.visibility === 'hidden' ||
|
|
8415
|
+
nodeStyle.opacity === '0'
|
|
8416
|
+
) {
|
|
8417
|
+
return;
|
|
8418
|
+
}
|
|
8419
|
+
if (nodeStyle.zIndex !== 'auto') {
|
|
8420
|
+
currentZ = parseInt(nodeStyle.zIndex);
|
|
8421
|
+
}
|
|
8422
|
+
}
|
|
8423
|
+
|
|
8424
|
+
// Prepare the item. If it needs async work, it returns a 'job'
|
|
8425
|
+
const result = prepareRenderItem(
|
|
8426
|
+
node,
|
|
8427
|
+
{ ...layoutConfig, root },
|
|
8428
|
+
order,
|
|
8429
|
+
pptx,
|
|
8430
|
+
currentZ,
|
|
8431
|
+
nodeStyle
|
|
8432
|
+
);
|
|
8433
|
+
|
|
8427
8434
|
if (result) {
|
|
8428
|
-
if (result.items)
|
|
8435
|
+
if (result.items) {
|
|
8436
|
+
// Push items immediately to queue (data might be missing but filled later)
|
|
8437
|
+
renderQueue.push(...result.items);
|
|
8438
|
+
}
|
|
8439
|
+
if (result.job) {
|
|
8440
|
+
// Push the promise-returning function to the task list
|
|
8441
|
+
asyncTasks.push(result.job);
|
|
8442
|
+
}
|
|
8429
8443
|
if (result.stopRecursion) return;
|
|
8430
8444
|
}
|
|
8431
|
-
|
|
8445
|
+
|
|
8446
|
+
// Recurse children synchronously
|
|
8447
|
+
const childNodes = node.childNodes;
|
|
8448
|
+
for (let i = 0; i < childNodes.length; i++) {
|
|
8449
|
+
collect(childNodes[i], currentZ);
|
|
8450
|
+
}
|
|
8432
8451
|
}
|
|
8433
8452
|
|
|
8434
|
-
|
|
8453
|
+
// 1. Traverse and build the structure (Fast)
|
|
8454
|
+
collect(root, 0);
|
|
8435
8455
|
|
|
8436
|
-
|
|
8456
|
+
// 2. Execute all heavy tasks in parallel (Fast)
|
|
8457
|
+
if (asyncTasks.length > 0) {
|
|
8458
|
+
await Promise.all(asyncTasks.map((task) => task()));
|
|
8459
|
+
}
|
|
8460
|
+
|
|
8461
|
+
// 3. Cleanup and Sort
|
|
8462
|
+
// Remove items that failed to generate data (marked with skip)
|
|
8463
|
+
const finalQueue = renderQueue.filter(
|
|
8464
|
+
(item) => !item.skip && (item.type !== 'image' || item.options.data)
|
|
8465
|
+
);
|
|
8466
|
+
|
|
8467
|
+
finalQueue.sort((a, b) => {
|
|
8437
8468
|
if (a.zIndex !== b.zIndex) return a.zIndex - b.zIndex;
|
|
8438
8469
|
return a.domOrder - b.domOrder;
|
|
8439
8470
|
});
|
|
8440
8471
|
|
|
8441
|
-
|
|
8472
|
+
// 4. Add to Slide
|
|
8473
|
+
for (const item of finalQueue) {
|
|
8442
8474
|
if (item.type === 'shape') slide.addShape(item.shapeType, item.options);
|
|
8443
8475
|
if (item.type === 'image') slide.addImage(item.options);
|
|
8444
8476
|
if (item.type === 'text') slide.addText(item.textParts, item.options);
|
|
8445
8477
|
}
|
|
8446
8478
|
}
|
|
8447
8479
|
|
|
8448
|
-
|
|
8480
|
+
/**
|
|
8481
|
+
* Optimized html2canvas wrapper
|
|
8482
|
+
* Now strictly captures the node itself, not the root.
|
|
8483
|
+
*/
|
|
8484
|
+
async function elementToCanvasImage(node, widthPx, heightPx) {
|
|
8449
8485
|
return new Promise((resolve) => {
|
|
8450
|
-
const width = Math.ceil(widthPx);
|
|
8451
|
-
const height = Math.ceil(heightPx);
|
|
8452
|
-
|
|
8453
|
-
if (width <= 0 || height <= 0) {
|
|
8454
|
-
resolve(null);
|
|
8455
|
-
return;
|
|
8456
|
-
}
|
|
8457
|
-
|
|
8486
|
+
const width = Math.max(Math.ceil(widthPx), 1);
|
|
8487
|
+
const height = Math.max(Math.ceil(heightPx), 1);
|
|
8458
8488
|
const style = window.getComputedStyle(node);
|
|
8459
8489
|
|
|
8460
|
-
|
|
8461
|
-
|
|
8462
|
-
height: root.scrollHeight,
|
|
8463
|
-
useCORS: true,
|
|
8464
|
-
allowTaint: true,
|
|
8490
|
+
// Optimized: Capture ONLY the specific node
|
|
8491
|
+
html2canvas(node, {
|
|
8465
8492
|
backgroundColor: null,
|
|
8493
|
+
logging: false,
|
|
8494
|
+
scale: 2, // Slight quality boost
|
|
8466
8495
|
})
|
|
8467
8496
|
.then((canvas) => {
|
|
8468
|
-
const rootCanvas = canvas;
|
|
8469
|
-
const nodeRect = node.getBoundingClientRect();
|
|
8470
|
-
const rootRect = root.getBoundingClientRect();
|
|
8471
|
-
const sourceX = nodeRect.left - rootRect.left;
|
|
8472
|
-
const sourceY = nodeRect.top - rootRect.top;
|
|
8473
|
-
|
|
8474
8497
|
const destCanvas = document.createElement('canvas');
|
|
8475
8498
|
destCanvas.width = width;
|
|
8476
8499
|
destCanvas.height = height;
|
|
8477
8500
|
const ctx = destCanvas.getContext('2d');
|
|
8478
8501
|
|
|
8479
|
-
|
|
8502
|
+
// Draw the captured canvas into our sized canvas
|
|
8503
|
+
// html2canvas might return a larger canvas if scale > 1, so we fit it
|
|
8504
|
+
ctx.drawImage(canvas, 0, 0, canvas.width, canvas.height, 0, 0, width, height);
|
|
8480
8505
|
|
|
8481
|
-
//
|
|
8506
|
+
// Apply border radius clipping
|
|
8482
8507
|
let tl = parseFloat(style.borderTopLeftRadius) || 0;
|
|
8483
8508
|
let tr = parseFloat(style.borderTopRightRadius) || 0;
|
|
8484
8509
|
let br = parseFloat(style.borderBottomRightRadius) || 0;
|
|
@@ -8498,38 +8523,89 @@ async function elementToCanvasImage(node, widthPx, heightPx, root) {
|
|
|
8498
8523
|
bl *= f;
|
|
8499
8524
|
}
|
|
8500
8525
|
|
|
8501
|
-
|
|
8502
|
-
|
|
8503
|
-
|
|
8504
|
-
|
|
8505
|
-
|
|
8506
|
-
|
|
8507
|
-
|
|
8508
|
-
|
|
8509
|
-
|
|
8510
|
-
|
|
8511
|
-
|
|
8512
|
-
|
|
8513
|
-
|
|
8526
|
+
if (tl + tr + br + bl > 0) {
|
|
8527
|
+
ctx.globalCompositeOperation = 'destination-in';
|
|
8528
|
+
ctx.beginPath();
|
|
8529
|
+
ctx.moveTo(tl, 0);
|
|
8530
|
+
ctx.lineTo(width - tr, 0);
|
|
8531
|
+
ctx.arcTo(width, 0, width, tr, tr);
|
|
8532
|
+
ctx.lineTo(width, height - br);
|
|
8533
|
+
ctx.arcTo(width, height, width - br, height, br);
|
|
8534
|
+
ctx.lineTo(bl, height);
|
|
8535
|
+
ctx.arcTo(0, height, 0, height - bl, bl);
|
|
8536
|
+
ctx.lineTo(0, tl);
|
|
8537
|
+
ctx.arcTo(0, 0, tl, 0, tl);
|
|
8538
|
+
ctx.closePath();
|
|
8539
|
+
ctx.fill();
|
|
8540
|
+
}
|
|
8514
8541
|
|
|
8515
8542
|
resolve(destCanvas.toDataURL('image/png'));
|
|
8516
8543
|
})
|
|
8517
|
-
.catch(() =>
|
|
8544
|
+
.catch((e) => {
|
|
8545
|
+
console.warn('Canvas capture failed for node', node, e);
|
|
8546
|
+
resolve(null);
|
|
8547
|
+
});
|
|
8518
8548
|
});
|
|
8519
8549
|
}
|
|
8520
8550
|
|
|
8521
|
-
|
|
8551
|
+
/**
|
|
8552
|
+
* Replaces createRenderItem.
|
|
8553
|
+
* Returns { items: [], job: () => Promise, stopRecursion: boolean }
|
|
8554
|
+
*/
|
|
8555
|
+
function prepareRenderItem(node, config, domOrder, pptx, effectiveZIndex, computedStyle) {
|
|
8556
|
+
// 1. Text Node Handling
|
|
8557
|
+
if (node.nodeType === 3) {
|
|
8558
|
+
const textContent = node.nodeValue.trim();
|
|
8559
|
+
if (!textContent) return null;
|
|
8560
|
+
|
|
8561
|
+
const parent = node.parentElement;
|
|
8562
|
+
if (!parent) return null;
|
|
8563
|
+
|
|
8564
|
+
if (isTextContainer(parent)) return null; // Parent handles it
|
|
8565
|
+
|
|
8566
|
+
const range = document.createRange();
|
|
8567
|
+
range.selectNode(node);
|
|
8568
|
+
const rect = range.getBoundingClientRect();
|
|
8569
|
+
range.detach();
|
|
8570
|
+
|
|
8571
|
+
const style = window.getComputedStyle(parent);
|
|
8572
|
+
const widthPx = rect.width;
|
|
8573
|
+
const heightPx = rect.height;
|
|
8574
|
+
const unrotatedW = widthPx * PX_TO_INCH * config.scale;
|
|
8575
|
+
const unrotatedH = heightPx * PX_TO_INCH * config.scale;
|
|
8576
|
+
|
|
8577
|
+
const x = config.offX + (rect.left - config.rootX) * PX_TO_INCH * config.scale;
|
|
8578
|
+
const y = config.offY + (rect.top - config.rootY) * PX_TO_INCH * config.scale;
|
|
8579
|
+
|
|
8580
|
+
return {
|
|
8581
|
+
items: [
|
|
8582
|
+
{
|
|
8583
|
+
type: 'text',
|
|
8584
|
+
zIndex: effectiveZIndex,
|
|
8585
|
+
domOrder,
|
|
8586
|
+
textParts: [
|
|
8587
|
+
{
|
|
8588
|
+
text: textContent,
|
|
8589
|
+
options: getTextStyle(style, config.scale),
|
|
8590
|
+
},
|
|
8591
|
+
],
|
|
8592
|
+
options: { x, y, w: unrotatedW, h: unrotatedH, margin: 0, autoFit: false },
|
|
8593
|
+
},
|
|
8594
|
+
],
|
|
8595
|
+
stopRecursion: false,
|
|
8596
|
+
};
|
|
8597
|
+
}
|
|
8598
|
+
|
|
8522
8599
|
if (node.nodeType !== 1) return null;
|
|
8523
|
-
const style =
|
|
8524
|
-
if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0')
|
|
8525
|
-
return null;
|
|
8600
|
+
const style = computedStyle; // Use pre-computed style
|
|
8526
8601
|
|
|
8527
8602
|
const rect = node.getBoundingClientRect();
|
|
8528
8603
|
if (rect.width < 0.5 || rect.height < 0.5) return null;
|
|
8529
8604
|
|
|
8530
|
-
const zIndex =
|
|
8605
|
+
const zIndex = effectiveZIndex;
|
|
8531
8606
|
const rotation = getRotation(style.transform);
|
|
8532
8607
|
const elementOpacity = parseFloat(style.opacity);
|
|
8608
|
+
const safeOpacity = isNaN(elementOpacity) ? 1 : elementOpacity;
|
|
8533
8609
|
|
|
8534
8610
|
const widthPx = node.offsetWidth || rect.width;
|
|
8535
8611
|
const heightPx = node.offsetHeight || rect.height;
|
|
@@ -8545,21 +8621,31 @@ async function createRenderItem(node, config, domOrder, pptx) {
|
|
|
8545
8621
|
|
|
8546
8622
|
const items = [];
|
|
8547
8623
|
|
|
8548
|
-
|
|
8549
|
-
|
|
8550
|
-
|
|
8551
|
-
|
|
8552
|
-
|
|
8553
|
-
|
|
8554
|
-
|
|
8555
|
-
|
|
8556
|
-
|
|
8557
|
-
|
|
8624
|
+
// --- ASYNC JOB: SVGs / Icons ---
|
|
8625
|
+
if (
|
|
8626
|
+
node.nodeName.toUpperCase() === 'SVG' ||
|
|
8627
|
+
node.tagName.includes('-') ||
|
|
8628
|
+
node.tagName === 'ION-ICON'
|
|
8629
|
+
) {
|
|
8630
|
+
const item = {
|
|
8631
|
+
type: 'image',
|
|
8632
|
+
zIndex,
|
|
8633
|
+
domOrder,
|
|
8634
|
+
options: { x, y, w, h, rotate: rotation, data: null }, // Data null initially
|
|
8635
|
+
};
|
|
8636
|
+
|
|
8637
|
+
// Create Job
|
|
8638
|
+
const job = async () => {
|
|
8639
|
+
const pngData = await elementToCanvasImage(node, widthPx, heightPx);
|
|
8640
|
+
if (pngData) item.options.data = pngData;
|
|
8641
|
+
else item.skip = true;
|
|
8642
|
+
};
|
|
8643
|
+
|
|
8644
|
+
return { items: [item], job, stopRecursion: true };
|
|
8558
8645
|
}
|
|
8559
8646
|
|
|
8560
|
-
// ---
|
|
8647
|
+
// --- ASYNC JOB: IMG Tags ---
|
|
8561
8648
|
if (node.tagName === 'IMG') {
|
|
8562
|
-
// Extract individual corner radii
|
|
8563
8649
|
let radii = {
|
|
8564
8650
|
tl: parseFloat(style.borderTopLeftRadius) || 0,
|
|
8565
8651
|
tr: parseFloat(style.borderTopRightRadius) || 0,
|
|
@@ -8568,8 +8654,6 @@ async function createRenderItem(node, config, domOrder, pptx) {
|
|
|
8568
8654
|
};
|
|
8569
8655
|
|
|
8570
8656
|
const hasAnyRadius = radii.tl > 0 || radii.tr > 0 || radii.br > 0 || radii.bl > 0;
|
|
8571
|
-
|
|
8572
|
-
// Fallback: Check parent if image has no specific radius but parent clips it
|
|
8573
8657
|
if (!hasAnyRadius) {
|
|
8574
8658
|
const parent = node.parentElement;
|
|
8575
8659
|
const parentStyle = window.getComputedStyle(parent);
|
|
@@ -8580,10 +8664,6 @@ async function createRenderItem(node, config, domOrder, pptx) {
|
|
|
8580
8664
|
br: parseFloat(parentStyle.borderBottomRightRadius) || 0,
|
|
8581
8665
|
bl: parseFloat(parentStyle.borderBottomLeftRadius) || 0,
|
|
8582
8666
|
};
|
|
8583
|
-
// Simple heuristic: If image takes up full size of parent, inherit radii.
|
|
8584
|
-
// For complex grids (like slide-1), this blindly applies parent radius.
|
|
8585
|
-
// In a perfect world, we'd calculate intersection, but for now we apply parent radius
|
|
8586
|
-
// if the image is close to the parent's size, effectively masking it.
|
|
8587
8667
|
const pRect = parent.getBoundingClientRect();
|
|
8588
8668
|
if (Math.abs(pRect.width - rect.width) < 5 && Math.abs(pRect.height - rect.height) < 5) {
|
|
8589
8669
|
radii = pRadii;
|
|
@@ -8591,19 +8671,23 @@ async function createRenderItem(node, config, domOrder, pptx) {
|
|
|
8591
8671
|
}
|
|
8592
8672
|
}
|
|
8593
8673
|
|
|
8594
|
-
const
|
|
8595
|
-
|
|
8596
|
-
|
|
8597
|
-
|
|
8598
|
-
|
|
8599
|
-
|
|
8600
|
-
|
|
8601
|
-
|
|
8602
|
-
|
|
8674
|
+
const item = {
|
|
8675
|
+
type: 'image',
|
|
8676
|
+
zIndex,
|
|
8677
|
+
domOrder,
|
|
8678
|
+
options: { x, y, w, h, rotate: rotation, data: null },
|
|
8679
|
+
};
|
|
8680
|
+
|
|
8681
|
+
const job = async () => {
|
|
8682
|
+
const processed = await getProcessedImage(node.src, widthPx, heightPx, radii);
|
|
8683
|
+
if (processed) item.options.data = processed;
|
|
8684
|
+
else item.skip = true;
|
|
8685
|
+
};
|
|
8686
|
+
|
|
8687
|
+
return { items: [item], job, stopRecursion: true };
|
|
8603
8688
|
}
|
|
8604
|
-
// --- UPDATED IMG BLOCK END ---
|
|
8605
8689
|
|
|
8606
|
-
// Radii
|
|
8690
|
+
// Radii logic
|
|
8607
8691
|
const borderRadiusValue = parseFloat(style.borderRadius) || 0;
|
|
8608
8692
|
const borderBottomLeftRadius = parseFloat(style.borderBottomLeftRadius) || 0;
|
|
8609
8693
|
const borderBottomRightRadius = parseFloat(style.borderBottomRightRadius) || 0;
|
|
@@ -8621,25 +8705,30 @@ async function createRenderItem(node, config, domOrder, pptx) {
|
|
|
8621
8705
|
borderTopLeftRadius ||
|
|
8622
8706
|
borderTopRightRadius));
|
|
8623
8707
|
|
|
8624
|
-
//
|
|
8708
|
+
// --- ASYNC JOB: Clipped Divs via Canvas ---
|
|
8625
8709
|
if (hasPartialBorderRadius && isClippedByParent(node)) {
|
|
8626
8710
|
const marginLeft = parseFloat(style.marginLeft) || 0;
|
|
8627
8711
|
const marginTop = parseFloat(style.marginTop) || 0;
|
|
8628
8712
|
x += marginLeft * PX_TO_INCH * config.scale;
|
|
8629
8713
|
y += marginTop * PX_TO_INCH * config.scale;
|
|
8630
8714
|
|
|
8631
|
-
const
|
|
8632
|
-
|
|
8633
|
-
|
|
8634
|
-
|
|
8635
|
-
|
|
8636
|
-
|
|
8637
|
-
|
|
8638
|
-
|
|
8639
|
-
|
|
8640
|
-
|
|
8715
|
+
const item = {
|
|
8716
|
+
type: 'image',
|
|
8717
|
+
zIndex,
|
|
8718
|
+
domOrder,
|
|
8719
|
+
options: { x, y, w, h, rotate: rotation, data: null },
|
|
8720
|
+
};
|
|
8721
|
+
|
|
8722
|
+
const job = async () => {
|
|
8723
|
+
const canvasImageData = await elementToCanvasImage(node, widthPx, heightPx);
|
|
8724
|
+
if (canvasImageData) item.options.data = canvasImageData;
|
|
8725
|
+
else item.skip = true;
|
|
8726
|
+
};
|
|
8727
|
+
|
|
8728
|
+
return { items: [item], job, stopRecursion: true };
|
|
8641
8729
|
}
|
|
8642
8730
|
|
|
8731
|
+
// --- SYNC: Standard CSS Extraction ---
|
|
8643
8732
|
const bgColorObj = parseColor(style.backgroundColor);
|
|
8644
8733
|
const bgClip = style.webkitBackgroundClip || style.backgroundClip;
|
|
8645
8734
|
const isBgClipText = bgClip === 'text';
|
|
@@ -8678,7 +8767,7 @@ async function createRenderItem(node, config, domOrder, pptx) {
|
|
|
8678
8767
|
x -= bulletShift;
|
|
8679
8768
|
w += bulletShift;
|
|
8680
8769
|
textParts.push({
|
|
8681
|
-
text: '
|
|
8770
|
+
text: ' ',
|
|
8682
8771
|
options: {
|
|
8683
8772
|
color: parseColor(style.color).hex || '000000',
|
|
8684
8773
|
fontSize: fontSizePt,
|
|
@@ -8784,7 +8873,6 @@ async function createRenderItem(node, config, domOrder, pptx) {
|
|
|
8784
8873
|
});
|
|
8785
8874
|
}
|
|
8786
8875
|
if (hasCompositeBorder) {
|
|
8787
|
-
// Add border shapes after the main background
|
|
8788
8876
|
const borderItems = createCompositeBorderItems(
|
|
8789
8877
|
borderInfo.sides,
|
|
8790
8878
|
x,
|
|
@@ -8804,7 +8892,7 @@ async function createRenderItem(node, config, domOrder, pptx) {
|
|
|
8804
8892
|
hasShadow ||
|
|
8805
8893
|
textPayload
|
|
8806
8894
|
) {
|
|
8807
|
-
const finalAlpha =
|
|
8895
|
+
const finalAlpha = safeOpacity * bgColorObj.opacity;
|
|
8808
8896
|
const transparency = (1 - finalAlpha) * 100;
|
|
8809
8897
|
const useSolidFill = bgColorObj.hex && !isImageWrapper;
|
|
8810
8898
|
|
|
@@ -8826,14 +8914,7 @@ async function createRenderItem(node, config, domOrder, pptx) {
|
|
|
8826
8914
|
type: 'image',
|
|
8827
8915
|
zIndex,
|
|
8828
8916
|
domOrder,
|
|
8829
|
-
options: {
|
|
8830
|
-
data: shapeSvg,
|
|
8831
|
-
x,
|
|
8832
|
-
y,
|
|
8833
|
-
w,
|
|
8834
|
-
h,
|
|
8835
|
-
rotate: rotation,
|
|
8836
|
-
},
|
|
8917
|
+
options: { data: shapeSvg, x, y, w, h, rotate: rotation },
|
|
8837
8918
|
});
|
|
8838
8919
|
} else {
|
|
8839
8920
|
const shapeOpts = {
|
|
@@ -8848,9 +8929,7 @@ async function createRenderItem(node, config, domOrder, pptx) {
|
|
|
8848
8929
|
line: hasUniformBorder ? borderInfo.options : null,
|
|
8849
8930
|
};
|
|
8850
8931
|
|
|
8851
|
-
if (hasShadow)
|
|
8852
|
-
shapeOpts.shadow = getVisibleShadow(shadowStr, config.scale);
|
|
8853
|
-
}
|
|
8932
|
+
if (hasShadow) shapeOpts.shadow = getVisibleShadow(shadowStr, config.scale);
|
|
8854
8933
|
|
|
8855
8934
|
const borderRadius = parseFloat(style.borderRadius) || 0;
|
|
8856
8935
|
const aspectRatio = Math.max(widthPx, heightPx) / Math.min(widthPx, heightPx);
|
|
@@ -8913,77 +8992,49 @@ async function createRenderItem(node, config, domOrder, pptx) {
|
|
|
8913
8992
|
return { items, stopRecursion: !!textPayload };
|
|
8914
8993
|
}
|
|
8915
8994
|
|
|
8916
|
-
/**
|
|
8917
|
-
* Helper function to create individual border shapes
|
|
8918
|
-
*/
|
|
8919
8995
|
function createCompositeBorderItems(sides, x, y, w, h, scale, zIndex, domOrder) {
|
|
8920
8996
|
const items = [];
|
|
8921
8997
|
const pxToInch = 1 / 96;
|
|
8998
|
+
const common = { zIndex: zIndex + 1, domOrder, shapeType: 'rect' };
|
|
8922
8999
|
|
|
8923
|
-
|
|
8924
|
-
if (sides.top.width > 0) {
|
|
9000
|
+
if (sides.top.width > 0)
|
|
8925
9001
|
items.push({
|
|
8926
|
-
|
|
8927
|
-
|
|
8928
|
-
domOrder,
|
|
8929
|
-
shapeType: 'rect',
|
|
8930
|
-
options: {
|
|
8931
|
-
x: x,
|
|
8932
|
-
y: y,
|
|
8933
|
-
w: w,
|
|
8934
|
-
h: sides.top.width * pxToInch * scale,
|
|
8935
|
-
fill: { color: sides.top.color },
|
|
8936
|
-
},
|
|
9002
|
+
...common,
|
|
9003
|
+
options: { x, y, w, h: sides.top.width * pxToInch * scale, fill: { color: sides.top.color } },
|
|
8937
9004
|
});
|
|
8938
|
-
|
|
8939
|
-
// RIGHT BORDER
|
|
8940
|
-
if (sides.right.width > 0) {
|
|
9005
|
+
if (sides.right.width > 0)
|
|
8941
9006
|
items.push({
|
|
8942
|
-
|
|
8943
|
-
zIndex: zIndex + 1,
|
|
8944
|
-
domOrder,
|
|
8945
|
-
shapeType: 'rect',
|
|
9007
|
+
...common,
|
|
8946
9008
|
options: {
|
|
8947
9009
|
x: x + w - sides.right.width * pxToInch * scale,
|
|
8948
|
-
y
|
|
9010
|
+
y,
|
|
8949
9011
|
w: sides.right.width * pxToInch * scale,
|
|
8950
|
-
h
|
|
9012
|
+
h,
|
|
8951
9013
|
fill: { color: sides.right.color },
|
|
8952
9014
|
},
|
|
8953
9015
|
});
|
|
8954
|
-
|
|
8955
|
-
// BOTTOM BORDER
|
|
8956
|
-
if (sides.bottom.width > 0) {
|
|
9016
|
+
if (sides.bottom.width > 0)
|
|
8957
9017
|
items.push({
|
|
8958
|
-
|
|
8959
|
-
zIndex: zIndex + 1,
|
|
8960
|
-
domOrder,
|
|
8961
|
-
shapeType: 'rect',
|
|
9018
|
+
...common,
|
|
8962
9019
|
options: {
|
|
8963
|
-
x
|
|
9020
|
+
x,
|
|
8964
9021
|
y: y + h - sides.bottom.width * pxToInch * scale,
|
|
8965
|
-
w
|
|
9022
|
+
w,
|
|
8966
9023
|
h: sides.bottom.width * pxToInch * scale,
|
|
8967
9024
|
fill: { color: sides.bottom.color },
|
|
8968
9025
|
},
|
|
8969
9026
|
});
|
|
8970
|
-
|
|
8971
|
-
// LEFT BORDER
|
|
8972
|
-
if (sides.left.width > 0) {
|
|
9027
|
+
if (sides.left.width > 0)
|
|
8973
9028
|
items.push({
|
|
8974
|
-
|
|
8975
|
-
zIndex: zIndex + 1,
|
|
8976
|
-
domOrder,
|
|
8977
|
-
shapeType: 'rect',
|
|
9029
|
+
...common,
|
|
8978
9030
|
options: {
|
|
8979
|
-
x
|
|
8980
|
-
y
|
|
9031
|
+
x,
|
|
9032
|
+
y,
|
|
8981
9033
|
w: sides.left.width * pxToInch * scale,
|
|
8982
|
-
h
|
|
9034
|
+
h,
|
|
8983
9035
|
fill: { color: sides.left.color },
|
|
8984
9036
|
},
|
|
8985
9037
|
});
|
|
8986
|
-
}
|
|
8987
9038
|
|
|
8988
9039
|
return items;
|
|
8989
9040
|
}
|