docgen-utils 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +118 -0
- package/dist/bundle.js +36086 -0
- package/dist/bundle.min.js +197 -0
- package/dist/cli.js +47432 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/packages/cli/commands/export-docs.d.ts +5 -0
- package/dist/packages/cli/commands/export-docs.d.ts.map +1 -0
- package/dist/packages/cli/commands/export-docs.js +24 -0
- package/dist/packages/cli/commands/export-docs.js.map +1 -0
- package/dist/packages/cli/commands/export-slides.d.ts +5 -0
- package/dist/packages/cli/commands/export-slides.d.ts.map +1 -0
- package/dist/packages/cli/commands/export-slides.js +86 -0
- package/dist/packages/cli/commands/export-slides.js.map +1 -0
- package/dist/packages/cli/commands/import-docx.d.ts +5 -0
- package/dist/packages/cli/commands/import-docx.d.ts.map +1 -0
- package/dist/packages/cli/commands/import-docx.js +27 -0
- package/dist/packages/cli/commands/import-docx.js.map +1 -0
- package/dist/packages/cli/commands/import-pptx.d.ts +5 -0
- package/dist/packages/cli/commands/import-pptx.d.ts.map +1 -0
- package/dist/packages/cli/commands/import-pptx.js +44 -0
- package/dist/packages/cli/commands/import-pptx.js.map +1 -0
- package/dist/packages/cli/index.d.ts +11 -0
- package/dist/packages/cli/index.d.ts.map +1 -0
- package/dist/packages/cli/index.js +103 -0
- package/dist/packages/cli/index.js.map +1 -0
- package/dist/packages/docs/common.d.ts +183 -0
- package/dist/packages/docs/common.d.ts.map +1 -0
- package/dist/packages/docs/common.js +27 -0
- package/dist/packages/docs/common.js.map +1 -0
- package/dist/packages/docs/convert.d.ts +7 -0
- package/dist/packages/docs/convert.d.ts.map +1 -0
- package/dist/packages/docs/convert.js +1399 -0
- package/dist/packages/docs/convert.js.map +1 -0
- package/dist/packages/docs/create-document.d.ts +30 -0
- package/dist/packages/docs/create-document.d.ts.map +1 -0
- package/dist/packages/docs/create-document.js +170 -0
- package/dist/packages/docs/create-document.js.map +1 -0
- package/dist/packages/docs/export.d.ts +57 -0
- package/dist/packages/docs/export.d.ts.map +1 -0
- package/dist/packages/docs/export.js +430 -0
- package/dist/packages/docs/export.js.map +1 -0
- package/dist/packages/docs/import-docx.d.ts +13 -0
- package/dist/packages/docs/import-docx.d.ts.map +1 -0
- package/dist/packages/docs/import-docx.js +2299 -0
- package/dist/packages/docs/import-docx.js.map +1 -0
- package/dist/packages/docs/parse.d.ts +6 -0
- package/dist/packages/docs/parse.d.ts.map +1 -0
- package/dist/packages/docs/parse.js +4253 -0
- package/dist/packages/docs/parse.js.map +1 -0
- package/dist/packages/shared/dom-parser-shim.d.ts +30 -0
- package/dist/packages/shared/dom-parser-shim.d.ts.map +1 -0
- package/dist/packages/shared/dom-parser-shim.js +152 -0
- package/dist/packages/shared/dom-parser-shim.js.map +1 -0
- package/dist/packages/slides/common.d.ts +325 -0
- package/dist/packages/slides/common.d.ts.map +1 -0
- package/dist/packages/slides/common.js +12 -0
- package/dist/packages/slides/common.js.map +1 -0
- package/dist/packages/slides/convert.d.ts +35 -0
- package/dist/packages/slides/convert.d.ts.map +1 -0
- package/dist/packages/slides/convert.js +308 -0
- package/dist/packages/slides/convert.js.map +1 -0
- package/dist/packages/slides/createPresentation.d.ts +51 -0
- package/dist/packages/slides/createPresentation.d.ts.map +1 -0
- package/dist/packages/slides/createPresentation.js +265 -0
- package/dist/packages/slides/createPresentation.js.map +1 -0
- package/dist/packages/slides/export.d.ts +24 -0
- package/dist/packages/slides/export.d.ts.map +1 -0
- package/dist/packages/slides/export.js +52 -0
- package/dist/packages/slides/export.js.map +1 -0
- package/dist/packages/slides/import-pptx.d.ts +13 -0
- package/dist/packages/slides/import-pptx.d.ts.map +1 -0
- package/dist/packages/slides/import-pptx.js +619 -0
- package/dist/packages/slides/import-pptx.js.map +1 -0
- package/dist/packages/slides/parse.d.ts +45 -0
- package/dist/packages/slides/parse.d.ts.map +1 -0
- package/dist/packages/slides/parse.js +1185 -0
- package/dist/packages/slides/parse.js.map +1 -0
- package/dist/packages/slides/transform.d.ts +37 -0
- package/dist/packages/slides/transform.d.ts.map +1 -0
- package/dist/packages/slides/transform.js +140 -0
- package/dist/packages/slides/transform.js.map +1 -0
- package/dist/packages/slides/vendor/VENDORING.md +58 -0
- package/dist/packages/slides/vendor/pptxgen.d.ts +805 -0
- package/dist/packages/slides/vendor/pptxgen.js +7442 -0
- package/package.json +57 -0
|
@@ -0,0 +1,1185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* parse.ts - Extract slide data from a live browser DOM.
|
|
3
|
+
*
|
|
4
|
+
* This is a faithful TypeScript port of `extractSlideData()` and all its
|
|
5
|
+
* helper functions from dist/html2pptx.js. It also ports `parseCssGradient()`
|
|
6
|
+
* which is used both here and by other modules.
|
|
7
|
+
*
|
|
8
|
+
* The main export is `parseSlideHtml(doc)` which walks the DOM tree and returns
|
|
9
|
+
* a `ParsedSlide` intermediate representation consumed by the rendering layer.
|
|
10
|
+
*/
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Constants
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
const PT_PER_PX = 0.75;
|
|
15
|
+
const PX_PER_IN = 96;
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
// Single-weight fonts (bold is skipped for these)
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
const SINGLE_WEIGHT_FONTS = ['impact'];
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Exported helper functions
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
/** Convert pixel value to inches. */
|
|
24
|
+
export function pxToInch(px) {
|
|
25
|
+
return px / PX_PER_IN;
|
|
26
|
+
}
|
|
27
|
+
/** Convert a CSS pixel string (e.g. "16px") to points. */
|
|
28
|
+
export function pxToPoints(pxStr) {
|
|
29
|
+
return parseFloat(pxStr) * PT_PER_PX;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Convert an `rgb()` / `rgba()` color string to a 6-char hex string.
|
|
33
|
+
* Returns `'FFFFFF'` for transparent or unparseable values.
|
|
34
|
+
*/
|
|
35
|
+
export function rgbToHex(rgbStr) {
|
|
36
|
+
if (rgbStr === 'rgba(0, 0, 0, 0)' || rgbStr === 'transparent')
|
|
37
|
+
return 'FFFFFF';
|
|
38
|
+
const match = rgbStr.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
|
|
39
|
+
if (!match)
|
|
40
|
+
return 'FFFFFF';
|
|
41
|
+
return match
|
|
42
|
+
.slice(1)
|
|
43
|
+
.map((n) => parseInt(n).toString(16).padStart(2, '0'))
|
|
44
|
+
.join('');
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Extract transparency percentage from an `rgba()` string.
|
|
48
|
+
* Returns `null` for opaque or non-rgba values, otherwise 0-100.
|
|
49
|
+
*/
|
|
50
|
+
export function extractAlpha(rgbStr) {
|
|
51
|
+
const match = rgbStr.match(/rgba\((\d+),\s*(\d+),\s*(\d+),\s*([\d.]+)\)/);
|
|
52
|
+
if (!match || !match[4])
|
|
53
|
+
return null;
|
|
54
|
+
const alpha = parseFloat(match[4]);
|
|
55
|
+
return Math.round((1 - alpha) * 100);
|
|
56
|
+
}
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
// parseCssGradient (exported, used by other modules)
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
/**
|
|
61
|
+
* Parse a CSS gradient string (`linear-gradient(...)` or `radial-gradient(...)`)
|
|
62
|
+
* into a PptxGenJS-compatible `GradientFillProps` object.
|
|
63
|
+
*
|
|
64
|
+
* Returns `null` if the string does not contain a recognized gradient.
|
|
65
|
+
*/
|
|
66
|
+
export function parseCssGradient(gradientStr) {
|
|
67
|
+
const colorToHex = (colorStr) => {
|
|
68
|
+
colorStr = colorStr.trim();
|
|
69
|
+
if (colorStr.startsWith('#')) {
|
|
70
|
+
let hex = colorStr.slice(1);
|
|
71
|
+
if (hex.length === 3)
|
|
72
|
+
hex = hex.split('').map((c) => c + c).join('');
|
|
73
|
+
return hex.toUpperCase();
|
|
74
|
+
}
|
|
75
|
+
const rgbMatch = colorStr.match(/rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/);
|
|
76
|
+
if (rgbMatch) {
|
|
77
|
+
return rgbMatch
|
|
78
|
+
.slice(1)
|
|
79
|
+
.map((n) => parseInt(n).toString(16).padStart(2, '0'))
|
|
80
|
+
.join('')
|
|
81
|
+
.toUpperCase();
|
|
82
|
+
}
|
|
83
|
+
return 'FFFFFF';
|
|
84
|
+
};
|
|
85
|
+
const extractTransparency = (colorStr) => {
|
|
86
|
+
colorStr = colorStr.trim();
|
|
87
|
+
const rgbaMatch = colorStr.match(/rgba\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*,\s*([\d.]+)\s*\)/);
|
|
88
|
+
if (rgbaMatch) {
|
|
89
|
+
const alpha = parseFloat(rgbaMatch[1]);
|
|
90
|
+
if (alpha < 1) {
|
|
91
|
+
return Math.round((1 - alpha) * 100);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return null;
|
|
95
|
+
};
|
|
96
|
+
const splitGradientParts = (str) => {
|
|
97
|
+
const parts = [];
|
|
98
|
+
let current = '';
|
|
99
|
+
let depth = 0;
|
|
100
|
+
for (const char of str) {
|
|
101
|
+
if (char === '(')
|
|
102
|
+
depth++;
|
|
103
|
+
else if (char === ')')
|
|
104
|
+
depth--;
|
|
105
|
+
if (char === ',' && depth === 0) {
|
|
106
|
+
parts.push(current.trim());
|
|
107
|
+
current = '';
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
current += char;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (current.trim())
|
|
114
|
+
parts.push(current.trim());
|
|
115
|
+
return parts;
|
|
116
|
+
};
|
|
117
|
+
const extractGradientContent = (str, prefix) => {
|
|
118
|
+
const startIdx = str.indexOf(prefix);
|
|
119
|
+
if (startIdx === -1)
|
|
120
|
+
return null;
|
|
121
|
+
let depth = 0;
|
|
122
|
+
const start = startIdx + prefix.length;
|
|
123
|
+
for (let i = start; i < str.length; i++) {
|
|
124
|
+
if (str[i] === '(')
|
|
125
|
+
depth++;
|
|
126
|
+
else if (str[i] === ')') {
|
|
127
|
+
if (depth === 0)
|
|
128
|
+
return str.substring(start, i);
|
|
129
|
+
depth--;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return null;
|
|
133
|
+
};
|
|
134
|
+
// Try linear-gradient
|
|
135
|
+
const linearContent = extractGradientContent(gradientStr, 'linear-gradient(');
|
|
136
|
+
if (linearContent) {
|
|
137
|
+
const parts = splitGradientParts(linearContent);
|
|
138
|
+
let cssAngle = 180;
|
|
139
|
+
let colorStops = parts;
|
|
140
|
+
const angleMatch = parts[0].match(/^([\d.]+)deg$/);
|
|
141
|
+
if (angleMatch) {
|
|
142
|
+
cssAngle = parseFloat(angleMatch[1]);
|
|
143
|
+
colorStops = parts.slice(1);
|
|
144
|
+
}
|
|
145
|
+
else if (parts[0].startsWith('to ')) {
|
|
146
|
+
const dir = parts[0].replace('to ', '');
|
|
147
|
+
const dirMap = {
|
|
148
|
+
'top': 0,
|
|
149
|
+
'right': 90,
|
|
150
|
+
'bottom': 180,
|
|
151
|
+
'left': 270,
|
|
152
|
+
'top right': 45,
|
|
153
|
+
'right top': 45,
|
|
154
|
+
'bottom right': 135,
|
|
155
|
+
'right bottom': 135,
|
|
156
|
+
'bottom left': 225,
|
|
157
|
+
'left bottom': 225,
|
|
158
|
+
'top left': 315,
|
|
159
|
+
'left top': 315,
|
|
160
|
+
};
|
|
161
|
+
cssAngle = dirMap[dir] ?? 180;
|
|
162
|
+
colorStops = parts.slice(1);
|
|
163
|
+
}
|
|
164
|
+
const stops = colorStops.map((stop, idx) => {
|
|
165
|
+
const posMatch = stop.match(/([\d.]+)%\s*$/);
|
|
166
|
+
const position = posMatch
|
|
167
|
+
? parseFloat(posMatch[1])
|
|
168
|
+
: (idx / (colorStops.length - 1)) * 100;
|
|
169
|
+
const colorPart = posMatch
|
|
170
|
+
? stop.replace(/([\d.]+)%\s*$/, '').trim()
|
|
171
|
+
: stop.trim();
|
|
172
|
+
const stopData = { color: colorToHex(colorPart), position };
|
|
173
|
+
const transparency = extractTransparency(colorPart);
|
|
174
|
+
if (transparency !== null)
|
|
175
|
+
stopData.transparency = transparency;
|
|
176
|
+
return stopData;
|
|
177
|
+
});
|
|
178
|
+
return { type: 'linear', angle: cssAngle, stops };
|
|
179
|
+
}
|
|
180
|
+
// Try radial-gradient
|
|
181
|
+
const radialContent = extractGradientContent(gradientStr, 'radial-gradient(');
|
|
182
|
+
if (radialContent) {
|
|
183
|
+
const parts = splitGradientParts(radialContent);
|
|
184
|
+
let centerX = 50;
|
|
185
|
+
let centerY = 50;
|
|
186
|
+
let colorStops = parts;
|
|
187
|
+
const posMatch = parts[0].match(/at\s+([\d.]+)%?\s+([\d.]+)%?/);
|
|
188
|
+
if (posMatch) {
|
|
189
|
+
centerX = parseFloat(posMatch[1]);
|
|
190
|
+
centerY = parseFloat(posMatch[2]);
|
|
191
|
+
colorStops = parts.slice(1);
|
|
192
|
+
}
|
|
193
|
+
else if (parts[0].includes('circle') || parts[0].includes('ellipse')) {
|
|
194
|
+
colorStops = parts.slice(1);
|
|
195
|
+
}
|
|
196
|
+
const stops = colorStops.map((stop, idx) => {
|
|
197
|
+
const posMatch2 = stop.match(/([\d.]+)%\s*$/);
|
|
198
|
+
const position = posMatch2
|
|
199
|
+
? parseFloat(posMatch2[1])
|
|
200
|
+
: (idx / (colorStops.length - 1)) * 100;
|
|
201
|
+
const colorPart = posMatch2
|
|
202
|
+
? stop.replace(/([\d.]+)%\s*$/, '').trim()
|
|
203
|
+
: stop.trim();
|
|
204
|
+
const stopData = { color: colorToHex(colorPart), position };
|
|
205
|
+
const transparency = extractTransparency(colorPart);
|
|
206
|
+
if (transparency !== null)
|
|
207
|
+
stopData.transparency = transparency;
|
|
208
|
+
return stopData;
|
|
209
|
+
});
|
|
210
|
+
return { type: 'radial', centerX, centerY, stops };
|
|
211
|
+
}
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
// ---------------------------------------------------------------------------
|
|
215
|
+
// Internal helpers (not exported)
|
|
216
|
+
// ---------------------------------------------------------------------------
|
|
217
|
+
function shouldSkipBold(fontFamily) {
|
|
218
|
+
if (!fontFamily)
|
|
219
|
+
return false;
|
|
220
|
+
const normalizedFont = fontFamily.toLowerCase().replace(/['"]/g, '').split(',')[0].trim();
|
|
221
|
+
return SINGLE_WEIGHT_FONTS.includes(normalizedFont);
|
|
222
|
+
}
|
|
223
|
+
function applyTextTransform(text, textTransform) {
|
|
224
|
+
if (textTransform === 'uppercase')
|
|
225
|
+
return text.toUpperCase();
|
|
226
|
+
if (textTransform === 'lowercase')
|
|
227
|
+
return text.toLowerCase();
|
|
228
|
+
if (textTransform === 'capitalize') {
|
|
229
|
+
return text.replace(/\b\w/g, (c) => c.toUpperCase());
|
|
230
|
+
}
|
|
231
|
+
return text;
|
|
232
|
+
}
|
|
233
|
+
function getRotation(transform, writingMode) {
|
|
234
|
+
let angle = 0;
|
|
235
|
+
if (writingMode === 'vertical-rl') {
|
|
236
|
+
angle = 90;
|
|
237
|
+
}
|
|
238
|
+
else if (writingMode === 'vertical-lr') {
|
|
239
|
+
angle = 270;
|
|
240
|
+
}
|
|
241
|
+
if (transform && transform !== 'none') {
|
|
242
|
+
const rotateMatch = transform.match(/rotate\((-?\d+(?:\.\d+)?)deg\)/);
|
|
243
|
+
if (rotateMatch) {
|
|
244
|
+
angle += parseFloat(rotateMatch[1]);
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
const matrixMatch = transform.match(/matrix\(([^)]+)\)/);
|
|
248
|
+
if (matrixMatch) {
|
|
249
|
+
const values = matrixMatch[1].split(',').map(parseFloat);
|
|
250
|
+
const matrixAngle = Math.atan2(values[1], values[0]) * (180 / Math.PI);
|
|
251
|
+
angle += Math.round(matrixAngle);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
angle = angle % 360;
|
|
256
|
+
if (angle < 0)
|
|
257
|
+
angle += 360;
|
|
258
|
+
return angle === 0 ? null : angle;
|
|
259
|
+
}
|
|
260
|
+
function getPositionAndSize(el, rect, rotation) {
|
|
261
|
+
if (rotation === null) {
|
|
262
|
+
return { x: rect.left, y: rect.top, w: rect.width, h: rect.height };
|
|
263
|
+
}
|
|
264
|
+
const isVertical = rotation === 90 || rotation === 270;
|
|
265
|
+
if (isVertical) {
|
|
266
|
+
const centerX = rect.left + rect.width / 2;
|
|
267
|
+
const centerY = rect.top + rect.height / 2;
|
|
268
|
+
return {
|
|
269
|
+
x: centerX - rect.height / 2,
|
|
270
|
+
y: centerY - rect.width / 2,
|
|
271
|
+
w: rect.height,
|
|
272
|
+
h: rect.width,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
const centerX = rect.left + rect.width / 2;
|
|
276
|
+
const centerY = rect.top + rect.height / 2;
|
|
277
|
+
return {
|
|
278
|
+
x: centerX - el.offsetWidth / 2,
|
|
279
|
+
y: centerY - el.offsetHeight / 2,
|
|
280
|
+
w: el.offsetWidth,
|
|
281
|
+
h: el.offsetHeight,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
function parseBoxShadow(boxShadow) {
|
|
285
|
+
if (!boxShadow || boxShadow === 'none')
|
|
286
|
+
return null;
|
|
287
|
+
const insetMatch = boxShadow.match(/inset/);
|
|
288
|
+
if (insetMatch)
|
|
289
|
+
return null;
|
|
290
|
+
const colorMatch = boxShadow.match(/rgba?\([^)]+\)/);
|
|
291
|
+
const parts = boxShadow.match(/([-\d.]+)(px|pt)/g);
|
|
292
|
+
if (!parts || parts.length < 2)
|
|
293
|
+
return null;
|
|
294
|
+
const offsetX = parseFloat(parts[0]);
|
|
295
|
+
const offsetY = parseFloat(parts[1]);
|
|
296
|
+
const blur = parts.length > 2 ? parseFloat(parts[2]) : 0;
|
|
297
|
+
let angle = 0;
|
|
298
|
+
if (offsetX !== 0 || offsetY !== 0) {
|
|
299
|
+
angle = Math.atan2(offsetY, offsetX) * (180 / Math.PI);
|
|
300
|
+
if (angle < 0)
|
|
301
|
+
angle += 360;
|
|
302
|
+
}
|
|
303
|
+
const offset = Math.sqrt(offsetX * offsetX + offsetY * offsetY) * PT_PER_PX;
|
|
304
|
+
let opacity = 0.5;
|
|
305
|
+
if (colorMatch) {
|
|
306
|
+
const opacityMatch = colorMatch[0].match(/[\d.]+\)$/);
|
|
307
|
+
if (opacityMatch) {
|
|
308
|
+
opacity = parseFloat(opacityMatch[0].replace(')', ''));
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return {
|
|
312
|
+
type: 'outer',
|
|
313
|
+
angle: Math.round(angle),
|
|
314
|
+
blur: blur * 0.75,
|
|
315
|
+
color: colorMatch ? rgbToHex(colorMatch[0]) : '000000',
|
|
316
|
+
offset: offset,
|
|
317
|
+
opacity,
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
function parseInlineFormatting(element, baseOptions, runs, baseTextTransform, win) {
|
|
321
|
+
let prevNodeIsText = false;
|
|
322
|
+
let pendingSoftBreak = false;
|
|
323
|
+
element.childNodes.forEach((node) => {
|
|
324
|
+
let textTransform = baseTextTransform;
|
|
325
|
+
if (node.tagName === 'BR') {
|
|
326
|
+
pendingSoftBreak = true;
|
|
327
|
+
prevNodeIsText = false;
|
|
328
|
+
}
|
|
329
|
+
else if (node.nodeType === Node.TEXT_NODE) {
|
|
330
|
+
const text = textTransform(node.textContent.replace(/\s+/g, ' '));
|
|
331
|
+
const prevRun = runs[runs.length - 1];
|
|
332
|
+
if (prevNodeIsText && prevRun && !pendingSoftBreak) {
|
|
333
|
+
prevRun.text += text;
|
|
334
|
+
}
|
|
335
|
+
else {
|
|
336
|
+
const runOptions = { ...baseOptions };
|
|
337
|
+
if (pendingSoftBreak) {
|
|
338
|
+
runOptions.softBreakBefore = true;
|
|
339
|
+
pendingSoftBreak = false;
|
|
340
|
+
}
|
|
341
|
+
runs.push({ text, options: runOptions });
|
|
342
|
+
}
|
|
343
|
+
prevNodeIsText = true;
|
|
344
|
+
}
|
|
345
|
+
else if (node.nodeType === Node.ELEMENT_NODE && node.textContent.trim()) {
|
|
346
|
+
const el = node;
|
|
347
|
+
const options = { ...baseOptions };
|
|
348
|
+
const computed = win.getComputedStyle(el);
|
|
349
|
+
if (el.tagName === 'SPAN' ||
|
|
350
|
+
el.tagName === 'B' ||
|
|
351
|
+
el.tagName === 'STRONG' ||
|
|
352
|
+
el.tagName === 'I' ||
|
|
353
|
+
el.tagName === 'EM' ||
|
|
354
|
+
el.tagName === 'U') {
|
|
355
|
+
const isBold = computed.fontWeight === 'bold' || parseInt(computed.fontWeight) >= 600;
|
|
356
|
+
if (isBold && !shouldSkipBold(computed.fontFamily))
|
|
357
|
+
options.bold = true;
|
|
358
|
+
if (computed.fontStyle === 'italic')
|
|
359
|
+
options.italic = true;
|
|
360
|
+
if (computed.textDecoration && computed.textDecoration.includes('underline'))
|
|
361
|
+
options.underline = true;
|
|
362
|
+
if (computed.color && computed.color !== 'rgb(0, 0, 0)') {
|
|
363
|
+
options.color = rgbToHex(computed.color);
|
|
364
|
+
const transparency = extractAlpha(computed.color);
|
|
365
|
+
if (transparency !== null)
|
|
366
|
+
options.transparency = transparency;
|
|
367
|
+
}
|
|
368
|
+
if (computed.fontSize)
|
|
369
|
+
options.fontSize = pxToPoints(computed.fontSize);
|
|
370
|
+
if (computed.textTransform && computed.textTransform !== 'none') {
|
|
371
|
+
const transformStr = computed.textTransform;
|
|
372
|
+
textTransform = (text) => applyTextTransform(text, transformStr);
|
|
373
|
+
}
|
|
374
|
+
parseInlineFormatting(el, options, runs, textTransform, win);
|
|
375
|
+
}
|
|
376
|
+
prevNodeIsText = false;
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
if (runs.length > 0) {
|
|
380
|
+
runs[0].text = runs[0].text.replace(/^\s+/, '');
|
|
381
|
+
runs[runs.length - 1].text = runs[runs.length - 1].text.replace(/\s+$/, '');
|
|
382
|
+
}
|
|
383
|
+
return runs.filter((r) => r.text.length > 0);
|
|
384
|
+
}
|
|
385
|
+
// ---------------------------------------------------------------------------
|
|
386
|
+
// Main export
|
|
387
|
+
// ---------------------------------------------------------------------------
|
|
388
|
+
/**
|
|
389
|
+
* Parse a live browser DOM `Document` into a `ParsedSlide` intermediate
|
|
390
|
+
* representation.
|
|
391
|
+
*
|
|
392
|
+
* This is the TypeScript equivalent of `extractSlideData()` from html2pptx.js.
|
|
393
|
+
* It walks all elements in the document, extracts position, style, text, images,
|
|
394
|
+
* shapes, and lists, and returns a typed `ParsedSlide`.
|
|
395
|
+
*
|
|
396
|
+
* @param doc - A live browser `Document` object (typically from an iframe).
|
|
397
|
+
*/
|
|
398
|
+
export function parseSlideHtml(doc) {
|
|
399
|
+
const win = doc.defaultView || window;
|
|
400
|
+
const errors = [];
|
|
401
|
+
// -------------------------------------------------------------------------
|
|
402
|
+
// Extract background
|
|
403
|
+
// -------------------------------------------------------------------------
|
|
404
|
+
const body = doc.body;
|
|
405
|
+
const bodyStyle = win.getComputedStyle(body);
|
|
406
|
+
const bgImage = bodyStyle.backgroundImage;
|
|
407
|
+
const bgColor = bodyStyle.backgroundColor;
|
|
408
|
+
let background;
|
|
409
|
+
if (bgImage && bgImage !== 'none') {
|
|
410
|
+
if (bgImage.includes('linear-gradient') || bgImage.includes('radial-gradient')) {
|
|
411
|
+
const gradient = parseCssGradient(bgImage);
|
|
412
|
+
if (gradient) {
|
|
413
|
+
background = { type: 'gradient', gradient };
|
|
414
|
+
}
|
|
415
|
+
else {
|
|
416
|
+
background = { type: 'color', value: rgbToHex(bgColor) };
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
else {
|
|
420
|
+
const urlMatch = bgImage.match(/url\(["']?([^"')]+)["']?\)/);
|
|
421
|
+
if (urlMatch) {
|
|
422
|
+
background = { type: 'image', path: urlMatch[1] };
|
|
423
|
+
}
|
|
424
|
+
else {
|
|
425
|
+
background = { type: 'color', value: rgbToHex(bgColor) };
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
else {
|
|
430
|
+
background = { type: 'color', value: rgbToHex(bgColor) };
|
|
431
|
+
}
|
|
432
|
+
// -------------------------------------------------------------------------
|
|
433
|
+
// Process all elements
|
|
434
|
+
// -------------------------------------------------------------------------
|
|
435
|
+
const elements = [];
|
|
436
|
+
const placeholders = [];
|
|
437
|
+
const textTags = ['P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'UL', 'OL', 'LI', 'SPAN'];
|
|
438
|
+
const processed = new Set();
|
|
439
|
+
doc.querySelectorAll('*').forEach((el) => {
|
|
440
|
+
if (processed.has(el))
|
|
441
|
+
return;
|
|
442
|
+
const htmlEl = el;
|
|
443
|
+
// Validate text elements
|
|
444
|
+
if (textTags.includes(el.tagName) && el.tagName !== 'SPAN') {
|
|
445
|
+
const computed = win.getComputedStyle(el);
|
|
446
|
+
const hasBg = computed.backgroundColor && computed.backgroundColor !== 'rgba(0, 0, 0, 0)';
|
|
447
|
+
const hasBorder = (computed.borderWidth && parseFloat(computed.borderWidth) > 0) ||
|
|
448
|
+
(computed.borderTopWidth && parseFloat(computed.borderTopWidth) > 0) ||
|
|
449
|
+
(computed.borderRightWidth && parseFloat(computed.borderRightWidth) > 0) ||
|
|
450
|
+
(computed.borderBottomWidth && parseFloat(computed.borderBottomWidth) > 0) ||
|
|
451
|
+
(computed.borderLeftWidth && parseFloat(computed.borderLeftWidth) > 0);
|
|
452
|
+
const hasShadow = computed.boxShadow && computed.boxShadow !== 'none';
|
|
453
|
+
if (hasBg || hasBorder || hasShadow) {
|
|
454
|
+
errors.push(`Text element <${el.tagName.toLowerCase()}> has ${hasBg ? 'background' : hasBorder ? 'border' : 'shadow'}. ` +
|
|
455
|
+
'Backgrounds, borders, and shadows are only supported on <div> elements, not text elements.');
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
// Handle SPAN elements with backgrounds/borders as shapes
|
|
460
|
+
if (el.tagName === 'SPAN') {
|
|
461
|
+
const computed = win.getComputedStyle(el);
|
|
462
|
+
const hasBg = computed.backgroundColor && computed.backgroundColor !== 'rgba(0, 0, 0, 0)';
|
|
463
|
+
const hasBorder = (computed.borderWidth && parseFloat(computed.borderWidth) > 0) ||
|
|
464
|
+
(computed.borderTopWidth && parseFloat(computed.borderTopWidth) > 0) ||
|
|
465
|
+
(computed.borderRightWidth && parseFloat(computed.borderRightWidth) > 0) ||
|
|
466
|
+
(computed.borderBottomWidth && parseFloat(computed.borderBottomWidth) > 0) ||
|
|
467
|
+
(computed.borderLeftWidth && parseFloat(computed.borderLeftWidth) > 0);
|
|
468
|
+
if (hasBg || hasBorder) {
|
|
469
|
+
const rect = htmlEl.getBoundingClientRect();
|
|
470
|
+
if (rect.width > 0 && rect.height > 0) {
|
|
471
|
+
const text = el.textContent.trim();
|
|
472
|
+
const bgGradient = parseCssGradient(computed.backgroundImage);
|
|
473
|
+
const borderRadius = computed.borderRadius;
|
|
474
|
+
const radiusValue = parseFloat(borderRadius);
|
|
475
|
+
let rectRadius = 0;
|
|
476
|
+
if (radiusValue > 0) {
|
|
477
|
+
if (borderRadius.includes('%')) {
|
|
478
|
+
const minDim = Math.min(rect.width, rect.height);
|
|
479
|
+
rectRadius = (radiusValue / 100) * pxToInch(minDim);
|
|
480
|
+
}
|
|
481
|
+
else {
|
|
482
|
+
rectRadius = pxToInch(radiusValue);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
const borderTop = computed.borderTopWidth;
|
|
486
|
+
const borderRight = computed.borderRightWidth;
|
|
487
|
+
const borderBottom = computed.borderBottomWidth;
|
|
488
|
+
const borderLeft = computed.borderLeftWidth;
|
|
489
|
+
const hasUniformBorder = hasBorder &&
|
|
490
|
+
borderTop === borderRight &&
|
|
491
|
+
borderRight === borderBottom &&
|
|
492
|
+
borderBottom === borderLeft;
|
|
493
|
+
const shapeElement = {
|
|
494
|
+
type: 'shape',
|
|
495
|
+
position: {
|
|
496
|
+
x: pxToInch(rect.left),
|
|
497
|
+
y: pxToInch(rect.top),
|
|
498
|
+
w: pxToInch(rect.width),
|
|
499
|
+
h: pxToInch(rect.height),
|
|
500
|
+
},
|
|
501
|
+
text: text,
|
|
502
|
+
textRuns: null,
|
|
503
|
+
style: {
|
|
504
|
+
fontSize: pxToPoints(computed.fontSize),
|
|
505
|
+
fontFace: computed.fontFamily.split(',')[0].replace(/['"]/g, '').trim(),
|
|
506
|
+
color: rgbToHex(computed.color),
|
|
507
|
+
bold: parseInt(computed.fontWeight) >= 600,
|
|
508
|
+
align: 'center',
|
|
509
|
+
valign: 'middle',
|
|
510
|
+
},
|
|
511
|
+
shape: {
|
|
512
|
+
fill: hasBg ? rgbToHex(computed.backgroundColor) : null,
|
|
513
|
+
gradient: bgGradient,
|
|
514
|
+
transparency: null,
|
|
515
|
+
line: hasUniformBorder
|
|
516
|
+
? {
|
|
517
|
+
color: rgbToHex(computed.borderColor),
|
|
518
|
+
width: pxToPoints(borderTop),
|
|
519
|
+
}
|
|
520
|
+
: null,
|
|
521
|
+
rectRadius: rectRadius,
|
|
522
|
+
shadow: null,
|
|
523
|
+
},
|
|
524
|
+
};
|
|
525
|
+
elements.push(shapeElement);
|
|
526
|
+
processed.add(el);
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
// Handle plain SPANs that are direct children of DIV elements
|
|
531
|
+
const parent = el.parentElement;
|
|
532
|
+
if (parent && parent.tagName === 'DIV') {
|
|
533
|
+
const rect = htmlEl.getBoundingClientRect();
|
|
534
|
+
const text = el.textContent.trim();
|
|
535
|
+
if (rect.width > 0 && rect.height > 0 && text) {
|
|
536
|
+
const computed2 = win.getComputedStyle(el);
|
|
537
|
+
const fontSizePx = parseFloat(computed2.fontSize);
|
|
538
|
+
const lineHeightPx = parseFloat(computed2.lineHeight);
|
|
539
|
+
const lineHeightMultiplier = fontSizePx > 0 && !isNaN(lineHeightPx) ? lineHeightPx / fontSizePx : 1.0;
|
|
540
|
+
const textElement = {
|
|
541
|
+
type: 'p',
|
|
542
|
+
text: [{ text: text, options: {} }],
|
|
543
|
+
position: {
|
|
544
|
+
x: pxToInch(rect.left),
|
|
545
|
+
y: pxToInch(rect.top),
|
|
546
|
+
w: pxToInch(rect.width),
|
|
547
|
+
h: pxToInch(rect.height),
|
|
548
|
+
},
|
|
549
|
+
style: {
|
|
550
|
+
fontSize: pxToPoints(computed2.fontSize),
|
|
551
|
+
fontFace: computed2.fontFamily.split(',')[0].replace(/['"]/g, '').trim(),
|
|
552
|
+
color: rgbToHex(computed2.color),
|
|
553
|
+
bold: parseInt(computed2.fontWeight) >= 600,
|
|
554
|
+
italic: computed2.fontStyle === 'italic',
|
|
555
|
+
align: computed2.textAlign === 'center'
|
|
556
|
+
? 'center'
|
|
557
|
+
: computed2.textAlign === 'right'
|
|
558
|
+
? 'right'
|
|
559
|
+
: 'left',
|
|
560
|
+
valign: 'middle',
|
|
561
|
+
lineSpacing: lineHeightMultiplier * pxToPoints(computed2.fontSize),
|
|
562
|
+
},
|
|
563
|
+
};
|
|
564
|
+
elements.push(textElement);
|
|
565
|
+
processed.add(el);
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
// Extract placeholder elements
|
|
571
|
+
if (el.className &&
|
|
572
|
+
typeof el.className === 'string' &&
|
|
573
|
+
el.className.includes('placeholder')) {
|
|
574
|
+
const rect = htmlEl.getBoundingClientRect();
|
|
575
|
+
if (rect.width === 0 || rect.height === 0) {
|
|
576
|
+
errors.push(`Placeholder "${el.id || 'unnamed'}" has ${rect.width === 0 ? 'width: 0' : 'height: 0'}. Check the layout CSS.`);
|
|
577
|
+
}
|
|
578
|
+
else {
|
|
579
|
+
placeholders.push({
|
|
580
|
+
id: el.id || `placeholder-${placeholders.length}`,
|
|
581
|
+
x: pxToInch(rect.left),
|
|
582
|
+
y: pxToInch(rect.top),
|
|
583
|
+
w: pxToInch(rect.width),
|
|
584
|
+
h: pxToInch(rect.height),
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
processed.add(el);
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
// Extract images
|
|
591
|
+
if (el.tagName === 'IMG') {
|
|
592
|
+
const rect = htmlEl.getBoundingClientRect();
|
|
593
|
+
if (rect.width > 0 && rect.height > 0) {
|
|
594
|
+
const imgComputed = win.getComputedStyle(el);
|
|
595
|
+
const bodyRect = doc.body.getBoundingClientRect();
|
|
596
|
+
const coversWidth = rect.width >= bodyRect.width * 0.95;
|
|
597
|
+
const coversHeight = rect.height >= bodyRect.height * 0.95;
|
|
598
|
+
const nearOrigin = rect.left <= 10 && rect.top <= 10;
|
|
599
|
+
const isFullSlideImage = coversWidth && coversHeight && nearOrigin;
|
|
600
|
+
const objectFit = imgComputed.objectFit;
|
|
601
|
+
// Check for ancestor with overflow:hidden and border-radius
|
|
602
|
+
let imgRectRadius = null;
|
|
603
|
+
let ancestor = el.parentElement;
|
|
604
|
+
while (ancestor && ancestor !== doc.body) {
|
|
605
|
+
const ancestorComputed = win.getComputedStyle(ancestor);
|
|
606
|
+
const ancestorOverflow = ancestorComputed.overflow;
|
|
607
|
+
const ancestorBorderRadius = ancestorComputed.borderRadius;
|
|
608
|
+
if ((ancestorOverflow === 'hidden' || ancestorOverflow === 'clip') &&
|
|
609
|
+
ancestorBorderRadius) {
|
|
610
|
+
const radiusValue = parseFloat(ancestorBorderRadius);
|
|
611
|
+
if (radiusValue > 0) {
|
|
612
|
+
if (ancestorBorderRadius.includes('%')) {
|
|
613
|
+
const ancestorRect = ancestor.getBoundingClientRect();
|
|
614
|
+
const minDim = Math.min(ancestorRect.width, ancestorRect.height);
|
|
615
|
+
imgRectRadius = (radiusValue / 100) * pxToInch(minDim);
|
|
616
|
+
}
|
|
617
|
+
else if (ancestorBorderRadius.includes('pt')) {
|
|
618
|
+
imgRectRadius = radiusValue / 72;
|
|
619
|
+
}
|
|
620
|
+
else {
|
|
621
|
+
imgRectRadius = pxToInch(radiusValue);
|
|
622
|
+
}
|
|
623
|
+
break;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
ancestor = ancestor.parentElement;
|
|
627
|
+
}
|
|
628
|
+
const imageElement = {
|
|
629
|
+
type: isFullSlideImage ? 'slideBackgroundImage' : 'image',
|
|
630
|
+
src: el.src,
|
|
631
|
+
position: {
|
|
632
|
+
x: pxToInch(rect.left),
|
|
633
|
+
y: pxToInch(rect.top),
|
|
634
|
+
w: pxToInch(rect.width),
|
|
635
|
+
h: pxToInch(rect.height),
|
|
636
|
+
},
|
|
637
|
+
sizing: objectFit === 'cover' ? { type: 'cover' } : null,
|
|
638
|
+
};
|
|
639
|
+
if (imgRectRadius !== null) {
|
|
640
|
+
imageElement.rectRadius = imgRectRadius;
|
|
641
|
+
}
|
|
642
|
+
elements.push(imageElement);
|
|
643
|
+
processed.add(el);
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
// Extract DIVs with backgrounds/borders as shapes
|
|
648
|
+
const isContainer = el.tagName === 'DIV' && !textTags.includes(el.tagName);
|
|
649
|
+
if (isContainer) {
|
|
650
|
+
const computed = win.getComputedStyle(el);
|
|
651
|
+
const hasBg = computed.backgroundColor && computed.backgroundColor !== 'rgba(0, 0, 0, 0)';
|
|
652
|
+
// Check for background images or gradients
|
|
653
|
+
const elBgImage = computed.backgroundImage;
|
|
654
|
+
let bgImageUrl = null;
|
|
655
|
+
let bgImageSize = null;
|
|
656
|
+
let bgImagePosition = null;
|
|
657
|
+
let bgGradient = null;
|
|
658
|
+
if (elBgImage && elBgImage !== 'none') {
|
|
659
|
+
if (elBgImage.includes('linear-gradient') ||
|
|
660
|
+
elBgImage.includes('radial-gradient')) {
|
|
661
|
+
bgGradient = parseCssGradient(elBgImage);
|
|
662
|
+
}
|
|
663
|
+
else {
|
|
664
|
+
const urlMatch = elBgImage.match(/url\(["']?([^"')]+)["']?\)/);
|
|
665
|
+
if (urlMatch) {
|
|
666
|
+
bgImageUrl = urlMatch[1];
|
|
667
|
+
bgImageSize = computed.backgroundSize || 'auto';
|
|
668
|
+
bgImagePosition = computed.backgroundPosition || '0% 0%';
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
// Check for borders
|
|
673
|
+
const borderTop = computed.borderTopWidth;
|
|
674
|
+
const borderRight = computed.borderRightWidth;
|
|
675
|
+
const borderBottom = computed.borderBottomWidth;
|
|
676
|
+
const borderLeft = computed.borderLeftWidth;
|
|
677
|
+
const borders = [borderTop, borderRight, borderBottom, borderLeft].map((b) => parseFloat(b) || 0);
|
|
678
|
+
const hasBorder = borders.some((b) => b > 0);
|
|
679
|
+
const hasUniformBorder = hasBorder && borders.every((b) => b === borders[0]);
|
|
680
|
+
const borderLines = [];
|
|
681
|
+
if (hasBorder && !hasUniformBorder) {
|
|
682
|
+
const rect = htmlEl.getBoundingClientRect();
|
|
683
|
+
const x = pxToInch(rect.left);
|
|
684
|
+
const y = pxToInch(rect.top);
|
|
685
|
+
const w = pxToInch(rect.width);
|
|
686
|
+
const h = pxToInch(rect.height);
|
|
687
|
+
if (parseFloat(borderTop) > 0) {
|
|
688
|
+
const widthPt = pxToPoints(borderTop);
|
|
689
|
+
const inset = widthPt / 72 / 2;
|
|
690
|
+
borderLines.push({
|
|
691
|
+
type: 'line',
|
|
692
|
+
x1: x,
|
|
693
|
+
y1: y + inset,
|
|
694
|
+
x2: x + w,
|
|
695
|
+
y2: y + inset,
|
|
696
|
+
width: widthPt,
|
|
697
|
+
color: rgbToHex(computed.borderTopColor),
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
if (parseFloat(borderRight) > 0) {
|
|
701
|
+
const widthPt = pxToPoints(borderRight);
|
|
702
|
+
const inset = widthPt / 72 / 2;
|
|
703
|
+
borderLines.push({
|
|
704
|
+
type: 'line',
|
|
705
|
+
x1: x + w - inset,
|
|
706
|
+
y1: y,
|
|
707
|
+
x2: x + w - inset,
|
|
708
|
+
y2: y + h,
|
|
709
|
+
width: widthPt,
|
|
710
|
+
color: rgbToHex(computed.borderRightColor),
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
if (parseFloat(borderBottom) > 0) {
|
|
714
|
+
const widthPt = pxToPoints(borderBottom);
|
|
715
|
+
const inset = widthPt / 72 / 2;
|
|
716
|
+
borderLines.push({
|
|
717
|
+
type: 'line',
|
|
718
|
+
x1: x,
|
|
719
|
+
y1: y + h - inset,
|
|
720
|
+
x2: x + w,
|
|
721
|
+
y2: y + h - inset,
|
|
722
|
+
width: widthPt,
|
|
723
|
+
color: rgbToHex(computed.borderBottomColor),
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
if (parseFloat(borderLeft) > 0) {
|
|
727
|
+
const widthPt = pxToPoints(borderLeft);
|
|
728
|
+
const inset = widthPt / 72 / 2;
|
|
729
|
+
borderLines.push({
|
|
730
|
+
type: 'line',
|
|
731
|
+
x1: x + inset,
|
|
732
|
+
y1: y,
|
|
733
|
+
x2: x + inset,
|
|
734
|
+
y2: y + h,
|
|
735
|
+
width: widthPt,
|
|
736
|
+
color: rgbToHex(computed.borderLeftColor),
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
if (hasBg || hasBorder || bgImageUrl || bgGradient) {
|
|
741
|
+
const rect = htmlEl.getBoundingClientRect();
|
|
742
|
+
if (rect.width > 0 && rect.height > 0) {
|
|
743
|
+
const shadow = parseBoxShadow(computed.boxShadow);
|
|
744
|
+
// Add background image element first
|
|
745
|
+
if (bgImageUrl) {
|
|
746
|
+
const bgImgElement = {
|
|
747
|
+
type: 'backgroundImage',
|
|
748
|
+
src: bgImageUrl,
|
|
749
|
+
position: {
|
|
750
|
+
x: pxToInch(rect.left),
|
|
751
|
+
y: pxToInch(rect.top),
|
|
752
|
+
w: pxToInch(rect.width),
|
|
753
|
+
h: pxToInch(rect.height),
|
|
754
|
+
},
|
|
755
|
+
sizing: {
|
|
756
|
+
type: bgImageSize === 'cover'
|
|
757
|
+
? 'cover'
|
|
758
|
+
: bgImageSize === 'contain'
|
|
759
|
+
? 'contain'
|
|
760
|
+
: 'cover',
|
|
761
|
+
position: bgImagePosition,
|
|
762
|
+
},
|
|
763
|
+
};
|
|
764
|
+
elements.push(bgImgElement);
|
|
765
|
+
}
|
|
766
|
+
// Check for text children
|
|
767
|
+
const textChildren = Array.from(el.children).filter((child) => ['P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'SPAN'].includes(child.tagName));
|
|
768
|
+
const nonTextChildren = Array.from(el.children).filter((child) => !['P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'SPAN'].includes(child.tagName));
|
|
769
|
+
const isSingleTextChild = textChildren.length === 1 &&
|
|
770
|
+
textChildren[0].children.length === 0 &&
|
|
771
|
+
nonTextChildren.length === 0;
|
|
772
|
+
// Detect flexbox alignment
|
|
773
|
+
const display = computed.display;
|
|
774
|
+
const isFlexContainer = display === 'flex' || display === 'inline-flex';
|
|
775
|
+
const alignItems = computed.alignItems;
|
|
776
|
+
const justifyContent = computed.justifyContent;
|
|
777
|
+
let valign = 'top';
|
|
778
|
+
let align = 'left';
|
|
779
|
+
if (isFlexContainer) {
|
|
780
|
+
const flexDirection = computed.flexDirection || 'row';
|
|
781
|
+
if (flexDirection === 'row' || flexDirection === 'row-reverse') {
|
|
782
|
+
if (alignItems === 'center')
|
|
783
|
+
valign = 'middle';
|
|
784
|
+
else if (alignItems === 'flex-end' || alignItems === 'end')
|
|
785
|
+
valign = 'bottom';
|
|
786
|
+
else
|
|
787
|
+
valign = 'top';
|
|
788
|
+
if (justifyContent === 'center')
|
|
789
|
+
align = 'center';
|
|
790
|
+
else if (justifyContent === 'flex-end' || justifyContent === 'end')
|
|
791
|
+
align = 'right';
|
|
792
|
+
else
|
|
793
|
+
align = 'left';
|
|
794
|
+
}
|
|
795
|
+
else {
|
|
796
|
+
if (alignItems === 'center')
|
|
797
|
+
align = 'center';
|
|
798
|
+
else if (alignItems === 'flex-end' || alignItems === 'end')
|
|
799
|
+
align = 'right';
|
|
800
|
+
else
|
|
801
|
+
align = 'left';
|
|
802
|
+
if (justifyContent === 'center')
|
|
803
|
+
valign = 'middle';
|
|
804
|
+
else if (justifyContent === 'flex-end' || justifyContent === 'end')
|
|
805
|
+
valign = 'bottom';
|
|
806
|
+
else
|
|
807
|
+
valign = 'top';
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
else {
|
|
811
|
+
const textAlign = computed.textAlign;
|
|
812
|
+
if (textAlign === 'center') {
|
|
813
|
+
align = 'center';
|
|
814
|
+
}
|
|
815
|
+
else if (textAlign === 'right' || textAlign === 'end') {
|
|
816
|
+
align = 'right';
|
|
817
|
+
}
|
|
818
|
+
else {
|
|
819
|
+
const paddingLeft = parseFloat(computed.paddingLeft) || 0;
|
|
820
|
+
const paddingRight = parseFloat(computed.paddingRight) || 0;
|
|
821
|
+
const paddingDiff = Math.abs(paddingLeft - paddingRight);
|
|
822
|
+
if (paddingLeft > 0 && paddingDiff < 2) {
|
|
823
|
+
align = 'center';
|
|
824
|
+
}
|
|
825
|
+
else {
|
|
826
|
+
align = 'left';
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
let shapeText = '';
|
|
831
|
+
let shapeTextRuns = null;
|
|
832
|
+
let shapeStyle = null;
|
|
833
|
+
const hasTextChildren = textChildren.length > 0 && nonTextChildren.length === 0;
|
|
834
|
+
if (hasTextChildren) {
|
|
835
|
+
if (isSingleTextChild) {
|
|
836
|
+
const textEl = textChildren[0];
|
|
837
|
+
const textComputed = win.getComputedStyle(textEl);
|
|
838
|
+
shapeText = textEl.textContent.trim();
|
|
839
|
+
let fontFill = null;
|
|
840
|
+
const textBgClip = textComputed.webkitBackgroundClip ||
|
|
841
|
+
textComputed.backgroundClip;
|
|
842
|
+
const textFillColor = textComputed.webkitTextFillColor;
|
|
843
|
+
const textIsTextClip = textBgClip === 'text';
|
|
844
|
+
const textFillTransparent = textFillColor === 'transparent' ||
|
|
845
|
+
textFillColor === 'rgba(0, 0, 0, 0)' ||
|
|
846
|
+
(textFillColor &&
|
|
847
|
+
textFillColor.includes('rgba') &&
|
|
848
|
+
textFillColor.endsWith(', 0)'));
|
|
849
|
+
const parentBgClip = computed.webkitBackgroundClip || computed.backgroundClip;
|
|
850
|
+
const parentFillColor = computed.webkitTextFillColor;
|
|
851
|
+
const parentIsTextClip = parentBgClip === 'text';
|
|
852
|
+
const parentFillTransparent = parentFillColor === 'transparent' ||
|
|
853
|
+
parentFillColor === 'rgba(0, 0, 0, 0)' ||
|
|
854
|
+
(parentFillColor &&
|
|
855
|
+
parentFillColor.includes('rgba') &&
|
|
856
|
+
parentFillColor.endsWith(', 0)'));
|
|
857
|
+
if ((textIsTextClip && textFillTransparent) ||
|
|
858
|
+
(parentIsTextClip && parentFillTransparent)) {
|
|
859
|
+
const gradientSource = textIsTextClip && textFillTransparent
|
|
860
|
+
? textComputed.backgroundImage
|
|
861
|
+
: computed.backgroundImage;
|
|
862
|
+
if (gradientSource &&
|
|
863
|
+
gradientSource !== 'none' &&
|
|
864
|
+
(gradientSource.includes('linear-gradient') ||
|
|
865
|
+
gradientSource.includes('radial-gradient'))) {
|
|
866
|
+
fontFill = {
|
|
867
|
+
type: 'gradient',
|
|
868
|
+
gradient: parseCssGradient(gradientSource) || undefined,
|
|
869
|
+
};
|
|
870
|
+
bgGradient = null;
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
const effectiveColor = textComputed.color !== 'rgb(0, 0, 0)' ? textComputed.color : computed.color;
|
|
874
|
+
const isBold = textComputed.fontWeight === 'bold' ||
|
|
875
|
+
parseInt(textComputed.fontWeight) >= 600 ||
|
|
876
|
+
computed.fontWeight === 'bold' ||
|
|
877
|
+
parseInt(computed.fontWeight) >= 600;
|
|
878
|
+
let effectiveAlign = align;
|
|
879
|
+
const childTextAlign = textComputed.textAlign;
|
|
880
|
+
if (childTextAlign === 'center' ||
|
|
881
|
+
childTextAlign === 'right' ||
|
|
882
|
+
childTextAlign === 'end') {
|
|
883
|
+
effectiveAlign =
|
|
884
|
+
childTextAlign === 'end' ? 'right' : childTextAlign;
|
|
885
|
+
}
|
|
886
|
+
const whiteSpace = computed.whiteSpace || textComputed.whiteSpace;
|
|
887
|
+
const shouldNotWrap = whiteSpace === 'nowrap' ||
|
|
888
|
+
whiteSpace === 'pre' ||
|
|
889
|
+
rect.width < 100 ||
|
|
890
|
+
rect.height < 50;
|
|
891
|
+
shapeStyle = {
|
|
892
|
+
fontSize: pxToPoints(textComputed.fontSize || computed.fontSize),
|
|
893
|
+
fontFace: (textComputed.fontFamily || computed.fontFamily)
|
|
894
|
+
.split(',')[0]
|
|
895
|
+
.replace(/['"]/g, '')
|
|
896
|
+
.trim(),
|
|
897
|
+
color: fontFill ? null : rgbToHex(effectiveColor),
|
|
898
|
+
fontFill: fontFill,
|
|
899
|
+
bold: isBold,
|
|
900
|
+
align: effectiveAlign,
|
|
901
|
+
valign: valign,
|
|
902
|
+
inset: 0,
|
|
903
|
+
wrap: !shouldNotWrap,
|
|
904
|
+
};
|
|
905
|
+
processed.add(textEl);
|
|
906
|
+
}
|
|
907
|
+
else {
|
|
908
|
+
shapeTextRuns = [];
|
|
909
|
+
textChildren.forEach((textChild, idx) => {
|
|
910
|
+
const textEl = textChild;
|
|
911
|
+
const textComputed = win.getComputedStyle(textEl);
|
|
912
|
+
const text = textEl.textContent.trim();
|
|
913
|
+
if (!text)
|
|
914
|
+
return;
|
|
915
|
+
const isBold = textComputed.fontWeight === 'bold' ||
|
|
916
|
+
parseInt(textComputed.fontWeight) >= 600;
|
|
917
|
+
const isItalic = textComputed.fontStyle === 'italic';
|
|
918
|
+
const isUnderline = textComputed.textDecoration &&
|
|
919
|
+
textComputed.textDecoration.includes('underline');
|
|
920
|
+
const runText = idx > 0 && shapeTextRuns.length > 0 ? ' ' + text : text;
|
|
921
|
+
shapeTextRuns.push({
|
|
922
|
+
text: runText,
|
|
923
|
+
options: {
|
|
924
|
+
fontSize: pxToPoints(textComputed.fontSize),
|
|
925
|
+
fontFace: textComputed.fontFamily
|
|
926
|
+
.split(',')[0]
|
|
927
|
+
.replace(/['"]/g, '')
|
|
928
|
+
.trim(),
|
|
929
|
+
color: rgbToHex(textComputed.color),
|
|
930
|
+
bold: isBold,
|
|
931
|
+
italic: isItalic,
|
|
932
|
+
underline: isUnderline || false,
|
|
933
|
+
},
|
|
934
|
+
});
|
|
935
|
+
processed.add(textEl);
|
|
936
|
+
});
|
|
937
|
+
shapeStyle = {
|
|
938
|
+
align: align,
|
|
939
|
+
valign: valign,
|
|
940
|
+
inset: 0,
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
if (hasBg || hasUniformBorder || bgGradient) {
|
|
945
|
+
const shapeElement = {
|
|
946
|
+
type: 'shape',
|
|
947
|
+
text: shapeText,
|
|
948
|
+
textRuns: shapeTextRuns,
|
|
949
|
+
style: shapeStyle,
|
|
950
|
+
position: {
|
|
951
|
+
x: pxToInch(rect.left),
|
|
952
|
+
y: pxToInch(rect.top),
|
|
953
|
+
w: pxToInch(rect.width),
|
|
954
|
+
h: pxToInch(rect.height),
|
|
955
|
+
},
|
|
956
|
+
shape: {
|
|
957
|
+
fill: hasBg ? rgbToHex(computed.backgroundColor) : null,
|
|
958
|
+
gradient: bgGradient,
|
|
959
|
+
transparency: hasBg ? extractAlpha(computed.backgroundColor) : null,
|
|
960
|
+
line: hasUniformBorder
|
|
961
|
+
? {
|
|
962
|
+
color: rgbToHex(computed.borderColor),
|
|
963
|
+
width: pxToPoints(computed.borderWidth),
|
|
964
|
+
}
|
|
965
|
+
: null,
|
|
966
|
+
rectRadius: (() => {
|
|
967
|
+
const radius = computed.borderRadius;
|
|
968
|
+
const radiusValue = parseFloat(radius);
|
|
969
|
+
if (radiusValue === 0)
|
|
970
|
+
return 0;
|
|
971
|
+
if (radius.includes('%')) {
|
|
972
|
+
if (radiusValue >= 50)
|
|
973
|
+
return 1;
|
|
974
|
+
const minDim = Math.min(rect.width, rect.height);
|
|
975
|
+
return (radiusValue / 100) * pxToInch(minDim);
|
|
976
|
+
}
|
|
977
|
+
if (radius.includes('pt'))
|
|
978
|
+
return radiusValue / 72;
|
|
979
|
+
return radiusValue / PX_PER_IN;
|
|
980
|
+
})(),
|
|
981
|
+
shadow: shadow,
|
|
982
|
+
},
|
|
983
|
+
};
|
|
984
|
+
elements.push(shapeElement);
|
|
985
|
+
}
|
|
986
|
+
else if (shapeStyle && shapeStyle.fontFill) {
|
|
987
|
+
const fontFillTextElement = {
|
|
988
|
+
type: 'p',
|
|
989
|
+
text: shapeText,
|
|
990
|
+
position: {
|
|
991
|
+
x: pxToInch(rect.left),
|
|
992
|
+
y: pxToInch(rect.top),
|
|
993
|
+
w: pxToInch(rect.width),
|
|
994
|
+
h: pxToInch(rect.height),
|
|
995
|
+
},
|
|
996
|
+
style: shapeStyle,
|
|
997
|
+
};
|
|
998
|
+
elements.push(fontFillTextElement);
|
|
999
|
+
}
|
|
1000
|
+
else if (isSingleTextChild) {
|
|
1001
|
+
processed.delete(textChildren[0]);
|
|
1002
|
+
}
|
|
1003
|
+
elements.push(...borderLines);
|
|
1004
|
+
processed.add(el);
|
|
1005
|
+
return;
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
// Extract bullet lists
|
|
1010
|
+
if (el.tagName === 'UL' || el.tagName === 'OL') {
|
|
1011
|
+
const rect = htmlEl.getBoundingClientRect();
|
|
1012
|
+
if (rect.width === 0 || rect.height === 0)
|
|
1013
|
+
return;
|
|
1014
|
+
const liElements = Array.from(el.querySelectorAll('li'));
|
|
1015
|
+
const items = [];
|
|
1016
|
+
const ulComputed = win.getComputedStyle(el);
|
|
1017
|
+
const ulPaddingLeftPt = pxToPoints(ulComputed.paddingLeft);
|
|
1018
|
+
const marginLeft = ulPaddingLeftPt * 0.5;
|
|
1019
|
+
const textIndent = ulPaddingLeftPt * 0.5;
|
|
1020
|
+
liElements.forEach((li, idx) => {
|
|
1021
|
+
const isLast = idx === liElements.length - 1;
|
|
1022
|
+
const runs = parseInlineFormatting(li, { breakLine: false }, [], (x) => x, win);
|
|
1023
|
+
if (runs.length > 0) {
|
|
1024
|
+
runs[0].text = runs[0].text.replace(/^[•\-\*\u25AA\u25B8]\s*/, '');
|
|
1025
|
+
runs[0].options.bullet = { indent: textIndent };
|
|
1026
|
+
}
|
|
1027
|
+
if (runs.length > 0 && !isLast) {
|
|
1028
|
+
runs[runs.length - 1].options.breakLine = true;
|
|
1029
|
+
}
|
|
1030
|
+
items.push(...runs);
|
|
1031
|
+
});
|
|
1032
|
+
const liComputed = win.getComputedStyle(liElements[0] || el);
|
|
1033
|
+
const listFontSizePx = parseFloat(liComputed.fontSize);
|
|
1034
|
+
const listLineHeightPx = parseFloat(liComputed.lineHeight);
|
|
1035
|
+
const listLineHeightMultiplier = listFontSizePx > 0 && !isNaN(listLineHeightPx)
|
|
1036
|
+
? listLineHeightPx / listFontSizePx
|
|
1037
|
+
: 1.0;
|
|
1038
|
+
const listElement = {
|
|
1039
|
+
type: 'list',
|
|
1040
|
+
items: items,
|
|
1041
|
+
position: {
|
|
1042
|
+
x: pxToInch(rect.left),
|
|
1043
|
+
y: pxToInch(rect.top),
|
|
1044
|
+
w: pxToInch(rect.width),
|
|
1045
|
+
h: pxToInch(rect.height),
|
|
1046
|
+
},
|
|
1047
|
+
style: {
|
|
1048
|
+
fontSize: pxToPoints(liComputed.fontSize),
|
|
1049
|
+
fontFace: liComputed.fontFamily.split(',')[0].replace(/['"]/g, '').trim(),
|
|
1050
|
+
color: rgbToHex(liComputed.color),
|
|
1051
|
+
transparency: extractAlpha(liComputed.color),
|
|
1052
|
+
align: liComputed.textAlign === 'start'
|
|
1053
|
+
? 'left'
|
|
1054
|
+
: liComputed.textAlign,
|
|
1055
|
+
lineSpacing: pxToPoints(liComputed.fontSize) * listLineHeightMultiplier,
|
|
1056
|
+
paraSpaceBefore: 0,
|
|
1057
|
+
paraSpaceAfter: pxToPoints(liComputed.marginBottom),
|
|
1058
|
+
margin: [marginLeft, 0, 0, 0],
|
|
1059
|
+
},
|
|
1060
|
+
};
|
|
1061
|
+
elements.push(listElement);
|
|
1062
|
+
liElements.forEach((li) => processed.add(li));
|
|
1063
|
+
processed.add(el);
|
|
1064
|
+
return;
|
|
1065
|
+
}
|
|
1066
|
+
// Extract text elements
|
|
1067
|
+
if (!textTags.includes(el.tagName) || el.tagName === 'SPAN')
|
|
1068
|
+
return;
|
|
1069
|
+
const rect = htmlEl.getBoundingClientRect();
|
|
1070
|
+
const text = el.textContent.trim();
|
|
1071
|
+
if (rect.width === 0 || rect.height === 0 || !text)
|
|
1072
|
+
return;
|
|
1073
|
+
if (el.tagName !== 'LI' &&
|
|
1074
|
+
/^[•\-\*\u25AA\u25B8\u25CB\u25CF\u25C6\u25C7\u25A0\u25A1]\s/.test(text.trimStart())) {
|
|
1075
|
+
errors.push(`Text element <${el.tagName.toLowerCase()}> starts with bullet symbol "${text.substring(0, 20)}...". ` +
|
|
1076
|
+
'Use <ul> or <ol> lists instead of manual bullet symbols.');
|
|
1077
|
+
return;
|
|
1078
|
+
}
|
|
1079
|
+
const computed = win.getComputedStyle(el);
|
|
1080
|
+
const rotation = getRotation(computed.transform, computed.writingMode);
|
|
1081
|
+
const { x, y, w, h } = getPositionAndSize(htmlEl, rect, rotation);
|
|
1082
|
+
const fontSizePx = parseFloat(computed.fontSize);
|
|
1083
|
+
const lineHeightPx = parseFloat(computed.lineHeight);
|
|
1084
|
+
const lineHeightMultiplier = fontSizePx > 0 && !isNaN(lineHeightPx) ? lineHeightPx / fontSizePx : 1.0;
|
|
1085
|
+
let textAlign = computed.textAlign === 'start' ? 'left' : computed.textAlign;
|
|
1086
|
+
let valign = null;
|
|
1087
|
+
const checkFlexParent = (parent) => {
|
|
1088
|
+
if (!parent)
|
|
1089
|
+
return null;
|
|
1090
|
+
const parentComputed = win.getComputedStyle(parent);
|
|
1091
|
+
const parentDisplay = parentComputed.display;
|
|
1092
|
+
if (parentDisplay === 'flex' || parentDisplay === 'inline-flex') {
|
|
1093
|
+
const flexDirection = parentComputed.flexDirection || 'row';
|
|
1094
|
+
const alignItems = parentComputed.alignItems;
|
|
1095
|
+
if ((flexDirection === 'row' || flexDirection === 'row-reverse') &&
|
|
1096
|
+
parent.children.length > 1) {
|
|
1097
|
+
return { alignItems, flexDirection };
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
return null;
|
|
1101
|
+
};
|
|
1102
|
+
const parentEl = el.parentElement;
|
|
1103
|
+
let flexInfo = checkFlexParent(parentEl);
|
|
1104
|
+
if (!flexInfo && parentEl) {
|
|
1105
|
+
flexInfo = checkFlexParent(parentEl.parentElement);
|
|
1106
|
+
}
|
|
1107
|
+
if (flexInfo) {
|
|
1108
|
+
if (flexInfo.alignItems === 'center') {
|
|
1109
|
+
valign = 'middle';
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
const baseStyle = {
|
|
1113
|
+
fontSize: pxToPoints(computed.fontSize),
|
|
1114
|
+
fontFace: computed.fontFamily.split(',')[0].replace(/['"]/g, '').trim(),
|
|
1115
|
+
color: rgbToHex(computed.color),
|
|
1116
|
+
align: textAlign,
|
|
1117
|
+
valign: valign,
|
|
1118
|
+
lineSpacing: pxToPoints(computed.fontSize) * lineHeightMultiplier,
|
|
1119
|
+
paraSpaceBefore: pxToPoints(computed.marginTop),
|
|
1120
|
+
paraSpaceAfter: pxToPoints(computed.marginBottom),
|
|
1121
|
+
margin: [
|
|
1122
|
+
pxToPoints(computed.paddingLeft),
|
|
1123
|
+
pxToPoints(computed.paddingRight),
|
|
1124
|
+
pxToPoints(computed.paddingBottom),
|
|
1125
|
+
pxToPoints(computed.paddingTop),
|
|
1126
|
+
],
|
|
1127
|
+
};
|
|
1128
|
+
const transparency = extractAlpha(computed.color);
|
|
1129
|
+
if (transparency !== null)
|
|
1130
|
+
baseStyle.transparency = transparency;
|
|
1131
|
+
if (rotation !== null)
|
|
1132
|
+
baseStyle.rotate = rotation;
|
|
1133
|
+
const bgClip = computed.webkitBackgroundClip || computed.backgroundClip;
|
|
1134
|
+
const textFillColor = computed.webkitTextFillColor;
|
|
1135
|
+
const isTextFillTransparent = textFillColor === 'transparent' ||
|
|
1136
|
+
textFillColor === 'rgba(0, 0, 0, 0)' ||
|
|
1137
|
+
(textFillColor &&
|
|
1138
|
+
textFillColor.includes('rgba') &&
|
|
1139
|
+
textFillColor.endsWith(', 0)'));
|
|
1140
|
+
if (bgClip === 'text' && isTextFillTransparent) {
|
|
1141
|
+
const elBgImg = computed.backgroundImage;
|
|
1142
|
+
if (elBgImg &&
|
|
1143
|
+
elBgImg !== 'none' &&
|
|
1144
|
+
(elBgImg.includes('linear-gradient') || elBgImg.includes('radial-gradient'))) {
|
|
1145
|
+
const textGradient = parseCssGradient(elBgImg);
|
|
1146
|
+
if (textGradient) {
|
|
1147
|
+
baseStyle.fontFill = { type: 'gradient', gradient: textGradient };
|
|
1148
|
+
delete baseStyle.color;
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
const hasFormatting = el.querySelector('b, i, u, strong, em, span, br');
|
|
1153
|
+
if (hasFormatting) {
|
|
1154
|
+
const transformStr = computed.textTransform;
|
|
1155
|
+
const runs = parseInlineFormatting(el, {}, [], (str) => applyTextTransform(str, transformStr), win);
|
|
1156
|
+
const textElement = {
|
|
1157
|
+
type: el.tagName.toLowerCase(),
|
|
1158
|
+
text: runs,
|
|
1159
|
+
position: { x: pxToInch(x), y: pxToInch(y), w: pxToInch(w), h: pxToInch(h) },
|
|
1160
|
+
style: baseStyle,
|
|
1161
|
+
};
|
|
1162
|
+
elements.push(textElement);
|
|
1163
|
+
}
|
|
1164
|
+
else {
|
|
1165
|
+
const textTransformVal = computed.textTransform;
|
|
1166
|
+
const transformedText = applyTextTransform(text, textTransformVal);
|
|
1167
|
+
const isBold = computed.fontWeight === 'bold' || parseInt(computed.fontWeight) >= 600;
|
|
1168
|
+
const textElement = {
|
|
1169
|
+
type: el.tagName.toLowerCase(),
|
|
1170
|
+
text: transformedText,
|
|
1171
|
+
position: { x: pxToInch(x), y: pxToInch(y), w: pxToInch(w), h: pxToInch(h) },
|
|
1172
|
+
style: {
|
|
1173
|
+
...baseStyle,
|
|
1174
|
+
bold: isBold && !shouldSkipBold(computed.fontFamily),
|
|
1175
|
+
italic: computed.fontStyle === 'italic',
|
|
1176
|
+
underline: computed.textDecoration.includes('underline'),
|
|
1177
|
+
},
|
|
1178
|
+
};
|
|
1179
|
+
elements.push(textElement);
|
|
1180
|
+
}
|
|
1181
|
+
processed.add(el);
|
|
1182
|
+
});
|
|
1183
|
+
return { background, elements, placeholders, errors };
|
|
1184
|
+
}
|
|
1185
|
+
//# sourceMappingURL=parse.js.map
|