@zumer/snapdom 2.0.0-dev.1 → 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.js CHANGED
@@ -1,7 +1,7 @@
1
1
 
2
2
  /*
3
3
  * snapdom
4
- * v.2.0.0-dev.1
4
+ * v.2.0.0-dev.3
5
5
  * Author Juan Martin Muda
6
6
  * License MIT
7
7
  **/
@@ -374,19 +374,6 @@
374
374
  }
375
375
 
376
376
  // src/utils/image.js
377
- function createBackground(baseCanvas, backgroundColor) {
378
- if (!backgroundColor || !baseCanvas.width || !baseCanvas.height) {
379
- return baseCanvas;
380
- }
381
- const temp = document.createElement("canvas");
382
- temp.width = baseCanvas.width;
383
- temp.height = baseCanvas.height;
384
- const ctx = temp.getContext("2d");
385
- ctx.fillStyle = backgroundColor;
386
- ctx.fillRect(0, 0, temp.width, temp.height);
387
- ctx.drawImage(baseCanvas, 0, 0);
388
- return temp;
389
- }
390
377
  function inlineSingleBackgroundEntry(_0) {
391
378
  return __async(this, arguments, function* (entry, options = {}) {
392
379
  const isGradient = /^((repeating-)?(linear|radial|conic)-gradient)\(/i.test(entry);
@@ -664,11 +651,20 @@
664
651
  }
665
652
  }
666
653
  function isSafari() {
667
- const ua = typeof navigator !== "undefined" && navigator.userAgent ? navigator.userAgent : "";
668
- const isSafariUA = /^((?!chrome|android).)*safari/i.test(ua);
669
- const isUIWebView = /AppleWebKit/i.test(ua) && /Mobile/i.test(ua) && !/Safari/i.test(ua);
670
- const isWeChatUA = /(MicroMessenger|wxwork|WeCom|WindowsWechat|MacWechat)/i.test(ua);
671
- return isSafariUA || isUIWebView || isWeChatUA;
654
+ if (typeof navigator === "undefined") return false;
655
+ const ua = navigator.userAgent || "";
656
+ const uaLower = ua.toLowerCase();
657
+ const isSafariUA = uaLower.includes("safari") && !uaLower.includes("chrome") && !uaLower.includes("crios") && // Chrome on iOS
658
+ !uaLower.includes("fxios") && // Firefox on iOS
659
+ !uaLower.includes("android");
660
+ const isWebKit = /applewebkit/i.test(ua);
661
+ const isMobile = /mobile/i.test(ua);
662
+ const missingSafariToken = !/safari/i.test(ua);
663
+ const isUIWebView = isWebKit && isMobile && missingSafariToken;
664
+ const isWeChatUA = /(micromessenger|wxwork|wecom|windowswechat|macwechat)/i.test(ua);
665
+ const isBaiduUA = /(baiduboxapp|baidubrowser|baidusearch|baiduboxlite)/i.test(uaLower);
666
+ const isIOSWebKit = /ipad|iphone|ipod/.test(uaLower) && isWebKit;
667
+ return isSafariUA || isUIWebView || isWeChatUA || isBaiduUA || isIOSWebKit;
672
668
  }
673
669
 
674
670
  // src/modules/styles.js
@@ -714,6 +710,22 @@
714
710
  }
715
711
  out[prop] = val;
716
712
  }
713
+ const EXTRA_TEXT_DECORATION_PROPS = [
714
+ "text-decoration-line",
715
+ "text-decoration-color",
716
+ "text-decoration-style",
717
+ "text-decoration-thickness",
718
+ "text-underline-offset",
719
+ "text-decoration-skip-ink"
720
+ ];
721
+ for (const prop of EXTRA_TEXT_DECORATION_PROPS) {
722
+ if (out[prop]) continue;
723
+ try {
724
+ const v = style.getPropertyValue(prop);
725
+ if (v) out[prop] = v;
726
+ } catch (e) {
727
+ }
728
+ }
717
729
  if (options.embedFonts) {
718
730
  const EXTRA_FONT_PROPS = [
719
731
  "font-feature-settings",
@@ -960,7 +972,7 @@
960
972
  }
961
973
  }
962
974
 
963
- // src/core/clone.js
975
+ // src/utils/clone.helpers.js
964
976
  function idleCallback(childList, callback, fast) {
965
977
  return Promise.all(childList.map((child) => {
966
978
  return new Promise((resolve) => {
@@ -1177,6 +1189,143 @@
1177
1189
  return wrapper;
1178
1190
  });
1179
1191
  }
1192
+ var _blobToDataUrlCache = /* @__PURE__ */ new Map();
1193
+ function blobUrlToDataUrl(blobUrl) {
1194
+ return __async(this, null, function* () {
1195
+ var _a;
1196
+ if ((_a = cache.resource) == null ? void 0 : _a.has(blobUrl)) return cache.resource.get(blobUrl);
1197
+ if (_blobToDataUrlCache.has(blobUrl)) return _blobToDataUrlCache.get(blobUrl);
1198
+ const p = (() => __async(null, null, function* () {
1199
+ var _a2;
1200
+ const r = yield snapFetch(blobUrl, { as: "dataURL", silent: true });
1201
+ if (!r.ok || typeof r.data !== "string") {
1202
+ throw new Error(`[snapDOM] Failed to read blob URL: ${blobUrl}`);
1203
+ }
1204
+ (_a2 = cache.resource) == null ? void 0 : _a2.set(blobUrl, r.data);
1205
+ return r.data;
1206
+ }))();
1207
+ _blobToDataUrlCache.set(blobUrl, p);
1208
+ try {
1209
+ const data = yield p;
1210
+ _blobToDataUrlCache.set(blobUrl, data);
1211
+ return data;
1212
+ } catch (e) {
1213
+ _blobToDataUrlCache.delete(blobUrl);
1214
+ throw e;
1215
+ }
1216
+ });
1217
+ }
1218
+ var BLOB_URL_RE = /\bblob:[^)"'\s]+/g;
1219
+ function replaceBlobUrlsInCssText(cssText) {
1220
+ return __async(this, null, function* () {
1221
+ if (!cssText || cssText.indexOf("blob:") === -1) return cssText;
1222
+ const uniques = Array.from(new Set(cssText.match(BLOB_URL_RE) || []));
1223
+ if (uniques.length === 0) return cssText;
1224
+ let out = cssText;
1225
+ for (const u of uniques) {
1226
+ try {
1227
+ const d = yield blobUrlToDataUrl(u);
1228
+ out = out.split(u).join(d);
1229
+ } catch (e) {
1230
+ }
1231
+ }
1232
+ return out;
1233
+ });
1234
+ }
1235
+ function isBlobUrl(u) {
1236
+ return typeof u === "string" && u.startsWith("blob:");
1237
+ }
1238
+ function parseSrcset(srcset) {
1239
+ return (srcset || "").split(",").map((s) => s.trim()).filter(Boolean).map((item) => {
1240
+ const m = item.match(/^(\S+)(\s+.+)?$/);
1241
+ return m ? { url: m[1], desc: m[2] || "" } : null;
1242
+ }).filter(Boolean);
1243
+ }
1244
+ function stringifySrcset(parts) {
1245
+ return parts.map((p) => p.desc ? `${p.url} ${p.desc.trim()}` : p.url).join(", ");
1246
+ }
1247
+ function resolveBlobUrlsInTree(root) {
1248
+ return __async(this, null, function* () {
1249
+ var _a, _b;
1250
+ if (!root) return;
1251
+ const imgs = root.querySelectorAll ? root.querySelectorAll("img") : [];
1252
+ for (const img of imgs) {
1253
+ try {
1254
+ const srcAttr = img.getAttribute("src");
1255
+ const effective = srcAttr || img.currentSrc || "";
1256
+ if (isBlobUrl(effective)) {
1257
+ const data = yield blobUrlToDataUrl(effective);
1258
+ img.setAttribute("src", data);
1259
+ }
1260
+ const srcset = img.getAttribute("srcset");
1261
+ if (srcset && srcset.includes("blob:")) {
1262
+ const parts = parseSrcset(srcset);
1263
+ let changed = false;
1264
+ for (const p of parts) {
1265
+ if (isBlobUrl(p.url)) {
1266
+ try {
1267
+ p.url = yield blobUrlToDataUrl(p.url);
1268
+ changed = true;
1269
+ } catch (e) {
1270
+ }
1271
+ }
1272
+ }
1273
+ if (changed) img.setAttribute("srcset", stringifySrcset(parts));
1274
+ }
1275
+ } catch (e) {
1276
+ }
1277
+ }
1278
+ const svgImages = root.querySelectorAll ? root.querySelectorAll("image") : [];
1279
+ for (const node of svgImages) {
1280
+ try {
1281
+ const XLINK_NS = "http://www.w3.org/1999/xlink";
1282
+ const href = node.getAttribute("href") || ((_a = node.getAttributeNS) == null ? void 0 : _a.call(node, XLINK_NS, "href"));
1283
+ if (isBlobUrl(href)) {
1284
+ const d = yield blobUrlToDataUrl(href);
1285
+ node.setAttribute("href", d);
1286
+ (_b = node.removeAttributeNS) == null ? void 0 : _b.call(node, XLINK_NS, "href");
1287
+ }
1288
+ } catch (e) {
1289
+ }
1290
+ }
1291
+ const styled = root.querySelectorAll ? root.querySelectorAll("[style*='blob:']") : [];
1292
+ for (const el of styled) {
1293
+ try {
1294
+ const styleText = el.getAttribute("style");
1295
+ if (styleText && styleText.includes("blob:")) {
1296
+ const replaced = yield replaceBlobUrlsInCssText(styleText);
1297
+ el.setAttribute("style", replaced);
1298
+ }
1299
+ } catch (e) {
1300
+ }
1301
+ }
1302
+ const styleTags = root.querySelectorAll ? root.querySelectorAll("style") : [];
1303
+ for (const s of styleTags) {
1304
+ try {
1305
+ const css = s.textContent || "";
1306
+ if (css.includes("blob:")) {
1307
+ s.textContent = yield replaceBlobUrlsInCssText(css);
1308
+ }
1309
+ } catch (e) {
1310
+ }
1311
+ }
1312
+ const urlAttrs = ["poster"];
1313
+ for (const attr of urlAttrs) {
1314
+ const nodes = root.querySelectorAll ? root.querySelectorAll(`[${attr}^='blob:']`) : [];
1315
+ for (const n of nodes) {
1316
+ try {
1317
+ const u = n.getAttribute(attr);
1318
+ if (isBlobUrl(u)) {
1319
+ n.setAttribute(attr, yield blobUrlToDataUrl(u));
1320
+ }
1321
+ } catch (e) {
1322
+ }
1323
+ }
1324
+ }
1325
+ });
1326
+ }
1327
+
1328
+ // src/core/clone.js
1180
1329
  function deepClone(node, sessionCache, options) {
1181
1330
  return __async(this, null, function* () {
1182
1331
  var _a, _b, _c, _d, _e, _f;
@@ -1482,17 +1631,19 @@
1482
1631
  /layui/i,
1483
1632
  /lucide/i
1484
1633
  ];
1634
+ var ICON_FONT_URLS = Object.assign({
1635
+ materialIconsFilled: "https://fonts.gstatic.com/s/materialicons/v48/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.woff2",
1636
+ materialIconsOutlined: "https://fonts.gstatic.com/s/materialiconsoutlined/v110/gok-H7zzDkdnRel8-DQ6KAXJ69wP1tGnf4ZGhUcel5euIg.woff2",
1637
+ materialIconsRound: "https://fonts.gstatic.com/s/materialiconsround/v109/LDItaoyNOAY6Uewc665JcIzCKsKc_M9flwmPq_HTTw.woff2",
1638
+ materialIconsSharp: "https://fonts.gstatic.com/s/materialiconssharp/v110/oPWQ_lt5nv4pWNJpghLP75WiFR4kLh3kvmvRImcycg.woff2"
1639
+ }, typeof window !== "undefined" && window.__SNAPDOM_ICON_FONTS__ || {});
1485
1640
  var userIconFonts = [];
1486
1641
  function extendIconFonts(fonts) {
1487
1642
  const list = Array.isArray(fonts) ? fonts : [fonts];
1488
1643
  for (const f of list) {
1489
- if (f instanceof RegExp) {
1490
- userIconFonts.push(f);
1491
- } else if (typeof f === "string") {
1492
- userIconFonts.push(new RegExp(f, "i"));
1493
- } else {
1494
- console.warn("[snapdom] Ignored invalid iconFont value:", f);
1495
- }
1644
+ if (f instanceof RegExp) userIconFonts.push(f);
1645
+ else if (typeof f === "string") userIconFonts.push(new RegExp(f, "i"));
1646
+ else console.warn("[snapdom] Ignored invalid iconFont value:", f);
1496
1647
  }
1497
1648
  }
1498
1649
  function isIconFont2(input) {
@@ -1508,11 +1659,66 @@
1508
1659
  const s = String(family).toLowerCase();
1509
1660
  return /\bmaterial\s*icons\b/.test(s) || /\bmaterial\s*symbols\b/.test(s);
1510
1661
  }
1662
+ var loadedCanvasFamilies = /* @__PURE__ */ new Map();
1663
+ function parseAxes(variation = "") {
1664
+ const out = /* @__PURE__ */ Object.create(null);
1665
+ const v = String(variation || "");
1666
+ const rx = /['"]?\s*([A-Za-z]{3,4})\s*['"]?\s*([+-]?\d+(?:\.\d+)?)\s*/g;
1667
+ let m;
1668
+ while (m = rx.exec(v)) out[m[1].toUpperCase()] = Number(m[2]);
1669
+ return out;
1670
+ }
1671
+ function ensureLigatureCanvasFont(cssFamily, className, axes) {
1672
+ return __async(this, null, function* () {
1673
+ var _a;
1674
+ const fam = String(cssFamily || "");
1675
+ const lowerFam = fam.toLowerCase();
1676
+ const cls = String(className || "").toLowerCase();
1677
+ if (/\bmaterial\s*icons\b/.test(lowerFam) && !/\bsymbols\b/.test(lowerFam)) {
1678
+ return { familyForMeasure: fam, familyForCanvas: fam };
1679
+ }
1680
+ const isSymbols = /\bmaterial\s*symbols\b/.test(lowerFam);
1681
+ if (!isSymbols) {
1682
+ return { familyForMeasure: fam, familyForCanvas: fam };
1683
+ }
1684
+ const FILL = axes && ((_a = axes.FILL) != null ? _a : axes.fill);
1685
+ let style = "outlined";
1686
+ if (/\brounded\b/.test(cls) || /\bround\b/.test(cls)) style = "rounded";
1687
+ else if (/\bsharp\b/.test(cls)) style = "sharp";
1688
+ else if (/\boutlined\b/.test(cls)) style = "outlined";
1689
+ const filled = FILL === 1;
1690
+ let pick = null;
1691
+ if (filled) {
1692
+ if (style === "outlined" && ICON_FONT_URLS.materialIconsFilled) {
1693
+ pick = { url: ICON_FONT_URLS.materialIconsFilled, alias: "snapdom-mi-filled" };
1694
+ } else if (style === "rounded" && ICON_FONT_URLS.materialIconsRound) {
1695
+ pick = { url: ICON_FONT_URLS.materialIconsRound, alias: "snapdom-mi-round" };
1696
+ } else if (style === "sharp" && ICON_FONT_URLS.materialIconsSharp) {
1697
+ pick = { url: ICON_FONT_URLS.materialIconsSharp, alias: "snapdom-mi-sharp" };
1698
+ }
1699
+ }
1700
+ if (!pick) {
1701
+ return { familyForMeasure: fam, familyForCanvas: fam };
1702
+ }
1703
+ if (!loadedCanvasFamilies.has(pick.alias)) {
1704
+ try {
1705
+ const ff = new FontFace(pick.alias, `url(${pick.url})`, { style: "normal", weight: "400" });
1706
+ document.fonts.add(ff);
1707
+ yield ff.load();
1708
+ loadedCanvasFamilies.set(pick.alias, true);
1709
+ } catch (e) {
1710
+ return { familyForMeasure: fam, familyForCanvas: fam };
1711
+ }
1712
+ }
1713
+ const quoted = `"${pick.alias}"`;
1714
+ return { familyForMeasure: quoted, familyForCanvas: quoted };
1715
+ });
1716
+ }
1511
1717
  function ensureMaterialFontsReady(family = "Material Icons", px = 24) {
1512
1718
  return __async(this, null, function* () {
1513
1719
  try {
1514
1720
  yield Promise.all([
1515
- document.fonts.load(`400 ${px}px "${family.replace(/["']/g, "")}"`),
1721
+ document.fonts.load(`400 ${px}px "${String(family).replace(/["']/g, "")}"`),
1516
1722
  document.fonts.ready
1517
1723
  ]);
1518
1724
  } catch (e) {
@@ -1522,10 +1728,8 @@
1522
1728
  function resolvePaintColor(cs) {
1523
1729
  var _a, _b;
1524
1730
  let fill = ((_a = cs.getPropertyValue("-webkit-text-fill-color")) == null ? void 0 : _a.trim()) || "";
1525
- const isTransparent = /rgba?\(\s*0\s*,\s*0\s*,\s*0\s*,\s*0\s*\)/i.test(fill);
1526
- if (fill && !isTransparent && fill.toLowerCase() !== "currentcolor") {
1527
- return fill;
1528
- }
1731
+ const isTransparent = /^transparent$/i.test(fill) || /rgba?\(\s*0\s*,\s*0\s*,\s*0\s*,\s*0\s*\)/i.test(fill);
1732
+ if (fill && !isTransparent && fill.toLowerCase() !== "currentcolor") return fill;
1529
1733
  const c = (_b = cs.color) == null ? void 0 : _b.trim();
1530
1734
  return c && c !== "inherit" ? c : "#000";
1531
1735
  }
@@ -1535,18 +1739,21 @@
1535
1739
  weight = "normal",
1536
1740
  fontSize = 32,
1537
1741
  color = "#000",
1538
- variation = ""
1742
+ variation = "",
1743
+ className = ""
1539
1744
  } = {}) {
1540
1745
  const fam = String(family || "").replace(/^['"]+|['"]+$/g, "");
1541
1746
  const dpr = window.devicePixelRatio || 1;
1542
- yield ensureMaterialFontsReady(fam, fontSize);
1747
+ const axes = parseAxes(variation);
1748
+ const { familyForMeasure, familyForCanvas } = yield ensureLigatureCanvasFont(fam, className, axes);
1749
+ yield ensureMaterialFontsReady(familyForCanvas.replace(/^["']+|["']+$/g, ""), fontSize);
1543
1750
  const span = document.createElement("span");
1544
1751
  span.textContent = ligatureText;
1545
1752
  span.style.position = "absolute";
1546
1753
  span.style.visibility = "hidden";
1547
1754
  span.style.left = "-99999px";
1548
1755
  span.style.whiteSpace = "nowrap";
1549
- span.style.fontFamily = `"${fam}"`;
1756
+ span.style.fontFamily = familyForMeasure;
1550
1757
  span.style.fontWeight = String(weight || "normal");
1551
1758
  span.style.fontSize = `${fontSize}px`;
1552
1759
  span.style.lineHeight = "1";
@@ -1554,7 +1761,6 @@
1554
1761
  span.style.padding = "0";
1555
1762
  span.style.fontFeatureSettings = "'liga' 1";
1556
1763
  span.style.fontVariantLigatures = "normal";
1557
- if (variation) span.style.fontVariationSettings = variation;
1558
1764
  span.style.color = color;
1559
1765
  document.body.appendChild(span);
1560
1766
  const rect = span.getBoundingClientRect();
@@ -1566,7 +1772,7 @@
1566
1772
  canvas.height = height * dpr;
1567
1773
  const ctx = canvas.getContext("2d");
1568
1774
  ctx.scale(dpr, dpr);
1569
- ctx.font = `${weight ? `${weight} ` : ""}${fontSize}px "${fam}"`;
1775
+ ctx.font = `${weight ? `${weight} ` : ""}${fontSize}px ${familyForCanvas}`;
1570
1776
  ctx.textAlign = "left";
1571
1777
  ctx.textBaseline = "top";
1572
1778
  ctx.fillStyle = color;
@@ -1596,21 +1802,23 @@
1596
1802
  const el = cloneNodes[i];
1597
1803
  const src = sourceNodes[i] || null;
1598
1804
  try {
1599
- const csSrc = src ? getComputedStyle(src) : getComputedStyle(el);
1600
- const family = csSrc.fontFamily || "Material Icons";
1805
+ const cs = src ? getComputedStyle(src) : getComputedStyle(el);
1806
+ const family = cs.fontFamily || "Material Icons";
1601
1807
  if (!isMaterialFamily(family)) continue;
1602
1808
  const text = (src || el).textContent.trim();
1603
1809
  if (!text) continue;
1604
- const size = parseInt(csSrc.fontSize, 10) || 24;
1605
- const weight = csSrc.fontWeight && csSrc.fontWeight !== "normal" ? csSrc.fontWeight : "normal";
1606
- const color = resolvePaintColor(csSrc);
1607
- const variation = csSrc.fontVariationSettings && csSrc.fontVariationSettings !== "normal" ? csSrc.fontVariationSettings : "";
1810
+ const size = parseInt(cs.fontSize, 10) || 24;
1811
+ const weight = cs.fontWeight && cs.fontWeight !== "normal" ? cs.fontWeight : "normal";
1812
+ const color = resolvePaintColor(cs);
1813
+ const variation = cs.fontVariationSettings && cs.fontVariationSettings !== "normal" ? cs.fontVariationSettings : "";
1814
+ const className = (src || el).className || "";
1608
1815
  const { dataUrl, width, height } = yield materialIconToImage(text, {
1609
1816
  family,
1610
1817
  weight,
1611
1818
  fontSize: size,
1612
1819
  color,
1613
- variation
1820
+ variation,
1821
+ className
1614
1822
  });
1615
1823
  el.textContent = "";
1616
1824
  const img = el.ownerDocument.createElement("img");
@@ -1988,7 +2196,7 @@ ${imported}
1988
2196
  const styleSpec = (((_c = block.match(/font-style:\s*([^;]+);/i)) == null ? void 0 : _c[1]) || "normal").trim();
1989
2197
  const stretchSpec = (((_d = block.match(/font-stretch:\s*([^;]+);/i)) == null ? void 0 : _d[1]) || "100%").trim();
1990
2198
  const urange = (((_e = block.match(/unicode-range:\s*([^;]+);/i)) == null ? void 0 : _e[1]) || "").trim();
1991
- const srcRaw = (((_f = block.match(/src\s*:\s*([^;]+);/i)) == null ? void 0 : _f[1]) || "").trim();
2199
+ const srcRaw = (((_f = block.match(/src\s*:\s*([^;}]+)[;}]/i)) == null ? void 0 : _f[1]) || "").trim();
1992
2200
  const urls = extractSrcUrls(srcRaw, location.href);
1993
2201
  const srcPart = urls.length ? urls.map((u) => String(u).toLowerCase()).sort().join("|") : srcRaw.toLowerCase();
1994
2202
  const key = [
@@ -2200,7 +2408,7 @@ ${imported}
2200
2408
  const styleSpec = (((_d = face.match(/font-style:\s*([^;]+);/i)) == null ? void 0 : _d[1]) || "normal").trim();
2201
2409
  const stretchSpec = (((_e = face.match(/font-stretch:\s*([^;]+);/i)) == null ? void 0 : _e[1]) || "100%").trim();
2202
2410
  const urange = (((_f = face.match(/unicode-range:\s*([^;]+);/i)) == null ? void 0 : _f[1]) || "").trim();
2203
- const srcRaw = (((_g = face.match(/src\s*:\s*([^;]+);/i)) == null ? void 0 : _g[1]) || "").trim();
2411
+ const srcRaw = (((_g = face.match(/src\s*:\s*([^;}]+)[;}]/i)) == null ? void 0 : _g[1]) || "").trim();
2204
2412
  const srcUrls = extractSrcUrls(srcRaw, link.href);
2205
2413
  if (!faceMatchesRequired(family, styleSpec, weightSpec, stretchSpec)) continue;
2206
2414
  const ranges = parseUnicodeRange(urange);
@@ -2620,6 +2828,152 @@ ${imported}
2620
2828
  }
2621
2829
 
2622
2830
  // src/modules/pseudo.js
2831
+ var __preflightMemo = /* @__PURE__ */ new WeakMap();
2832
+ var CSS_RULE_SCAN_BUDGET = 300;
2833
+ function preflightWithFp(doc, sessionCache) {
2834
+ const fp = styleFingerprint(doc);
2835
+ if (!sessionCache) return shouldProcessPseudos(doc);
2836
+ if (sessionCache.__pseudoPreflightFp !== fp) {
2837
+ sessionCache.__pseudoPreflight = shouldProcessPseudos(doc);
2838
+ sessionCache.__pseudoPreflightFp = fp;
2839
+ }
2840
+ return !!sessionCache.__pseudoPreflight;
2841
+ }
2842
+ function safeRules(sheet) {
2843
+ try {
2844
+ return sheet && sheet.cssRules ? sheet.cssRules : null;
2845
+ } catch (e) {
2846
+ return null;
2847
+ }
2848
+ }
2849
+ function styleFingerprint(doc) {
2850
+ const nodes = doc.querySelectorAll('style,link[rel~="stylesheet"]');
2851
+ let fp = `n:${nodes.length}|`;
2852
+ let totalRules = 0;
2853
+ for (let i = 0; i < nodes.length; i++) {
2854
+ const n = nodes[i];
2855
+ if (n.tagName === "STYLE") {
2856
+ const len = n.textContent ? n.textContent.length : 0;
2857
+ fp += `S${len}|`;
2858
+ const sheet = (
2859
+ /** @type {HTMLStyleElement} */
2860
+ n.sheet
2861
+ );
2862
+ const rules = sheet ? safeRules(sheet) : null;
2863
+ if (rules) totalRules += rules.length;
2864
+ } else {
2865
+ const href = n.getAttribute("href") || "";
2866
+ const media = n.getAttribute("media") || "all";
2867
+ fp += `L${href}|m:${media}|`;
2868
+ const sheet = (
2869
+ /** @type {HTMLLinkElement} */
2870
+ n.sheet
2871
+ );
2872
+ const rules = sheet ? safeRules(sheet) : null;
2873
+ if (rules) totalRules += rules.length;
2874
+ }
2875
+ }
2876
+ const ass = (
2877
+ /** @type {any} */
2878
+ doc.adoptedStyleSheets
2879
+ );
2880
+ fp += `ass:${Array.isArray(ass) ? ass.length : 0}|tr:${totalRules}`;
2881
+ return fp;
2882
+ }
2883
+ function sheetHasNeedles(sheet, needles, state) {
2884
+ const rules = safeRules(sheet);
2885
+ if (!rules) return false;
2886
+ for (let i = 0; i < rules.length; i++) {
2887
+ if (state.budget <= 0) return false;
2888
+ const rule = rules[i];
2889
+ const css = rule && rule.cssText ? rule.cssText : "";
2890
+ state.budget--;
2891
+ for (const k of needles) {
2892
+ if (css.includes(k)) return true;
2893
+ }
2894
+ if (rule && rule.cssRules && rule.cssRules.length) {
2895
+ for (let j = 0; j < rule.cssRules.length && state.budget > 0; j++) {
2896
+ const inner = rule.cssRules[j];
2897
+ const innerCss = inner && inner.cssText ? inner.cssText : "";
2898
+ state.budget--;
2899
+ for (const k of needles) {
2900
+ if (innerCss.includes(k)) return true;
2901
+ }
2902
+ }
2903
+ }
2904
+ if (state.budget <= 0) return false;
2905
+ }
2906
+ return false;
2907
+ }
2908
+ function shouldProcessPseudos(doc = document) {
2909
+ const fp = styleFingerprint(doc);
2910
+ const memo = __preflightMemo.get(doc);
2911
+ if (memo && memo.fingerprint === fp) return memo.result;
2912
+ const NEEDLES = [
2913
+ // double-colon
2914
+ "::before",
2915
+ "::after",
2916
+ "::first-letter",
2917
+ // single-colon robustness
2918
+ ":before",
2919
+ ":after",
2920
+ ":first-letter",
2921
+ // counters
2922
+ "counter(",
2923
+ "counters(",
2924
+ "counter-increment",
2925
+ "counter-reset"
2926
+ ];
2927
+ const styleEls = doc.querySelectorAll("style");
2928
+ for (let i = 0; i < styleEls.length; i++) {
2929
+ const t = styleEls[i].textContent || "";
2930
+ for (const k of NEEDLES) if (t.includes(k)) {
2931
+ __preflightMemo.set(doc, { fingerprint: fp, result: true });
2932
+ return true;
2933
+ }
2934
+ }
2935
+ const ass = (
2936
+ /** @type {any} */
2937
+ doc.adoptedStyleSheets
2938
+ );
2939
+ if (Array.isArray(ass) && ass.length) {
2940
+ const state = { budget: CSS_RULE_SCAN_BUDGET };
2941
+ try {
2942
+ for (const sheet of ass) {
2943
+ if (sheetHasNeedles(sheet, NEEDLES, state)) {
2944
+ __preflightMemo.set(doc, { fingerprint: fp, result: true });
2945
+ return true;
2946
+ }
2947
+ }
2948
+ } catch (e) {
2949
+ }
2950
+ }
2951
+ {
2952
+ const nodes = doc.querySelectorAll('style,link[rel~="stylesheet"]');
2953
+ const state = { budget: CSS_RULE_SCAN_BUDGET };
2954
+ for (let i = 0; i < nodes.length && state.budget > 0; i++) {
2955
+ const n = nodes[i];
2956
+ let sheet = null;
2957
+ if (n.tagName === "STYLE") {
2958
+ sheet = /** @type {HTMLStyleElement} */
2959
+ n.sheet || null;
2960
+ } else {
2961
+ sheet = /** @type {HTMLLinkElement} */
2962
+ n.sheet || null;
2963
+ }
2964
+ if (sheet && sheetHasNeedles(sheet, NEEDLES, state)) {
2965
+ __preflightMemo.set(doc, { fingerprint: fp, result: true });
2966
+ return true;
2967
+ }
2968
+ }
2969
+ }
2970
+ if (doc.querySelector('[style*="counter("], [style*="counters("]')) {
2971
+ __preflightMemo.set(doc, { fingerprint: fp, result: true });
2972
+ return true;
2973
+ }
2974
+ __preflightMemo.set(doc, { fingerprint: fp, result: false });
2975
+ return false;
2976
+ }
2623
2977
  var __siblingCounters = /* @__PURE__ */ new WeakMap();
2624
2978
  var __pseudoEpoch = -1;
2625
2979
  function unquoteDoubleStrings2(s) {
@@ -2718,8 +3072,12 @@ ${imported}
2718
3072
  }
2719
3073
  function inlinePseudoElements(source, clone, sessionCache, options) {
2720
3074
  return __async(this, null, function* () {
2721
- var _a, _b, _c, _d;
3075
+ var _a, _b, _c, _d, _e;
2722
3076
  if (!(source instanceof Element) || !(clone instanceof Element)) return;
3077
+ const doc = source.ownerDocument || document;
3078
+ if (!preflightWithFp(doc, sessionCache)) {
3079
+ return;
3080
+ }
2723
3081
  const epoch = (_c = (_b = (_a = cache) == null ? void 0 : _a.session) == null ? void 0 : _b.__counterEpoch) != null ? _c : 0;
2724
3082
  if (__pseudoEpoch !== epoch) {
2725
3083
  __siblingCounters = /* @__PURE__ */ new WeakMap();
@@ -2736,7 +3094,7 @@ ${imported}
2736
3094
  for (const pseudo of ["::before", "::after", "::first-letter"]) {
2737
3095
  try {
2738
3096
  const style = getStyle(source, pseudo);
2739
- if (!style || typeof style[Symbol.iterator] !== "function") continue;
3097
+ if (!style) continue;
2740
3098
  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";
2741
3099
  if (isEmptyPseudo) continue;
2742
3100
  if (pseudo === "::first-letter") {
@@ -2766,7 +3124,8 @@ ${imported}
2766
3124
  clone.insertBefore(span, restNode);
2767
3125
  continue;
2768
3126
  }
2769
- const rawContent = style.content;
3127
+ const rawContent = (_d = style.content) != null ? _d : "";
3128
+ const isNoExplicitContent = rawContent === "" || rawContent === "none" || rawContent === "normal";
2770
3129
  const { text: cleanContent, incs } = resolvePseudoContentAndIncs(source, pseudo, counterCtx);
2771
3130
  const bg = style.backgroundImage;
2772
3131
  const bgColor = style.backgroundColor;
@@ -2778,7 +3137,7 @@ ${imported}
2778
3137
  const borderWidth = parseFloat(style.borderWidth);
2779
3138
  const transform = style.transform;
2780
3139
  const isIconFont22 = isIconFont2(fontFamily);
2781
- const hasExplicitContent = rawContent !== "none" && cleanContent !== "";
3140
+ const hasExplicitContent = !isNoExplicitContent && cleanContent !== "";
2782
3141
  const hasBg = bg && bg !== "none";
2783
3142
  const hasBgColor = bgColor && bgColor !== "transparent" && bgColor !== "rgba(0, 0, 0, 0)";
2784
3143
  const hasBorder = borderStyle && borderStyle !== "none" && borderWidth > 0;
@@ -2800,8 +3159,6 @@ ${imported}
2800
3159
  }
2801
3160
  const pseudoEl = document.createElement("span");
2802
3161
  pseudoEl.dataset.snapdomPseudo = pseudo;
2803
- pseudoEl.style.display = "inline";
2804
- pseudoEl.style.verticalAlign = "baseline";
2805
3162
  pseudoEl.style.pointerEvents = "none";
2806
3163
  const snapshot = snapshotComputedStyle(style);
2807
3164
  const key = getStyleKey(snapshot, "span");
@@ -2857,7 +3214,7 @@ ${imported}
2857
3214
  }
2858
3215
  }
2859
3216
  if (hasBgColor) pseudoEl.style.backgroundColor = bgColor;
2860
- const hasContent2 = pseudoEl.childNodes.length > 0 || ((_d = pseudoEl.textContent) == null ? void 0 : _d.trim()) !== "";
3217
+ const hasContent2 = pseudoEl.childNodes.length > 0 || ((_e = pseudoEl.textContent) == null ? void 0 : _e.trim()) !== "";
2861
3218
  const hasVisibleBox = hasContent2 || hasBg || hasBgColor || hasBorder || hasTransform;
2862
3219
  if (incs && incs.length && source.parentElement) {
2863
3220
  const map = __siblingCounters.get(source.parentElement) || /* @__PURE__ */ new Map();
@@ -3096,6 +3453,20 @@ ${imported}
3096
3453
  return out;
3097
3454
  }
3098
3455
 
3456
+ // src/utils/prepare.helpers.js
3457
+ function stabilizeLayout(element) {
3458
+ const style = getComputedStyle(element);
3459
+ const outlineStyle = style.outlineStyle;
3460
+ const outlineWidth = style.outlineWidth;
3461
+ const borderStyle = style.borderStyle;
3462
+ const borderWidth = style.borderWidth;
3463
+ const outlineVisible = outlineStyle !== "none" && parseFloat(outlineWidth) > 0;
3464
+ const borderAbsent = borderStyle === "none" || parseFloat(borderWidth) === 0;
3465
+ if (outlineVisible && borderAbsent) {
3466
+ element.style.border = `${outlineWidth} solid transparent`;
3467
+ }
3468
+ }
3469
+
3099
3470
  // src/core/prepare.js
3100
3471
  function prepareClone(_0) {
3101
3472
  return __async(this, arguments, function* (element, options = {}) {
@@ -3199,163 +3570,27 @@ ${imported}
3199
3570
  return { clone, classCSS, styleCache: sessionCache.styleCache };
3200
3571
  });
3201
3572
  }
3202
- function stabilizeLayout(element) {
3203
- const style = getComputedStyle(element);
3204
- const outlineStyle = style.outlineStyle;
3205
- const outlineWidth = style.outlineWidth;
3206
- const borderStyle = style.borderStyle;
3207
- const borderWidth = style.borderWidth;
3208
- const outlineVisible = outlineStyle !== "none" && parseFloat(outlineWidth) > 0;
3209
- const borderAbsent = borderStyle === "none" || parseFloat(borderWidth) === 0;
3210
- if (outlineVisible && borderAbsent) {
3211
- element.style.border = `${outlineWidth} solid transparent`;
3212
- }
3573
+
3574
+ // src/modules/images.js
3575
+ function extractImageDimensions(img) {
3576
+ var _a, _b, _c, _d;
3577
+ const dsW = parseInt(((_a = img.dataset) == null ? void 0 : _a.snapdomWidth) || "", 10) || 0;
3578
+ const dsH = parseInt(((_b = img.dataset) == null ? void 0 : _b.snapdomHeight) || "", 10) || 0;
3579
+ const attrW = parseInt(img.getAttribute("width") || "", 10) || 0;
3580
+ const attrH = parseInt(img.getAttribute("height") || "", 10) || 0;
3581
+ const styleW = parseFloat(((_c = img.style) == null ? void 0 : _c.width) || "") || 0;
3582
+ const styleH = parseFloat(((_d = img.style) == null ? void 0 : _d.height) || "") || 0;
3583
+ const w = dsW || styleW || attrW || img.width || img.naturalWidth || 100;
3584
+ const h = dsH || styleH || attrH || img.height || img.naturalHeight || 100;
3585
+ return { width: w, height: h };
3213
3586
  }
3214
- var _blobToDataUrlCache = /* @__PURE__ */ new Map();
3215
- function blobUrlToDataUrl(blobUrl) {
3216
- return __async(this, null, function* () {
3217
- var _a;
3218
- if ((_a = cache.resource) == null ? void 0 : _a.has(blobUrl)) return cache.resource.get(blobUrl);
3219
- if (_blobToDataUrlCache.has(blobUrl)) return _blobToDataUrlCache.get(blobUrl);
3220
- const p = (() => __async(null, null, function* () {
3221
- var _a2;
3222
- const r = yield snapFetch(blobUrl, { as: "dataURL", silent: true });
3223
- if (!r.ok || typeof r.data !== "string") {
3224
- throw new Error(`[snapDOM] Failed to read blob URL: ${blobUrl}`);
3225
- }
3226
- (_a2 = cache.resource) == null ? void 0 : _a2.set(blobUrl, r.data);
3227
- return r.data;
3228
- }))();
3229
- _blobToDataUrlCache.set(blobUrl, p);
3230
- try {
3231
- const data = yield p;
3232
- _blobToDataUrlCache.set(blobUrl, data);
3233
- return data;
3234
- } catch (e) {
3235
- _blobToDataUrlCache.delete(blobUrl);
3236
- throw e;
3237
- }
3238
- });
3239
- }
3240
- var BLOB_URL_RE = /\bblob:[^)"'\s]+/g;
3241
- function replaceBlobUrlsInCssText(cssText) {
3242
- return __async(this, null, function* () {
3243
- if (!cssText || cssText.indexOf("blob:") === -1) return cssText;
3244
- const uniques = Array.from(new Set(cssText.match(BLOB_URL_RE) || []));
3245
- if (uniques.length === 0) return cssText;
3246
- let out = cssText;
3247
- for (const u of uniques) {
3248
- try {
3249
- const d = yield blobUrlToDataUrl(u);
3250
- out = out.split(u).join(d);
3251
- } catch (e) {
3252
- }
3253
- }
3254
- return out;
3255
- });
3256
- }
3257
- function isBlobUrl(u) {
3258
- return typeof u === "string" && u.startsWith("blob:");
3259
- }
3260
- function parseSrcset(srcset) {
3261
- return (srcset || "").split(",").map((s) => s.trim()).filter(Boolean).map((item) => {
3262
- const m = item.match(/^(\S+)(\s+.+)?$/);
3263
- return m ? { url: m[1], desc: m[2] || "" } : null;
3264
- }).filter(Boolean);
3265
- }
3266
- function stringifySrcset(parts) {
3267
- return parts.map((p) => p.desc ? `${p.url} ${p.desc.trim()}` : p.url).join(", ");
3268
- }
3269
- function resolveBlobUrlsInTree(root) {
3270
- return __async(this, null, function* () {
3271
- var _a, _b;
3272
- if (!root) return;
3273
- const imgs = root.querySelectorAll ? root.querySelectorAll("img") : [];
3274
- for (const img of imgs) {
3275
- try {
3276
- const srcAttr = img.getAttribute("src");
3277
- const effective = srcAttr || img.currentSrc || "";
3278
- if (isBlobUrl(effective)) {
3279
- const data = yield blobUrlToDataUrl(effective);
3280
- img.setAttribute("src", data);
3281
- }
3282
- const srcset = img.getAttribute("srcset");
3283
- if (srcset && srcset.includes("blob:")) {
3284
- const parts = parseSrcset(srcset);
3285
- let changed = false;
3286
- for (const p of parts) {
3287
- if (isBlobUrl(p.url)) {
3288
- try {
3289
- p.url = yield blobUrlToDataUrl(p.url);
3290
- changed = true;
3291
- } catch (e) {
3292
- }
3293
- }
3294
- }
3295
- if (changed) img.setAttribute("srcset", stringifySrcset(parts));
3296
- }
3297
- } catch (e) {
3298
- }
3299
- }
3300
- const svgImages = root.querySelectorAll ? root.querySelectorAll("image") : [];
3301
- for (const node of svgImages) {
3302
- try {
3303
- const XLINK_NS = "http://www.w3.org/1999/xlink";
3304
- const href = node.getAttribute("href") || ((_a = node.getAttributeNS) == null ? void 0 : _a.call(node, XLINK_NS, "href"));
3305
- if (isBlobUrl(href)) {
3306
- const d = yield blobUrlToDataUrl(href);
3307
- node.setAttribute("href", d);
3308
- (_b = node.removeAttributeNS) == null ? void 0 : _b.call(node, XLINK_NS, "href");
3309
- }
3310
- } catch (e) {
3311
- }
3312
- }
3313
- const styled = root.querySelectorAll ? root.querySelectorAll("[style*='blob:']") : [];
3314
- for (const el of styled) {
3315
- try {
3316
- const styleText = el.getAttribute("style");
3317
- if (styleText && styleText.includes("blob:")) {
3318
- const replaced = yield replaceBlobUrlsInCssText(styleText);
3319
- el.setAttribute("style", replaced);
3320
- }
3321
- } catch (e) {
3322
- }
3323
- }
3324
- const styleTags = root.querySelectorAll ? root.querySelectorAll("style") : [];
3325
- for (const s of styleTags) {
3326
- try {
3327
- const css = s.textContent || "";
3328
- if (css.includes("blob:")) {
3329
- s.textContent = yield replaceBlobUrlsInCssText(css);
3330
- }
3331
- } catch (e) {
3332
- }
3333
- }
3334
- const urlAttrs = ["poster"];
3335
- for (const attr of urlAttrs) {
3336
- const nodes = root.querySelectorAll ? root.querySelectorAll(`[${attr}^='blob:']`) : [];
3337
- for (const n of nodes) {
3338
- try {
3339
- const u = n.getAttribute(attr);
3340
- if (isBlobUrl(u)) {
3341
- n.setAttribute(attr, yield blobUrlToDataUrl(u));
3342
- }
3343
- } catch (e) {
3344
- }
3345
- }
3346
- }
3347
- });
3348
- }
3349
-
3350
- // src/modules/images.js
3351
- function inlineImages(_0) {
3352
- return __async(this, arguments, function* (clone, options = {}) {
3353
- const imgs = Array.from(clone.querySelectorAll("img"));
3354
- const processImg = (img) => __async(null, null, function* () {
3355
- var _a, _b, _c, _d;
3356
- if (!img.getAttribute("src")) {
3357
- const eff = img.currentSrc || img.src || "";
3358
- if (eff) img.setAttribute("src", eff);
3587
+ function inlineImages(_0) {
3588
+ return __async(this, arguments, function* (clone, options = {}) {
3589
+ const imgs = Array.from(clone.querySelectorAll("img"));
3590
+ const processImg = (img) => __async(null, null, function* () {
3591
+ if (!img.getAttribute("src")) {
3592
+ const eff = img.currentSrc || img.src || "";
3593
+ if (eff) img.setAttribute("src", eff);
3359
3594
  }
3360
3595
  img.removeAttribute("srcset");
3361
3596
  img.removeAttribute("sizes");
@@ -3368,41 +3603,30 @@ ${imported}
3368
3603
  if (!img.height) img.height = img.naturalHeight || 100;
3369
3604
  return;
3370
3605
  }
3606
+ const { width: fbW, height: fbH } = extractImageDimensions(img);
3371
3607
  const { fallbackURL } = options || {};
3372
3608
  if (fallbackURL) {
3373
3609
  try {
3374
- const dsW = parseInt(((_a = img.dataset) == null ? void 0 : _a.snapdomWidth) || "", 10) || 0;
3375
- const dsH = parseInt(((_b = img.dataset) == null ? void 0 : _b.snapdomHeight) || "", 10) || 0;
3376
- const attrW = parseInt(img.getAttribute("width") || "", 10) || 0;
3377
- const attrH = parseInt(img.getAttribute("height") || "", 10) || 0;
3378
- const styleW = parseFloat(((_c = img.style) == null ? void 0 : _c.width) || "") || 0;
3379
- const styleH = parseFloat(((_d = img.style) == null ? void 0 : _d.height) || "") || 0;
3380
- const fbW = dsW || styleW || attrW || img.width || void 0;
3381
- const fbH = dsH || styleH || attrH || img.height || void 0;
3382
3610
  const fallbackUrl = typeof fallbackURL === "function" ? yield fallbackURL({ width: fbW, height: fbH, src, element: img }) : fallbackURL;
3383
3611
  if (fallbackUrl) {
3384
3612
  const fallbackData = yield snapFetch(fallbackUrl, { as: "dataURL", useProxy: options.useProxy });
3385
3613
  img.src = fallbackData.data;
3386
- if (!img.width && fbW) img.width = fbW;
3387
- if (!img.height && fbH) img.height = fbH;
3388
- if (!img.width) img.width = img.naturalWidth || 100;
3389
- if (!img.height) img.height = img.naturalHeight || 100;
3614
+ if (!img.width) img.width = fbW;
3615
+ if (!img.height) img.height = fbH;
3390
3616
  return;
3391
3617
  }
3392
3618
  } catch (e) {
3393
3619
  }
3394
3620
  }
3395
- const w = img.width || img.naturalWidth || 100;
3396
- const h = img.height || img.naturalHeight || 100;
3397
3621
  if (options.placeholders !== false) {
3398
3622
  const fallback = document.createElement("div");
3399
3623
  fallback.style.cssText = [
3400
- `width:${w}px`,
3401
- `height:${h}px`,
3624
+ `width:${fbW}px`,
3625
+ `height:${fbH}px`,
3402
3626
  "background:#ccc",
3403
3627
  "display:inline-block",
3404
3628
  "text-align:center",
3405
- `line-height:${h}px`,
3629
+ `line-height:${fbH}px`,
3406
3630
  "color:#666",
3407
3631
  "font-size:12px",
3408
3632
  "overflow:hidden"
@@ -3411,7 +3635,7 @@ ${imported}
3411
3635
  img.replaceWith(fallback);
3412
3636
  } else {
3413
3637
  const spacer = document.createElement("div");
3414
- spacer.style.cssText = `display:inline-block;width:${w}px;height:${h}px;visibility:hidden;`;
3638
+ spacer.style.cssText = `display:inline-block;width:${fbW}px;height:${fbH}px;visibility:hidden;`;
3415
3639
  img.replaceWith(spacer);
3416
3640
  }
3417
3641
  });
@@ -3659,7 +3883,7 @@ ${imported}
3659
3883
  return context;
3660
3884
  }
3661
3885
 
3662
- // src/core/capture.js
3886
+ // src/utils/capture.helpers.js
3663
3887
  function stripRootShadows(originalEl, cloneRoot) {
3664
3888
  if (!originalEl || !cloneRoot || !cloneRoot.style) return;
3665
3889
  const cs = getComputedStyle(originalEl);
@@ -3682,219 +3906,180 @@ ${imported}
3682
3906
  } catch (e) {
3683
3907
  }
3684
3908
  }
3685
- function captureDOM(element, options) {
3686
- return __async(this, null, function* () {
3687
- if (!element) throw new Error("Element cannot be null or undefined");
3688
- applyCachePolicy(options.cache);
3689
- const fast = options.fast;
3690
- const outerTransforms = options.outerTransforms !== false;
3691
- const outerShadows = !!options.outerShadows;
3692
- let state = { element, options, plugins: options.plugins };
3693
- let clone, classCSS, styleCache;
3694
- let fontsCSS = "";
3695
- let baseCSS = "";
3696
- let dataURL;
3697
- let svgString;
3698
- let rootTransform2D = null;
3699
- yield runHook("beforeSnap", state);
3700
- yield runHook("beforeClone", state);
3701
- const undoClamp = lineClamp(state.element);
3702
- try {
3703
- ({ clone, classCSS, styleCache } = yield prepareClone(state.element, state.options));
3704
- if (!outerTransforms && clone) {
3705
- rootTransform2D = normalizeRootTransforms(state.element, clone);
3909
+ function removeAllComments(root) {
3910
+ const it = document.createTreeWalker(root, NodeFilter.SHOW_COMMENT);
3911
+ const toRemove = [];
3912
+ while (it.nextNode()) toRemove.push(it.currentNode);
3913
+ for (const n of toRemove) n.remove();
3914
+ }
3915
+ function sanitizeAttributesForXHTML(root, opts = {}) {
3916
+ const { stripFrameworkDirectives = true } = opts;
3917
+ const ALLOWED_PREFIXES = /* @__PURE__ */ new Set(["xml", "xlink"]);
3918
+ const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
3919
+ while (walker.nextNode()) {
3920
+ const el = walker.currentNode;
3921
+ for (const attr of Array.from(el.attributes)) {
3922
+ const name = attr.name;
3923
+ if (name.includes("@")) {
3924
+ el.removeAttribute(name);
3925
+ continue;
3706
3926
  }
3707
- if (!outerShadows && clone) {
3708
- stripRootShadows(state.element, clone);
3927
+ if (name.includes(":")) {
3928
+ const prefix = name.split(":", 1)[0];
3929
+ if (!ALLOWED_PREFIXES.has(prefix)) {
3930
+ el.removeAttribute(name);
3931
+ continue;
3932
+ }
3933
+ }
3934
+ if (!stripFrameworkDirectives) continue;
3935
+ if (name.startsWith("x-") || // Alpine
3936
+ name.startsWith("v-") || // Vue
3937
+ name.startsWith(":") || // Vue/Alpine shorthand
3938
+ name.startsWith("on:") || // Svelte
3939
+ name.startsWith("bind:") || // Svelte
3940
+ name.startsWith("let:") || // Svelte
3941
+ name.startsWith("class:")) {
3942
+ el.removeAttribute(name);
3943
+ continue;
3709
3944
  }
3710
- } finally {
3711
- undoClamp();
3712
3945
  }
3713
- state = __spreadValues({ clone, classCSS, styleCache }, state);
3714
- yield runHook("afterClone", state);
3715
- try {
3716
- yield ligatureIconToImage(state.clone, state.element);
3717
- } catch (e) {
3946
+ }
3947
+ }
3948
+ function sanitizeCloneForXHTML(root, opts = {}) {
3949
+ if (!root) return;
3950
+ sanitizeAttributesForXHTML(root, opts);
3951
+ removeAllComments(root);
3952
+ }
3953
+ function authorHasExplicitSize(el) {
3954
+ var _a;
3955
+ try {
3956
+ const s = ((_a = el.getAttribute) == null ? void 0 : _a.call(el, "style")) || "";
3957
+ return /\b(height|width|block-size|inline-size)\s*:/.test(s);
3958
+ } catch (e) {
3959
+ return false;
3960
+ }
3961
+ }
3962
+ function isReplacedElement(el) {
3963
+ return el instanceof HTMLImageElement || el instanceof HTMLCanvasElement || el instanceof HTMLVideoElement || el instanceof HTMLIFrameElement || el instanceof SVGElement || el instanceof HTMLObjectElement || el instanceof HTMLEmbedElement;
3964
+ }
3965
+ function shouldShrinkBox(srcEl, cs) {
3966
+ if (!(srcEl instanceof Element)) return false;
3967
+ if (authorHasExplicitSize(srcEl)) return false;
3968
+ if (isReplacedElement(srcEl)) return false;
3969
+ const pos = cs.position;
3970
+ if (pos === "absolute" || pos === "fixed" || pos === "sticky") return false;
3971
+ const disp = cs.display || "";
3972
+ if (disp.includes("flex") || disp.includes("grid") || disp.startsWith("table")) return false;
3973
+ if (cs.transform && cs.transform !== "none") return false;
3974
+ return true;
3975
+ }
3976
+ function shrinkAutoSizeBoxes(sourceRoot, cloneRoot, styleCache = /* @__PURE__ */ new Map()) {
3977
+ function walk(src, cln) {
3978
+ if (!(src instanceof Element) || !(cln instanceof Element)) return;
3979
+ const lostKids = src.childElementCount > cln.childElementCount;
3980
+ const cs = styleCache.get(src) || getComputedStyle(src);
3981
+ if (!styleCache.has(src)) styleCache.set(src, cs);
3982
+ if (lostKids && shouldShrinkBox(src, cs)) {
3983
+ if (!cln.style.height) cln.style.height = "auto";
3984
+ if (!cln.style.width) cln.style.width = "auto";
3985
+ cln.style.removeProperty("block-size");
3986
+ cln.style.removeProperty("inline-size");
3987
+ if (!cln.style.minHeight) cln.style.minHeight = "0";
3988
+ if (!cln.style.minWidth) cln.style.minWidth = "0";
3989
+ if (!cln.style.maxHeight) cln.style.maxHeight = "none";
3990
+ if (!cln.style.maxWidth) cln.style.maxWidth = "none";
3991
+ const oy = cs.overflowY || cs.overflowBlock || "visible";
3992
+ const ox = cs.overflowX || cs.overflowInline || "visible";
3993
+ if (oy !== "visible" || ox !== "visible") {
3994
+ cln.style.overflow = "visible";
3995
+ }
3718
3996
  }
3719
- yield new Promise((resolve) => {
3720
- idle(() => __async(null, null, function* () {
3721
- yield inlineImages(state.clone, state.options);
3722
- resolve();
3723
- }), { fast });
3724
- });
3725
- yield new Promise((resolve) => {
3726
- idle(() => __async(null, null, function* () {
3727
- yield inlineBackgroundImages(state.element, state.clone, state.styleCache, state.options);
3728
- resolve();
3729
- }), { fast });
3730
- });
3731
- if (options.embedFonts) {
3732
- yield new Promise((resolve) => {
3733
- idle(() => __async(null, null, function* () {
3734
- const required = collectUsedFontVariants(state.element);
3735
- const usedCodepoints = collectUsedCodepoints(state.element);
3736
- if (isSafari()) {
3737
- const families = new Set(
3738
- Array.from(required).map((k) => String(k).split("__")[0]).filter(Boolean)
3739
- );
3740
- yield ensureFontsReady(families, 1);
3741
- }
3742
- fontsCSS = yield embedCustomFonts({
3743
- required,
3744
- usedCodepoints,
3745
- preCached: false,
3746
- exclude: state.options.excludeFonts,
3747
- useProxy: state.options.useProxy
3748
- });
3749
- resolve();
3750
- }), { fast });
3751
- });
3997
+ const sKids = Array.from(src.children);
3998
+ const cKids = Array.from(cln.children);
3999
+ for (let i = 0; i < Math.min(sKids.length, cKids.length); i++) {
4000
+ walk(sKids[i], cKids[i]);
3752
4001
  }
3753
- const usedTags = collectUsedTagNames(state.clone).sort();
3754
- const tagKey = usedTags.join(",");
3755
- if (cache.baseStyle.has(tagKey)) {
3756
- baseCSS = cache.baseStyle.get(tagKey);
3757
- } else {
3758
- yield new Promise((resolve) => {
3759
- idle(() => {
3760
- baseCSS = generateDedupedBaseCSS(usedTags);
3761
- cache.baseStyle.set(tagKey, baseCSS);
3762
- resolve();
3763
- }, { fast });
3764
- });
4002
+ }
4003
+ walk(sourceRoot, cloneRoot);
4004
+ }
4005
+ function isInNormalFlow(el) {
4006
+ const cs = getComputedStyle(el);
4007
+ if (cs.display === "none") return false;
4008
+ if (cs.position === "absolute" || cs.position === "fixed" || cs.position === "sticky") return false;
4009
+ if ((cs.cssFloat || cs.float || "none") !== "none") return false;
4010
+ if (cs.transform && cs.transform !== "none") return false;
4011
+ return true;
4012
+ }
4013
+ function willBeExcluded(el, options) {
4014
+ if (!(el instanceof Element)) return false;
4015
+ if (el.getAttribute("data-capture") === "exclude" && (options == null ? void 0 : options.excludeMode) === "remove") return true;
4016
+ if (Array.isArray(options == null ? void 0 : options.exclude)) {
4017
+ for (const sel of options.exclude) {
4018
+ try {
4019
+ if (el.matches(sel)) return options.excludeMode === "remove";
4020
+ } catch (e) {
4021
+ }
3765
4022
  }
3766
- state = __spreadValues({ fontsCSS, baseCSS }, state);
3767
- yield runHook("beforeRender", state);
3768
- yield new Promise((resolve) => {
3769
- idle(() => {
3770
- const csEl = getComputedStyle(state.element);
3771
- const rect = state.element.getBoundingClientRect();
3772
- const w0 = Math.max(1, limitDecimals(state.element.offsetWidth || parseFloat(csEl.width) || rect.width || 1));
3773
- const h0 = Math.max(1, limitDecimals(state.element.offsetHeight || parseFloat(csEl.height) || rect.height || 1));
3774
- const coerceNum = (v, def = NaN) => {
3775
- const n = typeof v === "string" ? parseFloat(v) : v;
3776
- return Number.isFinite(n) ? n : def;
3777
- };
3778
- const optW = coerceNum(state.options.width);
3779
- const optH = coerceNum(state.options.height);
3780
- let w = w0, h = h0;
3781
- const hasW = Number.isFinite(optW);
3782
- const hasH = Number.isFinite(optH);
3783
- const aspect0 = h0 > 0 ? w0 / h0 : 1;
3784
- if (hasW && hasH) {
3785
- w = Math.max(1, limitDecimals(optW));
3786
- h = Math.max(1, limitDecimals(optH));
3787
- } else if (hasW) {
3788
- w = Math.max(1, limitDecimals(optW));
3789
- h = Math.max(1, limitDecimals(w / (aspect0 || 1)));
3790
- } else if (hasH) {
3791
- h = Math.max(1, limitDecimals(optH));
3792
- w = Math.max(1, limitDecimals(h * (aspect0 || 1)));
3793
- } else {
3794
- w = w0;
3795
- h = h0;
3796
- }
3797
- let minX = 0, minY = 0, maxX = w0, maxY = h0;
3798
- if (!outerTransforms && rootTransform2D && Number.isFinite(rootTransform2D.a)) {
3799
- const M2 = {
3800
- a: rootTransform2D.a,
3801
- b: rootTransform2D.b || 0,
3802
- c: rootTransform2D.c || 0,
3803
- d: rootTransform2D.d || 1,
3804
- e: 0,
3805
- f: 0
3806
- };
3807
- const bb2 = bboxWithOriginFull(w0, h0, M2, 0, 0);
3808
- minX = limitDecimals(bb2.minX);
3809
- minY = limitDecimals(bb2.minY);
3810
- maxX = limitDecimals(bb2.maxX);
3811
- maxY = limitDecimals(bb2.maxY);
3812
- } else {
3813
- const useTFBBox = outerTransforms && hasTFBBox(state.element);
3814
- if (useTFBBox) {
3815
- const baseTransform2 = csEl.transform && csEl.transform !== "none" ? csEl.transform : "";
3816
- const ind2 = readIndividualTransforms(state.element);
3817
- const TOTAL = readTotalTransformMatrix({
3818
- baseTransform: baseTransform2,
3819
- rotate: ind2.rotate || "0deg",
3820
- scale: ind2.scale,
3821
- translate: ind2.translate
3822
- });
3823
- const { ox: ox2, oy: oy2 } = parseTransformOriginPx(csEl, w0, h0);
3824
- const M = TOTAL.is2D ? TOTAL : new DOMMatrix(TOTAL.toString());
3825
- const bb = bboxWithOriginFull(w0, h0, M, ox2, oy2);
3826
- minX = limitDecimals(bb.minX);
3827
- minY = limitDecimals(bb.minY);
3828
- maxX = limitDecimals(bb.maxX);
3829
- maxY = limitDecimals(bb.maxY);
3830
- }
3831
- }
3832
- const bleedShadow = parseBoxShadow(csEl);
3833
- const bleedBlur = parseFilterBlur(csEl);
3834
- const bleedOutline = parseOutline(csEl);
3835
- const drop = parseFilterDropShadows(csEl);
3836
- const bleed = !outerShadows ? { top: 0, right: 0, bottom: 0, left: 0 } : {
3837
- top: limitDecimals(bleedShadow.top + bleedBlur.top + bleedOutline.top + drop.bleed.top),
3838
- right: limitDecimals(bleedShadow.right + bleedBlur.right + bleedOutline.right + drop.bleed.right),
3839
- bottom: limitDecimals(bleedShadow.bottom + bleedBlur.bottom + bleedOutline.bottom + drop.bleed.bottom),
3840
- left: limitDecimals(bleedShadow.left + bleedBlur.left + bleedOutline.left + drop.bleed.left)
3841
- };
3842
- minX = limitDecimals(minX - bleed.left);
3843
- minY = limitDecimals(minY - bleed.top);
3844
- maxX = limitDecimals(maxX + bleed.right);
3845
- maxY = limitDecimals(maxY + bleed.bottom);
3846
- const vbW0 = Math.max(1, limitDecimals(maxX - minX));
3847
- const vbH0 = Math.max(1, limitDecimals(maxY - minY));
3848
- const scaleW = hasW || hasH ? limitDecimals(w / w0) : 1;
3849
- const scaleH = hasH || hasW ? limitDecimals(h / h0) : 1;
3850
- const outW = Math.max(1, limitDecimals(vbW0 * scaleW));
3851
- const outH = Math.max(1, limitDecimals(vbH0 * scaleH));
3852
- const svgNS = "http://www.w3.org/2000/svg";
3853
- const basePad = isSafari() ? 1 : 0;
3854
- const extraPad = !outerTransforms ? 1 : 0;
3855
- const pad = limitDecimals(basePad + extraPad);
3856
- const fo = document.createElementNS(svgNS, "foreignObject");
3857
- const vbMinX = limitDecimals(minX);
3858
- const vbMinY = limitDecimals(minY);
3859
- fo.setAttribute("x", String(limitDecimals(-(vbMinX - pad))));
3860
- fo.setAttribute("y", String(limitDecimals(-(vbMinY - pad))));
3861
- fo.setAttribute("width", String(limitDecimals(w0 + pad * 2)));
3862
- fo.setAttribute("height", String(limitDecimals(h0 + pad * 2)));
3863
- fo.style.overflow = "visible";
3864
- const styleTag = document.createElement("style");
3865
- styleTag.textContent = state.baseCSS + state.fontsCSS + "svg{overflow:visible;} foreignObject{overflow:visible;}" + state.classCSS;
3866
- fo.appendChild(styleTag);
3867
- const container = document.createElement("div");
3868
- container.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
3869
- container.style.width = `${limitDecimals(w0)}px`;
3870
- container.style.height = `${limitDecimals(h0)}px`;
3871
- container.style.overflow = "visible";
3872
- state.clone.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
3873
- container.appendChild(state.clone);
3874
- fo.appendChild(container);
3875
- const serializer = new XMLSerializer();
3876
- const foString = serializer.serializeToString(fo);
3877
- const vbW = limitDecimals(vbW0 + pad * 2);
3878
- const vbH = limitDecimals(vbH0 + pad * 2);
3879
- const wantsSize = hasW || hasH;
3880
- options.meta = { w0, h0, vbW, vbH, targetW: w, targetH: h };
3881
- const svgOutW = isSafari() && wantsSize ? vbW : limitDecimals(outW + pad * 2);
3882
- const svgOutH = isSafari() && wantsSize ? vbH : limitDecimals(outH + pad * 2);
3883
- const svgHeader = `<svg xmlns="${svgNS}" width="${svgOutW}" height="${svgOutH}" viewBox="0 0 ${vbW} ${vbH}">`;
3884
- const svgFooter = "</svg>";
3885
- svgString = svgHeader + foString + svgFooter;
3886
- dataURL = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgString)}`;
3887
- state = __spreadValues({ svgString, dataURL }, state);
3888
- resolve();
3889
- }, { fast });
3890
- });
3891
- yield runHook("afterRender", state);
3892
- const sandbox = document.getElementById("snapdom-sandbox");
3893
- if (sandbox && sandbox.style.position === "absolute") sandbox.remove();
3894
- return state.dataURL;
3895
- });
4023
+ }
4024
+ return false;
4025
+ }
4026
+ function estimateKeptHeight(container, options) {
4027
+ const csC = getComputedStyle(container);
4028
+ const rC = container.getBoundingClientRect();
4029
+ let minTop = Infinity;
4030
+ let maxBottom = -Infinity;
4031
+ let found = false;
4032
+ const kids = Array.from(container.children);
4033
+ for (const k of kids) {
4034
+ if (willBeExcluded(k, options)) continue;
4035
+ if (!isInNormalFlow(k)) continue;
4036
+ const rk = k.getBoundingClientRect();
4037
+ const top = rk.top - rC.top;
4038
+ const bottom = rk.bottom - rC.top;
4039
+ if (bottom <= top) continue;
4040
+ if (top < minTop) minTop = top;
4041
+ if (bottom > maxBottom) maxBottom = bottom;
4042
+ found = true;
4043
+ }
4044
+ const contentSpan = found ? Math.max(0, maxBottom - minTop) : 0;
4045
+ const bt = parseFloat(csC.borderTopWidth) || 0;
4046
+ const bb = parseFloat(csC.borderBottomWidth) || 0;
4047
+ const pt = parseFloat(csC.paddingTop) || 0;
4048
+ const pb = parseFloat(csC.paddingBottom) || 0;
4049
+ return bt + bb + pt + pb + contentSpan;
3896
4050
  }
3897
4051
  var limitDecimals = (v, n = 3) => Number.isFinite(v) ? Math.round(v * __pow(10, n)) / __pow(10, n) : v;
4052
+
4053
+ // src/utils/transforms.helpers.js
4054
+ function parseBoxShadow(cs) {
4055
+ var _a;
4056
+ const v = cs.boxShadow || "";
4057
+ if (!v || v === "none") return { top: 0, right: 0, bottom: 0, left: 0 };
4058
+ const parts = v.split(/\),(?=(?:[^()]*\([^()]*\))*[^()]*$)/).map((s) => s.trim());
4059
+ let t = 0, r = 0, b2 = 0, l = 0;
4060
+ for (const part of parts) {
4061
+ const nums = ((_a = part.match(/-?\d+(\.\d+)?px/g)) == null ? void 0 : _a.map((n) => parseFloat(n))) || [];
4062
+ if (nums.length < 2) continue;
4063
+ const [ox2, oy2, blur = 0, spread = 0] = nums;
4064
+ const extX = Math.abs(ox2) + blur + spread;
4065
+ const extY = Math.abs(oy2) + blur + spread;
4066
+ r = Math.max(r, extX + Math.max(ox2, 0));
4067
+ l = Math.max(l, extX + Math.max(-ox2, 0));
4068
+ b2 = Math.max(b2, extY + Math.max(oy2, 0));
4069
+ t = Math.max(t, extY + Math.max(-oy2, 0));
4070
+ }
4071
+ return { top: Math.ceil(t), right: Math.ceil(r), bottom: Math.ceil(b2), left: Math.ceil(l) };
4072
+ }
4073
+ function parseFilterBlur(cs) {
4074
+ const m = (cs.filter || "").match(/blur\(\s*([0-9.]+)px\s*\)/);
4075
+ const b2 = m ? Math.ceil(parseFloat(m[1]) || 0) : 0;
4076
+ return { top: b2, right: b2, bottom: b2, left: b2 };
4077
+ }
4078
+ function parseOutline(cs) {
4079
+ if ((cs.outlineStyle || "none") === "none") return { top: 0, right: 0, bottom: 0, left: 0 };
4080
+ const w2 = Math.ceil(parseFloat(cs.outlineWidth || "0") || 0);
4081
+ return { top: w2, right: w2, bottom: w2, left: w2 };
4082
+ }
3898
4083
  function parseFilterDropShadows(cs) {
3899
4084
  var _a;
3900
4085
  const raw = `${cs.filter || ""} ${cs.webkitFilter || ""}`.trim();
@@ -3984,35 +4169,6 @@ ${imported}
3984
4169
  return null;
3985
4170
  }
3986
4171
  }
3987
- function parseBoxShadow(cs) {
3988
- var _a;
3989
- const v = cs.boxShadow || "";
3990
- if (!v || v === "none") return { top: 0, right: 0, bottom: 0, left: 0 };
3991
- const parts = v.split(/\),(?=(?:[^()]*\([^()]*\))*[^()]*$)/).map((s) => s.trim());
3992
- let t = 0, r = 0, b2 = 0, l = 0;
3993
- for (const part of parts) {
3994
- const nums = ((_a = part.match(/-?\d+(\.\d+)?px/g)) == null ? void 0 : _a.map((n) => parseFloat(n))) || [];
3995
- if (nums.length < 2) continue;
3996
- const [ox2, oy2, blur = 0, spread = 0] = nums;
3997
- const extX = Math.abs(ox2) + blur + spread;
3998
- const extY = Math.abs(oy2) + blur + spread;
3999
- r = Math.max(r, extX + Math.max(ox2, 0));
4000
- l = Math.max(l, extX + Math.max(-ox2, 0));
4001
- b2 = Math.max(b2, extY + Math.max(oy2, 0));
4002
- t = Math.max(t, extY + Math.max(-oy2, 0));
4003
- }
4004
- return { top: Math.ceil(t), right: Math.ceil(r), bottom: Math.ceil(b2), left: Math.ceil(l) };
4005
- }
4006
- function parseFilterBlur(cs) {
4007
- const m = (cs.filter || "").match(/blur\(\s*([0-9.]+)px\s*\)/);
4008
- const b2 = m ? Math.ceil(parseFloat(m[1]) || 0) : 0;
4009
- return { top: b2, right: b2, bottom: b2, left: b2 };
4010
- }
4011
- function parseOutline(cs) {
4012
- if ((cs.outlineStyle || "none") === "none") return { top: 0, right: 0, bottom: 0, left: 0 };
4013
- const w2 = Math.ceil(parseFloat(cs.outlineWidth || "0") || 0);
4014
- return { top: w2, right: w2, bottom: w2, left: w2 };
4015
- }
4016
4172
  function bboxWithOriginFull(w2, h2, M, ox2, oy2) {
4017
4173
  const a2 = M.a, b2 = M.b, c2 = M.c, d2 = M.d, e2 = M.e || 0, f2 = M.f || 0;
4018
4174
  function pt(x, y) {
@@ -4032,17 +4188,24 @@ ${imported}
4032
4188
  }
4033
4189
  return { minX: minX2, minY: minY2, maxX: maxX2, maxY: maxY2, width: maxX2 - minX2, height: maxY2 - minY2 };
4034
4190
  }
4035
- function hasTFBBox(el) {
4036
- return hasBBoxAffectingTransform(el);
4037
- }
4038
- function matrixFromComputed(el) {
4039
- const tr = getComputedStyle(el).transform;
4040
- if (!tr || tr === "none") return new DOMMatrix();
4041
- try {
4042
- return new DOMMatrix(tr);
4043
- } catch (e) {
4044
- return new WebKitCSSMatrix(tr);
4045
- }
4191
+ function parseTransformOriginPx(cs, w, h) {
4192
+ const raw = (cs.transformOrigin || "0 0").trim().split(/\s+/);
4193
+ const [oxRaw, oyRaw] = [raw[0] || "0", raw[1] || "0"];
4194
+ const toPx = (token, size) => {
4195
+ const t = token.toLowerCase();
4196
+ if (t === "left" || t === "top") return 0;
4197
+ if (t === "center") return size / 2;
4198
+ if (t === "right") return size;
4199
+ if (t === "bottom") return size;
4200
+ if (t.endsWith("px")) return parseFloat(t) || 0;
4201
+ if (t.endsWith("%")) return (parseFloat(t) || 0) * size / 100;
4202
+ if (/^-?\d+(\.\d+)?$/.test(t)) return parseFloat(t) || 0;
4203
+ return 0;
4204
+ };
4205
+ return {
4206
+ ox: toPx(oxRaw, w),
4207
+ oy: toPx(oyRaw, h)
4208
+ };
4046
4209
  }
4047
4210
  function readIndividualTransforms(el) {
4048
4211
  var _a, _b, _c, _d, _e, _f, _g, _h;
@@ -4100,35 +4263,6 @@ ${imported}
4100
4263
  out.translate = cs.translate && cs.translate !== "none" ? cs.translate : null;
4101
4264
  return out;
4102
4265
  }
4103
- function hasBBoxAffectingTransform(el) {
4104
- const cs = getComputedStyle(el);
4105
- const t = cs.transform || "none";
4106
- 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);
4107
- if (hasMatrix) return true;
4108
- const r = cs.rotate && cs.rotate !== "none" && cs.rotate !== "0deg";
4109
- const s = cs.scale && cs.scale !== "none" && cs.scale !== "1";
4110
- const tr = cs.translate && cs.translate !== "none" && cs.translate !== "0px 0px";
4111
- return Boolean(r || s || tr);
4112
- }
4113
- function parseTransformOriginPx(cs, w, h) {
4114
- const raw = (cs.transformOrigin || "0 0").trim().split(/\s+/);
4115
- const [oxRaw, oyRaw] = [raw[0] || "0", raw[1] || "0"];
4116
- const toPx = (token, size) => {
4117
- const t = token.toLowerCase();
4118
- if (t === "left" || t === "top") return 0;
4119
- if (t === "center") return size / 2;
4120
- if (t === "right") return size;
4121
- if (t === "bottom") return size;
4122
- if (t.endsWith("px")) return parseFloat(t) || 0;
4123
- if (t.endsWith("%")) return (parseFloat(t) || 0) * size / 100;
4124
- if (/^-?\d+(\.\d+)?$/.test(t)) return parseFloat(t) || 0;
4125
- return 0;
4126
- };
4127
- return {
4128
- ox: toPx(oxRaw, w),
4129
- oy: toPx(oyRaw, h)
4130
- };
4131
- }
4132
4266
  var __measureHost = null;
4133
4267
  function getMeasureHost() {
4134
4268
  if (__measureHost) return __measureHost;
@@ -4163,6 +4297,259 @@ ${imported}
4163
4297
  host.removeChild(tmp);
4164
4298
  return M;
4165
4299
  }
4300
+ function hasBBoxAffectingTransform(el) {
4301
+ const cs = getComputedStyle(el);
4302
+ const t = cs.transform || "none";
4303
+ 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);
4304
+ if (hasMatrix) return true;
4305
+ const r = cs.rotate && cs.rotate !== "none" && cs.rotate !== "0deg";
4306
+ const s = cs.scale && cs.scale !== "none" && cs.scale !== "1";
4307
+ const tr = cs.translate && cs.translate !== "none" && cs.translate !== "0px 0px";
4308
+ return Boolean(r || s || tr);
4309
+ }
4310
+ function matrixFromComputed(el) {
4311
+ const tr = getComputedStyle(el).transform;
4312
+ if (!tr || tr === "none") return new DOMMatrix();
4313
+ try {
4314
+ return new DOMMatrix(tr);
4315
+ } catch (e) {
4316
+ return new WebKitCSSMatrix(tr);
4317
+ }
4318
+ }
4319
+
4320
+ // src/core/capture.js
4321
+ function captureDOM(element, options) {
4322
+ return __async(this, null, function* () {
4323
+ var _a;
4324
+ if (!element) throw new Error("Element cannot be null or undefined");
4325
+ applyCachePolicy(options.cache);
4326
+ const fast = options.fast;
4327
+ const outerTransforms = options.outerTransforms !== false;
4328
+ const outerShadows = !!options.outerShadows;
4329
+ let state = { element, options, plugins: options.plugins };
4330
+ let clone, classCSS, styleCache;
4331
+ let fontsCSS = "";
4332
+ let baseCSS = "";
4333
+ let dataURL;
4334
+ let svgString;
4335
+ let rootTransform2D = null;
4336
+ yield runHook("beforeSnap", state);
4337
+ yield runHook("beforeClone", state);
4338
+ const undoClamp = lineClamp(state.element);
4339
+ try {
4340
+ ({ clone, classCSS, styleCache } = yield prepareClone(state.element, state.options));
4341
+ if (!outerTransforms && clone) {
4342
+ rootTransform2D = normalizeRootTransforms(state.element, clone);
4343
+ }
4344
+ if (!outerShadows && clone) {
4345
+ stripRootShadows(state.element, clone);
4346
+ }
4347
+ } finally {
4348
+ undoClamp();
4349
+ }
4350
+ state = __spreadValues({ clone, classCSS, styleCache }, state);
4351
+ yield runHook("afterClone", state);
4352
+ sanitizeCloneForXHTML(state.clone);
4353
+ if (((_a = state.options) == null ? void 0 : _a.excludeMode) === "remove") {
4354
+ try {
4355
+ shrinkAutoSizeBoxes(state.element, state.clone, state.styleCache);
4356
+ } catch (e) {
4357
+ console.warn("[snapdom] shrink pass failed:", e);
4358
+ }
4359
+ }
4360
+ try {
4361
+ yield ligatureIconToImage(state.clone, state.element);
4362
+ } catch (e) {
4363
+ }
4364
+ yield new Promise((resolve) => {
4365
+ idle(() => __async(null, null, function* () {
4366
+ yield inlineImages(state.clone, state.options);
4367
+ resolve();
4368
+ }), { fast });
4369
+ });
4370
+ yield new Promise((resolve) => {
4371
+ idle(() => __async(null, null, function* () {
4372
+ yield inlineBackgroundImages(state.element, state.clone, state.styleCache, state.options);
4373
+ resolve();
4374
+ }), { fast });
4375
+ });
4376
+ if (options.embedFonts) {
4377
+ yield new Promise((resolve) => {
4378
+ idle(() => __async(null, null, function* () {
4379
+ const required = collectUsedFontVariants(state.element);
4380
+ const usedCodepoints = collectUsedCodepoints(state.element);
4381
+ if (isSafari()) {
4382
+ const families = new Set(
4383
+ Array.from(required).map((k) => String(k).split("__")[0]).filter(Boolean)
4384
+ );
4385
+ yield ensureFontsReady(families, 1);
4386
+ }
4387
+ fontsCSS = yield embedCustomFonts({
4388
+ required,
4389
+ usedCodepoints,
4390
+ preCached: false,
4391
+ exclude: state.options.excludeFonts,
4392
+ useProxy: state.options.useProxy
4393
+ });
4394
+ resolve();
4395
+ }), { fast });
4396
+ });
4397
+ }
4398
+ const usedTags = collectUsedTagNames(state.clone).sort();
4399
+ const tagKey = usedTags.join(",");
4400
+ if (cache.baseStyle.has(tagKey)) {
4401
+ baseCSS = cache.baseStyle.get(tagKey);
4402
+ } else {
4403
+ yield new Promise((resolve) => {
4404
+ idle(() => {
4405
+ baseCSS = generateDedupedBaseCSS(usedTags);
4406
+ cache.baseStyle.set(tagKey, baseCSS);
4407
+ resolve();
4408
+ }, { fast });
4409
+ });
4410
+ }
4411
+ state = __spreadValues({ fontsCSS, baseCSS }, state);
4412
+ yield runHook("beforeRender", state);
4413
+ yield new Promise((resolve) => {
4414
+ idle(() => {
4415
+ var _a2;
4416
+ const csEl = getComputedStyle(state.element);
4417
+ const rect = state.element.getBoundingClientRect();
4418
+ let w0 = Math.max(1, limitDecimals(state.element.offsetWidth || parseFloat(csEl.width) || rect.width || 1));
4419
+ let h0 = Math.max(1, limitDecimals(state.element.offsetHeight || parseFloat(csEl.height) || rect.height || 1));
4420
+ if (((_a2 = state.options) == null ? void 0 : _a2.excludeMode) === "remove") {
4421
+ const hEst = estimateKeptHeight(state.element, state.options);
4422
+ const EPS = 1;
4423
+ if (Number.isFinite(hEst) && hEst > 0) {
4424
+ h0 = Math.max(1, Math.min(h0, limitDecimals(hEst + EPS)));
4425
+ }
4426
+ }
4427
+ const coerceNum = (v, def = NaN) => {
4428
+ const n = typeof v === "string" ? parseFloat(v) : v;
4429
+ return Number.isFinite(n) ? n : def;
4430
+ };
4431
+ const optW = coerceNum(state.options.width);
4432
+ const optH = coerceNum(state.options.height);
4433
+ let w = w0, h = h0;
4434
+ const hasW = Number.isFinite(optW);
4435
+ const hasH = Number.isFinite(optH);
4436
+ const aspect0 = h0 > 0 ? w0 / h0 : 1;
4437
+ if (hasW && hasH) {
4438
+ w = Math.max(1, limitDecimals(optW));
4439
+ h = Math.max(1, limitDecimals(optH));
4440
+ } else if (hasW) {
4441
+ w = Math.max(1, limitDecimals(optW));
4442
+ h = Math.max(1, limitDecimals(w / (aspect0 || 1)));
4443
+ } else if (hasH) {
4444
+ h = Math.max(1, limitDecimals(optH));
4445
+ w = Math.max(1, limitDecimals(h * (aspect0 || 1)));
4446
+ } else {
4447
+ w = w0;
4448
+ h = h0;
4449
+ }
4450
+ let minX = 0, minY = 0, maxX = w0, maxY = h0;
4451
+ if (!outerTransforms && rootTransform2D && Number.isFinite(rootTransform2D.a)) {
4452
+ const M2 = {
4453
+ a: rootTransform2D.a,
4454
+ b: rootTransform2D.b || 0,
4455
+ c: rootTransform2D.c || 0,
4456
+ d: rootTransform2D.d || 1,
4457
+ e: 0,
4458
+ f: 0
4459
+ };
4460
+ const bb2 = bboxWithOriginFull(w0, h0, M2, 0, 0);
4461
+ minX = limitDecimals(bb2.minX);
4462
+ minY = limitDecimals(bb2.minY);
4463
+ maxX = limitDecimals(bb2.maxX);
4464
+ maxY = limitDecimals(bb2.maxY);
4465
+ } else {
4466
+ const useTFBBox = outerTransforms && hasTFBBox(state.element);
4467
+ if (useTFBBox) {
4468
+ const baseTransform2 = csEl.transform && csEl.transform !== "none" ? csEl.transform : "";
4469
+ const ind2 = readIndividualTransforms(state.element);
4470
+ const TOTAL = readTotalTransformMatrix({
4471
+ baseTransform: baseTransform2,
4472
+ rotate: ind2.rotate || "0deg",
4473
+ scale: ind2.scale,
4474
+ translate: ind2.translate
4475
+ });
4476
+ const { ox: ox2, oy: oy2 } = parseTransformOriginPx(csEl, w0, h0);
4477
+ const M = TOTAL.is2D ? TOTAL : new DOMMatrix(TOTAL.toString());
4478
+ const bb = bboxWithOriginFull(w0, h0, M, ox2, oy2);
4479
+ minX = limitDecimals(bb.minX);
4480
+ minY = limitDecimals(bb.minY);
4481
+ maxX = limitDecimals(bb.maxX);
4482
+ maxY = limitDecimals(bb.maxY);
4483
+ }
4484
+ }
4485
+ const bleedShadow = parseBoxShadow(csEl);
4486
+ const bleedBlur = parseFilterBlur(csEl);
4487
+ const bleedOutline = parseOutline(csEl);
4488
+ const drop = parseFilterDropShadows(csEl);
4489
+ const bleed = !outerShadows ? { top: 0, right: 0, bottom: 0, left: 0 } : {
4490
+ top: limitDecimals(bleedShadow.top + bleedBlur.top + bleedOutline.top + drop.bleed.top),
4491
+ right: limitDecimals(bleedShadow.right + bleedBlur.right + bleedOutline.right + drop.bleed.right),
4492
+ bottom: limitDecimals(bleedShadow.bottom + bleedBlur.bottom + bleedOutline.bottom + drop.bleed.bottom),
4493
+ left: limitDecimals(bleedShadow.left + bleedBlur.left + bleedOutline.left + drop.bleed.left)
4494
+ };
4495
+ minX = limitDecimals(minX - bleed.left);
4496
+ minY = limitDecimals(minY - bleed.top);
4497
+ maxX = limitDecimals(maxX + bleed.right);
4498
+ maxY = limitDecimals(maxY + bleed.bottom);
4499
+ const vbW0 = Math.max(1, limitDecimals(maxX - minX));
4500
+ const vbH0 = Math.max(1, limitDecimals(maxY - minY));
4501
+ const scaleW = hasW || hasH ? limitDecimals(w / w0) : 1;
4502
+ const scaleH = hasH || hasW ? limitDecimals(h / h0) : 1;
4503
+ const outW = Math.max(1, limitDecimals(vbW0 * scaleW));
4504
+ const outH = Math.max(1, limitDecimals(vbH0 * scaleH));
4505
+ const svgNS = "http://www.w3.org/2000/svg";
4506
+ const basePad = isSafari() ? 1 : 0;
4507
+ const extraPad = !outerTransforms ? 1 : 0;
4508
+ const pad = limitDecimals(basePad + extraPad);
4509
+ const fo = document.createElementNS(svgNS, "foreignObject");
4510
+ const vbMinX = limitDecimals(minX);
4511
+ const vbMinY = limitDecimals(minY);
4512
+ fo.setAttribute("x", String(limitDecimals(-(vbMinX - pad))));
4513
+ fo.setAttribute("y", String(limitDecimals(-(vbMinY - pad))));
4514
+ fo.setAttribute("width", String(limitDecimals(w0 + pad * 2)));
4515
+ fo.setAttribute("height", String(limitDecimals(h0 + pad * 2)));
4516
+ fo.style.overflow = "visible";
4517
+ const styleTag = document.createElement("style");
4518
+ styleTag.textContent = state.baseCSS + state.fontsCSS + "svg{overflow:visible;} foreignObject{overflow:visible;}" + state.classCSS;
4519
+ fo.appendChild(styleTag);
4520
+ const container = document.createElement("div");
4521
+ container.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
4522
+ container.style.width = `${limitDecimals(w0)}px`;
4523
+ container.style.height = `${limitDecimals(h0)}px`;
4524
+ container.style.overflow = "visible";
4525
+ state.clone.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
4526
+ container.appendChild(state.clone);
4527
+ fo.appendChild(container);
4528
+ const serializer = new XMLSerializer();
4529
+ const foString = serializer.serializeToString(fo);
4530
+ const vbW = limitDecimals(vbW0 + pad * 2);
4531
+ const vbH = limitDecimals(vbH0 + pad * 2);
4532
+ const wantsSize = hasW || hasH;
4533
+ options.meta = { w0, h0, vbW, vbH, targetW: w, targetH: h };
4534
+ const svgOutW = isSafari() && wantsSize ? vbW : limitDecimals(outW + pad * 2);
4535
+ const svgOutH = isSafari() && wantsSize ? vbH : limitDecimals(outH + pad * 2);
4536
+ const svgHeader = `<svg xmlns="${svgNS}" width="${svgOutW}" height="${svgOutH}" viewBox="0 0 ${vbW} ${vbH}">`;
4537
+ const svgFooter = "</svg>";
4538
+ svgString = svgHeader + foString + svgFooter;
4539
+ dataURL = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgString)}`;
4540
+ state = __spreadValues({ svgString, dataURL }, state);
4541
+ resolve();
4542
+ }, { fast });
4543
+ });
4544
+ yield runHook("afterRender", state);
4545
+ const sandbox = document.getElementById("snapdom-sandbox");
4546
+ if (sandbox && sandbox.style.position === "absolute") sandbox.remove();
4547
+ return state.dataURL;
4548
+ });
4549
+ }
4550
+ function hasTFBBox(el) {
4551
+ return hasBBoxAffectingTransform(el);
4552
+ }
4166
4553
 
4167
4554
  // src/core/context.js
4168
4555
  function normalizeCachePolicy(v) {
@@ -4321,7 +4708,7 @@ ${imported}
4321
4708
  }
4322
4709
  function toCanvas(url, options) {
4323
4710
  return __async(this, null, function* () {
4324
- let { width: optW, height: optH, scale = 1, dpr = 1, meta = {} } = options;
4711
+ let { width: optW, height: optH, scale = 1, dpr = 1, meta = {}, backgroundColor } = options;
4325
4712
  url = maybeConvertBoxShadowForSafari(url);
4326
4713
  const img = new Image();
4327
4714
  img.loading = "eager";
@@ -4360,6 +4747,12 @@ ${imported}
4360
4747
  canvas.style.height = `${outH}px`;
4361
4748
  const ctx = canvas.getContext("2d");
4362
4749
  if (dpr !== 1) ctx.scale(dpr, dpr);
4750
+ if (backgroundColor) {
4751
+ ctx.save();
4752
+ ctx.fillStyle = backgroundColor;
4753
+ ctx.fillRect(0, 0, outW, outH);
4754
+ ctx.restore();
4755
+ }
4363
4756
  ctx.drawImage(img, 0, 0, outW, outH);
4364
4757
  return canvas;
4365
4758
  });
@@ -4369,12 +4762,11 @@ ${imported}
4369
4762
  function rasterize(url, options) {
4370
4763
  return __async(this, null, function* () {
4371
4764
  const canvas = yield toCanvas(url, options);
4372
- const finalCanvas = options.backgroundColor ? createBackground(canvas, options.backgroundColor) : canvas;
4373
4765
  const img = new Image();
4374
- img.src = finalCanvas.toDataURL(`image/${options.format}`, options.quality);
4766
+ img.src = canvas.toDataURL(`image/${options.format}`, options.quality);
4375
4767
  yield img.decode();
4376
- img.style.width = `${finalCanvas.width / options.dpr}px`;
4377
- img.style.height = `${finalCanvas.height / options.dpr}px`;
4768
+ img.style.width = `${canvas.width / options.dpr}px`;
4769
+ img.style.height = `${canvas.height / options.dpr}px`;
4378
4770
  return img;
4379
4771
  });
4380
4772
  }
@@ -4438,13 +4830,8 @@ ${imported}
4438
4830
  return new Blob([svgText], { type: "image/svg+xml" });
4439
4831
  }
4440
4832
  const canvas = yield toCanvas(url, options);
4441
- const finalCanvas = options.backgroundColor ? createBackground(canvas, options.backgroundColor) : canvas;
4442
4833
  return new Promise(
4443
- (resolve) => finalCanvas.toBlob(
4444
- (blob) => resolve(blob),
4445
- `image/${type}`,
4446
- options.quality
4447
- )
4834
+ (resolve) => canvas.toBlob((blob) => resolve(blob), `image/${type}`, options.quality)
4448
4835
  );
4449
4836
  });
4450
4837
  }
@@ -4464,9 +4851,8 @@ ${imported}
4464
4851
  return;
4465
4852
  }
4466
4853
  const canvas = yield toCanvas(url, options);
4467
- const finalCanvas = options.backgroundColor ? createBackground(canvas, options.backgroundColor) : canvas;
4468
4854
  const a = document.createElement("a");
4469
- a.href = finalCanvas.toDataURL(`image/${options.format}`, options.quality);
4855
+ a.href = canvas.toDataURL(`image/${options.format}`, options.quality);
4470
4856
  a.download = options.filename;
4471
4857
  a.click();
4472
4858
  });