dom-to-pptx 1.0.6 → 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 +16 -0
- package/{Readme.md → README.md} +329 -288
- package/dist/dom-to-pptx.bundle.js +16497 -27916
- package/dist/dom-to-pptx.cjs +1221 -1114
- package/dist/dom-to-pptx.min.js +1221 -1114
- package/dist/dom-to-pptx.mjs +1221 -1114
- package/package.json +1 -1
- package/rollup.config.js +13 -7
- package/src/index.js +755 -657
- package/src/utils.js +488 -479
package/dist/dom-to-pptx.cjs
CHANGED
|
@@ -7844,484 +7844,493 @@ var parseBackgroundColor = function (context, element, backgroundColorOverride)
|
|
|
7844
7844
|
: defaultBackgroundColor;
|
|
7845
7845
|
};
|
|
7846
7846
|
|
|
7847
|
-
// src/utils.js
|
|
7848
|
-
|
|
7849
|
-
/**
|
|
7850
|
-
* Checks if any parent element has overflow: hidden which would clip this element
|
|
7851
|
-
* @param {HTMLElement} node - The DOM node to check
|
|
7852
|
-
* @returns {boolean} - True if a parent has overflow-hidden or overflow-clip
|
|
7853
|
-
*/
|
|
7854
|
-
function isClippedByParent(node) {
|
|
7855
|
-
let parent = node.parentElement;
|
|
7856
|
-
while (parent && parent !== document.body) {
|
|
7857
|
-
const style = window.getComputedStyle(parent);
|
|
7858
|
-
const overflow = style.overflow;
|
|
7859
|
-
if (overflow === 'hidden' || overflow === 'clip') {
|
|
7860
|
-
return true;
|
|
7861
|
-
}
|
|
7862
|
-
parent = parent.parentElement;
|
|
7863
|
-
}
|
|
7864
|
-
return false;
|
|
7865
|
-
}
|
|
7866
|
-
|
|
7867
|
-
// Helper to save gradient text
|
|
7868
|
-
function getGradientFallbackColor(bgImage) {
|
|
7869
|
-
if (!bgImage) return null;
|
|
7870
|
-
const hexMatch = bgImage.match(/#(?:[0-9a-fA-F]{3}){1,2}/);
|
|
7871
|
-
if (hexMatch) return hexMatch[0];
|
|
7872
|
-
const rgbMatch = bgImage.match(/rgba?\(.*?\)/);
|
|
7873
|
-
if (rgbMatch) return rgbMatch[0];
|
|
7874
|
-
return null;
|
|
7875
|
-
}
|
|
7876
|
-
|
|
7877
|
-
function mapDashType(style) {
|
|
7878
|
-
if (style === 'dashed') return 'dash';
|
|
7879
|
-
if (style === 'dotted') return 'dot';
|
|
7880
|
-
return 'solid';
|
|
7881
|
-
}
|
|
7882
|
-
|
|
7883
|
-
/**
|
|
7884
|
-
* Analyzes computed border styles and determines the rendering strategy.
|
|
7885
|
-
*/
|
|
7886
|
-
function getBorderInfo(style, scale) {
|
|
7887
|
-
const top = {
|
|
7888
|
-
width: parseFloat(style.borderTopWidth) || 0,
|
|
7889
|
-
style: style.borderTopStyle,
|
|
7890
|
-
color: parseColor(style.borderTopColor).hex,
|
|
7891
|
-
};
|
|
7892
|
-
const right = {
|
|
7893
|
-
width: parseFloat(style.borderRightWidth) || 0,
|
|
7894
|
-
style: style.borderRightStyle,
|
|
7895
|
-
color: parseColor(style.borderRightColor).hex,
|
|
7896
|
-
};
|
|
7897
|
-
const bottom = {
|
|
7898
|
-
width: parseFloat(style.borderBottomWidth) || 0,
|
|
7899
|
-
style: style.borderBottomStyle,
|
|
7900
|
-
color: parseColor(style.borderBottomColor).hex,
|
|
7901
|
-
};
|
|
7902
|
-
const left = {
|
|
7903
|
-
width: parseFloat(style.borderLeftWidth) || 0,
|
|
7904
|
-
style: style.borderLeftStyle,
|
|
7905
|
-
color: parseColor(style.borderLeftColor).hex,
|
|
7906
|
-
};
|
|
7907
|
-
|
|
7908
|
-
const hasAnyBorder = top.width > 0 || right.width > 0 || bottom.width > 0 || left.width > 0;
|
|
7909
|
-
if (!hasAnyBorder) return { type: 'none' };
|
|
7910
|
-
|
|
7911
|
-
// Check if all sides are uniform
|
|
7912
|
-
const isUniform =
|
|
7913
|
-
top.width === right.width &&
|
|
7914
|
-
top.width === bottom.width &&
|
|
7915
|
-
top.width === left.width &&
|
|
7916
|
-
top.style === right.style &&
|
|
7917
|
-
top.style === bottom.style &&
|
|
7918
|
-
top.style === left.style &&
|
|
7919
|
-
top.color === right.color &&
|
|
7920
|
-
top.color === bottom.color &&
|
|
7921
|
-
top.color === left.color;
|
|
7922
|
-
|
|
7923
|
-
if (isUniform) {
|
|
7924
|
-
return {
|
|
7925
|
-
type: 'uniform',
|
|
7926
|
-
options: {
|
|
7927
|
-
width: top.width * 0.75 * scale,
|
|
7928
|
-
color: top.color,
|
|
7929
|
-
transparency: (1 - parseColor(style.borderTopColor).opacity) * 100,
|
|
7930
|
-
dashType: mapDashType(top.style),
|
|
7931
|
-
},
|
|
7932
|
-
};
|
|
7933
|
-
} else {
|
|
7934
|
-
return {
|
|
7935
|
-
type: 'composite',
|
|
7936
|
-
sides: { top, right, bottom, left },
|
|
7937
|
-
};
|
|
7938
|
-
}
|
|
7939
|
-
}
|
|
7940
|
-
|
|
7941
|
-
/**
|
|
7942
|
-
* Generates an SVG image for composite borders that respects border-radius.
|
|
7943
|
-
*/
|
|
7944
|
-
function generateCompositeBorderSVG(w, h, radius, sides) {
|
|
7945
|
-
radius = radius / 2; // Adjust for SVG rendering
|
|
7946
|
-
const clipId = 'clip_' + Math.random().toString(36).substr(2, 9);
|
|
7947
|
-
let borderRects = '';
|
|
7948
|
-
|
|
7949
|
-
if (sides.top.width > 0 && sides.top.color) {
|
|
7950
|
-
borderRects += `<rect x="0" y="0" width="${w}" height="${sides.top.width}" fill="#${sides.top.color}" />`;
|
|
7951
|
-
}
|
|
7952
|
-
if (sides.right.width > 0 && sides.right.color) {
|
|
7953
|
-
borderRects += `<rect x="${w - sides.right.width}" y="0" width="${sides.right.width}" height="${h}" fill="#${sides.right.color}" />`;
|
|
7954
|
-
}
|
|
7955
|
-
if (sides.bottom.width > 0 && sides.bottom.color) {
|
|
7956
|
-
borderRects += `<rect x="0" y="${h - sides.bottom.width}" width="${w}" height="${sides.bottom.width}" fill="#${sides.bottom.color}" />`;
|
|
7957
|
-
}
|
|
7958
|
-
if (sides.left.width > 0 && sides.left.color) {
|
|
7959
|
-
borderRects += `<rect x="0" y="0" width="${sides.left.width}" height="${h}" fill="#${sides.left.color}" />`;
|
|
7960
|
-
}
|
|
7961
|
-
|
|
7962
|
-
const svg = `
|
|
7963
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">
|
|
7964
|
-
<defs>
|
|
7965
|
-
<clipPath id="${clipId}">
|
|
7966
|
-
<rect x="0" y="0" width="${w}" height="${h}" rx="${radius}" ry="${radius}" />
|
|
7967
|
-
</clipPath>
|
|
7968
|
-
</defs>
|
|
7969
|
-
<g clip-path="url(#${clipId})">
|
|
7970
|
-
${borderRects}
|
|
7971
|
-
</g>
|
|
7972
|
-
</svg>`;
|
|
7973
|
-
|
|
7974
|
-
return 'data:image/svg+xml;base64,' + btoa(svg);
|
|
7975
|
-
}
|
|
7976
|
-
|
|
7977
|
-
/**
|
|
7978
|
-
* Generates an SVG data URL for a solid shape with non-uniform corner radii.
|
|
7979
|
-
*/
|
|
7980
|
-
function generateCustomShapeSVG(w, h, color, opacity, radii) {
|
|
7981
|
-
let { tl, tr, br, bl } = radii;
|
|
7982
|
-
|
|
7983
|
-
// Clamp radii using CSS spec logic (avoid overlap)
|
|
7984
|
-
const factor = Math.min(
|
|
7985
|
-
w / (tl + tr) || Infinity,
|
|
7986
|
-
h / (tr + br) || Infinity,
|
|
7987
|
-
w / (br + bl) || Infinity,
|
|
7988
|
-
h / (bl + tl) || Infinity
|
|
7989
|
-
);
|
|
7990
|
-
|
|
7991
|
-
if (factor < 1) {
|
|
7992
|
-
tl *= factor;
|
|
7993
|
-
tr *= factor;
|
|
7994
|
-
br *= factor;
|
|
7995
|
-
bl *= factor;
|
|
7996
|
-
}
|
|
7997
|
-
|
|
7998
|
-
const path = `
|
|
7999
|
-
M ${tl} 0
|
|
8000
|
-
L ${w - tr} 0
|
|
8001
|
-
A ${tr} ${tr} 0 0 1 ${w} ${tr}
|
|
8002
|
-
L ${w} ${h - br}
|
|
8003
|
-
A ${br} ${br} 0 0 1 ${w - br} ${h}
|
|
8004
|
-
L ${bl} ${h}
|
|
8005
|
-
A ${bl} ${bl} 0 0 1 0 ${h - bl}
|
|
8006
|
-
L 0 ${tl}
|
|
8007
|
-
A ${tl} ${tl} 0 0 1 ${tl} 0
|
|
8008
|
-
Z
|
|
8009
|
-
`;
|
|
8010
|
-
|
|
8011
|
-
const svg = `
|
|
8012
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">
|
|
8013
|
-
<path d="${path}" fill="#${color}" fill-opacity="${opacity}" />
|
|
8014
|
-
</svg>`;
|
|
8015
|
-
|
|
8016
|
-
return 'data:image/svg+xml;base64,' + btoa(svg);
|
|
8017
|
-
}
|
|
8018
|
-
|
|
8019
|
-
function parseColor(str) {
|
|
8020
|
-
if (!str || str === 'transparent' || str.startsWith('rgba(0, 0, 0, 0)')) {
|
|
8021
|
-
return { hex: null, opacity: 0 };
|
|
8022
|
-
}
|
|
8023
|
-
if (str.startsWith('#')) {
|
|
8024
|
-
let hex = str.slice(1);
|
|
8025
|
-
if (hex.length === 3)
|
|
8026
|
-
hex = hex
|
|
8027
|
-
.split('')
|
|
8028
|
-
.map((c) => c + c)
|
|
8029
|
-
.join('');
|
|
8030
|
-
return { hex: hex.toUpperCase(), opacity: 1 };
|
|
8031
|
-
}
|
|
8032
|
-
const match = str.match(/[\d.]+/g);
|
|
8033
|
-
if (match && match.length >= 3) {
|
|
8034
|
-
const r = parseInt(match[0]);
|
|
8035
|
-
const g = parseInt(match[1]);
|
|
8036
|
-
const b = parseInt(match[2]);
|
|
8037
|
-
const a = match.length > 3 ? parseFloat(match[3]) : 1;
|
|
8038
|
-
const hex = ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1).toUpperCase();
|
|
8039
|
-
return { hex, opacity: a };
|
|
8040
|
-
}
|
|
8041
|
-
return { hex: null, opacity: 0 };
|
|
8042
|
-
}
|
|
8043
|
-
|
|
8044
|
-
function getPadding(style, scale) {
|
|
8045
|
-
const pxToInch = 1 / 96;
|
|
8046
|
-
return [
|
|
8047
|
-
(parseFloat(style.paddingTop) || 0) * pxToInch * scale,
|
|
8048
|
-
(parseFloat(style.paddingRight) || 0) * pxToInch * scale,
|
|
8049
|
-
(parseFloat(style.paddingBottom) || 0) * pxToInch * scale,
|
|
8050
|
-
(parseFloat(style.paddingLeft) || 0) * pxToInch * scale,
|
|
8051
|
-
];
|
|
8052
|
-
}
|
|
8053
|
-
|
|
8054
|
-
function getSoftEdges(filterStr, scale) {
|
|
8055
|
-
if (!filterStr || filterStr === 'none') return null;
|
|
8056
|
-
const match = filterStr.match(/blur\(([\d.]+)px\)/);
|
|
8057
|
-
if (match) return parseFloat(match[1]) * 0.75 * scale;
|
|
8058
|
-
return null;
|
|
8059
|
-
}
|
|
8060
|
-
|
|
8061
|
-
function getTextStyle(style, scale) {
|
|
8062
|
-
let colorObj = parseColor(style.color);
|
|
8063
|
-
|
|
8064
|
-
const bgClip = style.webkitBackgroundClip || style.backgroundClip;
|
|
8065
|
-
if (colorObj.opacity === 0 && bgClip === 'text') {
|
|
8066
|
-
const fallback = getGradientFallbackColor(style.backgroundImage);
|
|
8067
|
-
if (fallback) colorObj = parseColor(fallback);
|
|
8068
|
-
}
|
|
8069
|
-
|
|
8070
|
-
return {
|
|
8071
|
-
color: colorObj.hex || '000000',
|
|
8072
|
-
fontFace: style.fontFamily.split(',')[0].replace(/['"]/g, ''),
|
|
8073
|
-
fontSize: parseFloat(style.fontSize) * 0.75 * scale,
|
|
8074
|
-
bold: parseInt(style.fontWeight) >= 600,
|
|
8075
|
-
italic: style.fontStyle === 'italic',
|
|
8076
|
-
underline: style.textDecoration.includes('underline'),
|
|
8077
|
-
};
|
|
8078
|
-
}
|
|
8079
|
-
|
|
8080
|
-
/**
|
|
8081
|
-
* Determines if a given DOM node is primarily a text container.
|
|
8082
|
-
*/
|
|
8083
|
-
function isTextContainer(node) {
|
|
8084
|
-
const hasText = node.textContent.trim().length > 0;
|
|
8085
|
-
if (!hasText) return false;
|
|
8086
|
-
|
|
8087
|
-
const children = Array.from(node.children);
|
|
8088
|
-
if (children.length === 0) return true;
|
|
8089
|
-
|
|
8090
|
-
// Check if children are purely inline text formatting or visual shapes
|
|
8091
|
-
const isSafeInline = (el) => {
|
|
8092
|
-
|
|
8093
|
-
|
|
8094
|
-
|
|
8095
|
-
|
|
8096
|
-
const
|
|
8097
|
-
const
|
|
8098
|
-
|
|
8099
|
-
|
|
8100
|
-
|
|
8101
|
-
|
|
8102
|
-
|
|
8103
|
-
|
|
8104
|
-
|
|
8105
|
-
|
|
8106
|
-
|
|
8107
|
-
|
|
8108
|
-
|
|
8109
|
-
|
|
8110
|
-
|
|
8111
|
-
|
|
8112
|
-
|
|
8113
|
-
|
|
8114
|
-
|
|
8115
|
-
|
|
8116
|
-
|
|
8117
|
-
|
|
8118
|
-
|
|
8119
|
-
|
|
8120
|
-
|
|
8121
|
-
|
|
8122
|
-
|
|
8123
|
-
|
|
8124
|
-
|
|
8125
|
-
|
|
8126
|
-
return
|
|
8127
|
-
}
|
|
8128
|
-
|
|
8129
|
-
function
|
|
8130
|
-
|
|
8131
|
-
|
|
8132
|
-
|
|
8133
|
-
|
|
8134
|
-
|
|
8135
|
-
|
|
8136
|
-
|
|
8137
|
-
|
|
8138
|
-
|
|
8139
|
-
|
|
8140
|
-
|
|
8141
|
-
|
|
8142
|
-
|
|
8143
|
-
|
|
8144
|
-
|
|
8145
|
-
|
|
8146
|
-
|
|
8147
|
-
|
|
8148
|
-
|
|
8149
|
-
|
|
8150
|
-
|
|
8151
|
-
|
|
8152
|
-
|
|
8153
|
-
|
|
8154
|
-
|
|
8155
|
-
|
|
8156
|
-
|
|
8157
|
-
|
|
8158
|
-
|
|
8159
|
-
|
|
8160
|
-
|
|
8161
|
-
|
|
8162
|
-
|
|
8163
|
-
|
|
8164
|
-
|
|
8165
|
-
|
|
8166
|
-
|
|
8167
|
-
|
|
8168
|
-
|
|
8169
|
-
|
|
8170
|
-
|
|
8171
|
-
|
|
8172
|
-
|
|
8173
|
-
|
|
8174
|
-
|
|
8175
|
-
|
|
8176
|
-
|
|
8177
|
-
|
|
8178
|
-
|
|
8179
|
-
|
|
8180
|
-
|
|
8181
|
-
|
|
8182
|
-
|
|
8183
|
-
|
|
8184
|
-
|
|
8185
|
-
|
|
8186
|
-
|
|
8187
|
-
|
|
8188
|
-
|
|
8189
|
-
|
|
8190
|
-
|
|
8191
|
-
|
|
8192
|
-
|
|
8193
|
-
|
|
8194
|
-
|
|
8195
|
-
|
|
8196
|
-
|
|
8197
|
-
|
|
8198
|
-
|
|
8199
|
-
|
|
8200
|
-
|
|
8201
|
-
|
|
8202
|
-
|
|
8203
|
-
|
|
8204
|
-
|
|
8205
|
-
|
|
8206
|
-
|
|
8207
|
-
|
|
8208
|
-
|
|
8209
|
-
|
|
8210
|
-
|
|
8211
|
-
|
|
8212
|
-
|
|
8213
|
-
|
|
8214
|
-
|
|
8215
|
-
|
|
8216
|
-
|
|
8217
|
-
|
|
8218
|
-
|
|
8219
|
-
|
|
8220
|
-
|
|
8221
|
-
|
|
8222
|
-
|
|
8223
|
-
|
|
8224
|
-
|
|
8225
|
-
|
|
8226
|
-
|
|
8227
|
-
|
|
8228
|
-
|
|
8229
|
-
|
|
8230
|
-
|
|
8231
|
-
|
|
8232
|
-
|
|
8233
|
-
|
|
8234
|
-
|
|
8235
|
-
if (
|
|
8236
|
-
|
|
8237
|
-
|
|
8238
|
-
|
|
8239
|
-
|
|
8240
|
-
|
|
8241
|
-
|
|
8242
|
-
|
|
8243
|
-
|
|
8244
|
-
|
|
8245
|
-
|
|
8246
|
-
|
|
8247
|
-
y2 = '0%';
|
|
8248
|
-
stopsStartIdx = 1;
|
|
8249
|
-
} else if (parts[0].includes('to
|
|
8250
|
-
|
|
8251
|
-
|
|
8252
|
-
|
|
8253
|
-
|
|
8254
|
-
|
|
8255
|
-
|
|
8256
|
-
|
|
8257
|
-
|
|
8258
|
-
|
|
8259
|
-
|
|
8260
|
-
|
|
8261
|
-
|
|
8262
|
-
|
|
8263
|
-
|
|
8264
|
-
|
|
8265
|
-
|
|
8266
|
-
|
|
8267
|
-
|
|
8268
|
-
|
|
8269
|
-
|
|
8270
|
-
|
|
8271
|
-
|
|
8272
|
-
|
|
8273
|
-
|
|
8274
|
-
|
|
8275
|
-
|
|
8276
|
-
|
|
8277
|
-
|
|
8278
|
-
|
|
8279
|
-
|
|
8280
|
-
|
|
8281
|
-
|
|
8282
|
-
|
|
8283
|
-
|
|
8284
|
-
|
|
8285
|
-
|
|
8286
|
-
|
|
8287
|
-
|
|
8288
|
-
|
|
8289
|
-
|
|
8290
|
-
|
|
8291
|
-
|
|
8292
|
-
|
|
8293
|
-
|
|
8294
|
-
|
|
8295
|
-
|
|
8296
|
-
|
|
8297
|
-
|
|
8298
|
-
|
|
8299
|
-
|
|
8300
|
-
|
|
8301
|
-
|
|
8302
|
-
|
|
8303
|
-
|
|
8304
|
-
|
|
8305
|
-
|
|
8306
|
-
|
|
8307
|
-
|
|
8308
|
-
|
|
8309
|
-
|
|
8310
|
-
|
|
8311
|
-
|
|
8312
|
-
|
|
8313
|
-
|
|
8314
|
-
|
|
8315
|
-
|
|
8316
|
-
|
|
8317
|
-
|
|
8318
|
-
|
|
8319
|
-
|
|
8320
|
-
|
|
8321
|
-
|
|
8322
|
-
|
|
8323
|
-
|
|
8324
|
-
|
|
7847
|
+
// src/utils.js
|
|
7848
|
+
|
|
7849
|
+
/**
|
|
7850
|
+
* Checks if any parent element has overflow: hidden which would clip this element
|
|
7851
|
+
* @param {HTMLElement} node - The DOM node to check
|
|
7852
|
+
* @returns {boolean} - True if a parent has overflow-hidden or overflow-clip
|
|
7853
|
+
*/
|
|
7854
|
+
function isClippedByParent(node) {
|
|
7855
|
+
let parent = node.parentElement;
|
|
7856
|
+
while (parent && parent !== document.body) {
|
|
7857
|
+
const style = window.getComputedStyle(parent);
|
|
7858
|
+
const overflow = style.overflow;
|
|
7859
|
+
if (overflow === 'hidden' || overflow === 'clip') {
|
|
7860
|
+
return true;
|
|
7861
|
+
}
|
|
7862
|
+
parent = parent.parentElement;
|
|
7863
|
+
}
|
|
7864
|
+
return false;
|
|
7865
|
+
}
|
|
7866
|
+
|
|
7867
|
+
// Helper to save gradient text
|
|
7868
|
+
function getGradientFallbackColor(bgImage) {
|
|
7869
|
+
if (!bgImage) return null;
|
|
7870
|
+
const hexMatch = bgImage.match(/#(?:[0-9a-fA-F]{3}){1,2}/);
|
|
7871
|
+
if (hexMatch) return hexMatch[0];
|
|
7872
|
+
const rgbMatch = bgImage.match(/rgba?\(.*?\)/);
|
|
7873
|
+
if (rgbMatch) return rgbMatch[0];
|
|
7874
|
+
return null;
|
|
7875
|
+
}
|
|
7876
|
+
|
|
7877
|
+
function mapDashType(style) {
|
|
7878
|
+
if (style === 'dashed') return 'dash';
|
|
7879
|
+
if (style === 'dotted') return 'dot';
|
|
7880
|
+
return 'solid';
|
|
7881
|
+
}
|
|
7882
|
+
|
|
7883
|
+
/**
|
|
7884
|
+
* Analyzes computed border styles and determines the rendering strategy.
|
|
7885
|
+
*/
|
|
7886
|
+
function getBorderInfo(style, scale) {
|
|
7887
|
+
const top = {
|
|
7888
|
+
width: parseFloat(style.borderTopWidth) || 0,
|
|
7889
|
+
style: style.borderTopStyle,
|
|
7890
|
+
color: parseColor(style.borderTopColor).hex,
|
|
7891
|
+
};
|
|
7892
|
+
const right = {
|
|
7893
|
+
width: parseFloat(style.borderRightWidth) || 0,
|
|
7894
|
+
style: style.borderRightStyle,
|
|
7895
|
+
color: parseColor(style.borderRightColor).hex,
|
|
7896
|
+
};
|
|
7897
|
+
const bottom = {
|
|
7898
|
+
width: parseFloat(style.borderBottomWidth) || 0,
|
|
7899
|
+
style: style.borderBottomStyle,
|
|
7900
|
+
color: parseColor(style.borderBottomColor).hex,
|
|
7901
|
+
};
|
|
7902
|
+
const left = {
|
|
7903
|
+
width: parseFloat(style.borderLeftWidth) || 0,
|
|
7904
|
+
style: style.borderLeftStyle,
|
|
7905
|
+
color: parseColor(style.borderLeftColor).hex,
|
|
7906
|
+
};
|
|
7907
|
+
|
|
7908
|
+
const hasAnyBorder = top.width > 0 || right.width > 0 || bottom.width > 0 || left.width > 0;
|
|
7909
|
+
if (!hasAnyBorder) return { type: 'none' };
|
|
7910
|
+
|
|
7911
|
+
// Check if all sides are uniform
|
|
7912
|
+
const isUniform =
|
|
7913
|
+
top.width === right.width &&
|
|
7914
|
+
top.width === bottom.width &&
|
|
7915
|
+
top.width === left.width &&
|
|
7916
|
+
top.style === right.style &&
|
|
7917
|
+
top.style === bottom.style &&
|
|
7918
|
+
top.style === left.style &&
|
|
7919
|
+
top.color === right.color &&
|
|
7920
|
+
top.color === bottom.color &&
|
|
7921
|
+
top.color === left.color;
|
|
7922
|
+
|
|
7923
|
+
if (isUniform) {
|
|
7924
|
+
return {
|
|
7925
|
+
type: 'uniform',
|
|
7926
|
+
options: {
|
|
7927
|
+
width: top.width * 0.75 * scale,
|
|
7928
|
+
color: top.color,
|
|
7929
|
+
transparency: (1 - parseColor(style.borderTopColor).opacity) * 100,
|
|
7930
|
+
dashType: mapDashType(top.style),
|
|
7931
|
+
},
|
|
7932
|
+
};
|
|
7933
|
+
} else {
|
|
7934
|
+
return {
|
|
7935
|
+
type: 'composite',
|
|
7936
|
+
sides: { top, right, bottom, left },
|
|
7937
|
+
};
|
|
7938
|
+
}
|
|
7939
|
+
}
|
|
7940
|
+
|
|
7941
|
+
/**
|
|
7942
|
+
* Generates an SVG image for composite borders that respects border-radius.
|
|
7943
|
+
*/
|
|
7944
|
+
function generateCompositeBorderSVG(w, h, radius, sides) {
|
|
7945
|
+
radius = radius / 2; // Adjust for SVG rendering
|
|
7946
|
+
const clipId = 'clip_' + Math.random().toString(36).substr(2, 9);
|
|
7947
|
+
let borderRects = '';
|
|
7948
|
+
|
|
7949
|
+
if (sides.top.width > 0 && sides.top.color) {
|
|
7950
|
+
borderRects += `<rect x="0" y="0" width="${w}" height="${sides.top.width}" fill="#${sides.top.color}" />`;
|
|
7951
|
+
}
|
|
7952
|
+
if (sides.right.width > 0 && sides.right.color) {
|
|
7953
|
+
borderRects += `<rect x="${w - sides.right.width}" y="0" width="${sides.right.width}" height="${h}" fill="#${sides.right.color}" />`;
|
|
7954
|
+
}
|
|
7955
|
+
if (sides.bottom.width > 0 && sides.bottom.color) {
|
|
7956
|
+
borderRects += `<rect x="0" y="${h - sides.bottom.width}" width="${w}" height="${sides.bottom.width}" fill="#${sides.bottom.color}" />`;
|
|
7957
|
+
}
|
|
7958
|
+
if (sides.left.width > 0 && sides.left.color) {
|
|
7959
|
+
borderRects += `<rect x="0" y="0" width="${sides.left.width}" height="${h}" fill="#${sides.left.color}" />`;
|
|
7960
|
+
}
|
|
7961
|
+
|
|
7962
|
+
const svg = `
|
|
7963
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">
|
|
7964
|
+
<defs>
|
|
7965
|
+
<clipPath id="${clipId}">
|
|
7966
|
+
<rect x="0" y="0" width="${w}" height="${h}" rx="${radius}" ry="${radius}" />
|
|
7967
|
+
</clipPath>
|
|
7968
|
+
</defs>
|
|
7969
|
+
<g clip-path="url(#${clipId})">
|
|
7970
|
+
${borderRects}
|
|
7971
|
+
</g>
|
|
7972
|
+
</svg>`;
|
|
7973
|
+
|
|
7974
|
+
return 'data:image/svg+xml;base64,' + btoa(svg);
|
|
7975
|
+
}
|
|
7976
|
+
|
|
7977
|
+
/**
|
|
7978
|
+
* Generates an SVG data URL for a solid shape with non-uniform corner radii.
|
|
7979
|
+
*/
|
|
7980
|
+
function generateCustomShapeSVG(w, h, color, opacity, radii) {
|
|
7981
|
+
let { tl, tr, br, bl } = radii;
|
|
7982
|
+
|
|
7983
|
+
// Clamp radii using CSS spec logic (avoid overlap)
|
|
7984
|
+
const factor = Math.min(
|
|
7985
|
+
w / (tl + tr) || Infinity,
|
|
7986
|
+
h / (tr + br) || Infinity,
|
|
7987
|
+
w / (br + bl) || Infinity,
|
|
7988
|
+
h / (bl + tl) || Infinity
|
|
7989
|
+
);
|
|
7990
|
+
|
|
7991
|
+
if (factor < 1) {
|
|
7992
|
+
tl *= factor;
|
|
7993
|
+
tr *= factor;
|
|
7994
|
+
br *= factor;
|
|
7995
|
+
bl *= factor;
|
|
7996
|
+
}
|
|
7997
|
+
|
|
7998
|
+
const path = `
|
|
7999
|
+
M ${tl} 0
|
|
8000
|
+
L ${w - tr} 0
|
|
8001
|
+
A ${tr} ${tr} 0 0 1 ${w} ${tr}
|
|
8002
|
+
L ${w} ${h - br}
|
|
8003
|
+
A ${br} ${br} 0 0 1 ${w - br} ${h}
|
|
8004
|
+
L ${bl} ${h}
|
|
8005
|
+
A ${bl} ${bl} 0 0 1 0 ${h - bl}
|
|
8006
|
+
L 0 ${tl}
|
|
8007
|
+
A ${tl} ${tl} 0 0 1 ${tl} 0
|
|
8008
|
+
Z
|
|
8009
|
+
`;
|
|
8010
|
+
|
|
8011
|
+
const svg = `
|
|
8012
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">
|
|
8013
|
+
<path d="${path}" fill="#${color}" fill-opacity="${opacity}" />
|
|
8014
|
+
</svg>`;
|
|
8015
|
+
|
|
8016
|
+
return 'data:image/svg+xml;base64,' + btoa(svg);
|
|
8017
|
+
}
|
|
8018
|
+
|
|
8019
|
+
function parseColor(str) {
|
|
8020
|
+
if (!str || str === 'transparent' || str.startsWith('rgba(0, 0, 0, 0)')) {
|
|
8021
|
+
return { hex: null, opacity: 0 };
|
|
8022
|
+
}
|
|
8023
|
+
if (str.startsWith('#')) {
|
|
8024
|
+
let hex = str.slice(1);
|
|
8025
|
+
if (hex.length === 3)
|
|
8026
|
+
hex = hex
|
|
8027
|
+
.split('')
|
|
8028
|
+
.map((c) => c + c)
|
|
8029
|
+
.join('');
|
|
8030
|
+
return { hex: hex.toUpperCase(), opacity: 1 };
|
|
8031
|
+
}
|
|
8032
|
+
const match = str.match(/[\d.]+/g);
|
|
8033
|
+
if (match && match.length >= 3) {
|
|
8034
|
+
const r = parseInt(match[0]);
|
|
8035
|
+
const g = parseInt(match[1]);
|
|
8036
|
+
const b = parseInt(match[2]);
|
|
8037
|
+
const a = match.length > 3 ? parseFloat(match[3]) : 1;
|
|
8038
|
+
const hex = ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1).toUpperCase();
|
|
8039
|
+
return { hex, opacity: a };
|
|
8040
|
+
}
|
|
8041
|
+
return { hex: null, opacity: 0 };
|
|
8042
|
+
}
|
|
8043
|
+
|
|
8044
|
+
function getPadding(style, scale) {
|
|
8045
|
+
const pxToInch = 1 / 96;
|
|
8046
|
+
return [
|
|
8047
|
+
(parseFloat(style.paddingTop) || 0) * pxToInch * scale,
|
|
8048
|
+
(parseFloat(style.paddingRight) || 0) * pxToInch * scale,
|
|
8049
|
+
(parseFloat(style.paddingBottom) || 0) * pxToInch * scale,
|
|
8050
|
+
(parseFloat(style.paddingLeft) || 0) * pxToInch * scale,
|
|
8051
|
+
];
|
|
8052
|
+
}
|
|
8053
|
+
|
|
8054
|
+
function getSoftEdges(filterStr, scale) {
|
|
8055
|
+
if (!filterStr || filterStr === 'none') return null;
|
|
8056
|
+
const match = filterStr.match(/blur\(([\d.]+)px\)/);
|
|
8057
|
+
if (match) return parseFloat(match[1]) * 0.75 * scale;
|
|
8058
|
+
return null;
|
|
8059
|
+
}
|
|
8060
|
+
|
|
8061
|
+
function getTextStyle(style, scale) {
|
|
8062
|
+
let colorObj = parseColor(style.color);
|
|
8063
|
+
|
|
8064
|
+
const bgClip = style.webkitBackgroundClip || style.backgroundClip;
|
|
8065
|
+
if (colorObj.opacity === 0 && bgClip === 'text') {
|
|
8066
|
+
const fallback = getGradientFallbackColor(style.backgroundImage);
|
|
8067
|
+
if (fallback) colorObj = parseColor(fallback);
|
|
8068
|
+
}
|
|
8069
|
+
|
|
8070
|
+
return {
|
|
8071
|
+
color: colorObj.hex || '000000',
|
|
8072
|
+
fontFace: style.fontFamily.split(',')[0].replace(/['"]/g, ''),
|
|
8073
|
+
fontSize: parseFloat(style.fontSize) * 0.75 * scale,
|
|
8074
|
+
bold: parseInt(style.fontWeight) >= 600,
|
|
8075
|
+
italic: style.fontStyle === 'italic',
|
|
8076
|
+
underline: style.textDecoration.includes('underline'),
|
|
8077
|
+
};
|
|
8078
|
+
}
|
|
8079
|
+
|
|
8080
|
+
/**
|
|
8081
|
+
* Determines if a given DOM node is primarily a text container.
|
|
8082
|
+
*/
|
|
8083
|
+
function isTextContainer(node) {
|
|
8084
|
+
const hasText = node.textContent.trim().length > 0;
|
|
8085
|
+
if (!hasText) return false;
|
|
8086
|
+
|
|
8087
|
+
const children = Array.from(node.children);
|
|
8088
|
+
if (children.length === 0) return true;
|
|
8089
|
+
|
|
8090
|
+
// Check if children are purely inline text formatting or visual shapes
|
|
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
|
+
|
|
8096
|
+
const style = window.getComputedStyle(el);
|
|
8097
|
+
const display = style.display;
|
|
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);
|
|
8101
|
+
const isInlineDisplay = display.includes('inline');
|
|
8102
|
+
|
|
8103
|
+
if (!isInlineTag && !isInlineDisplay) return false;
|
|
8104
|
+
|
|
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.
|
|
8109
|
+
const bgColor = parseColor(style.backgroundColor);
|
|
8110
|
+
const hasVisibleBg = bgColor.hex && bgColor.opacity > 0;
|
|
8111
|
+
const hasBorder = parseFloat(style.borderWidth) > 0 && parseColor(style.borderColor).opacity > 0;
|
|
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;
|
|
8119
|
+
if (!hasContent && (hasVisibleBg || hasBorder)) {
|
|
8120
|
+
return false;
|
|
8121
|
+
}
|
|
8122
|
+
|
|
8123
|
+
return true;
|
|
8124
|
+
};
|
|
8125
|
+
|
|
8126
|
+
return children.every(isSafeInline);
|
|
8127
|
+
}
|
|
8128
|
+
|
|
8129
|
+
function getRotation(transformStr) {
|
|
8130
|
+
if (!transformStr || transformStr === 'none') return 0;
|
|
8131
|
+
const values = transformStr.split('(')[1].split(')')[0].split(',');
|
|
8132
|
+
if (values.length < 4) return 0;
|
|
8133
|
+
const a = parseFloat(values[0]);
|
|
8134
|
+
const b = parseFloat(values[1]);
|
|
8135
|
+
return Math.round(Math.atan2(b, a) * (180 / Math.PI));
|
|
8136
|
+
}
|
|
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
|
+
|
|
8201
|
+
function getVisibleShadow(shadowStr, scale) {
|
|
8202
|
+
if (!shadowStr || shadowStr === 'none') return null;
|
|
8203
|
+
const shadows = shadowStr.split(/,(?![^()]*\))/);
|
|
8204
|
+
for (let s of shadows) {
|
|
8205
|
+
s = s.trim();
|
|
8206
|
+
if (s.startsWith('rgba(0, 0, 0, 0)')) continue;
|
|
8207
|
+
const match = s.match(
|
|
8208
|
+
/(rgba?\([^)]+\)|#[0-9a-fA-F]+)\s+(-?[\d.]+)px\s+(-?[\d.]+)px\s+([\d.]+)px/
|
|
8209
|
+
);
|
|
8210
|
+
if (match) {
|
|
8211
|
+
const colorStr = match[1];
|
|
8212
|
+
const x = parseFloat(match[2]);
|
|
8213
|
+
const y = parseFloat(match[3]);
|
|
8214
|
+
const blur = parseFloat(match[4]);
|
|
8215
|
+
const distance = Math.sqrt(x * x + y * y);
|
|
8216
|
+
let angle = Math.atan2(y, x) * (180 / Math.PI);
|
|
8217
|
+
if (angle < 0) angle += 360;
|
|
8218
|
+
const colorObj = parseColor(colorStr);
|
|
8219
|
+
return {
|
|
8220
|
+
type: 'outer',
|
|
8221
|
+
angle: angle,
|
|
8222
|
+
blur: blur * 0.75 * scale,
|
|
8223
|
+
offset: distance * 0.75 * scale,
|
|
8224
|
+
color: colorObj.hex || '000000',
|
|
8225
|
+
opacity: colorObj.opacity,
|
|
8226
|
+
};
|
|
8227
|
+
}
|
|
8228
|
+
}
|
|
8229
|
+
return null;
|
|
8230
|
+
}
|
|
8231
|
+
|
|
8232
|
+
function generateGradientSVG(w, h, bgString, radius, border) {
|
|
8233
|
+
try {
|
|
8234
|
+
const match = bgString.match(/linear-gradient\((.*)\)/);
|
|
8235
|
+
if (!match) return null;
|
|
8236
|
+
const content = match[1];
|
|
8237
|
+
const parts = content.split(/,(?![^()]*\))/).map((p) => p.trim());
|
|
8238
|
+
|
|
8239
|
+
let x1 = '0%',
|
|
8240
|
+
y1 = '0%',
|
|
8241
|
+
x2 = '0%',
|
|
8242
|
+
y2 = '100%';
|
|
8243
|
+
let stopsStartIdx = 0;
|
|
8244
|
+
if (parts[0].includes('to right')) {
|
|
8245
|
+
x1 = '0%';
|
|
8246
|
+
x2 = '100%';
|
|
8247
|
+
y2 = '0%';
|
|
8248
|
+
stopsStartIdx = 1;
|
|
8249
|
+
} else if (parts[0].includes('to left')) {
|
|
8250
|
+
x1 = '100%';
|
|
8251
|
+
x2 = '0%';
|
|
8252
|
+
y2 = '0%';
|
|
8253
|
+
stopsStartIdx = 1;
|
|
8254
|
+
} else if (parts[0].includes('to top')) {
|
|
8255
|
+
y1 = '100%';
|
|
8256
|
+
y2 = '0%';
|
|
8257
|
+
stopsStartIdx = 1;
|
|
8258
|
+
} else if (parts[0].includes('to bottom')) {
|
|
8259
|
+
y1 = '0%';
|
|
8260
|
+
y2 = '100%';
|
|
8261
|
+
stopsStartIdx = 1;
|
|
8262
|
+
}
|
|
8263
|
+
|
|
8264
|
+
let stopsXML = '';
|
|
8265
|
+
const stopParts = parts.slice(stopsStartIdx);
|
|
8266
|
+
stopParts.forEach((part, idx) => {
|
|
8267
|
+
let color = part;
|
|
8268
|
+
let offset = Math.round((idx / (stopParts.length - 1)) * 100) + '%';
|
|
8269
|
+
const posMatch = part.match(/(.*?)\s+(\d+(\.\d+)?%?)$/);
|
|
8270
|
+
if (posMatch) {
|
|
8271
|
+
color = posMatch[1];
|
|
8272
|
+
offset = posMatch[2];
|
|
8273
|
+
}
|
|
8274
|
+
let opacity = 1;
|
|
8275
|
+
if (color.includes('rgba')) {
|
|
8276
|
+
const rgba = color.match(/[\d.]+/g);
|
|
8277
|
+
if (rgba && rgba.length > 3) {
|
|
8278
|
+
opacity = rgba[3];
|
|
8279
|
+
color = `rgb(${rgba[0]},${rgba[1]},${rgba[2]})`;
|
|
8280
|
+
}
|
|
8281
|
+
}
|
|
8282
|
+
stopsXML += `<stop offset="${offset}" stop-color="${color}" stop-opacity="${opacity}"/>`;
|
|
8283
|
+
});
|
|
8284
|
+
|
|
8285
|
+
let strokeAttr = '';
|
|
8286
|
+
if (border) {
|
|
8287
|
+
strokeAttr = `stroke="#${border.color}" stroke-width="${border.width}"`;
|
|
8288
|
+
}
|
|
8289
|
+
|
|
8290
|
+
const svg = `
|
|
8291
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">
|
|
8292
|
+
<defs><linearGradient id="grad" x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}">${stopsXML}</linearGradient></defs>
|
|
8293
|
+
<rect x="0" y="0" width="${w}" height="${h}" rx="${radius}" ry="${radius}" fill="url(#grad)" ${strokeAttr} />
|
|
8294
|
+
</svg>`;
|
|
8295
|
+
return 'data:image/svg+xml;base64,' + btoa(svg);
|
|
8296
|
+
} catch {
|
|
8297
|
+
return null;
|
|
8298
|
+
}
|
|
8299
|
+
}
|
|
8300
|
+
|
|
8301
|
+
function generateBlurredSVG(w, h, color, radius, blurPx) {
|
|
8302
|
+
const padding = blurPx * 3;
|
|
8303
|
+
const fullW = w + padding * 2;
|
|
8304
|
+
const fullH = h + padding * 2;
|
|
8305
|
+
const x = padding;
|
|
8306
|
+
const y = padding;
|
|
8307
|
+
let shapeTag = '';
|
|
8308
|
+
const isCircle = radius >= Math.min(w, h) / 2 - 1 && Math.abs(w - h) < 2;
|
|
8309
|
+
|
|
8310
|
+
if (isCircle) {
|
|
8311
|
+
const cx = x + w / 2;
|
|
8312
|
+
const cy = y + h / 2;
|
|
8313
|
+
const rx = w / 2;
|
|
8314
|
+
const ry = h / 2;
|
|
8315
|
+
shapeTag = `<ellipse cx="${cx}" cy="${cy}" rx="${rx}" ry="${ry}" fill="#${color}" filter="url(#f1)" />`;
|
|
8316
|
+
} else {
|
|
8317
|
+
shapeTag = `<rect x="${x}" y="${y}" width="${w}" height="${h}" rx="${radius}" ry="${radius}" fill="#${color}" filter="url(#f1)" />`;
|
|
8318
|
+
}
|
|
8319
|
+
|
|
8320
|
+
const svg = `
|
|
8321
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="${fullW}" height="${fullH}" viewBox="0 0 ${fullW} ${fullH}">
|
|
8322
|
+
<defs>
|
|
8323
|
+
<filter id="f1" x="-50%" y="-50%" width="200%" height="200%">
|
|
8324
|
+
<feGaussianBlur in="SourceGraphic" stdDeviation="${blurPx}" />
|
|
8325
|
+
</filter>
|
|
8326
|
+
</defs>
|
|
8327
|
+
${shapeTag}
|
|
8328
|
+
</svg>`;
|
|
8329
|
+
|
|
8330
|
+
return {
|
|
8331
|
+
data: 'data:image/svg+xml;base64,' + btoa(svg),
|
|
8332
|
+
padding: padding,
|
|
8333
|
+
};
|
|
8325
8334
|
}
|
|
8326
8335
|
|
|
8327
8336
|
// src/image-processor.js
|
|
@@ -8404,642 +8413,740 @@ async function getProcessedImage(src, targetW, targetH, radius) {
|
|
|
8404
8413
|
});
|
|
8405
8414
|
}
|
|
8406
8415
|
|
|
8407
|
-
// src/index.js
|
|
8408
|
-
|
|
8409
|
-
// Normalize import
|
|
8410
|
-
const PptxGenJS = PptxGenJSImport__namespace?.default ?? PptxGenJSImport__namespace;
|
|
8411
|
-
|
|
8412
|
-
const PPI = 96;
|
|
8413
|
-
const PX_TO_INCH = 1 / PPI;
|
|
8414
|
-
|
|
8415
|
-
/**
|
|
8416
|
-
* Main export function. Accepts single element or an array.
|
|
8417
|
-
* @param {HTMLElement | string | Array<HTMLElement | string>} target - The root element(s) to convert.
|
|
8418
|
-
* @param {Object} options - { fileName: string }
|
|
8419
|
-
*/
|
|
8420
|
-
async function exportToPptx(target, options = {}) {
|
|
8421
|
-
const resolvePptxConstructor = (pkg) => {
|
|
8422
|
-
if (!pkg) return null;
|
|
8423
|
-
if (typeof pkg === 'function') return pkg;
|
|
8424
|
-
if (pkg && typeof pkg.default === 'function') return pkg.default;
|
|
8425
|
-
if (pkg && typeof pkg.PptxGenJS === 'function') return pkg.PptxGenJS;
|
|
8426
|
-
if (pkg && pkg.PptxGenJS && typeof pkg.PptxGenJS.default === 'function')
|
|
8427
|
-
return pkg.PptxGenJS.default;
|
|
8428
|
-
return null;
|
|
8429
|
-
};
|
|
8430
|
-
|
|
8431
|
-
const PptxConstructor = resolvePptxConstructor(PptxGenJS);
|
|
8432
|
-
if (!PptxConstructor) throw new Error('PptxGenJS constructor not found.');
|
|
8433
|
-
const pptx = new PptxConstructor();
|
|
8434
|
-
pptx.layout = 'LAYOUT_16x9';
|
|
8435
|
-
|
|
8436
|
-
const elements = Array.isArray(target) ? target : [target];
|
|
8437
|
-
|
|
8438
|
-
for (const el of elements) {
|
|
8439
|
-
const root = typeof el === 'string' ? document.querySelector(el) : el;
|
|
8440
|
-
if (!root) {
|
|
8441
|
-
console.warn('Element not found, skipping slide:', el);
|
|
8442
|
-
continue;
|
|
8443
|
-
}
|
|
8444
|
-
const slide = pptx.addSlide();
|
|
8445
|
-
await processSlide(root, slide, pptx);
|
|
8446
|
-
}
|
|
8447
|
-
|
|
8448
|
-
const fileName = options.fileName || 'export.pptx';
|
|
8449
|
-
pptx.writeFile({ fileName });
|
|
8450
|
-
}
|
|
8451
|
-
|
|
8452
|
-
/**
|
|
8453
|
-
* Worker function to process a single DOM element into a single PPTX slide.
|
|
8454
|
-
* @param {HTMLElement} root - The root element for this slide.
|
|
8455
|
-
* @param {PptxGenJS.Slide} slide - The PPTX slide object to add content to.
|
|
8456
|
-
* @param {PptxGenJS} pptx - The main PPTX instance.
|
|
8457
|
-
*/
|
|
8458
|
-
async function processSlide(root, slide, pptx) {
|
|
8459
|
-
const rootRect = root.getBoundingClientRect();
|
|
8460
|
-
const PPTX_WIDTH_IN = 10;
|
|
8461
|
-
const PPTX_HEIGHT_IN = 5.625;
|
|
8462
|
-
|
|
8463
|
-
const contentWidthIn = rootRect.width * PX_TO_INCH;
|
|
8464
|
-
const contentHeightIn = rootRect.height * PX_TO_INCH;
|
|
8465
|
-
const scale = Math.min(PPTX_WIDTH_IN / contentWidthIn, PPTX_HEIGHT_IN / contentHeightIn);
|
|
8466
|
-
|
|
8467
|
-
const layoutConfig = {
|
|
8468
|
-
rootX: rootRect.x,
|
|
8469
|
-
rootY: rootRect.y,
|
|
8470
|
-
scale: scale,
|
|
8471
|
-
offX: (PPTX_WIDTH_IN - contentWidthIn * scale) / 2,
|
|
8472
|
-
offY: (PPTX_HEIGHT_IN - contentHeightIn * scale) / 2,
|
|
8473
|
-
};
|
|
8474
|
-
|
|
8475
|
-
const renderQueue = [];
|
|
8476
|
-
|
|
8477
|
-
|
|
8478
|
-
|
|
8479
|
-
|
|
8480
|
-
|
|
8481
|
-
|
|
8482
|
-
|
|
8483
|
-
|
|
8484
|
-
|
|
8485
|
-
|
|
8486
|
-
|
|
8487
|
-
|
|
8488
|
-
|
|
8489
|
-
|
|
8490
|
-
|
|
8491
|
-
|
|
8492
|
-
|
|
8493
|
-
|
|
8494
|
-
|
|
8495
|
-
|
|
8496
|
-
|
|
8497
|
-
|
|
8498
|
-
|
|
8499
|
-
|
|
8500
|
-
}
|
|
8501
|
-
|
|
8502
|
-
|
|
8503
|
-
|
|
8504
|
-
|
|
8505
|
-
|
|
8506
|
-
|
|
8507
|
-
|
|
8508
|
-
|
|
8509
|
-
|
|
8510
|
-
|
|
8511
|
-
|
|
8512
|
-
|
|
8513
|
-
|
|
8514
|
-
|
|
8515
|
-
|
|
8516
|
-
|
|
8517
|
-
|
|
8518
|
-
|
|
8519
|
-
|
|
8520
|
-
|
|
8521
|
-
.
|
|
8522
|
-
|
|
8523
|
-
|
|
8524
|
-
|
|
8525
|
-
|
|
8526
|
-
|
|
8527
|
-
|
|
8528
|
-
|
|
8529
|
-
|
|
8530
|
-
|
|
8531
|
-
|
|
8532
|
-
|
|
8533
|
-
|
|
8534
|
-
|
|
8535
|
-
|
|
8536
|
-
|
|
8537
|
-
|
|
8538
|
-
|
|
8539
|
-
|
|
8540
|
-
|
|
8541
|
-
|
|
8542
|
-
|
|
8543
|
-
|
|
8544
|
-
|
|
8545
|
-
|
|
8546
|
-
|
|
8547
|
-
|
|
8548
|
-
|
|
8549
|
-
|
|
8550
|
-
|
|
8551
|
-
|
|
8552
|
-
|
|
8553
|
-
|
|
8554
|
-
|
|
8555
|
-
|
|
8556
|
-
|
|
8557
|
-
|
|
8558
|
-
|
|
8559
|
-
|
|
8560
|
-
|
|
8561
|
-
|
|
8562
|
-
|
|
8563
|
-
|
|
8564
|
-
|
|
8565
|
-
|
|
8566
|
-
|
|
8567
|
-
|
|
8568
|
-
|
|
8569
|
-
|
|
8570
|
-
|
|
8571
|
-
|
|
8572
|
-
|
|
8573
|
-
}
|
|
8574
|
-
|
|
8575
|
-
|
|
8576
|
-
|
|
8577
|
-
|
|
8578
|
-
|
|
8579
|
-
|
|
8580
|
-
|
|
8581
|
-
|
|
8582
|
-
|
|
8583
|
-
|
|
8584
|
-
|
|
8585
|
-
|
|
8586
|
-
|
|
8587
|
-
|
|
8588
|
-
|
|
8589
|
-
|
|
8590
|
-
|
|
8591
|
-
|
|
8592
|
-
|
|
8593
|
-
|
|
8594
|
-
|
|
8595
|
-
|
|
8596
|
-
|
|
8597
|
-
|
|
8598
|
-
|
|
8599
|
-
|
|
8600
|
-
|
|
8601
|
-
|
|
8602
|
-
|
|
8603
|
-
|
|
8604
|
-
|
|
8605
|
-
|
|
8606
|
-
|
|
8607
|
-
|
|
8608
|
-
|
|
8609
|
-
|
|
8610
|
-
|
|
8611
|
-
|
|
8612
|
-
|
|
8613
|
-
|
|
8614
|
-
|
|
8615
|
-
|
|
8616
|
-
|
|
8617
|
-
|
|
8618
|
-
|
|
8619
|
-
|
|
8620
|
-
|
|
8621
|
-
|
|
8622
|
-
|
|
8623
|
-
|
|
8624
|
-
|
|
8625
|
-
|
|
8626
|
-
|
|
8627
|
-
|
|
8628
|
-
|
|
8629
|
-
|
|
8630
|
-
|
|
8631
|
-
|
|
8632
|
-
|
|
8633
|
-
|
|
8634
|
-
|
|
8635
|
-
|
|
8636
|
-
|
|
8637
|
-
|
|
8638
|
-
|
|
8639
|
-
|
|
8640
|
-
|
|
8641
|
-
|
|
8642
|
-
|
|
8643
|
-
|
|
8644
|
-
|
|
8645
|
-
|
|
8646
|
-
|
|
8647
|
-
|
|
8648
|
-
|
|
8649
|
-
|
|
8650
|
-
|
|
8651
|
-
|
|
8652
|
-
|
|
8653
|
-
|
|
8654
|
-
|
|
8655
|
-
|
|
8656
|
-
|
|
8657
|
-
|
|
8658
|
-
|
|
8659
|
-
|
|
8660
|
-
|
|
8661
|
-
|
|
8662
|
-
|
|
8663
|
-
|
|
8664
|
-
|
|
8665
|
-
|
|
8666
|
-
|
|
8667
|
-
|
|
8668
|
-
|
|
8669
|
-
|
|
8670
|
-
|
|
8671
|
-
|
|
8672
|
-
|
|
8673
|
-
|
|
8674
|
-
|
|
8675
|
-
|
|
8676
|
-
|
|
8677
|
-
|
|
8678
|
-
|
|
8679
|
-
|
|
8680
|
-
|
|
8681
|
-
|
|
8682
|
-
|
|
8683
|
-
|
|
8684
|
-
|
|
8685
|
-
|
|
8686
|
-
|
|
8687
|
-
|
|
8688
|
-
|
|
8689
|
-
|
|
8690
|
-
|
|
8691
|
-
|
|
8692
|
-
|
|
8693
|
-
|
|
8694
|
-
|
|
8695
|
-
|
|
8696
|
-
|
|
8697
|
-
|
|
8698
|
-
|
|
8699
|
-
|
|
8700
|
-
const
|
|
8701
|
-
|
|
8702
|
-
|
|
8703
|
-
|
|
8704
|
-
|
|
8705
|
-
|
|
8706
|
-
|
|
8707
|
-
|
|
8708
|
-
|
|
8709
|
-
|
|
8710
|
-
|
|
8711
|
-
|
|
8712
|
-
|
|
8713
|
-
|
|
8714
|
-
|
|
8715
|
-
|
|
8716
|
-
|
|
8717
|
-
|
|
8718
|
-
|
|
8719
|
-
|
|
8720
|
-
|
|
8721
|
-
|
|
8722
|
-
|
|
8723
|
-
|
|
8724
|
-
|
|
8725
|
-
|
|
8726
|
-
|
|
8727
|
-
|
|
8728
|
-
|
|
8729
|
-
|
|
8730
|
-
|
|
8731
|
-
const
|
|
8732
|
-
|
|
8733
|
-
|
|
8734
|
-
|
|
8735
|
-
|
|
8736
|
-
|
|
8737
|
-
|
|
8738
|
-
|
|
8739
|
-
}
|
|
8740
|
-
|
|
8741
|
-
|
|
8742
|
-
|
|
8743
|
-
|
|
8744
|
-
|
|
8745
|
-
|
|
8746
|
-
|
|
8747
|
-
|
|
8748
|
-
|
|
8749
|
-
|
|
8750
|
-
|
|
8751
|
-
|
|
8752
|
-
|
|
8753
|
-
|
|
8754
|
-
|
|
8755
|
-
|
|
8756
|
-
|
|
8757
|
-
|
|
8758
|
-
|
|
8759
|
-
|
|
8760
|
-
|
|
8761
|
-
|
|
8762
|
-
|
|
8763
|
-
|
|
8764
|
-
|
|
8765
|
-
|
|
8766
|
-
|
|
8767
|
-
|
|
8768
|
-
|
|
8769
|
-
|
|
8770
|
-
|
|
8771
|
-
|
|
8772
|
-
|
|
8773
|
-
|
|
8774
|
-
|
|
8775
|
-
|
|
8776
|
-
|
|
8777
|
-
|
|
8778
|
-
|
|
8779
|
-
|
|
8780
|
-
|
|
8781
|
-
|
|
8782
|
-
|
|
8783
|
-
|
|
8784
|
-
|
|
8785
|
-
|
|
8786
|
-
|
|
8787
|
-
|
|
8788
|
-
|
|
8789
|
-
|
|
8790
|
-
|
|
8791
|
-
|
|
8792
|
-
|
|
8793
|
-
|
|
8794
|
-
|
|
8795
|
-
|
|
8796
|
-
|
|
8797
|
-
|
|
8798
|
-
|
|
8799
|
-
|
|
8800
|
-
|
|
8801
|
-
|
|
8802
|
-
|
|
8803
|
-
|
|
8804
|
-
|
|
8805
|
-
|
|
8806
|
-
|
|
8807
|
-
|
|
8808
|
-
|
|
8809
|
-
|
|
8810
|
-
|
|
8811
|
-
|
|
8812
|
-
|
|
8813
|
-
|
|
8814
|
-
|
|
8815
|
-
|
|
8816
|
-
|
|
8817
|
-
|
|
8818
|
-
|
|
8819
|
-
|
|
8820
|
-
|
|
8821
|
-
|
|
8822
|
-
|
|
8823
|
-
|
|
8824
|
-
|
|
8825
|
-
|
|
8826
|
-
|
|
8827
|
-
|
|
8828
|
-
|
|
8829
|
-
|
|
8830
|
-
|
|
8831
|
-
|
|
8832
|
-
|
|
8833
|
-
|
|
8834
|
-
|
|
8835
|
-
|
|
8836
|
-
|
|
8837
|
-
|
|
8838
|
-
|
|
8839
|
-
|
|
8840
|
-
|
|
8841
|
-
|
|
8842
|
-
|
|
8843
|
-
|
|
8844
|
-
|
|
8845
|
-
|
|
8846
|
-
|
|
8847
|
-
|
|
8848
|
-
|
|
8849
|
-
|
|
8850
|
-
|
|
8851
|
-
|
|
8852
|
-
|
|
8853
|
-
|
|
8854
|
-
|
|
8855
|
-
|
|
8856
|
-
|
|
8857
|
-
|
|
8858
|
-
|
|
8859
|
-
|
|
8860
|
-
)
|
|
8861
|
-
|
|
8862
|
-
|
|
8863
|
-
const
|
|
8864
|
-
|
|
8865
|
-
if (
|
|
8866
|
-
const
|
|
8867
|
-
|
|
8868
|
-
|
|
8869
|
-
|
|
8870
|
-
|
|
8871
|
-
|
|
8872
|
-
|
|
8873
|
-
|
|
8874
|
-
|
|
8875
|
-
|
|
8876
|
-
|
|
8877
|
-
|
|
8878
|
-
|
|
8879
|
-
|
|
8880
|
-
|
|
8881
|
-
|
|
8882
|
-
|
|
8883
|
-
|
|
8884
|
-
|
|
8885
|
-
|
|
8886
|
-
|
|
8887
|
-
|
|
8888
|
-
|
|
8889
|
-
|
|
8890
|
-
|
|
8891
|
-
|
|
8892
|
-
|
|
8893
|
-
|
|
8894
|
-
|
|
8895
|
-
|
|
8896
|
-
|
|
8897
|
-
|
|
8898
|
-
|
|
8899
|
-
|
|
8900
|
-
|
|
8901
|
-
|
|
8902
|
-
|
|
8903
|
-
|
|
8904
|
-
|
|
8905
|
-
|
|
8906
|
-
|
|
8907
|
-
|
|
8908
|
-
|
|
8909
|
-
|
|
8910
|
-
|
|
8911
|
-
|
|
8912
|
-
|
|
8913
|
-
|
|
8914
|
-
|
|
8915
|
-
|
|
8916
|
-
|
|
8917
|
-
|
|
8918
|
-
|
|
8919
|
-
|
|
8920
|
-
|
|
8921
|
-
|
|
8922
|
-
|
|
8923
|
-
|
|
8924
|
-
|
|
8925
|
-
|
|
8926
|
-
|
|
8927
|
-
|
|
8928
|
-
|
|
8929
|
-
|
|
8930
|
-
|
|
8931
|
-
|
|
8932
|
-
|
|
8933
|
-
|
|
8934
|
-
|
|
8935
|
-
|
|
8936
|
-
|
|
8937
|
-
|
|
8938
|
-
|
|
8939
|
-
|
|
8940
|
-
|
|
8941
|
-
|
|
8942
|
-
|
|
8943
|
-
|
|
8944
|
-
|
|
8945
|
-
|
|
8946
|
-
|
|
8947
|
-
|
|
8948
|
-
|
|
8949
|
-
|
|
8950
|
-
|
|
8951
|
-
|
|
8952
|
-
|
|
8953
|
-
|
|
8954
|
-
|
|
8955
|
-
|
|
8956
|
-
|
|
8957
|
-
|
|
8958
|
-
|
|
8959
|
-
|
|
8960
|
-
|
|
8961
|
-
|
|
8962
|
-
|
|
8963
|
-
|
|
8964
|
-
|
|
8965
|
-
|
|
8966
|
-
|
|
8967
|
-
|
|
8968
|
-
|
|
8969
|
-
|
|
8970
|
-
|
|
8971
|
-
|
|
8972
|
-
|
|
8973
|
-
|
|
8974
|
-
|
|
8975
|
-
|
|
8976
|
-
|
|
8977
|
-
|
|
8978
|
-
|
|
8979
|
-
|
|
8980
|
-
|
|
8981
|
-
|
|
8982
|
-
|
|
8983
|
-
|
|
8984
|
-
|
|
8985
|
-
|
|
8986
|
-
|
|
8987
|
-
|
|
8988
|
-
|
|
8989
|
-
|
|
8990
|
-
|
|
8991
|
-
|
|
8992
|
-
|
|
8993
|
-
|
|
8994
|
-
|
|
8995
|
-
|
|
8996
|
-
|
|
8997
|
-
|
|
8998
|
-
|
|
8999
|
-
|
|
9000
|
-
|
|
9001
|
-
|
|
9002
|
-
|
|
9003
|
-
|
|
9004
|
-
|
|
9005
|
-
|
|
9006
|
-
|
|
9007
|
-
|
|
9008
|
-
|
|
9009
|
-
|
|
9010
|
-
|
|
9011
|
-
|
|
9012
|
-
|
|
9013
|
-
|
|
9014
|
-
|
|
9015
|
-
|
|
9016
|
-
|
|
9017
|
-
|
|
9018
|
-
|
|
9019
|
-
|
|
9020
|
-
|
|
9021
|
-
|
|
9022
|
-
|
|
9023
|
-
|
|
9024
|
-
|
|
9025
|
-
|
|
9026
|
-
|
|
9027
|
-
|
|
9028
|
-
|
|
9029
|
-
|
|
9030
|
-
|
|
9031
|
-
|
|
9032
|
-
|
|
9033
|
-
|
|
9034
|
-
|
|
9035
|
-
|
|
9036
|
-
|
|
9037
|
-
|
|
9038
|
-
|
|
9039
|
-
|
|
9040
|
-
|
|
9041
|
-
|
|
9042
|
-
|
|
8416
|
+
// src/index.js
|
|
8417
|
+
|
|
8418
|
+
// Normalize import
|
|
8419
|
+
const PptxGenJS = PptxGenJSImport__namespace?.default ?? PptxGenJSImport__namespace;
|
|
8420
|
+
|
|
8421
|
+
const PPI = 96;
|
|
8422
|
+
const PX_TO_INCH = 1 / PPI;
|
|
8423
|
+
|
|
8424
|
+
/**
|
|
8425
|
+
* Main export function. Accepts single element or an array.
|
|
8426
|
+
* @param {HTMLElement | string | Array<HTMLElement | string>} target - The root element(s) to convert.
|
|
8427
|
+
* @param {Object} options - { fileName: string }
|
|
8428
|
+
*/
|
|
8429
|
+
async function exportToPptx(target, options = {}) {
|
|
8430
|
+
const resolvePptxConstructor = (pkg) => {
|
|
8431
|
+
if (!pkg) return null;
|
|
8432
|
+
if (typeof pkg === 'function') return pkg;
|
|
8433
|
+
if (pkg && typeof pkg.default === 'function') return pkg.default;
|
|
8434
|
+
if (pkg && typeof pkg.PptxGenJS === 'function') return pkg.PptxGenJS;
|
|
8435
|
+
if (pkg && pkg.PptxGenJS && typeof pkg.PptxGenJS.default === 'function')
|
|
8436
|
+
return pkg.PptxGenJS.default;
|
|
8437
|
+
return null;
|
|
8438
|
+
};
|
|
8439
|
+
|
|
8440
|
+
const PptxConstructor = resolvePptxConstructor(PptxGenJS);
|
|
8441
|
+
if (!PptxConstructor) throw new Error('PptxGenJS constructor not found.');
|
|
8442
|
+
const pptx = new PptxConstructor();
|
|
8443
|
+
pptx.layout = 'LAYOUT_16x9';
|
|
8444
|
+
|
|
8445
|
+
const elements = Array.isArray(target) ? target : [target];
|
|
8446
|
+
|
|
8447
|
+
for (const el of elements) {
|
|
8448
|
+
const root = typeof el === 'string' ? document.querySelector(el) : el;
|
|
8449
|
+
if (!root) {
|
|
8450
|
+
console.warn('Element not found, skipping slide:', el);
|
|
8451
|
+
continue;
|
|
8452
|
+
}
|
|
8453
|
+
const slide = pptx.addSlide();
|
|
8454
|
+
await processSlide(root, slide, pptx);
|
|
8455
|
+
}
|
|
8456
|
+
|
|
8457
|
+
const fileName = options.fileName || 'export.pptx';
|
|
8458
|
+
pptx.writeFile({ fileName });
|
|
8459
|
+
}
|
|
8460
|
+
|
|
8461
|
+
/**
|
|
8462
|
+
* Worker function to process a single DOM element into a single PPTX slide.
|
|
8463
|
+
* @param {HTMLElement} root - The root element for this slide.
|
|
8464
|
+
* @param {PptxGenJS.Slide} slide - The PPTX slide object to add content to.
|
|
8465
|
+
* @param {PptxGenJS} pptx - The main PPTX instance.
|
|
8466
|
+
*/
|
|
8467
|
+
async function processSlide(root, slide, pptx) {
|
|
8468
|
+
const rootRect = root.getBoundingClientRect();
|
|
8469
|
+
const PPTX_WIDTH_IN = 10;
|
|
8470
|
+
const PPTX_HEIGHT_IN = 5.625;
|
|
8471
|
+
|
|
8472
|
+
const contentWidthIn = rootRect.width * PX_TO_INCH;
|
|
8473
|
+
const contentHeightIn = rootRect.height * PX_TO_INCH;
|
|
8474
|
+
const scale = Math.min(PPTX_WIDTH_IN / contentWidthIn, PPTX_HEIGHT_IN / contentHeightIn);
|
|
8475
|
+
|
|
8476
|
+
const layoutConfig = {
|
|
8477
|
+
rootX: rootRect.x,
|
|
8478
|
+
rootY: rootRect.y,
|
|
8479
|
+
scale: scale,
|
|
8480
|
+
offX: (PPTX_WIDTH_IN - contentWidthIn * scale) / 2,
|
|
8481
|
+
offY: (PPTX_HEIGHT_IN - contentHeightIn * scale) / 2,
|
|
8482
|
+
};
|
|
8483
|
+
|
|
8484
|
+
const renderQueue = [];
|
|
8485
|
+
const asyncTasks = []; // Queue for heavy operations (Images, Canvas)
|
|
8486
|
+
let domOrderCounter = 0;
|
|
8487
|
+
|
|
8488
|
+
// Sync Traversal Function
|
|
8489
|
+
function collect(node, parentZIndex) {
|
|
8490
|
+
const order = domOrderCounter++;
|
|
8491
|
+
|
|
8492
|
+
let currentZ = parentZIndex;
|
|
8493
|
+
let nodeStyle = null;
|
|
8494
|
+
const nodeType = node.nodeType;
|
|
8495
|
+
|
|
8496
|
+
if (nodeType === 1) {
|
|
8497
|
+
nodeStyle = window.getComputedStyle(node);
|
|
8498
|
+
// Optimization: Skip completely hidden elements immediately
|
|
8499
|
+
if (
|
|
8500
|
+
nodeStyle.display === 'none' ||
|
|
8501
|
+
nodeStyle.visibility === 'hidden' ||
|
|
8502
|
+
nodeStyle.opacity === '0'
|
|
8503
|
+
) {
|
|
8504
|
+
return;
|
|
8505
|
+
}
|
|
8506
|
+
if (nodeStyle.zIndex !== 'auto') {
|
|
8507
|
+
currentZ = parseInt(nodeStyle.zIndex);
|
|
8508
|
+
}
|
|
8509
|
+
}
|
|
8510
|
+
|
|
8511
|
+
// Prepare the item. If it needs async work, it returns a 'job'
|
|
8512
|
+
const result = prepareRenderItem(
|
|
8513
|
+
node,
|
|
8514
|
+
{ ...layoutConfig, root },
|
|
8515
|
+
order,
|
|
8516
|
+
pptx,
|
|
8517
|
+
currentZ,
|
|
8518
|
+
nodeStyle
|
|
8519
|
+
);
|
|
8520
|
+
|
|
8521
|
+
if (result) {
|
|
8522
|
+
if (result.items) {
|
|
8523
|
+
// Push items immediately to queue (data might be missing but filled later)
|
|
8524
|
+
renderQueue.push(...result.items);
|
|
8525
|
+
}
|
|
8526
|
+
if (result.job) {
|
|
8527
|
+
// Push the promise-returning function to the task list
|
|
8528
|
+
asyncTasks.push(result.job);
|
|
8529
|
+
}
|
|
8530
|
+
if (result.stopRecursion) return;
|
|
8531
|
+
}
|
|
8532
|
+
|
|
8533
|
+
// Recurse children synchronously
|
|
8534
|
+
const childNodes = node.childNodes;
|
|
8535
|
+
for (let i = 0; i < childNodes.length; i++) {
|
|
8536
|
+
collect(childNodes[i], currentZ);
|
|
8537
|
+
}
|
|
8538
|
+
}
|
|
8539
|
+
|
|
8540
|
+
// 1. Traverse and build the structure (Fast)
|
|
8541
|
+
collect(root, 0);
|
|
8542
|
+
|
|
8543
|
+
// 2. Execute all heavy tasks in parallel (Fast)
|
|
8544
|
+
if (asyncTasks.length > 0) {
|
|
8545
|
+
await Promise.all(asyncTasks.map((task) => task()));
|
|
8546
|
+
}
|
|
8547
|
+
|
|
8548
|
+
// 3. Cleanup and Sort
|
|
8549
|
+
// Remove items that failed to generate data (marked with skip)
|
|
8550
|
+
const finalQueue = renderQueue.filter(
|
|
8551
|
+
(item) => !item.skip && (item.type !== 'image' || item.options.data)
|
|
8552
|
+
);
|
|
8553
|
+
|
|
8554
|
+
finalQueue.sort((a, b) => {
|
|
8555
|
+
if (a.zIndex !== b.zIndex) return a.zIndex - b.zIndex;
|
|
8556
|
+
return a.domOrder - b.domOrder;
|
|
8557
|
+
});
|
|
8558
|
+
|
|
8559
|
+
// 4. Add to Slide
|
|
8560
|
+
for (const item of finalQueue) {
|
|
8561
|
+
if (item.type === 'shape') slide.addShape(item.shapeType, item.options);
|
|
8562
|
+
if (item.type === 'image') slide.addImage(item.options);
|
|
8563
|
+
if (item.type === 'text') slide.addText(item.textParts, item.options);
|
|
8564
|
+
}
|
|
8565
|
+
}
|
|
8566
|
+
|
|
8567
|
+
/**
|
|
8568
|
+
* Optimized html2canvas wrapper
|
|
8569
|
+
* Now strictly captures the node itself, not the root.
|
|
8570
|
+
*/
|
|
8571
|
+
async function elementToCanvasImage(node, widthPx, heightPx) {
|
|
8572
|
+
return new Promise((resolve) => {
|
|
8573
|
+
const width = Math.max(Math.ceil(widthPx), 1);
|
|
8574
|
+
const height = Math.max(Math.ceil(heightPx), 1);
|
|
8575
|
+
const style = window.getComputedStyle(node);
|
|
8576
|
+
|
|
8577
|
+
// Optimized: Capture ONLY the specific node
|
|
8578
|
+
html2canvas(node, {
|
|
8579
|
+
backgroundColor: null,
|
|
8580
|
+
logging: false,
|
|
8581
|
+
scale: 2, // Slight quality boost
|
|
8582
|
+
})
|
|
8583
|
+
.then((canvas) => {
|
|
8584
|
+
const destCanvas = document.createElement('canvas');
|
|
8585
|
+
destCanvas.width = width;
|
|
8586
|
+
destCanvas.height = height;
|
|
8587
|
+
const ctx = destCanvas.getContext('2d');
|
|
8588
|
+
|
|
8589
|
+
// Draw the captured canvas into our sized canvas
|
|
8590
|
+
// html2canvas might return a larger canvas if scale > 1, so we fit it
|
|
8591
|
+
ctx.drawImage(canvas, 0, 0, canvas.width, canvas.height, 0, 0, width, height);
|
|
8592
|
+
|
|
8593
|
+
// Apply border radius clipping
|
|
8594
|
+
let tl = parseFloat(style.borderTopLeftRadius) || 0;
|
|
8595
|
+
let tr = parseFloat(style.borderTopRightRadius) || 0;
|
|
8596
|
+
let br = parseFloat(style.borderBottomRightRadius) || 0;
|
|
8597
|
+
let bl = parseFloat(style.borderBottomLeftRadius) || 0;
|
|
8598
|
+
|
|
8599
|
+
const f = Math.min(
|
|
8600
|
+
width / (tl + tr) || Infinity,
|
|
8601
|
+
height / (tr + br) || Infinity,
|
|
8602
|
+
width / (br + bl) || Infinity,
|
|
8603
|
+
height / (bl + tl) || Infinity
|
|
8604
|
+
);
|
|
8605
|
+
|
|
8606
|
+
if (f < 1) {
|
|
8607
|
+
tl *= f;
|
|
8608
|
+
tr *= f;
|
|
8609
|
+
br *= f;
|
|
8610
|
+
bl *= f;
|
|
8611
|
+
}
|
|
8612
|
+
|
|
8613
|
+
if (tl + tr + br + bl > 0) {
|
|
8614
|
+
ctx.globalCompositeOperation = 'destination-in';
|
|
8615
|
+
ctx.beginPath();
|
|
8616
|
+
ctx.moveTo(tl, 0);
|
|
8617
|
+
ctx.lineTo(width - tr, 0);
|
|
8618
|
+
ctx.arcTo(width, 0, width, tr, tr);
|
|
8619
|
+
ctx.lineTo(width, height - br);
|
|
8620
|
+
ctx.arcTo(width, height, width - br, height, br);
|
|
8621
|
+
ctx.lineTo(bl, height);
|
|
8622
|
+
ctx.arcTo(0, height, 0, height - bl, bl);
|
|
8623
|
+
ctx.lineTo(0, tl);
|
|
8624
|
+
ctx.arcTo(0, 0, tl, 0, tl);
|
|
8625
|
+
ctx.closePath();
|
|
8626
|
+
ctx.fill();
|
|
8627
|
+
}
|
|
8628
|
+
|
|
8629
|
+
resolve(destCanvas.toDataURL('image/png'));
|
|
8630
|
+
})
|
|
8631
|
+
.catch((e) => {
|
|
8632
|
+
console.warn('Canvas capture failed for node', node, e);
|
|
8633
|
+
resolve(null);
|
|
8634
|
+
});
|
|
8635
|
+
});
|
|
8636
|
+
}
|
|
8637
|
+
|
|
8638
|
+
/**
|
|
8639
|
+
* Replaces createRenderItem.
|
|
8640
|
+
* Returns { items: [], job: () => Promise, stopRecursion: boolean }
|
|
8641
|
+
*/
|
|
8642
|
+
function prepareRenderItem(node, config, domOrder, pptx, effectiveZIndex, computedStyle) {
|
|
8643
|
+
// 1. Text Node Handling
|
|
8644
|
+
if (node.nodeType === 3) {
|
|
8645
|
+
const textContent = node.nodeValue.trim();
|
|
8646
|
+
if (!textContent) return null;
|
|
8647
|
+
|
|
8648
|
+
const parent = node.parentElement;
|
|
8649
|
+
if (!parent) return null;
|
|
8650
|
+
|
|
8651
|
+
if (isTextContainer(parent)) return null; // Parent handles it
|
|
8652
|
+
|
|
8653
|
+
const range = document.createRange();
|
|
8654
|
+
range.selectNode(node);
|
|
8655
|
+
const rect = range.getBoundingClientRect();
|
|
8656
|
+
range.detach();
|
|
8657
|
+
|
|
8658
|
+
const style = window.getComputedStyle(parent);
|
|
8659
|
+
const widthPx = rect.width;
|
|
8660
|
+
const heightPx = rect.height;
|
|
8661
|
+
const unrotatedW = widthPx * PX_TO_INCH * config.scale;
|
|
8662
|
+
const unrotatedH = heightPx * PX_TO_INCH * config.scale;
|
|
8663
|
+
|
|
8664
|
+
const x = config.offX + (rect.left - config.rootX) * PX_TO_INCH * config.scale;
|
|
8665
|
+
const y = config.offY + (rect.top - config.rootY) * PX_TO_INCH * config.scale;
|
|
8666
|
+
|
|
8667
|
+
return {
|
|
8668
|
+
items: [
|
|
8669
|
+
{
|
|
8670
|
+
type: 'text',
|
|
8671
|
+
zIndex: effectiveZIndex,
|
|
8672
|
+
domOrder,
|
|
8673
|
+
textParts: [
|
|
8674
|
+
{
|
|
8675
|
+
text: textContent,
|
|
8676
|
+
options: getTextStyle(style, config.scale),
|
|
8677
|
+
},
|
|
8678
|
+
],
|
|
8679
|
+
options: { x, y, w: unrotatedW, h: unrotatedH, margin: 0, autoFit: false },
|
|
8680
|
+
},
|
|
8681
|
+
],
|
|
8682
|
+
stopRecursion: false,
|
|
8683
|
+
};
|
|
8684
|
+
}
|
|
8685
|
+
|
|
8686
|
+
if (node.nodeType !== 1) return null;
|
|
8687
|
+
const style = computedStyle; // Use pre-computed style
|
|
8688
|
+
|
|
8689
|
+
const rect = node.getBoundingClientRect();
|
|
8690
|
+
if (rect.width < 0.5 || rect.height < 0.5) return null;
|
|
8691
|
+
|
|
8692
|
+
const zIndex = effectiveZIndex;
|
|
8693
|
+
const rotation = getRotation(style.transform);
|
|
8694
|
+
const elementOpacity = parseFloat(style.opacity);
|
|
8695
|
+
const safeOpacity = isNaN(elementOpacity) ? 1 : elementOpacity;
|
|
8696
|
+
|
|
8697
|
+
const widthPx = node.offsetWidth || rect.width;
|
|
8698
|
+
const heightPx = node.offsetHeight || rect.height;
|
|
8699
|
+
const unrotatedW = widthPx * PX_TO_INCH * config.scale;
|
|
8700
|
+
const unrotatedH = heightPx * PX_TO_INCH * config.scale;
|
|
8701
|
+
const centerX = rect.left + rect.width / 2;
|
|
8702
|
+
const centerY = rect.top + rect.height / 2;
|
|
8703
|
+
|
|
8704
|
+
let x = config.offX + (centerX - config.rootX) * PX_TO_INCH * config.scale - unrotatedW / 2;
|
|
8705
|
+
let y = config.offY + (centerY - config.rootY) * PX_TO_INCH * config.scale - unrotatedH / 2;
|
|
8706
|
+
let w = unrotatedW;
|
|
8707
|
+
let h = unrotatedH;
|
|
8708
|
+
|
|
8709
|
+
const items = [];
|
|
8710
|
+
|
|
8711
|
+
// --- ASYNC JOB: SVG Tags ---
|
|
8712
|
+
if (node.nodeName.toUpperCase() === 'SVG') {
|
|
8713
|
+
const item = {
|
|
8714
|
+
type: 'image',
|
|
8715
|
+
zIndex,
|
|
8716
|
+
domOrder,
|
|
8717
|
+
options: { data: null, x, y, w, h, rotate: rotation },
|
|
8718
|
+
};
|
|
8719
|
+
|
|
8720
|
+
const job = async () => {
|
|
8721
|
+
const processed = await svgToPng(node);
|
|
8722
|
+
if (processed) item.options.data = processed;
|
|
8723
|
+
else item.skip = true;
|
|
8724
|
+
};
|
|
8725
|
+
|
|
8726
|
+
return { items: [item], job, stopRecursion: true };
|
|
8727
|
+
}
|
|
8728
|
+
|
|
8729
|
+
// --- ASYNC JOB: IMG Tags ---
|
|
8730
|
+
if (node.tagName === 'IMG') {
|
|
8731
|
+
let radii = {
|
|
8732
|
+
tl: parseFloat(style.borderTopLeftRadius) || 0,
|
|
8733
|
+
tr: parseFloat(style.borderTopRightRadius) || 0,
|
|
8734
|
+
br: parseFloat(style.borderBottomRightRadius) || 0,
|
|
8735
|
+
bl: parseFloat(style.borderBottomLeftRadius) || 0,
|
|
8736
|
+
};
|
|
8737
|
+
|
|
8738
|
+
const hasAnyRadius = radii.tl > 0 || radii.tr > 0 || radii.br > 0 || radii.bl > 0;
|
|
8739
|
+
if (!hasAnyRadius) {
|
|
8740
|
+
const parent = node.parentElement;
|
|
8741
|
+
const parentStyle = window.getComputedStyle(parent);
|
|
8742
|
+
if (parentStyle.overflow !== 'visible') {
|
|
8743
|
+
const pRadii = {
|
|
8744
|
+
tl: parseFloat(parentStyle.borderTopLeftRadius) || 0,
|
|
8745
|
+
tr: parseFloat(parentStyle.borderTopRightRadius) || 0,
|
|
8746
|
+
br: parseFloat(parentStyle.borderBottomRightRadius) || 0,
|
|
8747
|
+
bl: parseFloat(parentStyle.borderBottomLeftRadius) || 0,
|
|
8748
|
+
};
|
|
8749
|
+
const pRect = parent.getBoundingClientRect();
|
|
8750
|
+
if (Math.abs(pRect.width - rect.width) < 5 && Math.abs(pRect.height - rect.height) < 5) {
|
|
8751
|
+
radii = pRadii;
|
|
8752
|
+
}
|
|
8753
|
+
}
|
|
8754
|
+
}
|
|
8755
|
+
|
|
8756
|
+
const item = {
|
|
8757
|
+
type: 'image',
|
|
8758
|
+
zIndex,
|
|
8759
|
+
domOrder,
|
|
8760
|
+
options: { x, y, w, h, rotate: rotation, data: null },
|
|
8761
|
+
};
|
|
8762
|
+
|
|
8763
|
+
const job = async () => {
|
|
8764
|
+
const processed = await getProcessedImage(node.src, widthPx, heightPx, radii);
|
|
8765
|
+
if (processed) item.options.data = processed;
|
|
8766
|
+
else item.skip = true;
|
|
8767
|
+
};
|
|
8768
|
+
|
|
8769
|
+
return { items: [item], job, stopRecursion: true };
|
|
8770
|
+
}
|
|
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
|
+
|
|
8800
|
+
// Radii logic
|
|
8801
|
+
const borderRadiusValue = parseFloat(style.borderRadius) || 0;
|
|
8802
|
+
const borderBottomLeftRadius = parseFloat(style.borderBottomLeftRadius) || 0;
|
|
8803
|
+
const borderBottomRightRadius = parseFloat(style.borderBottomRightRadius) || 0;
|
|
8804
|
+
const borderTopLeftRadius = parseFloat(style.borderTopLeftRadius) || 0;
|
|
8805
|
+
const borderTopRightRadius = parseFloat(style.borderTopRightRadius) || 0;
|
|
8806
|
+
|
|
8807
|
+
const hasPartialBorderRadius =
|
|
8808
|
+
(borderBottomLeftRadius > 0 && borderBottomLeftRadius !== borderRadiusValue) ||
|
|
8809
|
+
(borderBottomRightRadius > 0 && borderBottomRightRadius !== borderRadiusValue) ||
|
|
8810
|
+
(borderTopLeftRadius > 0 && borderTopLeftRadius !== borderRadiusValue) ||
|
|
8811
|
+
(borderTopRightRadius > 0 && borderTopRightRadius !== borderRadiusValue) ||
|
|
8812
|
+
(borderRadiusValue === 0 &&
|
|
8813
|
+
(borderBottomLeftRadius ||
|
|
8814
|
+
borderBottomRightRadius ||
|
|
8815
|
+
borderTopLeftRadius ||
|
|
8816
|
+
borderTopRightRadius));
|
|
8817
|
+
|
|
8818
|
+
// --- ASYNC JOB: Clipped Divs via Canvas ---
|
|
8819
|
+
if (hasPartialBorderRadius && isClippedByParent(node)) {
|
|
8820
|
+
const marginLeft = parseFloat(style.marginLeft) || 0;
|
|
8821
|
+
const marginTop = parseFloat(style.marginTop) || 0;
|
|
8822
|
+
x += marginLeft * PX_TO_INCH * config.scale;
|
|
8823
|
+
y += marginTop * PX_TO_INCH * config.scale;
|
|
8824
|
+
|
|
8825
|
+
const item = {
|
|
8826
|
+
type: 'image',
|
|
8827
|
+
zIndex,
|
|
8828
|
+
domOrder,
|
|
8829
|
+
options: { x, y, w, h, rotate: rotation, data: null },
|
|
8830
|
+
};
|
|
8831
|
+
|
|
8832
|
+
const job = async () => {
|
|
8833
|
+
const canvasImageData = await elementToCanvasImage(node, widthPx, heightPx);
|
|
8834
|
+
if (canvasImageData) item.options.data = canvasImageData;
|
|
8835
|
+
else item.skip = true;
|
|
8836
|
+
};
|
|
8837
|
+
|
|
8838
|
+
return { items: [item], job, stopRecursion: true };
|
|
8839
|
+
}
|
|
8840
|
+
|
|
8841
|
+
// --- SYNC: Standard CSS Extraction ---
|
|
8842
|
+
const bgColorObj = parseColor(style.backgroundColor);
|
|
8843
|
+
const bgClip = style.webkitBackgroundClip || style.backgroundClip;
|
|
8844
|
+
const isBgClipText = bgClip === 'text';
|
|
8845
|
+
const hasGradient =
|
|
8846
|
+
!isBgClipText && style.backgroundImage && style.backgroundImage.includes('linear-gradient');
|
|
8847
|
+
|
|
8848
|
+
const borderColorObj = parseColor(style.borderColor);
|
|
8849
|
+
const borderWidth = parseFloat(style.borderWidth);
|
|
8850
|
+
const hasBorder = borderWidth > 0 && borderColorObj.hex;
|
|
8851
|
+
|
|
8852
|
+
const borderInfo = getBorderInfo(style, config.scale);
|
|
8853
|
+
const hasUniformBorder = borderInfo.type === 'uniform';
|
|
8854
|
+
const hasCompositeBorder = borderInfo.type === 'composite';
|
|
8855
|
+
|
|
8856
|
+
const shadowStr = style.boxShadow;
|
|
8857
|
+
const hasShadow = shadowStr && shadowStr !== 'none';
|
|
8858
|
+
const softEdge = getSoftEdges(style.filter, config.scale);
|
|
8859
|
+
|
|
8860
|
+
let isImageWrapper = false;
|
|
8861
|
+
const imgChild = Array.from(node.children).find((c) => c.tagName === 'IMG');
|
|
8862
|
+
if (imgChild) {
|
|
8863
|
+
const childW = imgChild.offsetWidth || imgChild.getBoundingClientRect().width;
|
|
8864
|
+
const childH = imgChild.offsetHeight || imgChild.getBoundingClientRect().height;
|
|
8865
|
+
if (childW >= widthPx - 2 && childH >= heightPx - 2) isImageWrapper = true;
|
|
8866
|
+
}
|
|
8867
|
+
|
|
8868
|
+
let textPayload = null;
|
|
8869
|
+
const isText = isTextContainer(node);
|
|
8870
|
+
|
|
8871
|
+
if (isText) {
|
|
8872
|
+
const textParts = [];
|
|
8873
|
+
const isList = style.display === 'list-item';
|
|
8874
|
+
if (isList) {
|
|
8875
|
+
const fontSizePt = parseFloat(style.fontSize) * 0.75 * config.scale;
|
|
8876
|
+
const bulletShift = (parseFloat(style.fontSize) || 16) * PX_TO_INCH * config.scale * 1.5;
|
|
8877
|
+
x -= bulletShift;
|
|
8878
|
+
w += bulletShift;
|
|
8879
|
+
textParts.push({
|
|
8880
|
+
text: ' ',
|
|
8881
|
+
options: {
|
|
8882
|
+
color: parseColor(style.color).hex || '000000',
|
|
8883
|
+
fontSize: fontSizePt,
|
|
8884
|
+
},
|
|
8885
|
+
});
|
|
8886
|
+
}
|
|
8887
|
+
|
|
8888
|
+
node.childNodes.forEach((child, index) => {
|
|
8889
|
+
let textVal = child.nodeType === 3 ? child.nodeValue : child.textContent;
|
|
8890
|
+
let nodeStyle = child.nodeType === 1 ? window.getComputedStyle(child) : style;
|
|
8891
|
+
textVal = textVal.replace(/[\n\r\t]+/g, ' ').replace(/\s{2,}/g, ' ');
|
|
8892
|
+
if (index === 0 && !isList) textVal = textVal.trimStart();
|
|
8893
|
+
else if (index === 0) textVal = textVal.trimStart();
|
|
8894
|
+
if (index === node.childNodes.length - 1) textVal = textVal.trimEnd();
|
|
8895
|
+
if (nodeStyle.textTransform === 'uppercase') textVal = textVal.toUpperCase();
|
|
8896
|
+
if (nodeStyle.textTransform === 'lowercase') textVal = textVal.toLowerCase();
|
|
8897
|
+
|
|
8898
|
+
if (textVal.length > 0) {
|
|
8899
|
+
textParts.push({
|
|
8900
|
+
text: textVal,
|
|
8901
|
+
options: getTextStyle(nodeStyle, config.scale),
|
|
8902
|
+
});
|
|
8903
|
+
}
|
|
8904
|
+
});
|
|
8905
|
+
|
|
8906
|
+
if (textParts.length > 0) {
|
|
8907
|
+
let align = style.textAlign || 'left';
|
|
8908
|
+
if (align === 'start') align = 'left';
|
|
8909
|
+
if (align === 'end') align = 'right';
|
|
8910
|
+
let valign = 'top';
|
|
8911
|
+
if (style.alignItems === 'center') valign = 'middle';
|
|
8912
|
+
if (style.justifyContent === 'center' && style.display.includes('flex')) align = 'center';
|
|
8913
|
+
|
|
8914
|
+
const pt = parseFloat(style.paddingTop) || 0;
|
|
8915
|
+
const pb = parseFloat(style.paddingBottom) || 0;
|
|
8916
|
+
if (Math.abs(pt - pb) < 2 && bgColorObj.hex) valign = 'middle';
|
|
8917
|
+
|
|
8918
|
+
let padding = getPadding(style, config.scale);
|
|
8919
|
+
if (align === 'center' && valign === 'middle') padding = [0, 0, 0, 0];
|
|
8920
|
+
|
|
8921
|
+
textPayload = { text: textParts, align, valign, inset: padding };
|
|
8922
|
+
}
|
|
8923
|
+
}
|
|
8924
|
+
|
|
8925
|
+
if (hasGradient || (softEdge && bgColorObj.hex && !isImageWrapper)) {
|
|
8926
|
+
let bgData = null;
|
|
8927
|
+
let padIn = 0;
|
|
8928
|
+
if (softEdge) {
|
|
8929
|
+
const svgInfo = generateBlurredSVG(
|
|
8930
|
+
widthPx,
|
|
8931
|
+
heightPx,
|
|
8932
|
+
bgColorObj.hex,
|
|
8933
|
+
borderRadiusValue,
|
|
8934
|
+
softEdge
|
|
8935
|
+
);
|
|
8936
|
+
bgData = svgInfo.data;
|
|
8937
|
+
padIn = svgInfo.padding * PX_TO_INCH * config.scale;
|
|
8938
|
+
} else {
|
|
8939
|
+
bgData = generateGradientSVG(
|
|
8940
|
+
widthPx,
|
|
8941
|
+
heightPx,
|
|
8942
|
+
style.backgroundImage,
|
|
8943
|
+
borderRadiusValue,
|
|
8944
|
+
hasBorder ? { color: borderColorObj.hex, width: borderWidth } : null
|
|
8945
|
+
);
|
|
8946
|
+
}
|
|
8947
|
+
|
|
8948
|
+
if (bgData) {
|
|
8949
|
+
items.push({
|
|
8950
|
+
type: 'image',
|
|
8951
|
+
zIndex,
|
|
8952
|
+
domOrder,
|
|
8953
|
+
options: {
|
|
8954
|
+
data: bgData,
|
|
8955
|
+
x: x - padIn,
|
|
8956
|
+
y: y - padIn,
|
|
8957
|
+
w: w + padIn * 2,
|
|
8958
|
+
h: h + padIn * 2,
|
|
8959
|
+
rotate: rotation,
|
|
8960
|
+
},
|
|
8961
|
+
});
|
|
8962
|
+
}
|
|
8963
|
+
|
|
8964
|
+
if (textPayload) {
|
|
8965
|
+
items.push({
|
|
8966
|
+
type: 'text',
|
|
8967
|
+
zIndex: zIndex + 1,
|
|
8968
|
+
domOrder,
|
|
8969
|
+
textParts: textPayload.text,
|
|
8970
|
+
options: {
|
|
8971
|
+
x,
|
|
8972
|
+
y,
|
|
8973
|
+
w,
|
|
8974
|
+
h,
|
|
8975
|
+
align: textPayload.align,
|
|
8976
|
+
valign: textPayload.valign,
|
|
8977
|
+
inset: textPayload.inset,
|
|
8978
|
+
rotate: rotation,
|
|
8979
|
+
margin: 0,
|
|
8980
|
+
wrap: true,
|
|
8981
|
+
autoFit: false,
|
|
8982
|
+
},
|
|
8983
|
+
});
|
|
8984
|
+
}
|
|
8985
|
+
if (hasCompositeBorder) {
|
|
8986
|
+
const borderItems = createCompositeBorderItems(
|
|
8987
|
+
borderInfo.sides,
|
|
8988
|
+
x,
|
|
8989
|
+
y,
|
|
8990
|
+
w,
|
|
8991
|
+
h,
|
|
8992
|
+
config.scale,
|
|
8993
|
+
zIndex,
|
|
8994
|
+
domOrder
|
|
8995
|
+
);
|
|
8996
|
+
items.push(...borderItems);
|
|
8997
|
+
}
|
|
8998
|
+
} else if (
|
|
8999
|
+
(bgColorObj.hex && !isImageWrapper) ||
|
|
9000
|
+
hasUniformBorder ||
|
|
9001
|
+
hasCompositeBorder ||
|
|
9002
|
+
hasShadow ||
|
|
9003
|
+
textPayload
|
|
9004
|
+
) {
|
|
9005
|
+
const finalAlpha = safeOpacity * bgColorObj.opacity;
|
|
9006
|
+
const transparency = (1 - finalAlpha) * 100;
|
|
9007
|
+
const useSolidFill = bgColorObj.hex && !isImageWrapper;
|
|
9008
|
+
|
|
9009
|
+
if (hasPartialBorderRadius && useSolidFill && !textPayload) {
|
|
9010
|
+
const shapeSvg = generateCustomShapeSVG(
|
|
9011
|
+
widthPx,
|
|
9012
|
+
heightPx,
|
|
9013
|
+
bgColorObj.hex,
|
|
9014
|
+
bgColorObj.opacity,
|
|
9015
|
+
{
|
|
9016
|
+
tl: parseFloat(style.borderTopLeftRadius) || 0,
|
|
9017
|
+
tr: parseFloat(style.borderTopRightRadius) || 0,
|
|
9018
|
+
br: parseFloat(style.borderBottomRightRadius) || 0,
|
|
9019
|
+
bl: parseFloat(style.borderBottomLeftRadius) || 0,
|
|
9020
|
+
}
|
|
9021
|
+
);
|
|
9022
|
+
|
|
9023
|
+
items.push({
|
|
9024
|
+
type: 'image',
|
|
9025
|
+
zIndex,
|
|
9026
|
+
domOrder,
|
|
9027
|
+
options: { data: shapeSvg, x, y, w, h, rotate: rotation },
|
|
9028
|
+
});
|
|
9029
|
+
} else {
|
|
9030
|
+
const shapeOpts = {
|
|
9031
|
+
x,
|
|
9032
|
+
y,
|
|
9033
|
+
w,
|
|
9034
|
+
h,
|
|
9035
|
+
rotate: rotation,
|
|
9036
|
+
fill: useSolidFill
|
|
9037
|
+
? { color: bgColorObj.hex, transparency: transparency }
|
|
9038
|
+
: { type: 'none' },
|
|
9039
|
+
line: hasUniformBorder ? borderInfo.options : null,
|
|
9040
|
+
};
|
|
9041
|
+
|
|
9042
|
+
if (hasShadow) shapeOpts.shadow = getVisibleShadow(shadowStr, config.scale);
|
|
9043
|
+
|
|
9044
|
+
const borderRadius = parseFloat(style.borderRadius) || 0;
|
|
9045
|
+
const aspectRatio = Math.max(widthPx, heightPx) / Math.min(widthPx, heightPx);
|
|
9046
|
+
const isCircle = aspectRatio < 1.1 && borderRadius >= Math.min(widthPx, heightPx) / 2 - 1;
|
|
9047
|
+
|
|
9048
|
+
let shapeType = pptx.ShapeType.rect;
|
|
9049
|
+
if (isCircle) shapeType = pptx.ShapeType.ellipse;
|
|
9050
|
+
else if (borderRadius > 0) {
|
|
9051
|
+
shapeType = pptx.ShapeType.roundRect;
|
|
9052
|
+
shapeOpts.rectRadius = Math.min(0.5, borderRadius / Math.min(widthPx, heightPx));
|
|
9053
|
+
}
|
|
9054
|
+
|
|
9055
|
+
if (textPayload) {
|
|
9056
|
+
const textOptions = {
|
|
9057
|
+
shape: shapeType,
|
|
9058
|
+
...shapeOpts,
|
|
9059
|
+
align: textPayload.align,
|
|
9060
|
+
valign: textPayload.valign,
|
|
9061
|
+
inset: textPayload.inset,
|
|
9062
|
+
margin: 0,
|
|
9063
|
+
wrap: true,
|
|
9064
|
+
autoFit: false,
|
|
9065
|
+
};
|
|
9066
|
+
items.push({
|
|
9067
|
+
type: 'text',
|
|
9068
|
+
zIndex,
|
|
9069
|
+
domOrder,
|
|
9070
|
+
textParts: textPayload.text,
|
|
9071
|
+
options: textOptions,
|
|
9072
|
+
});
|
|
9073
|
+
} else if (!hasPartialBorderRadius) {
|
|
9074
|
+
items.push({
|
|
9075
|
+
type: 'shape',
|
|
9076
|
+
zIndex,
|
|
9077
|
+
domOrder,
|
|
9078
|
+
shapeType,
|
|
9079
|
+
options: shapeOpts,
|
|
9080
|
+
});
|
|
9081
|
+
}
|
|
9082
|
+
}
|
|
9083
|
+
|
|
9084
|
+
if (hasCompositeBorder) {
|
|
9085
|
+
const borderSvgData = generateCompositeBorderSVG(
|
|
9086
|
+
widthPx,
|
|
9087
|
+
heightPx,
|
|
9088
|
+
borderRadiusValue,
|
|
9089
|
+
borderInfo.sides
|
|
9090
|
+
);
|
|
9091
|
+
if (borderSvgData) {
|
|
9092
|
+
items.push({
|
|
9093
|
+
type: 'image',
|
|
9094
|
+
zIndex: zIndex + 1,
|
|
9095
|
+
domOrder,
|
|
9096
|
+
options: { data: borderSvgData, x, y, w, h, rotate: rotation },
|
|
9097
|
+
});
|
|
9098
|
+
}
|
|
9099
|
+
}
|
|
9100
|
+
}
|
|
9101
|
+
|
|
9102
|
+
return { items, stopRecursion: !!textPayload };
|
|
9103
|
+
}
|
|
9104
|
+
|
|
9105
|
+
function createCompositeBorderItems(sides, x, y, w, h, scale, zIndex, domOrder) {
|
|
9106
|
+
const items = [];
|
|
9107
|
+
const pxToInch = 1 / 96;
|
|
9108
|
+
const common = { zIndex: zIndex + 1, domOrder, shapeType: 'rect' };
|
|
9109
|
+
|
|
9110
|
+
if (sides.top.width > 0)
|
|
9111
|
+
items.push({
|
|
9112
|
+
...common,
|
|
9113
|
+
options: { x, y, w, h: sides.top.width * pxToInch * scale, fill: { color: sides.top.color } },
|
|
9114
|
+
});
|
|
9115
|
+
if (sides.right.width > 0)
|
|
9116
|
+
items.push({
|
|
9117
|
+
...common,
|
|
9118
|
+
options: {
|
|
9119
|
+
x: x + w - sides.right.width * pxToInch * scale,
|
|
9120
|
+
y,
|
|
9121
|
+
w: sides.right.width * pxToInch * scale,
|
|
9122
|
+
h,
|
|
9123
|
+
fill: { color: sides.right.color },
|
|
9124
|
+
},
|
|
9125
|
+
});
|
|
9126
|
+
if (sides.bottom.width > 0)
|
|
9127
|
+
items.push({
|
|
9128
|
+
...common,
|
|
9129
|
+
options: {
|
|
9130
|
+
x,
|
|
9131
|
+
y: y + h - sides.bottom.width * pxToInch * scale,
|
|
9132
|
+
w,
|
|
9133
|
+
h: sides.bottom.width * pxToInch * scale,
|
|
9134
|
+
fill: { color: sides.bottom.color },
|
|
9135
|
+
},
|
|
9136
|
+
});
|
|
9137
|
+
if (sides.left.width > 0)
|
|
9138
|
+
items.push({
|
|
9139
|
+
...common,
|
|
9140
|
+
options: {
|
|
9141
|
+
x,
|
|
9142
|
+
y,
|
|
9143
|
+
w: sides.left.width * pxToInch * scale,
|
|
9144
|
+
h,
|
|
9145
|
+
fill: { color: sides.left.color },
|
|
9146
|
+
},
|
|
9147
|
+
});
|
|
9148
|
+
|
|
9149
|
+
return items;
|
|
9043
9150
|
}
|
|
9044
9151
|
|
|
9045
9152
|
exports.exportToPptx = exportToPptx;
|