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.min.js
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.domToPptx = {}, global.PptxGenJS));
|
|
5
5
|
})(this, (function (exports, PptxGenJSImport) { 'use strict';
|
|
6
6
|
|
|
7
|
-
function
|
|
7
|
+
function _interopNamespace(e) {
|
|
8
|
+
if (e && e.__esModule) return e;
|
|
8
9
|
var n = Object.create(null);
|
|
9
10
|
if (e) {
|
|
10
11
|
Object.keys(e).forEach(function (k) {
|
|
@@ -17,11 +18,11 @@
|
|
|
17
18
|
}
|
|
18
19
|
});
|
|
19
20
|
}
|
|
20
|
-
n
|
|
21
|
+
n["default"] = e;
|
|
21
22
|
return Object.freeze(n);
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
var PptxGenJSImport__namespace = /*#__PURE__*/
|
|
25
|
+
var PptxGenJSImport__namespace = /*#__PURE__*/_interopNamespace(PptxGenJSImport);
|
|
25
26
|
|
|
26
27
|
/*!
|
|
27
28
|
* html2canvas 1.4.1 <https://html2canvas.hertzen.com>
|
|
@@ -76,7 +77,7 @@
|
|
|
76
77
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
77
78
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
78
79
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
79
|
-
step((generator = generator.apply(thisArg, [])).next());
|
|
80
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
80
81
|
});
|
|
81
82
|
}
|
|
82
83
|
|
|
@@ -109,7 +110,7 @@
|
|
|
109
110
|
}
|
|
110
111
|
|
|
111
112
|
function __spreadArray(to, from, pack) {
|
|
112
|
-
if (arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
|
|
113
|
+
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
|
|
113
114
|
if (ar || !(i in from)) {
|
|
114
115
|
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
|
|
115
116
|
ar[i] = from[i];
|
|
@@ -7978,17 +7979,20 @@
|
|
|
7978
7979
|
*/
|
|
7979
7980
|
function generateCustomShapeSVG(w, h, color, opacity, radii) {
|
|
7980
7981
|
let { tl, tr, br, bl } = radii;
|
|
7981
|
-
|
|
7982
|
+
|
|
7982
7983
|
// Clamp radii using CSS spec logic (avoid overlap)
|
|
7983
7984
|
const factor = Math.min(
|
|
7984
|
-
|
|
7985
|
-
|
|
7986
|
-
|
|
7987
|
-
|
|
7985
|
+
w / (tl + tr) || Infinity,
|
|
7986
|
+
h / (tr + br) || Infinity,
|
|
7987
|
+
w / (br + bl) || Infinity,
|
|
7988
|
+
h / (bl + tl) || Infinity
|
|
7988
7989
|
);
|
|
7989
|
-
|
|
7990
|
+
|
|
7990
7991
|
if (factor < 1) {
|
|
7991
|
-
tl *= factor;
|
|
7992
|
+
tl *= factor;
|
|
7993
|
+
tr *= factor;
|
|
7994
|
+
br *= factor;
|
|
7995
|
+
bl *= factor;
|
|
7992
7996
|
}
|
|
7993
7997
|
|
|
7994
7998
|
const path = `
|
|
@@ -8019,7 +8023,10 @@
|
|
|
8019
8023
|
if (str.startsWith('#')) {
|
|
8020
8024
|
let hex = str.slice(1);
|
|
8021
8025
|
if (hex.length === 3)
|
|
8022
|
-
hex = hex
|
|
8026
|
+
hex = hex
|
|
8027
|
+
.split('')
|
|
8028
|
+
.map((c) => c + c)
|
|
8029
|
+
.join('');
|
|
8023
8030
|
return { hex: hex.toUpperCase(), opacity: 1 };
|
|
8024
8031
|
}
|
|
8025
8032
|
const match = str.match(/[\d.]+/g);
|
|
@@ -8082,25 +8089,35 @@
|
|
|
8082
8089
|
|
|
8083
8090
|
// Check if children are purely inline text formatting or visual shapes
|
|
8084
8091
|
const isSafeInline = (el) => {
|
|
8092
|
+
// 1. Reject Web Components / Icons / Images
|
|
8093
|
+
if (el.tagName.includes('-')) return false;
|
|
8094
|
+
if (el.tagName === 'IMG' || el.tagName === 'SVG') return false;
|
|
8095
|
+
|
|
8085
8096
|
const style = window.getComputedStyle(el);
|
|
8086
8097
|
const display = style.display;
|
|
8087
|
-
|
|
8088
|
-
//
|
|
8089
|
-
const isInlineTag = ['SPAN', 'B', 'STRONG', 'EM', 'I', 'A', 'SMALL'].includes(el.tagName);
|
|
8098
|
+
|
|
8099
|
+
// 2. Initial check: Must be a standard inline tag OR display:inline
|
|
8100
|
+
const isInlineTag = ['SPAN', 'B', 'STRONG', 'EM', 'I', 'A', 'SMALL', 'MARK'].includes(el.tagName);
|
|
8090
8101
|
const isInlineDisplay = display.includes('inline');
|
|
8091
8102
|
|
|
8092
8103
|
if (!isInlineTag && !isInlineDisplay) return false;
|
|
8093
8104
|
|
|
8094
|
-
//
|
|
8095
|
-
//
|
|
8096
|
-
//
|
|
8097
|
-
|
|
8105
|
+
// 3. CRITICAL FIX: Check for Structural Styling
|
|
8106
|
+
// PPTX Text Runs (parts of a text line) CANNOT have backgrounds, borders, or padding.
|
|
8107
|
+
// If a child element has these, the parent is NOT a simple text container;
|
|
8108
|
+
// it is a layout container composed of styled blocks.
|
|
8098
8109
|
const bgColor = parseColor(style.backgroundColor);
|
|
8099
8110
|
const hasVisibleBg = bgColor.hex && bgColor.opacity > 0;
|
|
8100
8111
|
const hasBorder = parseFloat(style.borderWidth) > 0 && parseColor(style.borderColor).opacity > 0;
|
|
8101
8112
|
|
|
8113
|
+
if (hasVisibleBg || hasBorder) {
|
|
8114
|
+
return false;
|
|
8115
|
+
}
|
|
8116
|
+
|
|
8117
|
+
// 4. Check for empty shapes (visual objects without text, like dots)
|
|
8118
|
+
const hasContent = el.textContent.trim().length > 0;
|
|
8102
8119
|
if (!hasContent && (hasVisibleBg || hasBorder)) {
|
|
8103
|
-
return false;
|
|
8120
|
+
return false;
|
|
8104
8121
|
}
|
|
8105
8122
|
|
|
8106
8123
|
return true;
|
|
@@ -8118,62 +8135,6 @@
|
|
|
8118
8135
|
return Math.round(Math.atan2(b, a) * (180 / Math.PI));
|
|
8119
8136
|
}
|
|
8120
8137
|
|
|
8121
|
-
function svgToPng(node) {
|
|
8122
|
-
return new Promise((resolve) => {
|
|
8123
|
-
const clone = node.cloneNode(true);
|
|
8124
|
-
const rect = node.getBoundingClientRect();
|
|
8125
|
-
const width = rect.width || 300;
|
|
8126
|
-
const height = rect.height || 150;
|
|
8127
|
-
|
|
8128
|
-
function inlineStyles(source, target) {
|
|
8129
|
-
const computed = window.getComputedStyle(source);
|
|
8130
|
-
const properties = [
|
|
8131
|
-
'fill', 'stroke', 'stroke-width', 'stroke-linecap',
|
|
8132
|
-
'stroke-linejoin', 'opacity', 'font-family', 'font-size', '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
|
-
|
|
8177
8138
|
function getVisibleShadow(shadowStr, scale) {
|
|
8178
8139
|
if (!shadowStr || shadowStr === 'none') return null;
|
|
8179
8140
|
const shadows = shadowStr.split(/,(?![^()]*\))/);
|
|
@@ -8212,16 +8173,29 @@
|
|
|
8212
8173
|
const content = match[1];
|
|
8213
8174
|
const parts = content.split(/,(?![^()]*\))/).map((p) => p.trim());
|
|
8214
8175
|
|
|
8215
|
-
let x1 = '0%',
|
|
8176
|
+
let x1 = '0%',
|
|
8177
|
+
y1 = '0%',
|
|
8178
|
+
x2 = '0%',
|
|
8179
|
+
y2 = '100%';
|
|
8216
8180
|
let stopsStartIdx = 0;
|
|
8217
8181
|
if (parts[0].includes('to right')) {
|
|
8218
|
-
x1 = '0%';
|
|
8182
|
+
x1 = '0%';
|
|
8183
|
+
x2 = '100%';
|
|
8184
|
+
y2 = '0%';
|
|
8185
|
+
stopsStartIdx = 1;
|
|
8219
8186
|
} else if (parts[0].includes('to left')) {
|
|
8220
|
-
x1 = '100%';
|
|
8187
|
+
x1 = '100%';
|
|
8188
|
+
x2 = '0%';
|
|
8189
|
+
y2 = '0%';
|
|
8190
|
+
stopsStartIdx = 1;
|
|
8221
8191
|
} else if (parts[0].includes('to top')) {
|
|
8222
|
-
y1 = '100%';
|
|
8192
|
+
y1 = '100%';
|
|
8193
|
+
y2 = '0%';
|
|
8194
|
+
stopsStartIdx = 1;
|
|
8223
8195
|
} else if (parts[0].includes('to bottom')) {
|
|
8224
|
-
y1 = '0%';
|
|
8196
|
+
y1 = '0%';
|
|
8197
|
+
y2 = '100%';
|
|
8198
|
+
stopsStartIdx = 1;
|
|
8225
8199
|
}
|
|
8226
8200
|
|
|
8227
8201
|
let stopsXML = '';
|
|
@@ -8296,81 +8270,84 @@
|
|
|
8296
8270
|
};
|
|
8297
8271
|
}
|
|
8298
8272
|
|
|
8299
|
-
// src/image-processor.js
|
|
8300
|
-
|
|
8301
|
-
async function getProcessedImage(src, targetW, targetH, radius) {
|
|
8302
|
-
return new Promise((resolve) => {
|
|
8303
|
-
const img = new Image();
|
|
8304
|
-
img.crossOrigin = 'Anonymous'; // Critical for canvas manipulation
|
|
8305
|
-
|
|
8306
|
-
img.onload = () => {
|
|
8307
|
-
const canvas = document.createElement('canvas');
|
|
8308
|
-
// Double resolution for better quality
|
|
8309
|
-
const scale = 2;
|
|
8310
|
-
canvas.width = targetW * scale;
|
|
8311
|
-
canvas.height = targetH * scale;
|
|
8312
|
-
const ctx = canvas.getContext('2d');
|
|
8313
|
-
ctx.scale(scale, scale);
|
|
8314
|
-
|
|
8315
|
-
// Normalize radius input to an object { tl, tr, br, bl }
|
|
8316
|
-
let r = { tl: 0, tr: 0, br: 0, bl: 0 };
|
|
8317
|
-
if (typeof radius === 'number') {
|
|
8318
|
-
r = { tl: radius, tr: radius, br: radius, bl: radius };
|
|
8319
|
-
} else if (typeof radius === 'object' && radius !== null) {
|
|
8320
|
-
r = { ...r, ...radius }; // Merge with defaults
|
|
8321
|
-
}
|
|
8322
|
-
|
|
8323
|
-
// 1. Draw the Mask (Custom Shape with specific corners)
|
|
8324
|
-
ctx.beginPath();
|
|
8325
|
-
|
|
8326
|
-
// Border Radius Clamping Logic (CSS Spec)
|
|
8327
|
-
// Prevents corners from overlapping if radii are too large for the container
|
|
8328
|
-
const factor = Math.min(
|
|
8329
|
-
|
|
8330
|
-
|
|
8331
|
-
|
|
8332
|
-
|
|
8333
|
-
);
|
|
8334
|
-
|
|
8335
|
-
if (factor < 1) {
|
|
8336
|
-
r.tl *= factor;
|
|
8337
|
-
|
|
8338
|
-
|
|
8339
|
-
|
|
8340
|
-
|
|
8341
|
-
|
|
8342
|
-
|
|
8343
|
-
ctx.
|
|
8344
|
-
ctx.
|
|
8345
|
-
ctx.
|
|
8346
|
-
ctx.
|
|
8347
|
-
ctx.
|
|
8348
|
-
ctx.
|
|
8349
|
-
|
|
8350
|
-
ctx.
|
|
8351
|
-
ctx.
|
|
8352
|
-
|
|
8353
|
-
|
|
8354
|
-
|
|
8355
|
-
ctx.
|
|
8356
|
-
|
|
8357
|
-
//
|
|
8358
|
-
|
|
8359
|
-
|
|
8360
|
-
|
|
8361
|
-
const
|
|
8362
|
-
const
|
|
8363
|
-
const
|
|
8364
|
-
const
|
|
8365
|
-
|
|
8366
|
-
|
|
8367
|
-
|
|
8368
|
-
|
|
8369
|
-
|
|
8370
|
-
|
|
8371
|
-
|
|
8372
|
-
|
|
8373
|
-
|
|
8273
|
+
// src/image-processor.js
|
|
8274
|
+
|
|
8275
|
+
async function getProcessedImage(src, targetW, targetH, radius) {
|
|
8276
|
+
return new Promise((resolve) => {
|
|
8277
|
+
const img = new Image();
|
|
8278
|
+
img.crossOrigin = 'Anonymous'; // Critical for canvas manipulation
|
|
8279
|
+
|
|
8280
|
+
img.onload = () => {
|
|
8281
|
+
const canvas = document.createElement('canvas');
|
|
8282
|
+
// Double resolution for better quality
|
|
8283
|
+
const scale = 2;
|
|
8284
|
+
canvas.width = targetW * scale;
|
|
8285
|
+
canvas.height = targetH * scale;
|
|
8286
|
+
const ctx = canvas.getContext('2d');
|
|
8287
|
+
ctx.scale(scale, scale);
|
|
8288
|
+
|
|
8289
|
+
// Normalize radius input to an object { tl, tr, br, bl }
|
|
8290
|
+
let r = { tl: 0, tr: 0, br: 0, bl: 0 };
|
|
8291
|
+
if (typeof radius === 'number') {
|
|
8292
|
+
r = { tl: radius, tr: radius, br: radius, bl: radius };
|
|
8293
|
+
} else if (typeof radius === 'object' && radius !== null) {
|
|
8294
|
+
r = { ...r, ...radius }; // Merge with defaults
|
|
8295
|
+
}
|
|
8296
|
+
|
|
8297
|
+
// 1. Draw the Mask (Custom Shape with specific corners)
|
|
8298
|
+
ctx.beginPath();
|
|
8299
|
+
|
|
8300
|
+
// Border Radius Clamping Logic (CSS Spec)
|
|
8301
|
+
// Prevents corners from overlapping if radii are too large for the container
|
|
8302
|
+
const factor = Math.min(
|
|
8303
|
+
targetW / (r.tl + r.tr) || Infinity,
|
|
8304
|
+
targetH / (r.tr + r.br) || Infinity,
|
|
8305
|
+
targetW / (r.br + r.bl) || Infinity,
|
|
8306
|
+
targetH / (r.bl + r.tl) || Infinity
|
|
8307
|
+
);
|
|
8308
|
+
|
|
8309
|
+
if (factor < 1) {
|
|
8310
|
+
r.tl *= factor;
|
|
8311
|
+
r.tr *= factor;
|
|
8312
|
+
r.br *= factor;
|
|
8313
|
+
r.bl *= factor;
|
|
8314
|
+
}
|
|
8315
|
+
|
|
8316
|
+
// Draw path: Top-Left -> Top-Right -> Bottom-Right -> Bottom-Left
|
|
8317
|
+
ctx.moveTo(r.tl, 0);
|
|
8318
|
+
ctx.lineTo(targetW - r.tr, 0);
|
|
8319
|
+
ctx.arcTo(targetW, 0, targetW, r.tr, r.tr);
|
|
8320
|
+
ctx.lineTo(targetW, targetH - r.br);
|
|
8321
|
+
ctx.arcTo(targetW, targetH, targetW - r.br, targetH, r.br);
|
|
8322
|
+
ctx.lineTo(r.bl, targetH);
|
|
8323
|
+
ctx.arcTo(0, targetH, 0, targetH - r.bl, r.bl);
|
|
8324
|
+
ctx.lineTo(0, r.tl);
|
|
8325
|
+
ctx.arcTo(0, 0, r.tl, 0, r.tl);
|
|
8326
|
+
|
|
8327
|
+
ctx.closePath();
|
|
8328
|
+
ctx.fillStyle = '#000';
|
|
8329
|
+
ctx.fill();
|
|
8330
|
+
|
|
8331
|
+
// 2. Composite Source-In (Crops the next image draw to the mask)
|
|
8332
|
+
ctx.globalCompositeOperation = 'source-in';
|
|
8333
|
+
|
|
8334
|
+
// 3. Draw Image (Object Cover Logic)
|
|
8335
|
+
const wRatio = targetW / img.width;
|
|
8336
|
+
const hRatio = targetH / img.height;
|
|
8337
|
+
const maxRatio = Math.max(wRatio, hRatio);
|
|
8338
|
+
const renderW = img.width * maxRatio;
|
|
8339
|
+
const renderH = img.height * maxRatio;
|
|
8340
|
+
const renderX = (targetW - renderW) / 2;
|
|
8341
|
+
const renderY = (targetH - renderH) / 2;
|
|
8342
|
+
|
|
8343
|
+
ctx.drawImage(img, renderX, renderY, renderW, renderH);
|
|
8344
|
+
|
|
8345
|
+
resolve(canvas.toDataURL('image/png'));
|
|
8346
|
+
};
|
|
8347
|
+
|
|
8348
|
+
img.onerror = () => resolve(null);
|
|
8349
|
+
img.src = src;
|
|
8350
|
+
});
|
|
8374
8351
|
}
|
|
8375
8352
|
|
|
8376
8353
|
// src/index.js
|
|
@@ -8442,66 +8419,115 @@
|
|
|
8442
8419
|
};
|
|
8443
8420
|
|
|
8444
8421
|
const renderQueue = [];
|
|
8422
|
+
const asyncTasks = []; // Queue for heavy operations (Images, Canvas)
|
|
8445
8423
|
let domOrderCounter = 0;
|
|
8446
8424
|
|
|
8447
|
-
|
|
8425
|
+
// Sync Traversal Function
|
|
8426
|
+
function collect(node, parentZIndex) {
|
|
8448
8427
|
const order = domOrderCounter++;
|
|
8449
|
-
|
|
8428
|
+
|
|
8429
|
+
let currentZ = parentZIndex;
|
|
8430
|
+
let nodeStyle = null;
|
|
8431
|
+
const nodeType = node.nodeType;
|
|
8432
|
+
|
|
8433
|
+
if (nodeType === 1) {
|
|
8434
|
+
nodeStyle = window.getComputedStyle(node);
|
|
8435
|
+
// Optimization: Skip completely hidden elements immediately
|
|
8436
|
+
if (
|
|
8437
|
+
nodeStyle.display === 'none' ||
|
|
8438
|
+
nodeStyle.visibility === 'hidden' ||
|
|
8439
|
+
nodeStyle.opacity === '0'
|
|
8440
|
+
) {
|
|
8441
|
+
return;
|
|
8442
|
+
}
|
|
8443
|
+
if (nodeStyle.zIndex !== 'auto') {
|
|
8444
|
+
currentZ = parseInt(nodeStyle.zIndex);
|
|
8445
|
+
}
|
|
8446
|
+
}
|
|
8447
|
+
|
|
8448
|
+
// Prepare the item. If it needs async work, it returns a 'job'
|
|
8449
|
+
const result = prepareRenderItem(
|
|
8450
|
+
node,
|
|
8451
|
+
{ ...layoutConfig, root },
|
|
8452
|
+
order,
|
|
8453
|
+
pptx,
|
|
8454
|
+
currentZ,
|
|
8455
|
+
nodeStyle
|
|
8456
|
+
);
|
|
8457
|
+
|
|
8450
8458
|
if (result) {
|
|
8451
|
-
if (result.items)
|
|
8459
|
+
if (result.items) {
|
|
8460
|
+
// Push items immediately to queue (data might be missing but filled later)
|
|
8461
|
+
renderQueue.push(...result.items);
|
|
8462
|
+
}
|
|
8463
|
+
if (result.job) {
|
|
8464
|
+
// Push the promise-returning function to the task list
|
|
8465
|
+
asyncTasks.push(result.job);
|
|
8466
|
+
}
|
|
8452
8467
|
if (result.stopRecursion) return;
|
|
8453
8468
|
}
|
|
8454
|
-
|
|
8469
|
+
|
|
8470
|
+
// Recurse children synchronously
|
|
8471
|
+
const childNodes = node.childNodes;
|
|
8472
|
+
for (let i = 0; i < childNodes.length; i++) {
|
|
8473
|
+
collect(childNodes[i], currentZ);
|
|
8474
|
+
}
|
|
8455
8475
|
}
|
|
8456
8476
|
|
|
8457
|
-
|
|
8477
|
+
// 1. Traverse and build the structure (Fast)
|
|
8478
|
+
collect(root, 0);
|
|
8458
8479
|
|
|
8459
|
-
|
|
8480
|
+
// 2. Execute all heavy tasks in parallel (Fast)
|
|
8481
|
+
if (asyncTasks.length > 0) {
|
|
8482
|
+
await Promise.all(asyncTasks.map((task) => task()));
|
|
8483
|
+
}
|
|
8484
|
+
|
|
8485
|
+
// 3. Cleanup and Sort
|
|
8486
|
+
// Remove items that failed to generate data (marked with skip)
|
|
8487
|
+
const finalQueue = renderQueue.filter(
|
|
8488
|
+
(item) => !item.skip && (item.type !== 'image' || item.options.data)
|
|
8489
|
+
);
|
|
8490
|
+
|
|
8491
|
+
finalQueue.sort((a, b) => {
|
|
8460
8492
|
if (a.zIndex !== b.zIndex) return a.zIndex - b.zIndex;
|
|
8461
8493
|
return a.domOrder - b.domOrder;
|
|
8462
8494
|
});
|
|
8463
8495
|
|
|
8464
|
-
|
|
8496
|
+
// 4. Add to Slide
|
|
8497
|
+
for (const item of finalQueue) {
|
|
8465
8498
|
if (item.type === 'shape') slide.addShape(item.shapeType, item.options);
|
|
8466
8499
|
if (item.type === 'image') slide.addImage(item.options);
|
|
8467
8500
|
if (item.type === 'text') slide.addText(item.textParts, item.options);
|
|
8468
8501
|
}
|
|
8469
8502
|
}
|
|
8470
8503
|
|
|
8471
|
-
|
|
8504
|
+
/**
|
|
8505
|
+
* Optimized html2canvas wrapper
|
|
8506
|
+
* Now strictly captures the node itself, not the root.
|
|
8507
|
+
*/
|
|
8508
|
+
async function elementToCanvasImage(node, widthPx, heightPx) {
|
|
8472
8509
|
return new Promise((resolve) => {
|
|
8473
|
-
const width = Math.ceil(widthPx);
|
|
8474
|
-
const height = Math.ceil(heightPx);
|
|
8475
|
-
|
|
8476
|
-
if (width <= 0 || height <= 0) {
|
|
8477
|
-
resolve(null);
|
|
8478
|
-
return;
|
|
8479
|
-
}
|
|
8480
|
-
|
|
8510
|
+
const width = Math.max(Math.ceil(widthPx), 1);
|
|
8511
|
+
const height = Math.max(Math.ceil(heightPx), 1);
|
|
8481
8512
|
const style = window.getComputedStyle(node);
|
|
8482
8513
|
|
|
8483
|
-
|
|
8484
|
-
|
|
8485
|
-
height: root.scrollHeight,
|
|
8486
|
-
useCORS: true,
|
|
8487
|
-
allowTaint: true,
|
|
8514
|
+
// Optimized: Capture ONLY the specific node
|
|
8515
|
+
html2canvas(node, {
|
|
8488
8516
|
backgroundColor: null,
|
|
8517
|
+
logging: false,
|
|
8518
|
+
scale: 2, // Slight quality boost
|
|
8489
8519
|
})
|
|
8490
8520
|
.then((canvas) => {
|
|
8491
|
-
const rootCanvas = canvas;
|
|
8492
|
-
const nodeRect = node.getBoundingClientRect();
|
|
8493
|
-
const rootRect = root.getBoundingClientRect();
|
|
8494
|
-
const sourceX = nodeRect.left - rootRect.left;
|
|
8495
|
-
const sourceY = nodeRect.top - rootRect.top;
|
|
8496
|
-
|
|
8497
8521
|
const destCanvas = document.createElement('canvas');
|
|
8498
8522
|
destCanvas.width = width;
|
|
8499
8523
|
destCanvas.height = height;
|
|
8500
8524
|
const ctx = destCanvas.getContext('2d');
|
|
8501
8525
|
|
|
8502
|
-
|
|
8526
|
+
// Draw the captured canvas into our sized canvas
|
|
8527
|
+
// html2canvas might return a larger canvas if scale > 1, so we fit it
|
|
8528
|
+
ctx.drawImage(canvas, 0, 0, canvas.width, canvas.height, 0, 0, width, height);
|
|
8503
8529
|
|
|
8504
|
-
//
|
|
8530
|
+
// Apply border radius clipping
|
|
8505
8531
|
let tl = parseFloat(style.borderTopLeftRadius) || 0;
|
|
8506
8532
|
let tr = parseFloat(style.borderTopRightRadius) || 0;
|
|
8507
8533
|
let br = parseFloat(style.borderBottomRightRadius) || 0;
|
|
@@ -8521,38 +8547,89 @@
|
|
|
8521
8547
|
bl *= f;
|
|
8522
8548
|
}
|
|
8523
8549
|
|
|
8524
|
-
|
|
8525
|
-
|
|
8526
|
-
|
|
8527
|
-
|
|
8528
|
-
|
|
8529
|
-
|
|
8530
|
-
|
|
8531
|
-
|
|
8532
|
-
|
|
8533
|
-
|
|
8534
|
-
|
|
8535
|
-
|
|
8536
|
-
|
|
8550
|
+
if (tl + tr + br + bl > 0) {
|
|
8551
|
+
ctx.globalCompositeOperation = 'destination-in';
|
|
8552
|
+
ctx.beginPath();
|
|
8553
|
+
ctx.moveTo(tl, 0);
|
|
8554
|
+
ctx.lineTo(width - tr, 0);
|
|
8555
|
+
ctx.arcTo(width, 0, width, tr, tr);
|
|
8556
|
+
ctx.lineTo(width, height - br);
|
|
8557
|
+
ctx.arcTo(width, height, width - br, height, br);
|
|
8558
|
+
ctx.lineTo(bl, height);
|
|
8559
|
+
ctx.arcTo(0, height, 0, height - bl, bl);
|
|
8560
|
+
ctx.lineTo(0, tl);
|
|
8561
|
+
ctx.arcTo(0, 0, tl, 0, tl);
|
|
8562
|
+
ctx.closePath();
|
|
8563
|
+
ctx.fill();
|
|
8564
|
+
}
|
|
8537
8565
|
|
|
8538
8566
|
resolve(destCanvas.toDataURL('image/png'));
|
|
8539
8567
|
})
|
|
8540
|
-
.catch(() =>
|
|
8568
|
+
.catch((e) => {
|
|
8569
|
+
console.warn('Canvas capture failed for node', node, e);
|
|
8570
|
+
resolve(null);
|
|
8571
|
+
});
|
|
8541
8572
|
});
|
|
8542
8573
|
}
|
|
8543
8574
|
|
|
8544
|
-
|
|
8575
|
+
/**
|
|
8576
|
+
* Replaces createRenderItem.
|
|
8577
|
+
* Returns { items: [], job: () => Promise, stopRecursion: boolean }
|
|
8578
|
+
*/
|
|
8579
|
+
function prepareRenderItem(node, config, domOrder, pptx, effectiveZIndex, computedStyle) {
|
|
8580
|
+
// 1. Text Node Handling
|
|
8581
|
+
if (node.nodeType === 3) {
|
|
8582
|
+
const textContent = node.nodeValue.trim();
|
|
8583
|
+
if (!textContent) return null;
|
|
8584
|
+
|
|
8585
|
+
const parent = node.parentElement;
|
|
8586
|
+
if (!parent) return null;
|
|
8587
|
+
|
|
8588
|
+
if (isTextContainer(parent)) return null; // Parent handles it
|
|
8589
|
+
|
|
8590
|
+
const range = document.createRange();
|
|
8591
|
+
range.selectNode(node);
|
|
8592
|
+
const rect = range.getBoundingClientRect();
|
|
8593
|
+
range.detach();
|
|
8594
|
+
|
|
8595
|
+
const style = window.getComputedStyle(parent);
|
|
8596
|
+
const widthPx = rect.width;
|
|
8597
|
+
const heightPx = rect.height;
|
|
8598
|
+
const unrotatedW = widthPx * PX_TO_INCH * config.scale;
|
|
8599
|
+
const unrotatedH = heightPx * PX_TO_INCH * config.scale;
|
|
8600
|
+
|
|
8601
|
+
const x = config.offX + (rect.left - config.rootX) * PX_TO_INCH * config.scale;
|
|
8602
|
+
const y = config.offY + (rect.top - config.rootY) * PX_TO_INCH * config.scale;
|
|
8603
|
+
|
|
8604
|
+
return {
|
|
8605
|
+
items: [
|
|
8606
|
+
{
|
|
8607
|
+
type: 'text',
|
|
8608
|
+
zIndex: effectiveZIndex,
|
|
8609
|
+
domOrder,
|
|
8610
|
+
textParts: [
|
|
8611
|
+
{
|
|
8612
|
+
text: textContent,
|
|
8613
|
+
options: getTextStyle(style, config.scale),
|
|
8614
|
+
},
|
|
8615
|
+
],
|
|
8616
|
+
options: { x, y, w: unrotatedW, h: unrotatedH, margin: 0, autoFit: false },
|
|
8617
|
+
},
|
|
8618
|
+
],
|
|
8619
|
+
stopRecursion: false,
|
|
8620
|
+
};
|
|
8621
|
+
}
|
|
8622
|
+
|
|
8545
8623
|
if (node.nodeType !== 1) return null;
|
|
8546
|
-
const style =
|
|
8547
|
-
if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0')
|
|
8548
|
-
return null;
|
|
8624
|
+
const style = computedStyle; // Use pre-computed style
|
|
8549
8625
|
|
|
8550
8626
|
const rect = node.getBoundingClientRect();
|
|
8551
8627
|
if (rect.width < 0.5 || rect.height < 0.5) return null;
|
|
8552
8628
|
|
|
8553
|
-
const zIndex =
|
|
8629
|
+
const zIndex = effectiveZIndex;
|
|
8554
8630
|
const rotation = getRotation(style.transform);
|
|
8555
8631
|
const elementOpacity = parseFloat(style.opacity);
|
|
8632
|
+
const safeOpacity = isNaN(elementOpacity) ? 1 : elementOpacity;
|
|
8556
8633
|
|
|
8557
8634
|
const widthPx = node.offsetWidth || rect.width;
|
|
8558
8635
|
const heightPx = node.offsetHeight || rect.height;
|
|
@@ -8568,21 +8645,31 @@
|
|
|
8568
8645
|
|
|
8569
8646
|
const items = [];
|
|
8570
8647
|
|
|
8571
|
-
|
|
8572
|
-
|
|
8573
|
-
|
|
8574
|
-
|
|
8575
|
-
|
|
8576
|
-
|
|
8577
|
-
|
|
8578
|
-
|
|
8579
|
-
|
|
8580
|
-
|
|
8648
|
+
// --- ASYNC JOB: SVGs / Icons ---
|
|
8649
|
+
if (
|
|
8650
|
+
node.nodeName.toUpperCase() === 'SVG' ||
|
|
8651
|
+
node.tagName.includes('-') ||
|
|
8652
|
+
node.tagName === 'ION-ICON'
|
|
8653
|
+
) {
|
|
8654
|
+
const item = {
|
|
8655
|
+
type: 'image',
|
|
8656
|
+
zIndex,
|
|
8657
|
+
domOrder,
|
|
8658
|
+
options: { x, y, w, h, rotate: rotation, data: null }, // Data null initially
|
|
8659
|
+
};
|
|
8660
|
+
|
|
8661
|
+
// Create Job
|
|
8662
|
+
const job = async () => {
|
|
8663
|
+
const pngData = await elementToCanvasImage(node, widthPx, heightPx);
|
|
8664
|
+
if (pngData) item.options.data = pngData;
|
|
8665
|
+
else item.skip = true;
|
|
8666
|
+
};
|
|
8667
|
+
|
|
8668
|
+
return { items: [item], job, stopRecursion: true };
|
|
8581
8669
|
}
|
|
8582
8670
|
|
|
8583
|
-
// ---
|
|
8671
|
+
// --- ASYNC JOB: IMG Tags ---
|
|
8584
8672
|
if (node.tagName === 'IMG') {
|
|
8585
|
-
// Extract individual corner radii
|
|
8586
8673
|
let radii = {
|
|
8587
8674
|
tl: parseFloat(style.borderTopLeftRadius) || 0,
|
|
8588
8675
|
tr: parseFloat(style.borderTopRightRadius) || 0,
|
|
@@ -8591,8 +8678,6 @@
|
|
|
8591
8678
|
};
|
|
8592
8679
|
|
|
8593
8680
|
const hasAnyRadius = radii.tl > 0 || radii.tr > 0 || radii.br > 0 || radii.bl > 0;
|
|
8594
|
-
|
|
8595
|
-
// Fallback: Check parent if image has no specific radius but parent clips it
|
|
8596
8681
|
if (!hasAnyRadius) {
|
|
8597
8682
|
const parent = node.parentElement;
|
|
8598
8683
|
const parentStyle = window.getComputedStyle(parent);
|
|
@@ -8603,10 +8688,6 @@
|
|
|
8603
8688
|
br: parseFloat(parentStyle.borderBottomRightRadius) || 0,
|
|
8604
8689
|
bl: parseFloat(parentStyle.borderBottomLeftRadius) || 0,
|
|
8605
8690
|
};
|
|
8606
|
-
// Simple heuristic: If image takes up full size of parent, inherit radii.
|
|
8607
|
-
// For complex grids (like slide-1), this blindly applies parent radius.
|
|
8608
|
-
// In a perfect world, we'd calculate intersection, but for now we apply parent radius
|
|
8609
|
-
// if the image is close to the parent's size, effectively masking it.
|
|
8610
8691
|
const pRect = parent.getBoundingClientRect();
|
|
8611
8692
|
if (Math.abs(pRect.width - rect.width) < 5 && Math.abs(pRect.height - rect.height) < 5) {
|
|
8612
8693
|
radii = pRadii;
|
|
@@ -8614,19 +8695,23 @@
|
|
|
8614
8695
|
}
|
|
8615
8696
|
}
|
|
8616
8697
|
|
|
8617
|
-
const
|
|
8618
|
-
|
|
8619
|
-
|
|
8620
|
-
|
|
8621
|
-
|
|
8622
|
-
|
|
8623
|
-
|
|
8624
|
-
|
|
8625
|
-
|
|
8698
|
+
const item = {
|
|
8699
|
+
type: 'image',
|
|
8700
|
+
zIndex,
|
|
8701
|
+
domOrder,
|
|
8702
|
+
options: { x, y, w, h, rotate: rotation, data: null },
|
|
8703
|
+
};
|
|
8704
|
+
|
|
8705
|
+
const job = async () => {
|
|
8706
|
+
const processed = await getProcessedImage(node.src, widthPx, heightPx, radii);
|
|
8707
|
+
if (processed) item.options.data = processed;
|
|
8708
|
+
else item.skip = true;
|
|
8709
|
+
};
|
|
8710
|
+
|
|
8711
|
+
return { items: [item], job, stopRecursion: true };
|
|
8626
8712
|
}
|
|
8627
|
-
// --- UPDATED IMG BLOCK END ---
|
|
8628
8713
|
|
|
8629
|
-
// Radii
|
|
8714
|
+
// Radii logic
|
|
8630
8715
|
const borderRadiusValue = parseFloat(style.borderRadius) || 0;
|
|
8631
8716
|
const borderBottomLeftRadius = parseFloat(style.borderBottomLeftRadius) || 0;
|
|
8632
8717
|
const borderBottomRightRadius = parseFloat(style.borderBottomRightRadius) || 0;
|
|
@@ -8644,25 +8729,30 @@
|
|
|
8644
8729
|
borderTopLeftRadius ||
|
|
8645
8730
|
borderTopRightRadius));
|
|
8646
8731
|
|
|
8647
|
-
//
|
|
8732
|
+
// --- ASYNC JOB: Clipped Divs via Canvas ---
|
|
8648
8733
|
if (hasPartialBorderRadius && isClippedByParent(node)) {
|
|
8649
8734
|
const marginLeft = parseFloat(style.marginLeft) || 0;
|
|
8650
8735
|
const marginTop = parseFloat(style.marginTop) || 0;
|
|
8651
8736
|
x += marginLeft * PX_TO_INCH * config.scale;
|
|
8652
8737
|
y += marginTop * PX_TO_INCH * config.scale;
|
|
8653
8738
|
|
|
8654
|
-
const
|
|
8655
|
-
|
|
8656
|
-
|
|
8657
|
-
|
|
8658
|
-
|
|
8659
|
-
|
|
8660
|
-
|
|
8661
|
-
|
|
8662
|
-
|
|
8663
|
-
|
|
8739
|
+
const item = {
|
|
8740
|
+
type: 'image',
|
|
8741
|
+
zIndex,
|
|
8742
|
+
domOrder,
|
|
8743
|
+
options: { x, y, w, h, rotate: rotation, data: null },
|
|
8744
|
+
};
|
|
8745
|
+
|
|
8746
|
+
const job = async () => {
|
|
8747
|
+
const canvasImageData = await elementToCanvasImage(node, widthPx, heightPx);
|
|
8748
|
+
if (canvasImageData) item.options.data = canvasImageData;
|
|
8749
|
+
else item.skip = true;
|
|
8750
|
+
};
|
|
8751
|
+
|
|
8752
|
+
return { items: [item], job, stopRecursion: true };
|
|
8664
8753
|
}
|
|
8665
8754
|
|
|
8755
|
+
// --- SYNC: Standard CSS Extraction ---
|
|
8666
8756
|
const bgColorObj = parseColor(style.backgroundColor);
|
|
8667
8757
|
const bgClip = style.webkitBackgroundClip || style.backgroundClip;
|
|
8668
8758
|
const isBgClipText = bgClip === 'text';
|
|
@@ -8701,7 +8791,7 @@
|
|
|
8701
8791
|
x -= bulletShift;
|
|
8702
8792
|
w += bulletShift;
|
|
8703
8793
|
textParts.push({
|
|
8704
|
-
text: '
|
|
8794
|
+
text: ' ',
|
|
8705
8795
|
options: {
|
|
8706
8796
|
color: parseColor(style.color).hex || '000000',
|
|
8707
8797
|
fontSize: fontSizePt,
|
|
@@ -8807,7 +8897,6 @@
|
|
|
8807
8897
|
});
|
|
8808
8898
|
}
|
|
8809
8899
|
if (hasCompositeBorder) {
|
|
8810
|
-
// Add border shapes after the main background
|
|
8811
8900
|
const borderItems = createCompositeBorderItems(
|
|
8812
8901
|
borderInfo.sides,
|
|
8813
8902
|
x,
|
|
@@ -8827,7 +8916,7 @@
|
|
|
8827
8916
|
hasShadow ||
|
|
8828
8917
|
textPayload
|
|
8829
8918
|
) {
|
|
8830
|
-
const finalAlpha =
|
|
8919
|
+
const finalAlpha = safeOpacity * bgColorObj.opacity;
|
|
8831
8920
|
const transparency = (1 - finalAlpha) * 100;
|
|
8832
8921
|
const useSolidFill = bgColorObj.hex && !isImageWrapper;
|
|
8833
8922
|
|
|
@@ -8849,14 +8938,7 @@
|
|
|
8849
8938
|
type: 'image',
|
|
8850
8939
|
zIndex,
|
|
8851
8940
|
domOrder,
|
|
8852
|
-
options: {
|
|
8853
|
-
data: shapeSvg,
|
|
8854
|
-
x,
|
|
8855
|
-
y,
|
|
8856
|
-
w,
|
|
8857
|
-
h,
|
|
8858
|
-
rotate: rotation,
|
|
8859
|
-
},
|
|
8941
|
+
options: { data: shapeSvg, x, y, w, h, rotate: rotation },
|
|
8860
8942
|
});
|
|
8861
8943
|
} else {
|
|
8862
8944
|
const shapeOpts = {
|
|
@@ -8871,9 +8953,7 @@
|
|
|
8871
8953
|
line: hasUniformBorder ? borderInfo.options : null,
|
|
8872
8954
|
};
|
|
8873
8955
|
|
|
8874
|
-
if (hasShadow)
|
|
8875
|
-
shapeOpts.shadow = getVisibleShadow(shadowStr, config.scale);
|
|
8876
|
-
}
|
|
8956
|
+
if (hasShadow) shapeOpts.shadow = getVisibleShadow(shadowStr, config.scale);
|
|
8877
8957
|
|
|
8878
8958
|
const borderRadius = parseFloat(style.borderRadius) || 0;
|
|
8879
8959
|
const aspectRatio = Math.max(widthPx, heightPx) / Math.min(widthPx, heightPx);
|
|
@@ -8936,77 +9016,49 @@
|
|
|
8936
9016
|
return { items, stopRecursion: !!textPayload };
|
|
8937
9017
|
}
|
|
8938
9018
|
|
|
8939
|
-
/**
|
|
8940
|
-
* Helper function to create individual border shapes
|
|
8941
|
-
*/
|
|
8942
9019
|
function createCompositeBorderItems(sides, x, y, w, h, scale, zIndex, domOrder) {
|
|
8943
9020
|
const items = [];
|
|
8944
9021
|
const pxToInch = 1 / 96;
|
|
9022
|
+
const common = { zIndex: zIndex + 1, domOrder, shapeType: 'rect' };
|
|
8945
9023
|
|
|
8946
|
-
|
|
8947
|
-
if (sides.top.width > 0) {
|
|
9024
|
+
if (sides.top.width > 0)
|
|
8948
9025
|
items.push({
|
|
8949
|
-
|
|
8950
|
-
|
|
8951
|
-
domOrder,
|
|
8952
|
-
shapeType: 'rect',
|
|
8953
|
-
options: {
|
|
8954
|
-
x: x,
|
|
8955
|
-
y: y,
|
|
8956
|
-
w: w,
|
|
8957
|
-
h: sides.top.width * pxToInch * scale,
|
|
8958
|
-
fill: { color: sides.top.color },
|
|
8959
|
-
},
|
|
9026
|
+
...common,
|
|
9027
|
+
options: { x, y, w, h: sides.top.width * pxToInch * scale, fill: { color: sides.top.color } },
|
|
8960
9028
|
});
|
|
8961
|
-
|
|
8962
|
-
// RIGHT BORDER
|
|
8963
|
-
if (sides.right.width > 0) {
|
|
9029
|
+
if (sides.right.width > 0)
|
|
8964
9030
|
items.push({
|
|
8965
|
-
|
|
8966
|
-
zIndex: zIndex + 1,
|
|
8967
|
-
domOrder,
|
|
8968
|
-
shapeType: 'rect',
|
|
9031
|
+
...common,
|
|
8969
9032
|
options: {
|
|
8970
9033
|
x: x + w - sides.right.width * pxToInch * scale,
|
|
8971
|
-
y
|
|
9034
|
+
y,
|
|
8972
9035
|
w: sides.right.width * pxToInch * scale,
|
|
8973
|
-
h
|
|
9036
|
+
h,
|
|
8974
9037
|
fill: { color: sides.right.color },
|
|
8975
9038
|
},
|
|
8976
9039
|
});
|
|
8977
|
-
|
|
8978
|
-
// BOTTOM BORDER
|
|
8979
|
-
if (sides.bottom.width > 0) {
|
|
9040
|
+
if (sides.bottom.width > 0)
|
|
8980
9041
|
items.push({
|
|
8981
|
-
|
|
8982
|
-
zIndex: zIndex + 1,
|
|
8983
|
-
domOrder,
|
|
8984
|
-
shapeType: 'rect',
|
|
9042
|
+
...common,
|
|
8985
9043
|
options: {
|
|
8986
|
-
x
|
|
9044
|
+
x,
|
|
8987
9045
|
y: y + h - sides.bottom.width * pxToInch * scale,
|
|
8988
|
-
w
|
|
9046
|
+
w,
|
|
8989
9047
|
h: sides.bottom.width * pxToInch * scale,
|
|
8990
9048
|
fill: { color: sides.bottom.color },
|
|
8991
9049
|
},
|
|
8992
9050
|
});
|
|
8993
|
-
|
|
8994
|
-
// LEFT BORDER
|
|
8995
|
-
if (sides.left.width > 0) {
|
|
9051
|
+
if (sides.left.width > 0)
|
|
8996
9052
|
items.push({
|
|
8997
|
-
|
|
8998
|
-
zIndex: zIndex + 1,
|
|
8999
|
-
domOrder,
|
|
9000
|
-
shapeType: 'rect',
|
|
9053
|
+
...common,
|
|
9001
9054
|
options: {
|
|
9002
|
-
x
|
|
9003
|
-
y
|
|
9055
|
+
x,
|
|
9056
|
+
y,
|
|
9004
9057
|
w: sides.left.width * pxToInch * scale,
|
|
9005
|
-
h
|
|
9058
|
+
h,
|
|
9006
9059
|
fill: { color: sides.left.color },
|
|
9007
9060
|
},
|
|
9008
9061
|
});
|
|
9009
|
-
}
|
|
9010
9062
|
|
|
9011
9063
|
return items;
|
|
9012
9064
|
}
|