docgen-utils 1.0.22 → 1.0.24
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/dist/bundle.js +185 -27
- package/dist/bundle.min.js +74 -73
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/packages/slides/common.d.ts +16 -0
- package/dist/packages/slides/common.d.ts.map +1 -1
- package/dist/packages/slides/convert.d.ts.map +1 -1
- package/dist/packages/slides/convert.js +64 -32
- package/dist/packages/slides/convert.js.map +1 -1
- package/dist/packages/slides/parse.d.ts.map +1 -1
- package/dist/packages/slides/parse.js +229 -14
- package/dist/packages/slides/parse.js.map +1 -1
- package/package.json +1 -1
|
@@ -110,6 +110,40 @@ function extractRotationAngle(computed) {
|
|
|
110
110
|
}
|
|
111
111
|
return null;
|
|
112
112
|
}
|
|
113
|
+
/**
|
|
114
|
+
* Extract translation (translateX, translateY) from a CSS transform matrix.
|
|
115
|
+
* CSS transforms like `translate(-50%, 20px)` are resolved to a matrix by the browser.
|
|
116
|
+
* The matrix format is: matrix(a, b, c, d, tx, ty) where tx/ty are the translations.
|
|
117
|
+
* Returns {x: number, y: number} in pixels, or null if no translation.
|
|
118
|
+
*/
|
|
119
|
+
function extractTranslation(computed) {
|
|
120
|
+
const transform = computed.transform;
|
|
121
|
+
if (!transform || transform === 'none')
|
|
122
|
+
return null;
|
|
123
|
+
// matrix(a, b, c, d, tx, ty) - 2D transform
|
|
124
|
+
const matrix2dMatch = transform.match(/matrix\(([-\d.e]+),\s*([-\d.e]+),\s*([-\d.e]+),\s*([-\d.e]+),\s*([-\d.e]+),\s*([-\d.e]+)\)/);
|
|
125
|
+
if (matrix2dMatch) {
|
|
126
|
+
const tx = parseFloat(matrix2dMatch[5]);
|
|
127
|
+
const ty = parseFloat(matrix2dMatch[6]);
|
|
128
|
+
if (tx !== 0 || ty !== 0) {
|
|
129
|
+
return { x: tx, y: ty };
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
// matrix3d(m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44)
|
|
133
|
+
// tx = m41, ty = m42
|
|
134
|
+
const matrix3dMatch = transform.match(/matrix3d\(([^)]+)\)/);
|
|
135
|
+
if (matrix3dMatch) {
|
|
136
|
+
const values = matrix3dMatch[1].split(',').map(v => parseFloat(v.trim()));
|
|
137
|
+
if (values.length >= 14) {
|
|
138
|
+
const tx = values[12]; // m41
|
|
139
|
+
const ty = values[13]; // m42
|
|
140
|
+
if (tx !== 0 || ty !== 0) {
|
|
141
|
+
return { x: tx, y: ty };
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
113
147
|
// ---------------------------------------------------------------------------
|
|
114
148
|
// Effective opacity (accumulated from ancestor chain)
|
|
115
149
|
// ---------------------------------------------------------------------------
|
|
@@ -1426,8 +1460,9 @@ function extractPseudoElements(el, win) {
|
|
|
1426
1460
|
pHeight = parentRect.height;
|
|
1427
1461
|
}
|
|
1428
1462
|
}
|
|
1429
|
-
// Skip
|
|
1430
|
-
|
|
1463
|
+
// Skip elements that are effectively invisible (less than 0.5px in either dimension).
|
|
1464
|
+
// The threshold is low to handle scaled HTML (1280px → 960px means 1px → 0.75px).
|
|
1465
|
+
if (pWidth < 0.5 || pHeight < 0.5)
|
|
1431
1466
|
continue;
|
|
1432
1467
|
// Determine position (relative to parent)
|
|
1433
1468
|
let pLeft = parseFloat(pComputed.left);
|
|
@@ -1605,8 +1640,13 @@ function extractPseudoElements(el, win) {
|
|
|
1605
1640
|
borderOffsetLeft = parseFloat(parentComputedForBorder.borderLeftWidth) || 0;
|
|
1606
1641
|
borderOffsetTop = parseFloat(parentComputedForBorder.borderTopWidth) || 0;
|
|
1607
1642
|
}
|
|
1608
|
-
|
|
1609
|
-
|
|
1643
|
+
// Apply CSS transform translation if present
|
|
1644
|
+
// Transforms like translateX/translateY/translate are resolved to a matrix by the browser
|
|
1645
|
+
const translation = extractTranslation(pComputed);
|
|
1646
|
+
const transformOffsetX = translation?.x ?? 0;
|
|
1647
|
+
const transformOffsetY = translation?.y ?? 0;
|
|
1648
|
+
const absLeft = parentRect.left + pLeft + borderOffsetLeft + transformOffsetX;
|
|
1649
|
+
const absTop = parentRect.top + pTop + borderOffsetTop + transformOffsetY;
|
|
1610
1650
|
// Check for visual content: background color, gradient, or border
|
|
1611
1651
|
const hasBg = pComputed.backgroundColor && pComputed.backgroundColor !== 'rgba(0, 0, 0, 0)';
|
|
1612
1652
|
const bgImage = pComputed.backgroundImage;
|
|
@@ -1822,7 +1862,22 @@ function parseInlineFormatting(element, baseOptions, runs, baseTextTransform, wi
|
|
|
1822
1862
|
const transformStr = computed.textTransform;
|
|
1823
1863
|
textTransform = (text) => applyTextTransform(text, transformStr);
|
|
1824
1864
|
}
|
|
1865
|
+
// If a <br> preceded this element, apply softBreakBefore to the first run
|
|
1866
|
+
// generated by the recursive call. We do this by noting the current runs
|
|
1867
|
+
// count, recursing, then marking the first new run (if any) with softBreakBefore.
|
|
1868
|
+
const runsBeforeRecurse = runs.length;
|
|
1869
|
+
const hadPendingSoftBreak = pendingSoftBreak;
|
|
1870
|
+
if (pendingSoftBreak) {
|
|
1871
|
+
pendingSoftBreak = false; // Clear before recursion
|
|
1872
|
+
}
|
|
1825
1873
|
parseInlineFormatting(el, options, runs, textTransform, win);
|
|
1874
|
+
// Apply softBreakBefore to the first run added by the recursive call
|
|
1875
|
+
if (hadPendingSoftBreak && runs.length > runsBeforeRecurse) {
|
|
1876
|
+
runs[runsBeforeRecurse].options = {
|
|
1877
|
+
...runs[runsBeforeRecurse].options,
|
|
1878
|
+
softBreakBefore: true,
|
|
1879
|
+
};
|
|
1880
|
+
}
|
|
1826
1881
|
}
|
|
1827
1882
|
prevNodeIsText = false;
|
|
1828
1883
|
}
|
|
@@ -2207,6 +2262,33 @@ export function parseSlideHtml(doc) {
|
|
|
2207
2262
|
const imgEl = el;
|
|
2208
2263
|
const natW = imgEl.naturalWidth;
|
|
2209
2264
|
const natH = imgEl.naturalHeight;
|
|
2265
|
+
// Parse object-position for cover crop anchor point.
|
|
2266
|
+
// CSS object-position defaults to "50% 50%" (center center).
|
|
2267
|
+
// We convert to [xFraction, yFraction] in the range 0.0–1.0.
|
|
2268
|
+
let objectPosition;
|
|
2269
|
+
const objPos = imgComputed.objectPosition;
|
|
2270
|
+
if (objPos && objectFit === 'cover') {
|
|
2271
|
+
const parts = objPos.trim().split(/\s+/);
|
|
2272
|
+
const parseFraction = (val, dimension) => {
|
|
2273
|
+
if (val.endsWith('%')) {
|
|
2274
|
+
return parseFloat(val) / 100;
|
|
2275
|
+
}
|
|
2276
|
+
// px or pt value — convert to fraction of the display dimension
|
|
2277
|
+
const px = parseFloat(val);
|
|
2278
|
+
if (!isNaN(px) && dimension > 0) {
|
|
2279
|
+
return px / dimension;
|
|
2280
|
+
}
|
|
2281
|
+
return 0.5; // default center
|
|
2282
|
+
};
|
|
2283
|
+
const displayW = wasClipped ? clippedW : rect.width;
|
|
2284
|
+
const displayH = wasClipped ? clippedH : rect.height;
|
|
2285
|
+
const xFrac = parts.length >= 1 ? parseFraction(parts[0], displayW) : 0.5;
|
|
2286
|
+
const yFrac = parts.length >= 2 ? parseFraction(parts[1], displayH) : xFrac;
|
|
2287
|
+
// Only store if non-default to keep output clean
|
|
2288
|
+
if (Math.abs(xFrac - 0.5) > 0.001 || Math.abs(yFrac - 0.5) > 0.001) {
|
|
2289
|
+
objectPosition = [xFrac, yFrac];
|
|
2290
|
+
}
|
|
2291
|
+
}
|
|
2210
2292
|
const imageElement = {
|
|
2211
2293
|
type: isFullSlideImage ? 'slideBackgroundImage' : 'image',
|
|
2212
2294
|
src: imgSrc,
|
|
@@ -2219,10 +2301,14 @@ export function parseSlideHtml(doc) {
|
|
|
2219
2301
|
sizing: objectFit === 'cover' ? { type: 'cover' } : null,
|
|
2220
2302
|
};
|
|
2221
2303
|
// Store natural dimensions for cover crop calculation in convert.ts
|
|
2222
|
-
if (objectFit === 'cover' && natW > 0 && natH > 0
|
|
2304
|
+
if (objectFit === 'cover' && natW > 0 && natH > 0) {
|
|
2223
2305
|
imageElement.naturalWidth = natW;
|
|
2224
2306
|
imageElement.naturalHeight = natH;
|
|
2225
2307
|
}
|
|
2308
|
+
// Store object-position for cover crop anchor
|
|
2309
|
+
if (objectPosition) {
|
|
2310
|
+
imageElement.objectPosition = objectPosition;
|
|
2311
|
+
}
|
|
2226
2312
|
if (imgRectRadius !== null) {
|
|
2227
2313
|
imageElement.rectRadius = imgRectRadius;
|
|
2228
2314
|
}
|
|
@@ -2772,8 +2858,34 @@ export function parseSlideHtml(doc) {
|
|
|
2772
2858
|
const allChildren = Array.from(el.children);
|
|
2773
2859
|
// Collect text-bearing children: standard text tags AND leaf DIVs (no child elements, only text nodes)
|
|
2774
2860
|
const textChildren = allChildren.filter((child) => {
|
|
2775
|
-
if (textTagSet.has(child.tagName))
|
|
2861
|
+
if (textTagSet.has(child.tagName)) {
|
|
2862
|
+
// SPANs with background or border styling should NOT be merged as text children.
|
|
2863
|
+
// They need to be processed independently as shape elements to preserve their styling.
|
|
2864
|
+
if (child.tagName === 'SPAN') {
|
|
2865
|
+
const spanComputed = win.getComputedStyle(child);
|
|
2866
|
+
const spanHasBg = spanComputed.backgroundColor && spanComputed.backgroundColor !== 'rgba(0, 0, 0, 0)';
|
|
2867
|
+
const spanHasBorder = (parseFloat(spanComputed.borderTopWidth) || 0) > 0 ||
|
|
2868
|
+
(parseFloat(spanComputed.borderRightWidth) || 0) > 0 ||
|
|
2869
|
+
(parseFloat(spanComputed.borderBottomWidth) || 0) > 0 ||
|
|
2870
|
+
(parseFloat(spanComputed.borderLeftWidth) || 0) > 0;
|
|
2871
|
+
const spanHasGradient = spanComputed.backgroundImage &&
|
|
2872
|
+
spanComputed.backgroundImage !== 'none' &&
|
|
2873
|
+
(spanComputed.backgroundImage.includes('linear-gradient') ||
|
|
2874
|
+
spanComputed.backgroundImage.includes('radial-gradient'));
|
|
2875
|
+
// Check if this is gradient text (background-clip: text) - those SHOULD be text children
|
|
2876
|
+
const spanBgClip = spanComputed.webkitBackgroundClip || spanComputed.backgroundClip;
|
|
2877
|
+
const spanTextFillColor = spanComputed.webkitTextFillColor;
|
|
2878
|
+
const isGradientText = spanBgClip === 'text' &&
|
|
2879
|
+
(spanTextFillColor === 'transparent' ||
|
|
2880
|
+
spanTextFillColor === 'rgba(0, 0, 0, 0)' ||
|
|
2881
|
+
(spanTextFillColor && spanTextFillColor.includes('rgba') && spanTextFillColor.endsWith(', 0)')));
|
|
2882
|
+
// Exclude styled SPANs (but not gradient text SPANs)
|
|
2883
|
+
if ((spanHasBg || spanHasBorder || spanHasGradient) && !isGradientText) {
|
|
2884
|
+
return false;
|
|
2885
|
+
}
|
|
2886
|
+
}
|
|
2776
2887
|
return true;
|
|
2888
|
+
}
|
|
2777
2889
|
// Include text-only DIVs: DIVs that contain only inline/text content
|
|
2778
2890
|
// The transformer wraps bare text in <p> tags, so a DIV with a single <p> child
|
|
2779
2891
|
// is still effectively a text-only DIV. Examples after transformation:
|
|
@@ -2926,8 +3038,17 @@ export function parseSlideHtml(doc) {
|
|
|
2926
3038
|
// - It's a single text child (isSingleTextChild), OR
|
|
2927
3039
|
// - There are ONLY text children (no non-text siblings to compete with for space), OR
|
|
2928
3040
|
// - There's direct text content (text nodes, not child elements)
|
|
2929
|
-
|
|
2930
|
-
|
|
3041
|
+
//
|
|
3042
|
+
// CRITICAL: If the parent styled DIV is a flex-row container with multiple
|
|
3043
|
+
// children, the children are laid out horizontally and must be extracted
|
|
3044
|
+
// separately with their own positions. Merging them into a single text box
|
|
3045
|
+
// would stack them vertically, breaking the layout.
|
|
3046
|
+
const isParentFlexRow = isFlexContainer &&
|
|
3047
|
+
(computed.flexDirection === 'row' || computed.flexDirection === 'row-reverse' || !computed.flexDirection) &&
|
|
3048
|
+
allChildren.length > 1;
|
|
3049
|
+
const isParentGrid = display === 'grid' || display === 'inline-grid';
|
|
3050
|
+
const shouldMergeText = !isParentFlexRow && !isParentGrid && (hasTextChildren && (isSingleTextChild ||
|
|
3051
|
+
nonTextChildren.length === 0) || hasDirectText);
|
|
2931
3052
|
if (shouldMergeText) {
|
|
2932
3053
|
if (isSingleTextChild) {
|
|
2933
3054
|
const textEl = textChildren[0];
|
|
@@ -3409,16 +3530,55 @@ export function parseSlideHtml(doc) {
|
|
|
3409
3530
|
(plainDivFlexDir === 'row' || plainDivFlexDir === 'row-reverse') &&
|
|
3410
3531
|
plainDivChildCount > 1;
|
|
3411
3532
|
const isPlainDivGrid = plainDivDisplay === 'grid' || plainDivDisplay === 'inline-grid';
|
|
3533
|
+
// Helper to check if a child element is inline text (not a styled SPAN/A that needs shape treatment)
|
|
3534
|
+
const isInlineTextChild = (ce) => {
|
|
3535
|
+
const tagName = ce.tagName.toUpperCase();
|
|
3536
|
+
if (!inlineTextTagsSet.has(tagName))
|
|
3537
|
+
return false;
|
|
3538
|
+
// SPANs and A elements with background/border styling need to be processed as shapes
|
|
3539
|
+
// They should NOT be treated as inline text children
|
|
3540
|
+
if (tagName === 'SPAN' || tagName === 'A') {
|
|
3541
|
+
const ceComputed = win.getComputedStyle(ce);
|
|
3542
|
+
const ceHasBg = ceComputed.backgroundColor && ceComputed.backgroundColor !== 'rgba(0, 0, 0, 0)';
|
|
3543
|
+
const ceHasBorder = (parseFloat(ceComputed.borderTopWidth) || 0) > 0 ||
|
|
3544
|
+
(parseFloat(ceComputed.borderRightWidth) || 0) > 0 ||
|
|
3545
|
+
(parseFloat(ceComputed.borderBottomWidth) || 0) > 0 ||
|
|
3546
|
+
(parseFloat(ceComputed.borderLeftWidth) || 0) > 0;
|
|
3547
|
+
const ceHasGradient = ceComputed.backgroundImage &&
|
|
3548
|
+
ceComputed.backgroundImage !== 'none' &&
|
|
3549
|
+
(ceComputed.backgroundImage.includes('linear-gradient') ||
|
|
3550
|
+
ceComputed.backgroundImage.includes('radial-gradient'));
|
|
3551
|
+
// Check if this is gradient text (background-clip: text) - those ARE inline text
|
|
3552
|
+
const ceBgClip = ceComputed.webkitBackgroundClip || ceComputed.backgroundClip;
|
|
3553
|
+
const ceTextFillColor = ceComputed.webkitTextFillColor;
|
|
3554
|
+
const isGradientText = ceBgClip === 'text' &&
|
|
3555
|
+
(ceTextFillColor === 'transparent' ||
|
|
3556
|
+
ceTextFillColor === 'rgba(0, 0, 0, 0)' ||
|
|
3557
|
+
(ceTextFillColor && ceTextFillColor.includes('rgba') && ceTextFillColor.endsWith(', 0)')));
|
|
3558
|
+
// Styled elements (except gradient text) should NOT be treated as inline text
|
|
3559
|
+
if ((ceHasBg || ceHasBorder || ceHasGradient) && !isGradientText) {
|
|
3560
|
+
return false;
|
|
3561
|
+
}
|
|
3562
|
+
}
|
|
3563
|
+
return true;
|
|
3564
|
+
};
|
|
3412
3565
|
const allChildrenAreInlineText = !isPlainDivFlexRow && !isPlainDivGrid && (childElements.length === 0 ||
|
|
3413
|
-
childElements.every(ce =>
|
|
3566
|
+
childElements.every(ce => isInlineTextChild(ce)));
|
|
3414
3567
|
// Only proceed if this DIV has meaningful text content
|
|
3415
3568
|
// AND has no structural child elements that would be extracted separately
|
|
3416
3569
|
// SVG children are decorative (already extracted as images) and shouldn't block text extraction
|
|
3570
|
+
// Styled SPANs/A elements (with bg/border) ARE structural - they become shapes
|
|
3417
3571
|
const structuralChildElements = childElements.filter(ce => {
|
|
3418
3572
|
const tagName = ce.tagName.toUpperCase();
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3573
|
+
if (tagName === 'BR' || tagName === 'SVG')
|
|
3574
|
+
return false;
|
|
3575
|
+
// If it's not an inline text element, it's structural
|
|
3576
|
+
if (!inlineTextTagsSet.has(tagName))
|
|
3577
|
+
return true;
|
|
3578
|
+
// For SPAN/A, check if it's styled (then it's structural)
|
|
3579
|
+
if (!isInlineTextChild(ce))
|
|
3580
|
+
return true;
|
|
3581
|
+
return false;
|
|
3422
3582
|
});
|
|
3423
3583
|
const hasStructuralChildren = structuralChildElements.length > 0;
|
|
3424
3584
|
// If all children are inline text elements (or no children), use the full textContent
|
|
@@ -3475,6 +3635,11 @@ export function parseSlideHtml(doc) {
|
|
|
3475
3635
|
// use parseInlineFormatting to preserve per-run styling (color, bold, etc.).
|
|
3476
3636
|
// Otherwise, create a single text run.
|
|
3477
3637
|
let textRuns;
|
|
3638
|
+
// Check if this is a flex-column container - children are stacked vertically
|
|
3639
|
+
// and should be separated by line breaks in the PPTX output
|
|
3640
|
+
const isFlexColumn = (plainDivDisplay === 'flex' || plainDivDisplay === 'inline-flex') &&
|
|
3641
|
+
(plainDivFlexDir === 'column' || plainDivFlexDir === 'column-reverse') &&
|
|
3642
|
+
childElements.length > 1;
|
|
3478
3643
|
if (allChildrenAreInlineText && childElements.length > 0) {
|
|
3479
3644
|
const baseRunOptions = {
|
|
3480
3645
|
fontSize: pxToPoints(computed2.fontSize),
|
|
@@ -3490,7 +3655,57 @@ export function parseSlideHtml(doc) {
|
|
|
3490
3655
|
if (computed2.textTransform && computed2.textTransform !== 'none') {
|
|
3491
3656
|
textTransformFn = (text) => applyTextTransform(text, computed2.textTransform);
|
|
3492
3657
|
}
|
|
3493
|
-
|
|
3658
|
+
// For flex-column containers, process each child separately and add
|
|
3659
|
+
// line breaks between them to preserve the vertical stacking
|
|
3660
|
+
if (isFlexColumn) {
|
|
3661
|
+
textRuns = [];
|
|
3662
|
+
for (let ci = 0; ci < childElements.length; ci++) {
|
|
3663
|
+
const childEl = childElements[ci];
|
|
3664
|
+
const childComputed = win.getComputedStyle(childEl);
|
|
3665
|
+
const childText = getTransformedText(childEl, childComputed);
|
|
3666
|
+
if (!childText)
|
|
3667
|
+
continue;
|
|
3668
|
+
// Apply text-transform
|
|
3669
|
+
const transformedChildText = textTransformFn(childText);
|
|
3670
|
+
// Build run options for this child, inheriting from parent but overriding
|
|
3671
|
+
// with child-specific styles
|
|
3672
|
+
const childRunOptions = { ...baseRunOptions };
|
|
3673
|
+
const childFontSize = pxToPoints(childComputed.fontSize);
|
|
3674
|
+
if (childFontSize)
|
|
3675
|
+
childRunOptions.fontSize = childFontSize;
|
|
3676
|
+
const childFontFace = extractFontFace(childComputed.fontFamily);
|
|
3677
|
+
if (childFontFace)
|
|
3678
|
+
childRunOptions.fontFace = childFontFace;
|
|
3679
|
+
const childColor = rgbToHex(childComputed.color);
|
|
3680
|
+
if (childColor)
|
|
3681
|
+
childRunOptions.color = childColor;
|
|
3682
|
+
if (parseInt(childComputed.fontWeight) >= 600) {
|
|
3683
|
+
childRunOptions.bold = true;
|
|
3684
|
+
}
|
|
3685
|
+
else if (parseInt(childComputed.fontWeight) < 600) {
|
|
3686
|
+
childRunOptions.bold = false;
|
|
3687
|
+
}
|
|
3688
|
+
if (childComputed.fontStyle === 'italic') {
|
|
3689
|
+
childRunOptions.italic = true;
|
|
3690
|
+
}
|
|
3691
|
+
else if (childComputed.fontStyle !== 'italic') {
|
|
3692
|
+
childRunOptions.italic = false;
|
|
3693
|
+
}
|
|
3694
|
+
// Check for letter-spacing
|
|
3695
|
+
const childLs = extractLetterSpacing(childComputed);
|
|
3696
|
+
if (childLs !== null)
|
|
3697
|
+
childRunOptions.charSpacing = childLs;
|
|
3698
|
+
// For all children except the last, add a newline at the end of the text
|
|
3699
|
+
// to create vertical stacking in the PPTX output
|
|
3700
|
+
const textWithBreak = ci < childElements.length - 1
|
|
3701
|
+
? transformedChildText + '\n'
|
|
3702
|
+
: transformedChildText;
|
|
3703
|
+
textRuns.push({ text: textWithBreak, options: childRunOptions });
|
|
3704
|
+
}
|
|
3705
|
+
}
|
|
3706
|
+
else {
|
|
3707
|
+
textRuns = parseInlineFormatting(el, baseRunOptions, [], textTransformFn, win);
|
|
3708
|
+
}
|
|
3494
3709
|
// Fallback to single run if parseInlineFormatting produced nothing
|
|
3495
3710
|
if (textRuns.length === 0) {
|
|
3496
3711
|
textRuns = [{ text: extractedText, options: {} }];
|
|
@@ -3924,9 +4139,9 @@ export function parseSlideHtml(doc) {
|
|
|
3924
4139
|
// Some DIVs (e.g., .image-side with overflow:hidden) have no bg/border/gradient
|
|
3925
4140
|
// themselves but have ::before/::after pseudo-elements with gradient overlays.
|
|
3926
4141
|
doc.querySelectorAll('div').forEach((divEl) => {
|
|
4142
|
+
const htmlDiv = divEl;
|
|
3927
4143
|
if (processed.has(divEl))
|
|
3928
4144
|
return; // Already handled in second pass
|
|
3929
|
-
const htmlDiv = divEl;
|
|
3930
4145
|
if (htmlDiv === body)
|
|
3931
4146
|
return; // Body already handled
|
|
3932
4147
|
const rect = htmlDiv.getBoundingClientRect();
|