@zumer/snapdom 2.0.0-dev.2 → 2.0.0-dev.3

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/snapdom.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
 
2
2
  /*
3
3
  * snapdom
4
- * v.2.0.0-dev.2
4
+ * v.2.0.0-dev.3
5
5
  * Author Juan Martin Muda
6
6
  * License MIT
7
7
  **/
@@ -919,7 +919,7 @@ function resolveCSSVars(sourceEl, cloneEl) {
919
919
  }
920
920
  }
921
921
 
922
- // src/core/clone.js
922
+ // src/utils/clone.helpers.js
923
923
  function idleCallback(childList, callback, fast) {
924
924
  return Promise.all(childList.map((child) => {
925
925
  return new Promise((resolve) => {
@@ -1131,6 +1131,134 @@ async function rasterizeIframe(iframe, sessionCache, options) {
1131
1131
  wrapper.appendChild(imgEl);
1132
1132
  return wrapper;
1133
1133
  }
1134
+ var _blobToDataUrlCache = /* @__PURE__ */ new Map();
1135
+ async function blobUrlToDataUrl(blobUrl) {
1136
+ if (cache.resource?.has(blobUrl)) return cache.resource.get(blobUrl);
1137
+ if (_blobToDataUrlCache.has(blobUrl)) return _blobToDataUrlCache.get(blobUrl);
1138
+ const p = (async () => {
1139
+ const r = await snapFetch(blobUrl, { as: "dataURL", silent: true });
1140
+ if (!r.ok || typeof r.data !== "string") {
1141
+ throw new Error(`[snapDOM] Failed to read blob URL: ${blobUrl}`);
1142
+ }
1143
+ cache.resource?.set(blobUrl, r.data);
1144
+ return r.data;
1145
+ })();
1146
+ _blobToDataUrlCache.set(blobUrl, p);
1147
+ try {
1148
+ const data = await p;
1149
+ _blobToDataUrlCache.set(blobUrl, data);
1150
+ return data;
1151
+ } catch (e) {
1152
+ _blobToDataUrlCache.delete(blobUrl);
1153
+ throw e;
1154
+ }
1155
+ }
1156
+ var BLOB_URL_RE = /\bblob:[^)"'\s]+/g;
1157
+ async function replaceBlobUrlsInCssText(cssText) {
1158
+ if (!cssText || cssText.indexOf("blob:") === -1) return cssText;
1159
+ const uniques = Array.from(new Set(cssText.match(BLOB_URL_RE) || []));
1160
+ if (uniques.length === 0) return cssText;
1161
+ let out = cssText;
1162
+ for (const u of uniques) {
1163
+ try {
1164
+ const d = await blobUrlToDataUrl(u);
1165
+ out = out.split(u).join(d);
1166
+ } catch {
1167
+ }
1168
+ }
1169
+ return out;
1170
+ }
1171
+ function isBlobUrl(u) {
1172
+ return typeof u === "string" && u.startsWith("blob:");
1173
+ }
1174
+ function parseSrcset(srcset) {
1175
+ return (srcset || "").split(",").map((s) => s.trim()).filter(Boolean).map((item) => {
1176
+ const m = item.match(/^(\S+)(\s+.+)?$/);
1177
+ return m ? { url: m[1], desc: m[2] || "" } : null;
1178
+ }).filter(Boolean);
1179
+ }
1180
+ function stringifySrcset(parts) {
1181
+ return parts.map((p) => p.desc ? `${p.url} ${p.desc.trim()}` : p.url).join(", ");
1182
+ }
1183
+ async function resolveBlobUrlsInTree(root) {
1184
+ if (!root) return;
1185
+ const imgs = root.querySelectorAll ? root.querySelectorAll("img") : [];
1186
+ for (const img of imgs) {
1187
+ try {
1188
+ const srcAttr = img.getAttribute("src");
1189
+ const effective = srcAttr || img.currentSrc || "";
1190
+ if (isBlobUrl(effective)) {
1191
+ const data = await blobUrlToDataUrl(effective);
1192
+ img.setAttribute("src", data);
1193
+ }
1194
+ const srcset = img.getAttribute("srcset");
1195
+ if (srcset && srcset.includes("blob:")) {
1196
+ const parts = parseSrcset(srcset);
1197
+ let changed = false;
1198
+ for (const p of parts) {
1199
+ if (isBlobUrl(p.url)) {
1200
+ try {
1201
+ p.url = await blobUrlToDataUrl(p.url);
1202
+ changed = true;
1203
+ } catch {
1204
+ }
1205
+ }
1206
+ }
1207
+ if (changed) img.setAttribute("srcset", stringifySrcset(parts));
1208
+ }
1209
+ } catch {
1210
+ }
1211
+ }
1212
+ const svgImages = root.querySelectorAll ? root.querySelectorAll("image") : [];
1213
+ for (const node of svgImages) {
1214
+ try {
1215
+ const XLINK_NS = "http://www.w3.org/1999/xlink";
1216
+ const href = node.getAttribute("href") || node.getAttributeNS?.(XLINK_NS, "href");
1217
+ if (isBlobUrl(href)) {
1218
+ const d = await blobUrlToDataUrl(href);
1219
+ node.setAttribute("href", d);
1220
+ node.removeAttributeNS?.(XLINK_NS, "href");
1221
+ }
1222
+ } catch {
1223
+ }
1224
+ }
1225
+ const styled = root.querySelectorAll ? root.querySelectorAll("[style*='blob:']") : [];
1226
+ for (const el of styled) {
1227
+ try {
1228
+ const styleText = el.getAttribute("style");
1229
+ if (styleText && styleText.includes("blob:")) {
1230
+ const replaced = await replaceBlobUrlsInCssText(styleText);
1231
+ el.setAttribute("style", replaced);
1232
+ }
1233
+ } catch {
1234
+ }
1235
+ }
1236
+ const styleTags = root.querySelectorAll ? root.querySelectorAll("style") : [];
1237
+ for (const s of styleTags) {
1238
+ try {
1239
+ const css = s.textContent || "";
1240
+ if (css.includes("blob:")) {
1241
+ s.textContent = await replaceBlobUrlsInCssText(css);
1242
+ }
1243
+ } catch {
1244
+ }
1245
+ }
1246
+ const urlAttrs = ["poster"];
1247
+ for (const attr of urlAttrs) {
1248
+ const nodes = root.querySelectorAll ? root.querySelectorAll(`[${attr}^='blob:']`) : [];
1249
+ for (const n of nodes) {
1250
+ try {
1251
+ const u = n.getAttribute(attr);
1252
+ if (isBlobUrl(u)) {
1253
+ n.setAttribute(attr, await blobUrlToDataUrl(u));
1254
+ }
1255
+ } catch {
1256
+ }
1257
+ }
1258
+ }
1259
+ }
1260
+
1261
+ // src/core/clone.js
1134
1262
  async function deepClone(node, sessionCache, options) {
1135
1263
  if (!node) throw new Error("Invalid node");
1136
1264
  const clonedAssignedNodes = /* @__PURE__ */ new Set();
@@ -2595,6 +2723,152 @@ function resolveCountersInContent(raw, node, ctx) {
2595
2723
  }
2596
2724
 
2597
2725
  // src/modules/pseudo.js
2726
+ var __preflightMemo = /* @__PURE__ */ new WeakMap();
2727
+ var CSS_RULE_SCAN_BUDGET = 300;
2728
+ function preflightWithFp(doc, sessionCache) {
2729
+ const fp = styleFingerprint(doc);
2730
+ if (!sessionCache) return shouldProcessPseudos(doc);
2731
+ if (sessionCache.__pseudoPreflightFp !== fp) {
2732
+ sessionCache.__pseudoPreflight = shouldProcessPseudos(doc);
2733
+ sessionCache.__pseudoPreflightFp = fp;
2734
+ }
2735
+ return !!sessionCache.__pseudoPreflight;
2736
+ }
2737
+ function safeRules(sheet) {
2738
+ try {
2739
+ return sheet && sheet.cssRules ? sheet.cssRules : null;
2740
+ } catch {
2741
+ return null;
2742
+ }
2743
+ }
2744
+ function styleFingerprint(doc) {
2745
+ const nodes = doc.querySelectorAll('style,link[rel~="stylesheet"]');
2746
+ let fp = `n:${nodes.length}|`;
2747
+ let totalRules = 0;
2748
+ for (let i = 0; i < nodes.length; i++) {
2749
+ const n = nodes[i];
2750
+ if (n.tagName === "STYLE") {
2751
+ const len = n.textContent ? n.textContent.length : 0;
2752
+ fp += `S${len}|`;
2753
+ const sheet = (
2754
+ /** @type {HTMLStyleElement} */
2755
+ n.sheet
2756
+ );
2757
+ const rules = sheet ? safeRules(sheet) : null;
2758
+ if (rules) totalRules += rules.length;
2759
+ } else {
2760
+ const href = n.getAttribute("href") || "";
2761
+ const media = n.getAttribute("media") || "all";
2762
+ fp += `L${href}|m:${media}|`;
2763
+ const sheet = (
2764
+ /** @type {HTMLLinkElement} */
2765
+ n.sheet
2766
+ );
2767
+ const rules = sheet ? safeRules(sheet) : null;
2768
+ if (rules) totalRules += rules.length;
2769
+ }
2770
+ }
2771
+ const ass = (
2772
+ /** @type {any} */
2773
+ doc.adoptedStyleSheets
2774
+ );
2775
+ fp += `ass:${Array.isArray(ass) ? ass.length : 0}|tr:${totalRules}`;
2776
+ return fp;
2777
+ }
2778
+ function sheetHasNeedles(sheet, needles, state) {
2779
+ const rules = safeRules(sheet);
2780
+ if (!rules) return false;
2781
+ for (let i = 0; i < rules.length; i++) {
2782
+ if (state.budget <= 0) return false;
2783
+ const rule = rules[i];
2784
+ const css = rule && rule.cssText ? rule.cssText : "";
2785
+ state.budget--;
2786
+ for (const k of needles) {
2787
+ if (css.includes(k)) return true;
2788
+ }
2789
+ if (rule && rule.cssRules && rule.cssRules.length) {
2790
+ for (let j = 0; j < rule.cssRules.length && state.budget > 0; j++) {
2791
+ const inner = rule.cssRules[j];
2792
+ const innerCss = inner && inner.cssText ? inner.cssText : "";
2793
+ state.budget--;
2794
+ for (const k of needles) {
2795
+ if (innerCss.includes(k)) return true;
2796
+ }
2797
+ }
2798
+ }
2799
+ if (state.budget <= 0) return false;
2800
+ }
2801
+ return false;
2802
+ }
2803
+ function shouldProcessPseudos(doc = document) {
2804
+ const fp = styleFingerprint(doc);
2805
+ const memo = __preflightMemo.get(doc);
2806
+ if (memo && memo.fingerprint === fp) return memo.result;
2807
+ const NEEDLES = [
2808
+ // double-colon
2809
+ "::before",
2810
+ "::after",
2811
+ "::first-letter",
2812
+ // single-colon robustness
2813
+ ":before",
2814
+ ":after",
2815
+ ":first-letter",
2816
+ // counters
2817
+ "counter(",
2818
+ "counters(",
2819
+ "counter-increment",
2820
+ "counter-reset"
2821
+ ];
2822
+ const styleEls = doc.querySelectorAll("style");
2823
+ for (let i = 0; i < styleEls.length; i++) {
2824
+ const t = styleEls[i].textContent || "";
2825
+ for (const k of NEEDLES) if (t.includes(k)) {
2826
+ __preflightMemo.set(doc, { fingerprint: fp, result: true });
2827
+ return true;
2828
+ }
2829
+ }
2830
+ const ass = (
2831
+ /** @type {any} */
2832
+ doc.adoptedStyleSheets
2833
+ );
2834
+ if (Array.isArray(ass) && ass.length) {
2835
+ const state = { budget: CSS_RULE_SCAN_BUDGET };
2836
+ try {
2837
+ for (const sheet of ass) {
2838
+ if (sheetHasNeedles(sheet, NEEDLES, state)) {
2839
+ __preflightMemo.set(doc, { fingerprint: fp, result: true });
2840
+ return true;
2841
+ }
2842
+ }
2843
+ } catch {
2844
+ }
2845
+ }
2846
+ {
2847
+ const nodes = doc.querySelectorAll('style,link[rel~="stylesheet"]');
2848
+ const state = { budget: CSS_RULE_SCAN_BUDGET };
2849
+ for (let i = 0; i < nodes.length && state.budget > 0; i++) {
2850
+ const n = nodes[i];
2851
+ let sheet = null;
2852
+ if (n.tagName === "STYLE") {
2853
+ sheet = /** @type {HTMLStyleElement} */
2854
+ n.sheet || null;
2855
+ } else {
2856
+ sheet = /** @type {HTMLLinkElement} */
2857
+ n.sheet || null;
2858
+ }
2859
+ if (sheet && sheetHasNeedles(sheet, NEEDLES, state)) {
2860
+ __preflightMemo.set(doc, { fingerprint: fp, result: true });
2861
+ return true;
2862
+ }
2863
+ }
2864
+ }
2865
+ if (doc.querySelector('[style*="counter("], [style*="counters("]')) {
2866
+ __preflightMemo.set(doc, { fingerprint: fp, result: true });
2867
+ return true;
2868
+ }
2869
+ __preflightMemo.set(doc, { fingerprint: fp, result: false });
2870
+ return false;
2871
+ }
2598
2872
  var __siblingCounters = /* @__PURE__ */ new WeakMap();
2599
2873
  var __pseudoEpoch = -1;
2600
2874
  function unquoteDoubleStrings2(s) {
@@ -2693,6 +2967,10 @@ function resolvePseudoContentAndIncs(node, pseudo, baseCtx) {
2693
2967
  }
2694
2968
  async function inlinePseudoElements(source, clone, sessionCache, options) {
2695
2969
  if (!(source instanceof Element) || !(clone instanceof Element)) return;
2970
+ const doc = source.ownerDocument || document;
2971
+ if (!preflightWithFp(doc, sessionCache)) {
2972
+ return;
2973
+ }
2696
2974
  const epoch = cache?.session?.__counterEpoch ?? 0;
2697
2975
  if (__pseudoEpoch !== epoch) {
2698
2976
  __siblingCounters = /* @__PURE__ */ new WeakMap();
@@ -2709,7 +2987,7 @@ async function inlinePseudoElements(source, clone, sessionCache, options) {
2709
2987
  for (const pseudo of ["::before", "::after", "::first-letter"]) {
2710
2988
  try {
2711
2989
  const style = getStyle(source, pseudo);
2712
- if (!style || typeof style[Symbol.iterator] !== "function") continue;
2990
+ if (!style) continue;
2713
2991
  const isEmptyPseudo = style.content === "none" && style.backgroundImage === "none" && style.backgroundColor === "transparent" && (style.borderStyle === "none" || parseFloat(style.borderWidth) === 0) && (!style.transform || style.transform === "none") && style.display === "inline";
2714
2992
  if (isEmptyPseudo) continue;
2715
2993
  if (pseudo === "::first-letter") {
@@ -2736,7 +3014,8 @@ async function inlinePseudoElements(source, clone, sessionCache, options) {
2736
3014
  clone.insertBefore(span, restNode);
2737
3015
  continue;
2738
3016
  }
2739
- const rawContent = style.content;
3017
+ const rawContent = style.content ?? "";
3018
+ const isNoExplicitContent = rawContent === "" || rawContent === "none" || rawContent === "normal";
2740
3019
  const { text: cleanContent, incs } = resolvePseudoContentAndIncs(source, pseudo, counterCtx);
2741
3020
  const bg = style.backgroundImage;
2742
3021
  const bgColor = style.backgroundColor;
@@ -2748,7 +3027,7 @@ async function inlinePseudoElements(source, clone, sessionCache, options) {
2748
3027
  const borderWidth = parseFloat(style.borderWidth);
2749
3028
  const transform = style.transform;
2750
3029
  const isIconFont22 = isIconFont2(fontFamily);
2751
- const hasExplicitContent = rawContent !== "none" && cleanContent !== "";
3030
+ const hasExplicitContent = !isNoExplicitContent && cleanContent !== "";
2752
3031
  const hasBg = bg && bg !== "none";
2753
3032
  const hasBgColor = bgColor && bgColor !== "transparent" && bgColor !== "rgba(0, 0, 0, 0)";
2754
3033
  const hasBorder = borderStyle && borderStyle !== "none" && borderWidth > 0;
@@ -2770,8 +3049,6 @@ async function inlinePseudoElements(source, clone, sessionCache, options) {
2770
3049
  }
2771
3050
  const pseudoEl = document.createElement("span");
2772
3051
  pseudoEl.dataset.snapdomPseudo = pseudo;
2773
- pseudoEl.style.display = "inline";
2774
- pseudoEl.style.verticalAlign = "baseline";
2775
3052
  pseudoEl.style.pointerEvents = "none";
2776
3053
  const snapshot = snapshotComputedStyle(style);
2777
3054
  const key = getStyleKey(snapshot, "span");
@@ -3063,6 +3340,20 @@ function _childrenWithoutPlaceholders(el, phAttr) {
3063
3340
  return out;
3064
3341
  }
3065
3342
 
3343
+ // src/utils/prepare.helpers.js
3344
+ function stabilizeLayout(element) {
3345
+ const style = getComputedStyle(element);
3346
+ const outlineStyle = style.outlineStyle;
3347
+ const outlineWidth = style.outlineWidth;
3348
+ const borderStyle = style.borderStyle;
3349
+ const borderWidth = style.borderWidth;
3350
+ const outlineVisible = outlineStyle !== "none" && parseFloat(outlineWidth) > 0;
3351
+ const borderAbsent = borderStyle === "none" || parseFloat(borderWidth) === 0;
3352
+ if (outlineVisible && borderAbsent) {
3353
+ element.style.border = `${outlineWidth} solid transparent`;
3354
+ }
3355
+ }
3356
+
3066
3357
  // src/core/prepare.js
3067
3358
  async function prepareClone(element, options = {}) {
3068
3359
  const sessionCache = {
@@ -3163,199 +3454,61 @@ async function prepareClone(element, options = {}) {
3163
3454
  }
3164
3455
  return { clone, classCSS, styleCache: sessionCache.styleCache };
3165
3456
  }
3166
- function stabilizeLayout(element) {
3167
- const style = getComputedStyle(element);
3168
- const outlineStyle = style.outlineStyle;
3169
- const outlineWidth = style.outlineWidth;
3170
- const borderStyle = style.borderStyle;
3171
- const borderWidth = style.borderWidth;
3172
- const outlineVisible = outlineStyle !== "none" && parseFloat(outlineWidth) > 0;
3173
- const borderAbsent = borderStyle === "none" || parseFloat(borderWidth) === 0;
3174
- if (outlineVisible && borderAbsent) {
3175
- element.style.border = `${outlineWidth} solid transparent`;
3176
- }
3457
+
3458
+ // src/modules/images.js
3459
+ function extractImageDimensions(img) {
3460
+ const dsW = parseInt(img.dataset?.snapdomWidth || "", 10) || 0;
3461
+ const dsH = parseInt(img.dataset?.snapdomHeight || "", 10) || 0;
3462
+ const attrW = parseInt(img.getAttribute("width") || "", 10) || 0;
3463
+ const attrH = parseInt(img.getAttribute("height") || "", 10) || 0;
3464
+ const styleW = parseFloat(img.style?.width || "") || 0;
3465
+ const styleH = parseFloat(img.style?.height || "") || 0;
3466
+ const w = dsW || styleW || attrW || img.width || img.naturalWidth || 100;
3467
+ const h = dsH || styleH || attrH || img.height || img.naturalHeight || 100;
3468
+ return { width: w, height: h };
3177
3469
  }
3178
- var _blobToDataUrlCache = /* @__PURE__ */ new Map();
3179
- async function blobUrlToDataUrl(blobUrl) {
3180
- if (cache.resource?.has(blobUrl)) return cache.resource.get(blobUrl);
3181
- if (_blobToDataUrlCache.has(blobUrl)) return _blobToDataUrlCache.get(blobUrl);
3182
- const p = (async () => {
3183
- const r = await snapFetch(blobUrl, { as: "dataURL", silent: true });
3184
- if (!r.ok || typeof r.data !== "string") {
3185
- throw new Error(`[snapDOM] Failed to read blob URL: ${blobUrl}`);
3470
+ async function inlineImages(clone, options = {}) {
3471
+ const imgs = Array.from(clone.querySelectorAll("img"));
3472
+ const processImg = async (img) => {
3473
+ if (!img.getAttribute("src")) {
3474
+ const eff = img.currentSrc || img.src || "";
3475
+ if (eff) img.setAttribute("src", eff);
3186
3476
  }
3187
- cache.resource?.set(blobUrl, r.data);
3188
- return r.data;
3189
- })();
3190
- _blobToDataUrlCache.set(blobUrl, p);
3191
- try {
3192
- const data = await p;
3193
- _blobToDataUrlCache.set(blobUrl, data);
3194
- return data;
3195
- } catch (e) {
3196
- _blobToDataUrlCache.delete(blobUrl);
3197
- throw e;
3198
- }
3199
- }
3200
- var BLOB_URL_RE = /\bblob:[^)"'\s]+/g;
3201
- async function replaceBlobUrlsInCssText(cssText) {
3202
- if (!cssText || cssText.indexOf("blob:") === -1) return cssText;
3203
- const uniques = Array.from(new Set(cssText.match(BLOB_URL_RE) || []));
3204
- if (uniques.length === 0) return cssText;
3205
- let out = cssText;
3206
- for (const u of uniques) {
3207
- try {
3208
- const d = await blobUrlToDataUrl(u);
3209
- out = out.split(u).join(d);
3210
- } catch {
3211
- }
3212
- }
3213
- return out;
3214
- }
3215
- function isBlobUrl(u) {
3216
- return typeof u === "string" && u.startsWith("blob:");
3217
- }
3218
- function parseSrcset(srcset) {
3219
- return (srcset || "").split(",").map((s) => s.trim()).filter(Boolean).map((item) => {
3220
- const m = item.match(/^(\S+)(\s+.+)?$/);
3221
- return m ? { url: m[1], desc: m[2] || "" } : null;
3222
- }).filter(Boolean);
3223
- }
3224
- function stringifySrcset(parts) {
3225
- return parts.map((p) => p.desc ? `${p.url} ${p.desc.trim()}` : p.url).join(", ");
3226
- }
3227
- async function resolveBlobUrlsInTree(root) {
3228
- if (!root) return;
3229
- const imgs = root.querySelectorAll ? root.querySelectorAll("img") : [];
3230
- for (const img of imgs) {
3231
- try {
3232
- const srcAttr = img.getAttribute("src");
3233
- const effective = srcAttr || img.currentSrc || "";
3234
- if (isBlobUrl(effective)) {
3235
- const data = await blobUrlToDataUrl(effective);
3236
- img.setAttribute("src", data);
3237
- }
3238
- const srcset = img.getAttribute("srcset");
3239
- if (srcset && srcset.includes("blob:")) {
3240
- const parts = parseSrcset(srcset);
3241
- let changed = false;
3242
- for (const p of parts) {
3243
- if (isBlobUrl(p.url)) {
3244
- try {
3245
- p.url = await blobUrlToDataUrl(p.url);
3246
- changed = true;
3247
- } catch {
3248
- }
3249
- }
3250
- }
3251
- if (changed) img.setAttribute("srcset", stringifySrcset(parts));
3252
- }
3253
- } catch {
3254
- }
3255
- }
3256
- const svgImages = root.querySelectorAll ? root.querySelectorAll("image") : [];
3257
- for (const node of svgImages) {
3258
- try {
3259
- const XLINK_NS = "http://www.w3.org/1999/xlink";
3260
- const href = node.getAttribute("href") || node.getAttributeNS?.(XLINK_NS, "href");
3261
- if (isBlobUrl(href)) {
3262
- const d = await blobUrlToDataUrl(href);
3263
- node.setAttribute("href", d);
3264
- node.removeAttributeNS?.(XLINK_NS, "href");
3265
- }
3266
- } catch {
3267
- }
3268
- }
3269
- const styled = root.querySelectorAll ? root.querySelectorAll("[style*='blob:']") : [];
3270
- for (const el of styled) {
3271
- try {
3272
- const styleText = el.getAttribute("style");
3273
- if (styleText && styleText.includes("blob:")) {
3274
- const replaced = await replaceBlobUrlsInCssText(styleText);
3275
- el.setAttribute("style", replaced);
3276
- }
3277
- } catch {
3278
- }
3279
- }
3280
- const styleTags = root.querySelectorAll ? root.querySelectorAll("style") : [];
3281
- for (const s of styleTags) {
3282
- try {
3283
- const css = s.textContent || "";
3284
- if (css.includes("blob:")) {
3285
- s.textContent = await replaceBlobUrlsInCssText(css);
3286
- }
3287
- } catch {
3288
- }
3289
- }
3290
- const urlAttrs = ["poster"];
3291
- for (const attr of urlAttrs) {
3292
- const nodes = root.querySelectorAll ? root.querySelectorAll(`[${attr}^='blob:']`) : [];
3293
- for (const n of nodes) {
3294
- try {
3295
- const u = n.getAttribute(attr);
3296
- if (isBlobUrl(u)) {
3297
- n.setAttribute(attr, await blobUrlToDataUrl(u));
3298
- }
3299
- } catch {
3300
- }
3301
- }
3302
- }
3303
- }
3304
-
3305
- // src/modules/images.js
3306
- async function inlineImages(clone, options = {}) {
3307
- const imgs = Array.from(clone.querySelectorAll("img"));
3308
- const processImg = async (img) => {
3309
- if (!img.getAttribute("src")) {
3310
- const eff = img.currentSrc || img.src || "";
3311
- if (eff) img.setAttribute("src", eff);
3312
- }
3313
- img.removeAttribute("srcset");
3314
- img.removeAttribute("sizes");
3315
- const src = img.src || "";
3316
- if (!src) return;
3317
- const r = await snapFetch(src, { as: "dataURL", useProxy: options.useProxy });
3318
- if (r.ok && typeof r.data === "string" && r.data.startsWith("data:")) {
3319
- img.src = r.data;
3320
- if (!img.width) img.width = img.naturalWidth || 100;
3321
- if (!img.height) img.height = img.naturalHeight || 100;
3322
- return;
3477
+ img.removeAttribute("srcset");
3478
+ img.removeAttribute("sizes");
3479
+ const src = img.src || "";
3480
+ if (!src) return;
3481
+ const r = await snapFetch(src, { as: "dataURL", useProxy: options.useProxy });
3482
+ if (r.ok && typeof r.data === "string" && r.data.startsWith("data:")) {
3483
+ img.src = r.data;
3484
+ if (!img.width) img.width = img.naturalWidth || 100;
3485
+ if (!img.height) img.height = img.naturalHeight || 100;
3486
+ return;
3323
3487
  }
3488
+ const { width: fbW, height: fbH } = extractImageDimensions(img);
3324
3489
  const { fallbackURL } = options || {};
3325
3490
  if (fallbackURL) {
3326
3491
  try {
3327
- const dsW = parseInt(img.dataset?.snapdomWidth || "", 10) || 0;
3328
- const dsH = parseInt(img.dataset?.snapdomHeight || "", 10) || 0;
3329
- const attrW = parseInt(img.getAttribute("width") || "", 10) || 0;
3330
- const attrH = parseInt(img.getAttribute("height") || "", 10) || 0;
3331
- const styleW = parseFloat(img.style?.width || "") || 0;
3332
- const styleH = parseFloat(img.style?.height || "") || 0;
3333
- const fbW = dsW || styleW || attrW || img.width || void 0;
3334
- const fbH = dsH || styleH || attrH || img.height || void 0;
3335
3492
  const fallbackUrl = typeof fallbackURL === "function" ? await fallbackURL({ width: fbW, height: fbH, src, element: img }) : fallbackURL;
3336
3493
  if (fallbackUrl) {
3337
3494
  const fallbackData = await snapFetch(fallbackUrl, { as: "dataURL", useProxy: options.useProxy });
3338
3495
  img.src = fallbackData.data;
3339
- if (!img.width && fbW) img.width = fbW;
3340
- if (!img.height && fbH) img.height = fbH;
3341
- if (!img.width) img.width = img.naturalWidth || 100;
3342
- if (!img.height) img.height = img.naturalHeight || 100;
3496
+ if (!img.width) img.width = fbW;
3497
+ if (!img.height) img.height = fbH;
3343
3498
  return;
3344
3499
  }
3345
3500
  } catch {
3346
3501
  }
3347
3502
  }
3348
- const w = img.width || img.naturalWidth || 100;
3349
- const h = img.height || img.naturalHeight || 100;
3350
3503
  if (options.placeholders !== false) {
3351
3504
  const fallback = document.createElement("div");
3352
3505
  fallback.style.cssText = [
3353
- `width:${w}px`,
3354
- `height:${h}px`,
3506
+ `width:${fbW}px`,
3507
+ `height:${fbH}px`,
3355
3508
  "background:#ccc",
3356
3509
  "display:inline-block",
3357
3510
  "text-align:center",
3358
- `line-height:${h}px`,
3511
+ `line-height:${fbH}px`,
3359
3512
  "color:#666",
3360
3513
  "font-size:12px",
3361
3514
  "overflow:hidden"
@@ -3364,7 +3517,7 @@ async function inlineImages(clone, options = {}) {
3364
3517
  img.replaceWith(fallback);
3365
3518
  } else {
3366
3519
  const spacer = document.createElement("div");
3367
- spacer.style.cssText = `display:inline-block;width:${w}px;height:${h}px;visibility:hidden;`;
3520
+ spacer.style.cssText = `display:inline-block;width:${fbW}px;height:${fbH}px;visibility:hidden;`;
3368
3521
  img.replaceWith(spacer);
3369
3522
  }
3370
3523
  };
@@ -3604,7 +3757,7 @@ function attachSessionPlugins(context, localDefs, force = false) {
3604
3757
  return context;
3605
3758
  }
3606
3759
 
3607
- // src/core/capture.js
3760
+ // src/utils/capture.helpers.js
3608
3761
  function stripRootShadows(originalEl, cloneRoot) {
3609
3762
  if (!originalEl || !cloneRoot || !cloneRoot.style) return;
3610
3763
  const cs = getComputedStyle(originalEl);
@@ -3671,283 +3824,134 @@ function sanitizeCloneForXHTML(root, opts = {}) {
3671
3824
  sanitizeAttributesForXHTML(root, opts);
3672
3825
  removeAllComments(root);
3673
3826
  }
3674
- async function captureDOM(element, options) {
3675
- if (!element) throw new Error("Element cannot be null or undefined");
3676
- applyCachePolicy(options.cache);
3677
- const fast = options.fast;
3678
- const outerTransforms = options.outerTransforms !== false;
3679
- const outerShadows = !!options.outerShadows;
3680
- let state = { element, options, plugins: options.plugins };
3681
- let clone, classCSS, styleCache;
3682
- let fontsCSS = "";
3683
- let baseCSS = "";
3684
- let dataURL;
3685
- let svgString;
3686
- let rootTransform2D = null;
3687
- await runHook("beforeSnap", state);
3688
- await runHook("beforeClone", state);
3689
- const undoClamp = lineClamp(state.element);
3827
+ function authorHasExplicitSize(el) {
3690
3828
  try {
3691
- ({ clone, classCSS, styleCache } = await prepareClone(state.element, state.options));
3692
- if (!outerTransforms && clone) {
3693
- rootTransform2D = normalizeRootTransforms(state.element, clone);
3829
+ const s = el.getAttribute?.("style") || "";
3830
+ return /\b(height|width|block-size|inline-size)\s*:/.test(s);
3831
+ } catch {
3832
+ return false;
3833
+ }
3834
+ }
3835
+ function isReplacedElement(el) {
3836
+ return el instanceof HTMLImageElement || el instanceof HTMLCanvasElement || el instanceof HTMLVideoElement || el instanceof HTMLIFrameElement || el instanceof SVGElement || el instanceof HTMLObjectElement || el instanceof HTMLEmbedElement;
3837
+ }
3838
+ function shouldShrinkBox(srcEl, cs) {
3839
+ if (!(srcEl instanceof Element)) return false;
3840
+ if (authorHasExplicitSize(srcEl)) return false;
3841
+ if (isReplacedElement(srcEl)) return false;
3842
+ const pos = cs.position;
3843
+ if (pos === "absolute" || pos === "fixed" || pos === "sticky") return false;
3844
+ const disp = cs.display || "";
3845
+ if (disp.includes("flex") || disp.includes("grid") || disp.startsWith("table")) return false;
3846
+ if (cs.transform && cs.transform !== "none") return false;
3847
+ return true;
3848
+ }
3849
+ function shrinkAutoSizeBoxes(sourceRoot, cloneRoot, styleCache = /* @__PURE__ */ new Map()) {
3850
+ function walk(src, cln) {
3851
+ if (!(src instanceof Element) || !(cln instanceof Element)) return;
3852
+ const lostKids = src.childElementCount > cln.childElementCount;
3853
+ const cs = styleCache.get(src) || getComputedStyle(src);
3854
+ if (!styleCache.has(src)) styleCache.set(src, cs);
3855
+ if (lostKids && shouldShrinkBox(src, cs)) {
3856
+ if (!cln.style.height) cln.style.height = "auto";
3857
+ if (!cln.style.width) cln.style.width = "auto";
3858
+ cln.style.removeProperty("block-size");
3859
+ cln.style.removeProperty("inline-size");
3860
+ if (!cln.style.minHeight) cln.style.minHeight = "0";
3861
+ if (!cln.style.minWidth) cln.style.minWidth = "0";
3862
+ if (!cln.style.maxHeight) cln.style.maxHeight = "none";
3863
+ if (!cln.style.maxWidth) cln.style.maxWidth = "none";
3864
+ const oy = cs.overflowY || cs.overflowBlock || "visible";
3865
+ const ox = cs.overflowX || cs.overflowInline || "visible";
3866
+ if (oy !== "visible" || ox !== "visible") {
3867
+ cln.style.overflow = "visible";
3868
+ }
3694
3869
  }
3695
- if (!outerShadows && clone) {
3696
- stripRootShadows(state.element, clone);
3870
+ const sKids = Array.from(src.children);
3871
+ const cKids = Array.from(cln.children);
3872
+ for (let i = 0; i < Math.min(sKids.length, cKids.length); i++) {
3873
+ walk(sKids[i], cKids[i]);
3697
3874
  }
3698
- } finally {
3699
- undoClamp();
3700
3875
  }
3701
- state = { clone, classCSS, styleCache, ...state };
3702
- await runHook("afterClone", state);
3703
- sanitizeCloneForXHTML(state.clone);
3704
- if (state.options?.excludeMode === "remove") {
3705
- try {
3706
- shrinkAutoSizeBoxes(state.element, state.clone, state.styleCache);
3707
- } catch (e) {
3708
- console.warn("[snapdom] shrink pass failed:", e);
3876
+ walk(sourceRoot, cloneRoot);
3877
+ }
3878
+ function isInNormalFlow(el) {
3879
+ const cs = getComputedStyle(el);
3880
+ if (cs.display === "none") return false;
3881
+ if (cs.position === "absolute" || cs.position === "fixed" || cs.position === "sticky") return false;
3882
+ if ((cs.cssFloat || cs.float || "none") !== "none") return false;
3883
+ if (cs.transform && cs.transform !== "none") return false;
3884
+ return true;
3885
+ }
3886
+ function willBeExcluded(el, options) {
3887
+ if (!(el instanceof Element)) return false;
3888
+ if (el.getAttribute("data-capture") === "exclude" && options?.excludeMode === "remove") return true;
3889
+ if (Array.isArray(options?.exclude)) {
3890
+ for (const sel of options.exclude) {
3891
+ try {
3892
+ if (el.matches(sel)) return options.excludeMode === "remove";
3893
+ } catch {
3894
+ }
3709
3895
  }
3710
3896
  }
3711
- try {
3712
- await ligatureIconToImage(state.clone, state.element);
3713
- } catch {
3714
- }
3715
- await new Promise((resolve) => {
3716
- idle(async () => {
3717
- await inlineImages(state.clone, state.options);
3718
- resolve();
3719
- }, { fast });
3720
- });
3721
- await new Promise((resolve) => {
3722
- idle(async () => {
3723
- await inlineBackgroundImages(state.element, state.clone, state.styleCache, state.options);
3724
- resolve();
3725
- }, { fast });
3726
- });
3727
- if (options.embedFonts) {
3728
- await new Promise((resolve) => {
3729
- idle(async () => {
3730
- const required = collectUsedFontVariants(state.element);
3731
- const usedCodepoints = collectUsedCodepoints(state.element);
3732
- if (isSafari()) {
3733
- const families = new Set(
3734
- Array.from(required).map((k) => String(k).split("__")[0]).filter(Boolean)
3735
- );
3736
- await ensureFontsReady(families, 1);
3737
- }
3738
- fontsCSS = await embedCustomFonts({
3739
- required,
3740
- usedCodepoints,
3741
- preCached: false,
3742
- exclude: state.options.excludeFonts,
3743
- useProxy: state.options.useProxy
3744
- });
3745
- resolve();
3746
- }, { fast });
3747
- });
3897
+ return false;
3898
+ }
3899
+ function estimateKeptHeight(container, options) {
3900
+ const csC = getComputedStyle(container);
3901
+ const rC = container.getBoundingClientRect();
3902
+ let minTop = Infinity;
3903
+ let maxBottom = -Infinity;
3904
+ let found = false;
3905
+ const kids = Array.from(container.children);
3906
+ for (const k of kids) {
3907
+ if (willBeExcluded(k, options)) continue;
3908
+ if (!isInNormalFlow(k)) continue;
3909
+ const rk = k.getBoundingClientRect();
3910
+ const top = rk.top - rC.top;
3911
+ const bottom = rk.bottom - rC.top;
3912
+ if (bottom <= top) continue;
3913
+ if (top < minTop) minTop = top;
3914
+ if (bottom > maxBottom) maxBottom = bottom;
3915
+ found = true;
3748
3916
  }
3749
- const usedTags = collectUsedTagNames(state.clone).sort();
3750
- const tagKey = usedTags.join(",");
3751
- if (cache.baseStyle.has(tagKey)) {
3752
- baseCSS = cache.baseStyle.get(tagKey);
3753
- } else {
3754
- await new Promise((resolve) => {
3755
- idle(() => {
3756
- baseCSS = generateDedupedBaseCSS(usedTags);
3757
- cache.baseStyle.set(tagKey, baseCSS);
3758
- resolve();
3759
- }, { fast });
3760
- });
3761
- }
3762
- state = { fontsCSS, baseCSS, ...state };
3763
- await runHook("beforeRender", state);
3764
- await new Promise((resolve) => {
3765
- idle(() => {
3766
- const csEl = getComputedStyle(state.element);
3767
- const rect = state.element.getBoundingClientRect();
3768
- let w0 = Math.max(1, limitDecimals(state.element.offsetWidth || parseFloat(csEl.width) || rect.width || 1));
3769
- let h0 = Math.max(1, limitDecimals(state.element.offsetHeight || parseFloat(csEl.height) || rect.height || 1));
3770
- if (state.options?.excludeMode === "remove") {
3771
- const hEst = estimateKeptHeight(state.element, state.options);
3772
- const EPS = 1;
3773
- if (Number.isFinite(hEst) && hEst > 0) {
3774
- h0 = Math.max(1, Math.min(h0, limitDecimals(hEst + EPS)));
3775
- }
3776
- }
3777
- const coerceNum = (v, def = NaN) => {
3778
- const n = typeof v === "string" ? parseFloat(v) : v;
3779
- return Number.isFinite(n) ? n : def;
3780
- };
3781
- const optW = coerceNum(state.options.width);
3782
- const optH = coerceNum(state.options.height);
3783
- let w = w0, h = h0;
3784
- const hasW = Number.isFinite(optW);
3785
- const hasH = Number.isFinite(optH);
3786
- const aspect0 = h0 > 0 ? w0 / h0 : 1;
3787
- if (hasW && hasH) {
3788
- w = Math.max(1, limitDecimals(optW));
3789
- h = Math.max(1, limitDecimals(optH));
3790
- } else if (hasW) {
3791
- w = Math.max(1, limitDecimals(optW));
3792
- h = Math.max(1, limitDecimals(w / (aspect0 || 1)));
3793
- } else if (hasH) {
3794
- h = Math.max(1, limitDecimals(optH));
3795
- w = Math.max(1, limitDecimals(h * (aspect0 || 1)));
3796
- } else {
3797
- w = w0;
3798
- h = h0;
3799
- }
3800
- let minX = 0, minY = 0, maxX = w0, maxY = h0;
3801
- if (!outerTransforms && rootTransform2D && Number.isFinite(rootTransform2D.a)) {
3802
- const M2 = {
3803
- a: rootTransform2D.a,
3804
- b: rootTransform2D.b || 0,
3805
- c: rootTransform2D.c || 0,
3806
- d: rootTransform2D.d || 1,
3807
- e: 0,
3808
- f: 0
3809
- };
3810
- const bb2 = bboxWithOriginFull(w0, h0, M2, 0, 0);
3811
- minX = limitDecimals(bb2.minX);
3812
- minY = limitDecimals(bb2.minY);
3813
- maxX = limitDecimals(bb2.maxX);
3814
- maxY = limitDecimals(bb2.maxY);
3815
- } else {
3816
- const useTFBBox = outerTransforms && hasTFBBox(state.element);
3817
- if (useTFBBox) {
3818
- const baseTransform2 = csEl.transform && csEl.transform !== "none" ? csEl.transform : "";
3819
- const ind2 = readIndividualTransforms(state.element);
3820
- const TOTAL = readTotalTransformMatrix({
3821
- baseTransform: baseTransform2,
3822
- rotate: ind2.rotate || "0deg",
3823
- scale: ind2.scale,
3824
- translate: ind2.translate
3825
- });
3826
- const { ox: ox2, oy: oy2 } = parseTransformOriginPx(csEl, w0, h0);
3827
- const M = TOTAL.is2D ? TOTAL : new DOMMatrix(TOTAL.toString());
3828
- const bb = bboxWithOriginFull(w0, h0, M, ox2, oy2);
3829
- minX = limitDecimals(bb.minX);
3830
- minY = limitDecimals(bb.minY);
3831
- maxX = limitDecimals(bb.maxX);
3832
- maxY = limitDecimals(bb.maxY);
3833
- }
3834
- }
3835
- const bleedShadow = parseBoxShadow(csEl);
3836
- const bleedBlur = parseFilterBlur(csEl);
3837
- const bleedOutline = parseOutline(csEl);
3838
- const drop = parseFilterDropShadows(csEl);
3839
- const bleed = !outerShadows ? { top: 0, right: 0, bottom: 0, left: 0 } : {
3840
- top: limitDecimals(bleedShadow.top + bleedBlur.top + bleedOutline.top + drop.bleed.top),
3841
- right: limitDecimals(bleedShadow.right + bleedBlur.right + bleedOutline.right + drop.bleed.right),
3842
- bottom: limitDecimals(bleedShadow.bottom + bleedBlur.bottom + bleedOutline.bottom + drop.bleed.bottom),
3843
- left: limitDecimals(bleedShadow.left + bleedBlur.left + bleedOutline.left + drop.bleed.left)
3844
- };
3845
- minX = limitDecimals(minX - bleed.left);
3846
- minY = limitDecimals(minY - bleed.top);
3847
- maxX = limitDecimals(maxX + bleed.right);
3848
- maxY = limitDecimals(maxY + bleed.bottom);
3849
- const vbW0 = Math.max(1, limitDecimals(maxX - minX));
3850
- const vbH0 = Math.max(1, limitDecimals(maxY - minY));
3851
- const scaleW = hasW || hasH ? limitDecimals(w / w0) : 1;
3852
- const scaleH = hasH || hasW ? limitDecimals(h / h0) : 1;
3853
- const outW = Math.max(1, limitDecimals(vbW0 * scaleW));
3854
- const outH = Math.max(1, limitDecimals(vbH0 * scaleH));
3855
- const svgNS = "http://www.w3.org/2000/svg";
3856
- const basePad = isSafari() ? 1 : 0;
3857
- const extraPad = !outerTransforms ? 1 : 0;
3858
- const pad = limitDecimals(basePad + extraPad);
3859
- const fo = document.createElementNS(svgNS, "foreignObject");
3860
- const vbMinX = limitDecimals(minX);
3861
- const vbMinY = limitDecimals(minY);
3862
- fo.setAttribute("x", String(limitDecimals(-(vbMinX - pad))));
3863
- fo.setAttribute("y", String(limitDecimals(-(vbMinY - pad))));
3864
- fo.setAttribute("width", String(limitDecimals(w0 + pad * 2)));
3865
- fo.setAttribute("height", String(limitDecimals(h0 + pad * 2)));
3866
- fo.style.overflow = "visible";
3867
- const styleTag = document.createElement("style");
3868
- styleTag.textContent = state.baseCSS + state.fontsCSS + "svg{overflow:visible;} foreignObject{overflow:visible;}" + state.classCSS;
3869
- fo.appendChild(styleTag);
3870
- const container = document.createElement("div");
3871
- container.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
3872
- container.style.width = `${limitDecimals(w0)}px`;
3873
- container.style.height = `${limitDecimals(h0)}px`;
3874
- container.style.overflow = "visible";
3875
- state.clone.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
3876
- container.appendChild(state.clone);
3877
- fo.appendChild(container);
3878
- const serializer = new XMLSerializer();
3879
- const foString = serializer.serializeToString(fo);
3880
- const vbW = limitDecimals(vbW0 + pad * 2);
3881
- const vbH = limitDecimals(vbH0 + pad * 2);
3882
- const wantsSize = hasW || hasH;
3883
- options.meta = { w0, h0, vbW, vbH, targetW: w, targetH: h };
3884
- const svgOutW = isSafari() && wantsSize ? vbW : limitDecimals(outW + pad * 2);
3885
- const svgOutH = isSafari() && wantsSize ? vbH : limitDecimals(outH + pad * 2);
3886
- const svgHeader = `<svg xmlns="${svgNS}" width="${svgOutW}" height="${svgOutH}" viewBox="0 0 ${vbW} ${vbH}">`;
3887
- const svgFooter = "</svg>";
3888
- svgString = svgHeader + foString + svgFooter;
3889
- dataURL = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgString)}`;
3890
- state = { svgString, dataURL, ...state };
3891
- resolve();
3892
- }, { fast });
3893
- });
3894
- await runHook("afterRender", state);
3895
- const sandbox = document.getElementById("snapdom-sandbox");
3896
- if (sandbox && sandbox.style.position === "absolute") sandbox.remove();
3897
- return state.dataURL;
3917
+ const contentSpan = found ? Math.max(0, maxBottom - minTop) : 0;
3918
+ const bt = parseFloat(csC.borderTopWidth) || 0;
3919
+ const bb = parseFloat(csC.borderBottomWidth) || 0;
3920
+ const pt = parseFloat(csC.paddingTop) || 0;
3921
+ const pb = parseFloat(csC.paddingBottom) || 0;
3922
+ return bt + bb + pt + pb + contentSpan;
3898
3923
  }
3899
- function authorHasExplicitSize(el) {
3900
- try {
3901
- const s = el.getAttribute?.("style") || "";
3902
- return /\b(height|width|block-size|inline-size)\s*:/.test(s);
3903
- } catch {
3904
- return false;
3924
+ var limitDecimals = (v, n = 3) => Number.isFinite(v) ? Math.round(v * 10 ** n) / 10 ** n : v;
3925
+
3926
+ // src/utils/transforms.helpers.js
3927
+ function parseBoxShadow(cs) {
3928
+ const v = cs.boxShadow || "";
3929
+ if (!v || v === "none") return { top: 0, right: 0, bottom: 0, left: 0 };
3930
+ const parts = v.split(/\),(?=(?:[^()]*\([^()]*\))*[^()]*$)/).map((s) => s.trim());
3931
+ let t = 0, r = 0, b2 = 0, l = 0;
3932
+ for (const part of parts) {
3933
+ const nums = part.match(/-?\d+(\.\d+)?px/g)?.map((n) => parseFloat(n)) || [];
3934
+ if (nums.length < 2) continue;
3935
+ const [ox2, oy2, blur = 0, spread = 0] = nums;
3936
+ const extX = Math.abs(ox2) + blur + spread;
3937
+ const extY = Math.abs(oy2) + blur + spread;
3938
+ r = Math.max(r, extX + Math.max(ox2, 0));
3939
+ l = Math.max(l, extX + Math.max(-ox2, 0));
3940
+ b2 = Math.max(b2, extY + Math.max(oy2, 0));
3941
+ t = Math.max(t, extY + Math.max(-oy2, 0));
3905
3942
  }
3943
+ return { top: Math.ceil(t), right: Math.ceil(r), bottom: Math.ceil(b2), left: Math.ceil(l) };
3906
3944
  }
3907
- function isReplacedElement(el) {
3908
- return el instanceof HTMLImageElement || el instanceof HTMLCanvasElement || el instanceof HTMLVideoElement || el instanceof HTMLIFrameElement || el instanceof SVGElement || el instanceof HTMLObjectElement || el instanceof HTMLEmbedElement;
3909
- }
3910
- function shouldShrinkBox(srcEl, cs) {
3911
- if (!(srcEl instanceof Element)) return false;
3912
- if (authorHasExplicitSize(srcEl)) return false;
3913
- if (isReplacedElement(srcEl)) return false;
3914
- const pos = cs.position;
3915
- if (pos === "absolute" || pos === "fixed" || pos === "sticky") return false;
3916
- const disp = cs.display || "";
3917
- if (disp.includes("flex") || disp.includes("grid") || disp.startsWith("table")) return false;
3918
- if (cs.transform && cs.transform !== "none") return false;
3919
- return true;
3945
+ function parseFilterBlur(cs) {
3946
+ const m = (cs.filter || "").match(/blur\(\s*([0-9.]+)px\s*\)/);
3947
+ const b2 = m ? Math.ceil(parseFloat(m[1]) || 0) : 0;
3948
+ return { top: b2, right: b2, bottom: b2, left: b2 };
3920
3949
  }
3921
- function shrinkAutoSizeBoxes(sourceRoot, cloneRoot, styleCache = /* @__PURE__ */ new Map()) {
3922
- function walk(src, cln) {
3923
- if (!(src instanceof Element) || !(cln instanceof Element)) return;
3924
- const lostKids = src.childElementCount > cln.childElementCount;
3925
- const cs = styleCache.get(src) || getComputedStyle(src);
3926
- if (!styleCache.has(src)) styleCache.set(src, cs);
3927
- if (lostKids && shouldShrinkBox(src, cs)) {
3928
- if (!cln.style.height) cln.style.height = "auto";
3929
- if (!cln.style.width) cln.style.width = "auto";
3930
- cln.style.removeProperty("block-size");
3931
- cln.style.removeProperty("inline-size");
3932
- if (!cln.style.minHeight) cln.style.minHeight = "0";
3933
- if (!cln.style.minWidth) cln.style.minWidth = "0";
3934
- if (!cln.style.maxHeight) cln.style.maxHeight = "none";
3935
- if (!cln.style.maxWidth) cln.style.maxWidth = "none";
3936
- const oy = cs.overflowY || cs.overflowBlock || "visible";
3937
- const ox = cs.overflowX || cs.overflowInline || "visible";
3938
- if (oy !== "visible" || ox !== "visible") {
3939
- cln.style.overflow = "visible";
3940
- }
3941
- }
3942
- const sKids = Array.from(src.children);
3943
- const cKids = Array.from(cln.children);
3944
- for (let i = 0; i < Math.min(sKids.length, cKids.length); i++) {
3945
- walk(sKids[i], cKids[i]);
3946
- }
3947
- }
3948
- walk(sourceRoot, cloneRoot);
3950
+ function parseOutline(cs) {
3951
+ if ((cs.outlineStyle || "none") === "none") return { top: 0, right: 0, bottom: 0, left: 0 };
3952
+ const w2 = Math.ceil(parseFloat(cs.outlineWidth || "0") || 0);
3953
+ return { top: w2, right: w2, bottom: w2, left: w2 };
3949
3954
  }
3950
- var limitDecimals = (v, n = 3) => Number.isFinite(v) ? Math.round(v * 10 ** n) / 10 ** n : v;
3951
3955
  function parseFilterDropShadows(cs) {
3952
3956
  const raw = `${cs.filter || ""} ${cs.webkitFilter || ""}`.trim();
3953
3957
  if (!raw || raw === "none") {
@@ -4036,34 +4040,6 @@ function normalizeRootTransforms(originalEl, cloneRoot) {
4036
4040
  return null;
4037
4041
  }
4038
4042
  }
4039
- function parseBoxShadow(cs) {
4040
- const v = cs.boxShadow || "";
4041
- if (!v || v === "none") return { top: 0, right: 0, bottom: 0, left: 0 };
4042
- const parts = v.split(/\),(?=(?:[^()]*\([^()]*\))*[^()]*$)/).map((s) => s.trim());
4043
- let t = 0, r = 0, b2 = 0, l = 0;
4044
- for (const part of parts) {
4045
- const nums = part.match(/-?\d+(\.\d+)?px/g)?.map((n) => parseFloat(n)) || [];
4046
- if (nums.length < 2) continue;
4047
- const [ox2, oy2, blur = 0, spread = 0] = nums;
4048
- const extX = Math.abs(ox2) + blur + spread;
4049
- const extY = Math.abs(oy2) + blur + spread;
4050
- r = Math.max(r, extX + Math.max(ox2, 0));
4051
- l = Math.max(l, extX + Math.max(-ox2, 0));
4052
- b2 = Math.max(b2, extY + Math.max(oy2, 0));
4053
- t = Math.max(t, extY + Math.max(-oy2, 0));
4054
- }
4055
- return { top: Math.ceil(t), right: Math.ceil(r), bottom: Math.ceil(b2), left: Math.ceil(l) };
4056
- }
4057
- function parseFilterBlur(cs) {
4058
- const m = (cs.filter || "").match(/blur\(\s*([0-9.]+)px\s*\)/);
4059
- const b2 = m ? Math.ceil(parseFloat(m[1]) || 0) : 0;
4060
- return { top: b2, right: b2, bottom: b2, left: b2 };
4061
- }
4062
- function parseOutline(cs) {
4063
- if ((cs.outlineStyle || "none") === "none") return { top: 0, right: 0, bottom: 0, left: 0 };
4064
- const w2 = Math.ceil(parseFloat(cs.outlineWidth || "0") || 0);
4065
- return { top: w2, right: w2, bottom: w2, left: w2 };
4066
- }
4067
4043
  function bboxWithOriginFull(w2, h2, M, ox2, oy2) {
4068
4044
  const a2 = M.a, b2 = M.b, c2 = M.c, d2 = M.d, e2 = M.e || 0, f2 = M.f || 0;
4069
4045
  function pt(x, y) {
@@ -4083,17 +4059,24 @@ function bboxWithOriginFull(w2, h2, M, ox2, oy2) {
4083
4059
  }
4084
4060
  return { minX: minX2, minY: minY2, maxX: maxX2, maxY: maxY2, width: maxX2 - minX2, height: maxY2 - minY2 };
4085
4061
  }
4086
- function hasTFBBox(el) {
4087
- return hasBBoxAffectingTransform(el);
4088
- }
4089
- function matrixFromComputed(el) {
4090
- const tr = getComputedStyle(el).transform;
4091
- if (!tr || tr === "none") return new DOMMatrix();
4092
- try {
4093
- return new DOMMatrix(tr);
4094
- } catch {
4095
- return new WebKitCSSMatrix(tr);
4096
- }
4062
+ function parseTransformOriginPx(cs, w, h) {
4063
+ const raw = (cs.transformOrigin || "0 0").trim().split(/\s+/);
4064
+ const [oxRaw, oyRaw] = [raw[0] || "0", raw[1] || "0"];
4065
+ const toPx = (token, size) => {
4066
+ const t = token.toLowerCase();
4067
+ if (t === "left" || t === "top") return 0;
4068
+ if (t === "center") return size / 2;
4069
+ if (t === "right") return size;
4070
+ if (t === "bottom") return size;
4071
+ if (t.endsWith("px")) return parseFloat(t) || 0;
4072
+ if (t.endsWith("%")) return (parseFloat(t) || 0) * size / 100;
4073
+ if (/^-?\d+(\.\d+)?$/.test(t)) return parseFloat(t) || 0;
4074
+ return 0;
4075
+ };
4076
+ return {
4077
+ ox: toPx(oxRaw, w),
4078
+ oy: toPx(oyRaw, h)
4079
+ };
4097
4080
  }
4098
4081
  function readIndividualTransforms(el) {
4099
4082
  const out = { rotate: "0deg", scale: null, translate: null };
@@ -4150,35 +4133,6 @@ function readIndividualTransforms(el) {
4150
4133
  out.translate = cs.translate && cs.translate !== "none" ? cs.translate : null;
4151
4134
  return out;
4152
4135
  }
4153
- function hasBBoxAffectingTransform(el) {
4154
- const cs = getComputedStyle(el);
4155
- const t = cs.transform || "none";
4156
- const hasMatrix = t !== "none" && !/^matrix\(\s*1\s*,\s*0\s*,\s*0\s*,\s*1\s*,\s*0\s*,\s*0\s*\)$/i.test(t);
4157
- if (hasMatrix) return true;
4158
- const r = cs.rotate && cs.rotate !== "none" && cs.rotate !== "0deg";
4159
- const s = cs.scale && cs.scale !== "none" && cs.scale !== "1";
4160
- const tr = cs.translate && cs.translate !== "none" && cs.translate !== "0px 0px";
4161
- return Boolean(r || s || tr);
4162
- }
4163
- function parseTransformOriginPx(cs, w, h) {
4164
- const raw = (cs.transformOrigin || "0 0").trim().split(/\s+/);
4165
- const [oxRaw, oyRaw] = [raw[0] || "0", raw[1] || "0"];
4166
- const toPx = (token, size) => {
4167
- const t = token.toLowerCase();
4168
- if (t === "left" || t === "top") return 0;
4169
- if (t === "center") return size / 2;
4170
- if (t === "right") return size;
4171
- if (t === "bottom") return size;
4172
- if (t.endsWith("px")) return parseFloat(t) || 0;
4173
- if (t.endsWith("%")) return (parseFloat(t) || 0) * size / 100;
4174
- if (/^-?\d+(\.\d+)?$/.test(t)) return parseFloat(t) || 0;
4175
- return 0;
4176
- };
4177
- return {
4178
- ox: toPx(oxRaw, w),
4179
- oy: toPx(oyRaw, h)
4180
- };
4181
- }
4182
4136
  var __measureHost = null;
4183
4137
  function getMeasureHost() {
4184
4138
  if (__measureHost) return __measureHost;
@@ -4213,51 +4167,254 @@ function readTotalTransformMatrix(t) {
4213
4167
  host.removeChild(tmp);
4214
4168
  return M;
4215
4169
  }
4216
- function isInNormalFlow(el) {
4170
+ function hasBBoxAffectingTransform(el) {
4217
4171
  const cs = getComputedStyle(el);
4218
- if (cs.display === "none") return false;
4219
- if (cs.position === "absolute" || cs.position === "fixed" || cs.position === "sticky") return false;
4220
- if ((cs.cssFloat || cs.float || "none") !== "none") return false;
4221
- if (cs.transform && cs.transform !== "none") return false;
4222
- return true;
4172
+ const t = cs.transform || "none";
4173
+ const hasMatrix = t !== "none" && !/^matrix\(\s*1\s*,\s*0\s*,\s*0\s*,\s*1\s*,\s*0\s*,\s*0\s*\)$/i.test(t);
4174
+ if (hasMatrix) return true;
4175
+ const r = cs.rotate && cs.rotate !== "none" && cs.rotate !== "0deg";
4176
+ const s = cs.scale && cs.scale !== "none" && cs.scale !== "1";
4177
+ const tr = cs.translate && cs.translate !== "none" && cs.translate !== "0px 0px";
4178
+ return Boolean(r || s || tr);
4223
4179
  }
4224
- function willBeExcluded(el, options) {
4225
- if (!(el instanceof Element)) return false;
4226
- if (el.getAttribute("data-capture") === "exclude" && options?.excludeMode === "remove") return true;
4227
- if (Array.isArray(options?.exclude)) {
4228
- for (const sel of options.exclude) {
4229
- try {
4230
- if (el.matches(sel)) return options.excludeMode === "remove";
4231
- } catch {
4232
- }
4233
- }
4180
+ function matrixFromComputed(el) {
4181
+ const tr = getComputedStyle(el).transform;
4182
+ if (!tr || tr === "none") return new DOMMatrix();
4183
+ try {
4184
+ return new DOMMatrix(tr);
4185
+ } catch {
4186
+ return new WebKitCSSMatrix(tr);
4234
4187
  }
4235
- return false;
4236
4188
  }
4237
- function estimateKeptHeight(container, options) {
4238
- const csC = getComputedStyle(container);
4239
- const rC = container.getBoundingClientRect();
4240
- let minTop = Infinity;
4241
- let maxBottom = -Infinity;
4242
- let found = false;
4243
- const kids = Array.from(container.children);
4244
- for (const k of kids) {
4245
- if (willBeExcluded(k, options)) continue;
4246
- if (!isInNormalFlow(k)) continue;
4247
- const rk = k.getBoundingClientRect();
4248
- const top = rk.top - rC.top;
4249
- const bottom = rk.bottom - rC.top;
4250
- if (bottom <= top) continue;
4251
- if (top < minTop) minTop = top;
4252
- if (bottom > maxBottom) maxBottom = bottom;
4253
- found = true;
4189
+
4190
+ // src/core/capture.js
4191
+ async function captureDOM(element, options) {
4192
+ if (!element) throw new Error("Element cannot be null or undefined");
4193
+ applyCachePolicy(options.cache);
4194
+ const fast = options.fast;
4195
+ const outerTransforms = options.outerTransforms !== false;
4196
+ const outerShadows = !!options.outerShadows;
4197
+ let state = { element, options, plugins: options.plugins };
4198
+ let clone, classCSS, styleCache;
4199
+ let fontsCSS = "";
4200
+ let baseCSS = "";
4201
+ let dataURL;
4202
+ let svgString;
4203
+ let rootTransform2D = null;
4204
+ await runHook("beforeSnap", state);
4205
+ await runHook("beforeClone", state);
4206
+ const undoClamp = lineClamp(state.element);
4207
+ try {
4208
+ ({ clone, classCSS, styleCache } = await prepareClone(state.element, state.options));
4209
+ if (!outerTransforms && clone) {
4210
+ rootTransform2D = normalizeRootTransforms(state.element, clone);
4211
+ }
4212
+ if (!outerShadows && clone) {
4213
+ stripRootShadows(state.element, clone);
4214
+ }
4215
+ } finally {
4216
+ undoClamp();
4254
4217
  }
4255
- const contentSpan = found ? Math.max(0, maxBottom - minTop) : 0;
4256
- const bt = parseFloat(csC.borderTopWidth) || 0;
4257
- const bb = parseFloat(csC.borderBottomWidth) || 0;
4258
- const pt = parseFloat(csC.paddingTop) || 0;
4259
- const pb = parseFloat(csC.paddingBottom) || 0;
4260
- return bt + bb + pt + pb + contentSpan;
4218
+ state = { clone, classCSS, styleCache, ...state };
4219
+ await runHook("afterClone", state);
4220
+ sanitizeCloneForXHTML(state.clone);
4221
+ if (state.options?.excludeMode === "remove") {
4222
+ try {
4223
+ shrinkAutoSizeBoxes(state.element, state.clone, state.styleCache);
4224
+ } catch (e) {
4225
+ console.warn("[snapdom] shrink pass failed:", e);
4226
+ }
4227
+ }
4228
+ try {
4229
+ await ligatureIconToImage(state.clone, state.element);
4230
+ } catch {
4231
+ }
4232
+ await new Promise((resolve) => {
4233
+ idle(async () => {
4234
+ await inlineImages(state.clone, state.options);
4235
+ resolve();
4236
+ }, { fast });
4237
+ });
4238
+ await new Promise((resolve) => {
4239
+ idle(async () => {
4240
+ await inlineBackgroundImages(state.element, state.clone, state.styleCache, state.options);
4241
+ resolve();
4242
+ }, { fast });
4243
+ });
4244
+ if (options.embedFonts) {
4245
+ await new Promise((resolve) => {
4246
+ idle(async () => {
4247
+ const required = collectUsedFontVariants(state.element);
4248
+ const usedCodepoints = collectUsedCodepoints(state.element);
4249
+ if (isSafari()) {
4250
+ const families = new Set(
4251
+ Array.from(required).map((k) => String(k).split("__")[0]).filter(Boolean)
4252
+ );
4253
+ await ensureFontsReady(families, 1);
4254
+ }
4255
+ fontsCSS = await embedCustomFonts({
4256
+ required,
4257
+ usedCodepoints,
4258
+ preCached: false,
4259
+ exclude: state.options.excludeFonts,
4260
+ useProxy: state.options.useProxy
4261
+ });
4262
+ resolve();
4263
+ }, { fast });
4264
+ });
4265
+ }
4266
+ const usedTags = collectUsedTagNames(state.clone).sort();
4267
+ const tagKey = usedTags.join(",");
4268
+ if (cache.baseStyle.has(tagKey)) {
4269
+ baseCSS = cache.baseStyle.get(tagKey);
4270
+ } else {
4271
+ await new Promise((resolve) => {
4272
+ idle(() => {
4273
+ baseCSS = generateDedupedBaseCSS(usedTags);
4274
+ cache.baseStyle.set(tagKey, baseCSS);
4275
+ resolve();
4276
+ }, { fast });
4277
+ });
4278
+ }
4279
+ state = { fontsCSS, baseCSS, ...state };
4280
+ await runHook("beforeRender", state);
4281
+ await new Promise((resolve) => {
4282
+ idle(() => {
4283
+ const csEl = getComputedStyle(state.element);
4284
+ const rect = state.element.getBoundingClientRect();
4285
+ let w0 = Math.max(1, limitDecimals(state.element.offsetWidth || parseFloat(csEl.width) || rect.width || 1));
4286
+ let h0 = Math.max(1, limitDecimals(state.element.offsetHeight || parseFloat(csEl.height) || rect.height || 1));
4287
+ if (state.options?.excludeMode === "remove") {
4288
+ const hEst = estimateKeptHeight(state.element, state.options);
4289
+ const EPS = 1;
4290
+ if (Number.isFinite(hEst) && hEst > 0) {
4291
+ h0 = Math.max(1, Math.min(h0, limitDecimals(hEst + EPS)));
4292
+ }
4293
+ }
4294
+ const coerceNum = (v, def = NaN) => {
4295
+ const n = typeof v === "string" ? parseFloat(v) : v;
4296
+ return Number.isFinite(n) ? n : def;
4297
+ };
4298
+ const optW = coerceNum(state.options.width);
4299
+ const optH = coerceNum(state.options.height);
4300
+ let w = w0, h = h0;
4301
+ const hasW = Number.isFinite(optW);
4302
+ const hasH = Number.isFinite(optH);
4303
+ const aspect0 = h0 > 0 ? w0 / h0 : 1;
4304
+ if (hasW && hasH) {
4305
+ w = Math.max(1, limitDecimals(optW));
4306
+ h = Math.max(1, limitDecimals(optH));
4307
+ } else if (hasW) {
4308
+ w = Math.max(1, limitDecimals(optW));
4309
+ h = Math.max(1, limitDecimals(w / (aspect0 || 1)));
4310
+ } else if (hasH) {
4311
+ h = Math.max(1, limitDecimals(optH));
4312
+ w = Math.max(1, limitDecimals(h * (aspect0 || 1)));
4313
+ } else {
4314
+ w = w0;
4315
+ h = h0;
4316
+ }
4317
+ let minX = 0, minY = 0, maxX = w0, maxY = h0;
4318
+ if (!outerTransforms && rootTransform2D && Number.isFinite(rootTransform2D.a)) {
4319
+ const M2 = {
4320
+ a: rootTransform2D.a,
4321
+ b: rootTransform2D.b || 0,
4322
+ c: rootTransform2D.c || 0,
4323
+ d: rootTransform2D.d || 1,
4324
+ e: 0,
4325
+ f: 0
4326
+ };
4327
+ const bb2 = bboxWithOriginFull(w0, h0, M2, 0, 0);
4328
+ minX = limitDecimals(bb2.minX);
4329
+ minY = limitDecimals(bb2.minY);
4330
+ maxX = limitDecimals(bb2.maxX);
4331
+ maxY = limitDecimals(bb2.maxY);
4332
+ } else {
4333
+ const useTFBBox = outerTransforms && hasTFBBox(state.element);
4334
+ if (useTFBBox) {
4335
+ const baseTransform2 = csEl.transform && csEl.transform !== "none" ? csEl.transform : "";
4336
+ const ind2 = readIndividualTransforms(state.element);
4337
+ const TOTAL = readTotalTransformMatrix({
4338
+ baseTransform: baseTransform2,
4339
+ rotate: ind2.rotate || "0deg",
4340
+ scale: ind2.scale,
4341
+ translate: ind2.translate
4342
+ });
4343
+ const { ox: ox2, oy: oy2 } = parseTransformOriginPx(csEl, w0, h0);
4344
+ const M = TOTAL.is2D ? TOTAL : new DOMMatrix(TOTAL.toString());
4345
+ const bb = bboxWithOriginFull(w0, h0, M, ox2, oy2);
4346
+ minX = limitDecimals(bb.minX);
4347
+ minY = limitDecimals(bb.minY);
4348
+ maxX = limitDecimals(bb.maxX);
4349
+ maxY = limitDecimals(bb.maxY);
4350
+ }
4351
+ }
4352
+ const bleedShadow = parseBoxShadow(csEl);
4353
+ const bleedBlur = parseFilterBlur(csEl);
4354
+ const bleedOutline = parseOutline(csEl);
4355
+ const drop = parseFilterDropShadows(csEl);
4356
+ const bleed = !outerShadows ? { top: 0, right: 0, bottom: 0, left: 0 } : {
4357
+ top: limitDecimals(bleedShadow.top + bleedBlur.top + bleedOutline.top + drop.bleed.top),
4358
+ right: limitDecimals(bleedShadow.right + bleedBlur.right + bleedOutline.right + drop.bleed.right),
4359
+ bottom: limitDecimals(bleedShadow.bottom + bleedBlur.bottom + bleedOutline.bottom + drop.bleed.bottom),
4360
+ left: limitDecimals(bleedShadow.left + bleedBlur.left + bleedOutline.left + drop.bleed.left)
4361
+ };
4362
+ minX = limitDecimals(minX - bleed.left);
4363
+ minY = limitDecimals(minY - bleed.top);
4364
+ maxX = limitDecimals(maxX + bleed.right);
4365
+ maxY = limitDecimals(maxY + bleed.bottom);
4366
+ const vbW0 = Math.max(1, limitDecimals(maxX - minX));
4367
+ const vbH0 = Math.max(1, limitDecimals(maxY - minY));
4368
+ const scaleW = hasW || hasH ? limitDecimals(w / w0) : 1;
4369
+ const scaleH = hasH || hasW ? limitDecimals(h / h0) : 1;
4370
+ const outW = Math.max(1, limitDecimals(vbW0 * scaleW));
4371
+ const outH = Math.max(1, limitDecimals(vbH0 * scaleH));
4372
+ const svgNS = "http://www.w3.org/2000/svg";
4373
+ const basePad = isSafari() ? 1 : 0;
4374
+ const extraPad = !outerTransforms ? 1 : 0;
4375
+ const pad = limitDecimals(basePad + extraPad);
4376
+ const fo = document.createElementNS(svgNS, "foreignObject");
4377
+ const vbMinX = limitDecimals(minX);
4378
+ const vbMinY = limitDecimals(minY);
4379
+ fo.setAttribute("x", String(limitDecimals(-(vbMinX - pad))));
4380
+ fo.setAttribute("y", String(limitDecimals(-(vbMinY - pad))));
4381
+ fo.setAttribute("width", String(limitDecimals(w0 + pad * 2)));
4382
+ fo.setAttribute("height", String(limitDecimals(h0 + pad * 2)));
4383
+ fo.style.overflow = "visible";
4384
+ const styleTag = document.createElement("style");
4385
+ styleTag.textContent = state.baseCSS + state.fontsCSS + "svg{overflow:visible;} foreignObject{overflow:visible;}" + state.classCSS;
4386
+ fo.appendChild(styleTag);
4387
+ const container = document.createElement("div");
4388
+ container.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
4389
+ container.style.width = `${limitDecimals(w0)}px`;
4390
+ container.style.height = `${limitDecimals(h0)}px`;
4391
+ container.style.overflow = "visible";
4392
+ state.clone.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
4393
+ container.appendChild(state.clone);
4394
+ fo.appendChild(container);
4395
+ const serializer = new XMLSerializer();
4396
+ const foString = serializer.serializeToString(fo);
4397
+ const vbW = limitDecimals(vbW0 + pad * 2);
4398
+ const vbH = limitDecimals(vbH0 + pad * 2);
4399
+ const wantsSize = hasW || hasH;
4400
+ options.meta = { w0, h0, vbW, vbH, targetW: w, targetH: h };
4401
+ const svgOutW = isSafari() && wantsSize ? vbW : limitDecimals(outW + pad * 2);
4402
+ const svgOutH = isSafari() && wantsSize ? vbH : limitDecimals(outH + pad * 2);
4403
+ const svgHeader = `<svg xmlns="${svgNS}" width="${svgOutW}" height="${svgOutH}" viewBox="0 0 ${vbW} ${vbH}">`;
4404
+ const svgFooter = "</svg>";
4405
+ svgString = svgHeader + foString + svgFooter;
4406
+ dataURL = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgString)}`;
4407
+ state = { svgString, dataURL, ...state };
4408
+ resolve();
4409
+ }, { fast });
4410
+ });
4411
+ await runHook("afterRender", state);
4412
+ const sandbox = document.getElementById("snapdom-sandbox");
4413
+ if (sandbox && sandbox.style.position === "absolute") sandbox.remove();
4414
+ return state.dataURL;
4415
+ }
4416
+ function hasTFBBox(el) {
4417
+ return hasBBoxAffectingTransform(el);
4261
4418
  }
4262
4419
 
4263
4420
  // src/core/context.js