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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -29,6 +29,8 @@
29
29
  </a>
30
30
  </p>
31
31
 
32
+ <p align="center">English | <a href="README_CN.md">简体中文</a></p>
33
+
32
34
  # snapDOM
33
35
 
34
36
  **snapDOM** is a fast and accurate DOM-to-image capture tool built for **Zumly**, a zoom-based view transition framework.
@@ -751,6 +753,7 @@ For detailed contribution guidelines, please see [CONTRIBUTING](https://github.c
751
753
  <a href="https://github.com/tinchox5" title="tinchox5"><img src="https://avatars.githubusercontent.com/u/11557901?v=4&s=100" style="border-radius:10px; width:60px; height:60px; object-fit:cover; margin:5px;" alt="tinchox5"/></a>
752
754
  <a href="https://github.com/Jarvis2018" title="Jarvis2018"><img src="https://avatars.githubusercontent.com/u/36788851?v=4&s=100" style="border-radius:10px; width:60px; height:60px; object-fit:cover; margin:5px;" alt="Jarvis2018"/></a>
753
755
  <a href="https://github.com/tarwin" title="tarwin"><img src="https://avatars.githubusercontent.com/u/646149?v=4&s=100" style="border-radius:10px; width:60px; height:60px; object-fit:cover; margin:5px;" alt="tarwin"/></a>
756
+ <a href="https://github.com/jswhisperer" title="jswhisperer"><img src="https://avatars.githubusercontent.com/u/1177690?v=4&s=100" style="border-radius:10px; width:60px; height:60px; object-fit:cover; margin:5px;" alt="jswhisperer"/></a>
754
757
  <a href="https://github.com/K1ender" title="K1ender"><img src="https://avatars.githubusercontent.com/u/146767945?v=4&s=100" style="border-radius:10px; width:60px; height:60px; object-fit:cover; margin:5px;" alt="K1ender"/></a>
755
758
  <a href="https://github.com/17biubiu" title="17biubiu"><img src="https://avatars.githubusercontent.com/u/13295895?v=4&s=100" style="border-radius:10px; width:60px; height:60px; object-fit:cover; margin:5px;" alt="17biubiu"/></a>
756
759
  <a href="https://github.com/av01d" title="av01d"><img src="https://avatars.githubusercontent.com/u/6247646?v=4&s=100" style="border-radius:10px; width:60px; height:60px; object-fit:cover; margin:5px;" alt="av01d"/></a>
@@ -758,7 +761,7 @@ For detailed contribution guidelines, please see [CONTRIBUTING](https://github.c
758
761
  <a href="https://github.com/pedrocateexte" title="pedrocateexte"><img src="https://avatars.githubusercontent.com/u/207524750?v=4&s=100" style="border-radius:10px; width:60px; height:60px; object-fit:cover; margin:5px;" alt="pedrocateexte"/></a>
759
762
  <a href="https://github.com/domialex" title="domialex"><img src="https://avatars.githubusercontent.com/u/4694217?v=4&s=100" style="border-radius:10px; width:60px; height:60px; object-fit:cover; margin:5px;" alt="domialex"/></a>
760
763
  <a href="https://github.com/elliots" title="elliots"><img src="https://avatars.githubusercontent.com/u/622455?v=4&s=100" style="border-radius:10px; width:60px; height:60px; object-fit:cover; margin:5px;" alt="elliots"/></a>
761
- <a href="https://github.com/jswhisperer" title="jswhisperer"><img src="https://avatars.githubusercontent.com/u/1177690?v=4&s=100" style="border-radius:10px; width:60px; height:60px; object-fit:cover; margin:5px;" alt="jswhisperer"/></a>
764
+ <a href="https://github.com/mon-jai" title="mon-jai"><img src="https://avatars.githubusercontent.com/u/91261297?v=4&s=100" style="border-radius:10px; width:60px; height:60px; object-fit:cover; margin:5px;" alt="mon-jai"/></a>
762
765
  <a href="https://github.com/sharuzzaman" title="sharuzzaman"><img src="https://avatars.githubusercontent.com/u/7421941?v=4&s=100" style="border-radius:10px; width:60px; height:60px; object-fit:cover; margin:5px;" alt="sharuzzaman"/></a>
763
766
  <a href="https://github.com/simon1uo" title="simon1uo"><img src="https://avatars.githubusercontent.com/u/60037549?v=4&s=100" style="border-radius:10px; width:60px; height:60px; object-fit:cover; margin:5px;" alt="simon1uo"/></a>
764
767
  <a href="https://github.com/titoBouzout" title="titoBouzout"><img src="https://avatars.githubusercontent.com/u/64156?v=4&s=100" style="border-radius:10px; width:60px; height:60px; object-fit:cover; margin:5px;" alt="titoBouzout"/></a>
@@ -784,4 +787,4 @@ If you'd like to support this project too, you can [become a sponsor](https://gi
784
787
 
785
788
  ## License
786
789
 
787
- MIT © Zumerlab
790
+ MIT © Zumerlab
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.2
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",
@@ -1482,17 +1494,19 @@
1482
1494
  /layui/i,
1483
1495
  /lucide/i
1484
1496
  ];
1497
+ var ICON_FONT_URLS = Object.assign({
1498
+ materialIconsFilled: "https://fonts.gstatic.com/s/materialicons/v48/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.woff2",
1499
+ materialIconsOutlined: "https://fonts.gstatic.com/s/materialiconsoutlined/v110/gok-H7zzDkdnRel8-DQ6KAXJ69wP1tGnf4ZGhUcel5euIg.woff2",
1500
+ materialIconsRound: "https://fonts.gstatic.com/s/materialiconsround/v109/LDItaoyNOAY6Uewc665JcIzCKsKc_M9flwmPq_HTTw.woff2",
1501
+ materialIconsSharp: "https://fonts.gstatic.com/s/materialiconssharp/v110/oPWQ_lt5nv4pWNJpghLP75WiFR4kLh3kvmvRImcycg.woff2"
1502
+ }, typeof window !== "undefined" && window.__SNAPDOM_ICON_FONTS__ || {});
1485
1503
  var userIconFonts = [];
1486
1504
  function extendIconFonts(fonts) {
1487
1505
  const list = Array.isArray(fonts) ? fonts : [fonts];
1488
1506
  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
- }
1507
+ if (f instanceof RegExp) userIconFonts.push(f);
1508
+ else if (typeof f === "string") userIconFonts.push(new RegExp(f, "i"));
1509
+ else console.warn("[snapdom] Ignored invalid iconFont value:", f);
1496
1510
  }
1497
1511
  }
1498
1512
  function isIconFont2(input) {
@@ -1508,11 +1522,66 @@
1508
1522
  const s = String(family).toLowerCase();
1509
1523
  return /\bmaterial\s*icons\b/.test(s) || /\bmaterial\s*symbols\b/.test(s);
1510
1524
  }
1525
+ var loadedCanvasFamilies = /* @__PURE__ */ new Map();
1526
+ function parseAxes(variation = "") {
1527
+ const out = /* @__PURE__ */ Object.create(null);
1528
+ const v = String(variation || "");
1529
+ const rx = /['"]?\s*([A-Za-z]{3,4})\s*['"]?\s*([+-]?\d+(?:\.\d+)?)\s*/g;
1530
+ let m;
1531
+ while (m = rx.exec(v)) out[m[1].toUpperCase()] = Number(m[2]);
1532
+ return out;
1533
+ }
1534
+ function ensureLigatureCanvasFont(cssFamily, className, axes) {
1535
+ return __async(this, null, function* () {
1536
+ var _a;
1537
+ const fam = String(cssFamily || "");
1538
+ const lowerFam = fam.toLowerCase();
1539
+ const cls = String(className || "").toLowerCase();
1540
+ if (/\bmaterial\s*icons\b/.test(lowerFam) && !/\bsymbols\b/.test(lowerFam)) {
1541
+ return { familyForMeasure: fam, familyForCanvas: fam };
1542
+ }
1543
+ const isSymbols = /\bmaterial\s*symbols\b/.test(lowerFam);
1544
+ if (!isSymbols) {
1545
+ return { familyForMeasure: fam, familyForCanvas: fam };
1546
+ }
1547
+ const FILL = axes && ((_a = axes.FILL) != null ? _a : axes.fill);
1548
+ let style = "outlined";
1549
+ if (/\brounded\b/.test(cls) || /\bround\b/.test(cls)) style = "rounded";
1550
+ else if (/\bsharp\b/.test(cls)) style = "sharp";
1551
+ else if (/\boutlined\b/.test(cls)) style = "outlined";
1552
+ const filled = FILL === 1;
1553
+ let pick = null;
1554
+ if (filled) {
1555
+ if (style === "outlined" && ICON_FONT_URLS.materialIconsFilled) {
1556
+ pick = { url: ICON_FONT_URLS.materialIconsFilled, alias: "snapdom-mi-filled" };
1557
+ } else if (style === "rounded" && ICON_FONT_URLS.materialIconsRound) {
1558
+ pick = { url: ICON_FONT_URLS.materialIconsRound, alias: "snapdom-mi-round" };
1559
+ } else if (style === "sharp" && ICON_FONT_URLS.materialIconsSharp) {
1560
+ pick = { url: ICON_FONT_URLS.materialIconsSharp, alias: "snapdom-mi-sharp" };
1561
+ }
1562
+ }
1563
+ if (!pick) {
1564
+ return { familyForMeasure: fam, familyForCanvas: fam };
1565
+ }
1566
+ if (!loadedCanvasFamilies.has(pick.alias)) {
1567
+ try {
1568
+ const ff = new FontFace(pick.alias, `url(${pick.url})`, { style: "normal", weight: "400" });
1569
+ document.fonts.add(ff);
1570
+ yield ff.load();
1571
+ loadedCanvasFamilies.set(pick.alias, true);
1572
+ } catch (e) {
1573
+ return { familyForMeasure: fam, familyForCanvas: fam };
1574
+ }
1575
+ }
1576
+ const quoted = `"${pick.alias}"`;
1577
+ return { familyForMeasure: quoted, familyForCanvas: quoted };
1578
+ });
1579
+ }
1511
1580
  function ensureMaterialFontsReady(family = "Material Icons", px = 24) {
1512
1581
  return __async(this, null, function* () {
1513
1582
  try {
1514
1583
  yield Promise.all([
1515
- document.fonts.load(`400 ${px}px "${family.replace(/["']/g, "")}"`),
1584
+ document.fonts.load(`400 ${px}px "${String(family).replace(/["']/g, "")}"`),
1516
1585
  document.fonts.ready
1517
1586
  ]);
1518
1587
  } catch (e) {
@@ -1522,10 +1591,8 @@
1522
1591
  function resolvePaintColor(cs) {
1523
1592
  var _a, _b;
1524
1593
  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
- }
1594
+ const isTransparent = /^transparent$/i.test(fill) || /rgba?\(\s*0\s*,\s*0\s*,\s*0\s*,\s*0\s*\)/i.test(fill);
1595
+ if (fill && !isTransparent && fill.toLowerCase() !== "currentcolor") return fill;
1529
1596
  const c = (_b = cs.color) == null ? void 0 : _b.trim();
1530
1597
  return c && c !== "inherit" ? c : "#000";
1531
1598
  }
@@ -1535,18 +1602,21 @@
1535
1602
  weight = "normal",
1536
1603
  fontSize = 32,
1537
1604
  color = "#000",
1538
- variation = ""
1605
+ variation = "",
1606
+ className = ""
1539
1607
  } = {}) {
1540
1608
  const fam = String(family || "").replace(/^['"]+|['"]+$/g, "");
1541
1609
  const dpr = window.devicePixelRatio || 1;
1542
- yield ensureMaterialFontsReady(fam, fontSize);
1610
+ const axes = parseAxes(variation);
1611
+ const { familyForMeasure, familyForCanvas } = yield ensureLigatureCanvasFont(fam, className, axes);
1612
+ yield ensureMaterialFontsReady(familyForCanvas.replace(/^["']+|["']+$/g, ""), fontSize);
1543
1613
  const span = document.createElement("span");
1544
1614
  span.textContent = ligatureText;
1545
1615
  span.style.position = "absolute";
1546
1616
  span.style.visibility = "hidden";
1547
1617
  span.style.left = "-99999px";
1548
1618
  span.style.whiteSpace = "nowrap";
1549
- span.style.fontFamily = `"${fam}"`;
1619
+ span.style.fontFamily = familyForMeasure;
1550
1620
  span.style.fontWeight = String(weight || "normal");
1551
1621
  span.style.fontSize = `${fontSize}px`;
1552
1622
  span.style.lineHeight = "1";
@@ -1554,7 +1624,6 @@
1554
1624
  span.style.padding = "0";
1555
1625
  span.style.fontFeatureSettings = "'liga' 1";
1556
1626
  span.style.fontVariantLigatures = "normal";
1557
- if (variation) span.style.fontVariationSettings = variation;
1558
1627
  span.style.color = color;
1559
1628
  document.body.appendChild(span);
1560
1629
  const rect = span.getBoundingClientRect();
@@ -1566,7 +1635,7 @@
1566
1635
  canvas.height = height * dpr;
1567
1636
  const ctx = canvas.getContext("2d");
1568
1637
  ctx.scale(dpr, dpr);
1569
- ctx.font = `${weight ? `${weight} ` : ""}${fontSize}px "${fam}"`;
1638
+ ctx.font = `${weight ? `${weight} ` : ""}${fontSize}px ${familyForCanvas}`;
1570
1639
  ctx.textAlign = "left";
1571
1640
  ctx.textBaseline = "top";
1572
1641
  ctx.fillStyle = color;
@@ -1596,21 +1665,23 @@
1596
1665
  const el = cloneNodes[i];
1597
1666
  const src = sourceNodes[i] || null;
1598
1667
  try {
1599
- const csSrc = src ? getComputedStyle(src) : getComputedStyle(el);
1600
- const family = csSrc.fontFamily || "Material Icons";
1668
+ const cs = src ? getComputedStyle(src) : getComputedStyle(el);
1669
+ const family = cs.fontFamily || "Material Icons";
1601
1670
  if (!isMaterialFamily(family)) continue;
1602
1671
  const text = (src || el).textContent.trim();
1603
1672
  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 : "";
1673
+ const size = parseInt(cs.fontSize, 10) || 24;
1674
+ const weight = cs.fontWeight && cs.fontWeight !== "normal" ? cs.fontWeight : "normal";
1675
+ const color = resolvePaintColor(cs);
1676
+ const variation = cs.fontVariationSettings && cs.fontVariationSettings !== "normal" ? cs.fontVariationSettings : "";
1677
+ const className = (src || el).className || "";
1608
1678
  const { dataUrl, width, height } = yield materialIconToImage(text, {
1609
1679
  family,
1610
1680
  weight,
1611
1681
  fontSize: size,
1612
1682
  color,
1613
- variation
1683
+ variation,
1684
+ className
1614
1685
  });
1615
1686
  el.textContent = "";
1616
1687
  const img = el.ownerDocument.createElement("img");
@@ -1988,7 +2059,7 @@ ${imported}
1988
2059
  const styleSpec = (((_c = block.match(/font-style:\s*([^;]+);/i)) == null ? void 0 : _c[1]) || "normal").trim();
1989
2060
  const stretchSpec = (((_d = block.match(/font-stretch:\s*([^;]+);/i)) == null ? void 0 : _d[1]) || "100%").trim();
1990
2061
  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();
2062
+ const srcRaw = (((_f = block.match(/src\s*:\s*([^;}]+)[;}]/i)) == null ? void 0 : _f[1]) || "").trim();
1992
2063
  const urls = extractSrcUrls(srcRaw, location.href);
1993
2064
  const srcPart = urls.length ? urls.map((u) => String(u).toLowerCase()).sort().join("|") : srcRaw.toLowerCase();
1994
2065
  const key = [
@@ -2200,7 +2271,7 @@ ${imported}
2200
2271
  const styleSpec = (((_d = face.match(/font-style:\s*([^;]+);/i)) == null ? void 0 : _d[1]) || "normal").trim();
2201
2272
  const stretchSpec = (((_e = face.match(/font-stretch:\s*([^;]+);/i)) == null ? void 0 : _e[1]) || "100%").trim();
2202
2273
  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();
2274
+ const srcRaw = (((_g = face.match(/src\s*:\s*([^;}]+)[;}]/i)) == null ? void 0 : _g[1]) || "").trim();
2204
2275
  const srcUrls = extractSrcUrls(srcRaw, link.href);
2205
2276
  if (!faceMatchesRequired(family, styleSpec, weightSpec, stretchSpec)) continue;
2206
2277
  const ranges = parseUnicodeRange(urange);
@@ -3682,8 +3753,53 @@ ${imported}
3682
3753
  } catch (e) {
3683
3754
  }
3684
3755
  }
3756
+ function removeAllComments(root) {
3757
+ const it = document.createTreeWalker(root, NodeFilter.SHOW_COMMENT);
3758
+ const toRemove = [];
3759
+ while (it.nextNode()) toRemove.push(it.currentNode);
3760
+ for (const n of toRemove) n.remove();
3761
+ }
3762
+ function sanitizeAttributesForXHTML(root, opts = {}) {
3763
+ const { stripFrameworkDirectives = true } = opts;
3764
+ const ALLOWED_PREFIXES = /* @__PURE__ */ new Set(["xml", "xlink"]);
3765
+ const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
3766
+ while (walker.nextNode()) {
3767
+ const el = walker.currentNode;
3768
+ for (const attr of Array.from(el.attributes)) {
3769
+ const name = attr.name;
3770
+ if (name.includes("@")) {
3771
+ el.removeAttribute(name);
3772
+ continue;
3773
+ }
3774
+ if (name.includes(":")) {
3775
+ const prefix = name.split(":", 1)[0];
3776
+ if (!ALLOWED_PREFIXES.has(prefix)) {
3777
+ el.removeAttribute(name);
3778
+ continue;
3779
+ }
3780
+ }
3781
+ if (!stripFrameworkDirectives) continue;
3782
+ if (name.startsWith("x-") || // Alpine
3783
+ name.startsWith("v-") || // Vue
3784
+ name.startsWith(":") || // Vue/Alpine shorthand
3785
+ name.startsWith("on:") || // Svelte
3786
+ name.startsWith("bind:") || // Svelte
3787
+ name.startsWith("let:") || // Svelte
3788
+ name.startsWith("class:")) {
3789
+ el.removeAttribute(name);
3790
+ continue;
3791
+ }
3792
+ }
3793
+ }
3794
+ }
3795
+ function sanitizeCloneForXHTML(root, opts = {}) {
3796
+ if (!root) return;
3797
+ sanitizeAttributesForXHTML(root, opts);
3798
+ removeAllComments(root);
3799
+ }
3685
3800
  function captureDOM(element, options) {
3686
3801
  return __async(this, null, function* () {
3802
+ var _a;
3687
3803
  if (!element) throw new Error("Element cannot be null or undefined");
3688
3804
  applyCachePolicy(options.cache);
3689
3805
  const fast = options.fast;
@@ -3712,6 +3828,14 @@ ${imported}
3712
3828
  }
3713
3829
  state = __spreadValues({ clone, classCSS, styleCache }, state);
3714
3830
  yield runHook("afterClone", state);
3831
+ sanitizeCloneForXHTML(state.clone);
3832
+ if (((_a = state.options) == null ? void 0 : _a.excludeMode) === "remove") {
3833
+ try {
3834
+ shrinkAutoSizeBoxes(state.element, state.clone, state.styleCache);
3835
+ } catch (e) {
3836
+ console.warn("[snapdom] shrink pass failed:", e);
3837
+ }
3838
+ }
3715
3839
  try {
3716
3840
  yield ligatureIconToImage(state.clone, state.element);
3717
3841
  } catch (e) {
@@ -3767,10 +3891,18 @@ ${imported}
3767
3891
  yield runHook("beforeRender", state);
3768
3892
  yield new Promise((resolve) => {
3769
3893
  idle(() => {
3894
+ var _a2;
3770
3895
  const csEl = getComputedStyle(state.element);
3771
3896
  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));
3897
+ let w0 = Math.max(1, limitDecimals(state.element.offsetWidth || parseFloat(csEl.width) || rect.width || 1));
3898
+ let h0 = Math.max(1, limitDecimals(state.element.offsetHeight || parseFloat(csEl.height) || rect.height || 1));
3899
+ if (((_a2 = state.options) == null ? void 0 : _a2.excludeMode) === "remove") {
3900
+ const hEst = estimateKeptHeight(state.element, state.options);
3901
+ const EPS = 1;
3902
+ if (Number.isFinite(hEst) && hEst > 0) {
3903
+ h0 = Math.max(1, Math.min(h0, limitDecimals(hEst + EPS)));
3904
+ }
3905
+ }
3774
3906
  const coerceNum = (v, def = NaN) => {
3775
3907
  const n = typeof v === "string" ? parseFloat(v) : v;
3776
3908
  return Number.isFinite(n) ? n : def;
@@ -3894,6 +4026,58 @@ ${imported}
3894
4026
  return state.dataURL;
3895
4027
  });
3896
4028
  }
4029
+ function authorHasExplicitSize(el) {
4030
+ var _a;
4031
+ try {
4032
+ const s = ((_a = el.getAttribute) == null ? void 0 : _a.call(el, "style")) || "";
4033
+ return /\b(height|width|block-size|inline-size)\s*:/.test(s);
4034
+ } catch (e) {
4035
+ return false;
4036
+ }
4037
+ }
4038
+ function isReplacedElement(el) {
4039
+ return el instanceof HTMLImageElement || el instanceof HTMLCanvasElement || el instanceof HTMLVideoElement || el instanceof HTMLIFrameElement || el instanceof SVGElement || el instanceof HTMLObjectElement || el instanceof HTMLEmbedElement;
4040
+ }
4041
+ function shouldShrinkBox(srcEl, cs) {
4042
+ if (!(srcEl instanceof Element)) return false;
4043
+ if (authorHasExplicitSize(srcEl)) return false;
4044
+ if (isReplacedElement(srcEl)) return false;
4045
+ const pos = cs.position;
4046
+ if (pos === "absolute" || pos === "fixed" || pos === "sticky") return false;
4047
+ const disp = cs.display || "";
4048
+ if (disp.includes("flex") || disp.includes("grid") || disp.startsWith("table")) return false;
4049
+ if (cs.transform && cs.transform !== "none") return false;
4050
+ return true;
4051
+ }
4052
+ function shrinkAutoSizeBoxes(sourceRoot, cloneRoot, styleCache = /* @__PURE__ */ new Map()) {
4053
+ function walk(src, cln) {
4054
+ if (!(src instanceof Element) || !(cln instanceof Element)) return;
4055
+ const lostKids = src.childElementCount > cln.childElementCount;
4056
+ const cs = styleCache.get(src) || getComputedStyle(src);
4057
+ if (!styleCache.has(src)) styleCache.set(src, cs);
4058
+ if (lostKids && shouldShrinkBox(src, cs)) {
4059
+ if (!cln.style.height) cln.style.height = "auto";
4060
+ if (!cln.style.width) cln.style.width = "auto";
4061
+ cln.style.removeProperty("block-size");
4062
+ cln.style.removeProperty("inline-size");
4063
+ if (!cln.style.minHeight) cln.style.minHeight = "0";
4064
+ if (!cln.style.minWidth) cln.style.minWidth = "0";
4065
+ if (!cln.style.maxHeight) cln.style.maxHeight = "none";
4066
+ if (!cln.style.maxWidth) cln.style.maxWidth = "none";
4067
+ const oy = cs.overflowY || cs.overflowBlock || "visible";
4068
+ const ox = cs.overflowX || cs.overflowInline || "visible";
4069
+ if (oy !== "visible" || ox !== "visible") {
4070
+ cln.style.overflow = "visible";
4071
+ }
4072
+ }
4073
+ const sKids = Array.from(src.children);
4074
+ const cKids = Array.from(cln.children);
4075
+ for (let i = 0; i < Math.min(sKids.length, cKids.length); i++) {
4076
+ walk(sKids[i], cKids[i]);
4077
+ }
4078
+ }
4079
+ walk(sourceRoot, cloneRoot);
4080
+ }
3897
4081
  var limitDecimals = (v, n = 3) => Number.isFinite(v) ? Math.round(v * __pow(10, n)) / __pow(10, n) : v;
3898
4082
  function parseFilterDropShadows(cs) {
3899
4083
  var _a;
@@ -4163,6 +4347,52 @@ ${imported}
4163
4347
  host.removeChild(tmp);
4164
4348
  return M;
4165
4349
  }
4350
+ function isInNormalFlow(el) {
4351
+ const cs = getComputedStyle(el);
4352
+ if (cs.display === "none") return false;
4353
+ if (cs.position === "absolute" || cs.position === "fixed" || cs.position === "sticky") return false;
4354
+ if ((cs.cssFloat || cs.float || "none") !== "none") return false;
4355
+ if (cs.transform && cs.transform !== "none") return false;
4356
+ return true;
4357
+ }
4358
+ function willBeExcluded(el, options) {
4359
+ if (!(el instanceof Element)) return false;
4360
+ if (el.getAttribute("data-capture") === "exclude" && (options == null ? void 0 : options.excludeMode) === "remove") return true;
4361
+ if (Array.isArray(options == null ? void 0 : options.exclude)) {
4362
+ for (const sel of options.exclude) {
4363
+ try {
4364
+ if (el.matches(sel)) return options.excludeMode === "remove";
4365
+ } catch (e) {
4366
+ }
4367
+ }
4368
+ }
4369
+ return false;
4370
+ }
4371
+ function estimateKeptHeight(container, options) {
4372
+ const csC = getComputedStyle(container);
4373
+ const rC = container.getBoundingClientRect();
4374
+ let minTop = Infinity;
4375
+ let maxBottom = -Infinity;
4376
+ let found = false;
4377
+ const kids = Array.from(container.children);
4378
+ for (const k of kids) {
4379
+ if (willBeExcluded(k, options)) continue;
4380
+ if (!isInNormalFlow(k)) continue;
4381
+ const rk = k.getBoundingClientRect();
4382
+ const top = rk.top - rC.top;
4383
+ const bottom = rk.bottom - rC.top;
4384
+ if (bottom <= top) continue;
4385
+ if (top < minTop) minTop = top;
4386
+ if (bottom > maxBottom) maxBottom = bottom;
4387
+ found = true;
4388
+ }
4389
+ const contentSpan = found ? Math.max(0, maxBottom - minTop) : 0;
4390
+ const bt = parseFloat(csC.borderTopWidth) || 0;
4391
+ const bb = parseFloat(csC.borderBottomWidth) || 0;
4392
+ const pt = parseFloat(csC.paddingTop) || 0;
4393
+ const pb = parseFloat(csC.paddingBottom) || 0;
4394
+ return bt + bb + pt + pb + contentSpan;
4395
+ }
4166
4396
 
4167
4397
  // src/core/context.js
4168
4398
  function normalizeCachePolicy(v) {
@@ -4321,7 +4551,7 @@ ${imported}
4321
4551
  }
4322
4552
  function toCanvas(url, options) {
4323
4553
  return __async(this, null, function* () {
4324
- let { width: optW, height: optH, scale = 1, dpr = 1, meta = {} } = options;
4554
+ let { width: optW, height: optH, scale = 1, dpr = 1, meta = {}, backgroundColor } = options;
4325
4555
  url = maybeConvertBoxShadowForSafari(url);
4326
4556
  const img = new Image();
4327
4557
  img.loading = "eager";
@@ -4360,6 +4590,12 @@ ${imported}
4360
4590
  canvas.style.height = `${outH}px`;
4361
4591
  const ctx = canvas.getContext("2d");
4362
4592
  if (dpr !== 1) ctx.scale(dpr, dpr);
4593
+ if (backgroundColor) {
4594
+ ctx.save();
4595
+ ctx.fillStyle = backgroundColor;
4596
+ ctx.fillRect(0, 0, outW, outH);
4597
+ ctx.restore();
4598
+ }
4363
4599
  ctx.drawImage(img, 0, 0, outW, outH);
4364
4600
  return canvas;
4365
4601
  });
@@ -4369,12 +4605,11 @@ ${imported}
4369
4605
  function rasterize(url, options) {
4370
4606
  return __async(this, null, function* () {
4371
4607
  const canvas = yield toCanvas(url, options);
4372
- const finalCanvas = options.backgroundColor ? createBackground(canvas, options.backgroundColor) : canvas;
4373
4608
  const img = new Image();
4374
- img.src = finalCanvas.toDataURL(`image/${options.format}`, options.quality);
4609
+ img.src = canvas.toDataURL(`image/${options.format}`, options.quality);
4375
4610
  yield img.decode();
4376
- img.style.width = `${finalCanvas.width / options.dpr}px`;
4377
- img.style.height = `${finalCanvas.height / options.dpr}px`;
4611
+ img.style.width = `${canvas.width / options.dpr}px`;
4612
+ img.style.height = `${canvas.height / options.dpr}px`;
4378
4613
  return img;
4379
4614
  });
4380
4615
  }
@@ -4438,13 +4673,8 @@ ${imported}
4438
4673
  return new Blob([svgText], { type: "image/svg+xml" });
4439
4674
  }
4440
4675
  const canvas = yield toCanvas(url, options);
4441
- const finalCanvas = options.backgroundColor ? createBackground(canvas, options.backgroundColor) : canvas;
4442
4676
  return new Promise(
4443
- (resolve) => finalCanvas.toBlob(
4444
- (blob) => resolve(blob),
4445
- `image/${type}`,
4446
- options.quality
4447
- )
4677
+ (resolve) => canvas.toBlob((blob) => resolve(blob), `image/${type}`, options.quality)
4448
4678
  );
4449
4679
  });
4450
4680
  }
@@ -4464,9 +4694,8 @@ ${imported}
4464
4694
  return;
4465
4695
  }
4466
4696
  const canvas = yield toCanvas(url, options);
4467
- const finalCanvas = options.backgroundColor ? createBackground(canvas, options.backgroundColor) : canvas;
4468
4697
  const a = document.createElement("a");
4469
- a.href = finalCanvas.toDataURL(`image/${options.format}`, options.quality);
4698
+ a.href = canvas.toDataURL(`image/${options.format}`, options.quality);
4470
4699
  a.download = options.filename;
4471
4700
  a.click();
4472
4701
  });