@zumer/snapdom 1.9.12-dev.3 → 1.9.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/snapdom.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
 
2
2
  /*
3
3
  * snapdom
4
- * v.1.9.12-dev.3
4
+ * v.1.9.12-dev.4
5
5
  * Author Juan Martin Muda
6
6
  * License MIT
7
7
  **/
@@ -617,8 +617,9 @@ function idle(fn, { fast = false } = {}) {
617
617
  function isSafari() {
618
618
  const ua = typeof navigator !== "undefined" && navigator.userAgent ? navigator.userAgent : "";
619
619
  const isSafariUA = /^((?!chrome|android).)*safari/i.test(ua);
620
+ const isUIWebView = /AppleWebKit/i.test(ua) && /Mobile/i.test(ua) && !/Safari/i.test(ua);
620
621
  const isWeChatUA = /(MicroMessenger|wxwork|WeCom|WindowsWechat|MacWechat)/i.test(ua);
621
- return isSafariUA || isWeChatUA;
622
+ return isSafariUA || isUIWebView || isWeChatUA;
622
623
  }
623
624
 
624
625
  // src/modules/styles.js
@@ -806,6 +807,64 @@ function stripHeightForWrappers(el, cs, snap) {
806
807
  delete snap["block-size"];
807
808
  }
808
809
 
810
+ // src/modules/CSSVar.js
811
+ function resolveCSSVars(sourceEl, cloneEl) {
812
+ if (!(sourceEl instanceof Element) || !(cloneEl instanceof Element)) return;
813
+ const styleAttr = sourceEl.getAttribute?.("style");
814
+ let hasVar = !!(styleAttr && styleAttr.includes("var("));
815
+ if (!hasVar && sourceEl.attributes?.length) {
816
+ const attrs = sourceEl.attributes;
817
+ for (let i = 0; i < attrs.length; i++) {
818
+ const a = attrs[i];
819
+ if (a && typeof a.value === "string" && a.value.includes("var(")) {
820
+ hasVar = true;
821
+ break;
822
+ }
823
+ }
824
+ }
825
+ if (!hasVar) return;
826
+ let cs;
827
+ try {
828
+ cs = getComputedStyle(sourceEl);
829
+ } catch {
830
+ return;
831
+ }
832
+ const author = sourceEl.style;
833
+ if (author && author.length) {
834
+ for (let i = 0; i < author.length; i++) {
835
+ const prop = author[i];
836
+ const val = author.getPropertyValue(prop);
837
+ if (!val || !val.includes("var(")) continue;
838
+ const resolved = cs.getPropertyValue(prop);
839
+ if (resolved) {
840
+ try {
841
+ cloneEl.style.setProperty(prop, resolved.trim(), author.getPropertyPriority(prop));
842
+ } catch {
843
+ }
844
+ }
845
+ }
846
+ }
847
+ if (sourceEl.attributes?.length) {
848
+ const attrs = sourceEl.attributes;
849
+ for (let i = 0; i < attrs.length; i++) {
850
+ const a = attrs[i];
851
+ if (!a || typeof a.value !== "string" || !a.value.includes("var(")) continue;
852
+ const propName = a.name;
853
+ let resolved = "";
854
+ try {
855
+ resolved = cs.getPropertyValue(propName);
856
+ } catch {
857
+ }
858
+ if (resolved) {
859
+ try {
860
+ cloneEl.style.setProperty(propName, resolved.trim());
861
+ } catch {
862
+ }
863
+ }
864
+ }
865
+ }
866
+ }
867
+
809
868
  // src/core/clone.js
810
869
  function idleCallback(childList, callback, fast) {
811
870
  return Promise.all(childList.map((child) => {
@@ -827,7 +886,7 @@ function idleCallback(childList, callback, fast) {
827
886
  function addNotSlottedRightmost(sel) {
828
887
  sel = sel.trim();
829
888
  if (!sel) return sel;
830
- if (/\:not\(\s*\[data-sd-slotted\]\s*\)\s*$/.test(sel)) return sel;
889
+ if (/:not\(\s*\[data-sd-slotted\]\s*\)\s*$/.test(sel)) return sel;
831
890
  return `${sel}:not([data-sd-slotted])`;
832
891
  }
833
892
  function wrapWithScope(selectorList, scopeSelector, excludeSlotted = true) {
@@ -1116,7 +1175,7 @@ async function deepClone(node, sessionCache, options) {
1116
1175
  inlineAllStyles(node, clone2, sessionCache, options);
1117
1176
  const placeholder = document.createElement("div");
1118
1177
  placeholder.textContent = node.getAttribute("data-placeholder-text") || "";
1119
- placeholder.style.cssText = `color:#666;font-size:12px;text-align:center;line-height:1.4;padding:0.5em;box-sizing:border-box;`;
1178
+ placeholder.style.cssText = "color:#666;font-size:12px;text-align:center;line-height:1.4;padding:0.5em;box-sizing:border-box;";
1120
1179
  clone2.appendChild(placeholder);
1121
1180
  return clone2;
1122
1181
  }
@@ -1133,6 +1192,7 @@ async function deepClone(node, sessionCache, options) {
1133
1192
  let clone;
1134
1193
  try {
1135
1194
  clone = node.cloneNode(false);
1195
+ resolveCSSVars(node, clone);
1136
1196
  sessionCache.nodeMap.set(clone, node);
1137
1197
  if (node.tagName === "IMG") {
1138
1198
  freezeImgSrcset(node, clone);
@@ -1188,7 +1248,7 @@ async function deepClone(node, sessionCache, options) {
1188
1248
  } else {
1189
1249
  deepClone(child, sessionCache, options).then((clonedChild) => {
1190
1250
  resolve(clonedChild || null);
1191
- }).catch((e) => {
1251
+ }).catch(() => {
1192
1252
  resolve(null);
1193
1253
  });
1194
1254
  }
@@ -1229,7 +1289,7 @@ async function deepClone(node, sessionCache, options) {
1229
1289
  markSlottedSubtree(clonedChild);
1230
1290
  }
1231
1291
  resolve(clonedChild || null);
1232
- }).catch((e) => {
1292
+ }).catch(() => {
1233
1293
  resolve(null);
1234
1294
  });
1235
1295
  };
@@ -1244,7 +1304,7 @@ async function deepClone(node, sessionCache, options) {
1244
1304
  if (clonedAssignedNodes.has(child)) return resolve(null);
1245
1305
  deepClone(child, sessionCache, options).then((clonedChild) => {
1246
1306
  resolve(clonedChild || null);
1247
- }).catch((e) => {
1307
+ }).catch(() => {
1248
1308
  resolve(null);
1249
1309
  });
1250
1310
  }
@@ -1307,7 +1367,10 @@ function isIconFont2(input) {
1307
1367
  async function iconToImage(unicodeChar, fontFamily, fontWeight, fontSize = 32, color = "#000") {
1308
1368
  fontFamily = fontFamily.replace(/^['"]+|['"]+$/g, "");
1309
1369
  const dpr = window.devicePixelRatio || 1;
1310
- await document.fonts.ready;
1370
+ try {
1371
+ await document.fonts.ready;
1372
+ } catch {
1373
+ }
1311
1374
  const span = document.createElement("span");
1312
1375
  span.textContent = unicodeChar;
1313
1376
  span.style.position = "absolute";
@@ -1325,8 +1388,8 @@ async function iconToImage(unicodeChar, fontFamily, fontWeight, fontSize = 32, c
1325
1388
  const height = Math.ceil(rect.height);
1326
1389
  document.body.removeChild(span);
1327
1390
  const canvas = document.createElement("canvas");
1328
- canvas.width = width * dpr;
1329
- canvas.height = height * dpr;
1391
+ canvas.width = Math.max(1, width * dpr);
1392
+ canvas.height = Math.max(1, height * dpr);
1330
1393
  const ctx = canvas.getContext("2d");
1331
1394
  ctx.scale(dpr, dpr);
1332
1395
  ctx.font = fontWeight ? `${fontWeight} ${fontSize}px "${fontFamily}"` : `${fontSize}px "${fontFamily}"`;
@@ -1444,6 +1507,303 @@ function familiesFromRequired(required) {
1444
1507
  }
1445
1508
  return out;
1446
1509
  }
1510
+ function rewriteRelativeUrls(cssText, baseHref) {
1511
+ if (!cssText) return cssText;
1512
+ return cssText.replace(
1513
+ /url\(\s*(['"]?)([^)'"]+)\1\s*\)/g,
1514
+ (m, q, u) => {
1515
+ const src = (u || "").trim();
1516
+ if (!src || /^data:|^blob:|^https?:|^file:|^about:/i.test(src)) return m;
1517
+ let abs = src;
1518
+ try {
1519
+ abs = new URL(src, baseHref || location.href).href;
1520
+ } catch {
1521
+ }
1522
+ return `url("${abs}")`;
1523
+ }
1524
+ );
1525
+ }
1526
+ var IMPORT_ANY_RE = /@import\s+(?:url\(\s*(['"]?)([^)"']+)\1\s*\)|(['"])([^"']+)\3)([^;]*);/g;
1527
+ var MAX_IMPORT_DEPTH = 4;
1528
+ async function inlineImportsAndRewrite(cssText, ownerHref, useProxy) {
1529
+ if (!cssText) return cssText;
1530
+ const visited = /* @__PURE__ */ new Set();
1531
+ function normalizeUrl(u, base) {
1532
+ try {
1533
+ return new URL(u, base || location.href).href;
1534
+ } catch {
1535
+ return u;
1536
+ }
1537
+ }
1538
+ async function resolveOnce(text, baseHref, depth = 0) {
1539
+ if (depth > MAX_IMPORT_DEPTH) {
1540
+ console.warn(`[snapDOM] @import depth exceeded (${MAX_IMPORT_DEPTH}) at ${baseHref}`);
1541
+ return text;
1542
+ }
1543
+ let out = "";
1544
+ let last = 0;
1545
+ let m;
1546
+ while (m = IMPORT_ANY_RE.exec(text)) {
1547
+ out += text.slice(last, m.index);
1548
+ last = IMPORT_ANY_RE.lastIndex;
1549
+ const rawUrl = (m[2] || m[4] || "").trim();
1550
+ const absUrl = normalizeUrl(rawUrl, baseHref);
1551
+ if (visited.has(absUrl)) {
1552
+ console.warn("[snapDOM] Skipping circular import");
1553
+ continue;
1554
+ }
1555
+ visited.add(absUrl);
1556
+ let imported = "";
1557
+ try {
1558
+ const r = await snapFetch(absUrl, { as: "text", useProxy, silent: true });
1559
+ if (r.ok && typeof r.data === "string") imported = r.data;
1560
+ } catch {
1561
+ }
1562
+ if (imported) {
1563
+ imported = rewriteRelativeUrls(imported, absUrl);
1564
+ imported = await resolveOnce(imported, absUrl, depth + 1);
1565
+ out += `
1566
+ /* inlined: ${absUrl} */
1567
+ ${imported}
1568
+ `;
1569
+ } else {
1570
+ out += m[0];
1571
+ }
1572
+ }
1573
+ out += text.slice(last);
1574
+ return out;
1575
+ }
1576
+ let rewritten = rewriteRelativeUrls(cssText, ownerHref || location.href);
1577
+ rewritten = await resolveOnce(rewritten, ownerHref || location.href, 0);
1578
+ return rewritten;
1579
+ }
1580
+ var URL_RE = /url\((["']?)([^"')]+)\1\)/g;
1581
+ var FACE_RE = /@font-face[^{}]*\{[^}]*\}/g;
1582
+ function parseUnicodeRange(ur) {
1583
+ if (!ur) return [];
1584
+ const ranges = [];
1585
+ const parts = ur.split(",").map((s) => s.trim()).filter(Boolean);
1586
+ for (const p of parts) {
1587
+ const m = p.match(/^U\+([0-9A-Fa-f?]+)(?:-([0-9A-Fa-f?]+))?$/);
1588
+ if (!m) continue;
1589
+ const a = m[1], b = m[2];
1590
+ const expand = (hex) => {
1591
+ if (!hex.includes("?")) return parseInt(hex, 16);
1592
+ const min = parseInt(hex.replace(/\?/g, "0"), 16);
1593
+ const max = parseInt(hex.replace(/\?/g, "F"), 16);
1594
+ return [min, max];
1595
+ };
1596
+ if (b) {
1597
+ const A = expand(a), B = expand(b);
1598
+ const min = Array.isArray(A) ? A[0] : A;
1599
+ const max = Array.isArray(B) ? B[1] : B;
1600
+ ranges.push([Math.min(min, max), Math.max(min, max)]);
1601
+ } else {
1602
+ const X = expand(a);
1603
+ if (Array.isArray(X)) ranges.push([X[0], X[1]]);
1604
+ else ranges.push([X, X]);
1605
+ }
1606
+ }
1607
+ return ranges;
1608
+ }
1609
+ function unicodeIntersects(used, ranges) {
1610
+ if (!ranges.length) return true;
1611
+ if (!used || used.size === 0) return true;
1612
+ for (const cp of used) {
1613
+ for (const [a, b] of ranges) if (cp >= a && cp <= b) return true;
1614
+ }
1615
+ return false;
1616
+ }
1617
+ function extractSrcUrls(srcValue, baseHref) {
1618
+ const urls = [];
1619
+ if (!srcValue) return urls;
1620
+ for (const m of srcValue.matchAll(URL_RE)) {
1621
+ let u = (m[2] || "").trim();
1622
+ if (!u || u.startsWith("data:")) continue;
1623
+ if (!/^https?:/i.test(u)) {
1624
+ try {
1625
+ u = new URL(u, baseHref || location.href).href;
1626
+ } catch {
1627
+ }
1628
+ }
1629
+ urls.push(u);
1630
+ }
1631
+ return urls;
1632
+ }
1633
+ async function inlineUrlsInCssBlock(cssBlock, baseHref, useProxy = "") {
1634
+ let out = cssBlock;
1635
+ for (const m of cssBlock.matchAll(URL_RE)) {
1636
+ const raw = extractURL(m[0]);
1637
+ if (!raw) continue;
1638
+ let abs = raw;
1639
+ if (!abs.startsWith("http") && !abs.startsWith("data:")) {
1640
+ try {
1641
+ abs = new URL(abs, baseHref || location.href).href;
1642
+ } catch {
1643
+ }
1644
+ }
1645
+ if (isIconFont2(abs)) continue;
1646
+ if (cache.resource?.has(abs)) {
1647
+ cache.font?.add(abs);
1648
+ out = out.replace(m[0], `url(${cache.resource.get(abs)})`);
1649
+ continue;
1650
+ }
1651
+ if (cache.font?.has(abs)) continue;
1652
+ try {
1653
+ const r = await snapFetch(abs, { as: "dataURL", useProxy, silent: true });
1654
+ if (r.ok && typeof r.data === "string") {
1655
+ const b64 = r.data;
1656
+ cache.resource?.set(abs, b64);
1657
+ cache.font?.add(abs);
1658
+ out = out.replace(m[0], `url(${b64})`);
1659
+ }
1660
+ } catch {
1661
+ console.warn("[snapDOM] Failed to fetch font resource:", abs);
1662
+ }
1663
+ }
1664
+ return out;
1665
+ }
1666
+ function subsetFromRanges(ranges) {
1667
+ if (!ranges.length) return null;
1668
+ const hit = (a, b) => ranges.some(([x, y]) => !(y < a || x > b));
1669
+ const latin = hit(0, 255) || hit(305, 305);
1670
+ const latinExt = hit(256, 591) || hit(7680, 7935);
1671
+ const greek = hit(880, 1023);
1672
+ const cyr = hit(1024, 1279);
1673
+ const viet = hit(7840, 7929) || hit(258, 259) || hit(416, 417) || hit(431, 432);
1674
+ if (viet) return "vietnamese";
1675
+ if (cyr) return "cyrillic";
1676
+ if (greek) return "greek";
1677
+ if (latinExt) return "latin-ext";
1678
+ if (latin) return "latin";
1679
+ return null;
1680
+ }
1681
+ function buildSimpleExcluder(ex = {}) {
1682
+ const famSet = new Set((ex.families || []).map((s) => String(s).toLowerCase()));
1683
+ const domSet = new Set((ex.domains || []).map((s) => String(s).toLowerCase()));
1684
+ const subSet = new Set((ex.subsets || []).map((s) => String(s).toLowerCase()));
1685
+ return (meta, parsedRanges) => {
1686
+ if (famSet.size && famSet.has(meta.family.toLowerCase())) return true;
1687
+ if (domSet.size) {
1688
+ for (const u of meta.srcUrls) {
1689
+ try {
1690
+ if (domSet.has(new URL(u).host.toLowerCase())) return true;
1691
+ } catch {
1692
+ }
1693
+ }
1694
+ }
1695
+ if (subSet.size) {
1696
+ const label = subsetFromRanges(parsedRanges);
1697
+ if (label && subSet.has(label)) return true;
1698
+ }
1699
+ return false;
1700
+ };
1701
+ }
1702
+ function dedupeFontFaces(cssText) {
1703
+ if (!cssText) return cssText;
1704
+ const FACE_RE_G = /@font-face[^{}]*\{[^}]*\}/gi;
1705
+ const seen = /* @__PURE__ */ new Set();
1706
+ const out = [];
1707
+ for (const block of cssText.match(FACE_RE_G) || []) {
1708
+ const familyRaw = block.match(/font-family:\s*([^;]+);/i)?.[1] || "";
1709
+ const family = pickPrimaryFamily(familyRaw);
1710
+ const weightSpec = (block.match(/font-weight:\s*([^;]+);/i)?.[1] || "400").trim();
1711
+ const styleSpec = (block.match(/font-style:\s*([^;]+);/i)?.[1] || "normal").trim();
1712
+ const stretchSpec = (block.match(/font-stretch:\s*([^;]+);/i)?.[1] || "100%").trim();
1713
+ const urange = (block.match(/unicode-range:\s*([^;]+);/i)?.[1] || "").trim();
1714
+ const srcRaw = (block.match(/src\s*:\s*([^;]+);/i)?.[1] || "").trim();
1715
+ const urls = extractSrcUrls(srcRaw, location.href);
1716
+ const srcPart = urls.length ? urls.map((u) => String(u).toLowerCase()).sort().join("|") : srcRaw.toLowerCase();
1717
+ const key = [
1718
+ String(family || "").toLowerCase(),
1719
+ weightSpec,
1720
+ styleSpec,
1721
+ stretchSpec,
1722
+ urange.toLowerCase(),
1723
+ srcPart
1724
+ ].join("|");
1725
+ if (!seen.has(key)) {
1726
+ seen.add(key);
1727
+ out.push(block);
1728
+ }
1729
+ }
1730
+ if (out.length === 0) return cssText;
1731
+ let i = 0;
1732
+ return cssText.replace(FACE_RE_G, () => out[i++] || "");
1733
+ }
1734
+ function buildFontsCacheKey(required, exclude, localFonts, useProxy) {
1735
+ const req = Array.from(required || []).sort().join("|");
1736
+ const ex = exclude ? JSON.stringify({
1737
+ families: (exclude.families || []).map((s) => String(s).toLowerCase()).sort(),
1738
+ domains: (exclude.domains || []).map((s) => String(s).toLowerCase()).sort(),
1739
+ subsets: (exclude.subsets || []).map((s) => String(s).toLowerCase()).sort()
1740
+ }) : "";
1741
+ const lf = (localFonts || []).map((f) => `${(f.family || "").toLowerCase()}::${f.weight || "normal"}::${f.style || "normal"}::${f.src || ""}`).sort().join("|");
1742
+ const px = useProxy || "";
1743
+ return `fonts-embed-css::req=${req}::ex=${ex}::lf=${lf}::px=${px}`;
1744
+ }
1745
+ async function collectFacesFromSheet(sheet, baseHref, emitFace, ctx) {
1746
+ let rules;
1747
+ try {
1748
+ rules = sheet.cssRules || [];
1749
+ } catch {
1750
+ return;
1751
+ }
1752
+ const normalizeUrl = (u, base) => {
1753
+ try {
1754
+ return new URL(u, base || location.href).href;
1755
+ } catch {
1756
+ return u;
1757
+ }
1758
+ };
1759
+ for (const rule of rules) {
1760
+ if (rule.type === CSSRule.IMPORT_RULE && rule.styleSheet) {
1761
+ const childHref = rule.href ? normalizeUrl(rule.href, baseHref) : baseHref;
1762
+ if (ctx.depth >= MAX_IMPORT_DEPTH) {
1763
+ console.warn(`[snapDOM] CSSOM import depth exceeded (${MAX_IMPORT_DEPTH}) at ${childHref}`);
1764
+ continue;
1765
+ }
1766
+ if (childHref && ctx.visitedSheets.has(childHref)) {
1767
+ console.warn(`[snapDOM] Skipping circular CSSOM import: ${childHref}`);
1768
+ continue;
1769
+ }
1770
+ if (childHref) ctx.visitedSheets.add(childHref);
1771
+ const nextCtx = { ...ctx, depth: (ctx.depth || 0) + 1 };
1772
+ await collectFacesFromSheet(rule.styleSheet, childHref, emitFace, nextCtx);
1773
+ continue;
1774
+ }
1775
+ if (rule.type === CSSRule.FONT_FACE_RULE) {
1776
+ const famRaw = (rule.style.getPropertyValue("font-family") || "").trim();
1777
+ const family = pickPrimaryFamily(famRaw);
1778
+ if (!family || isIconFont2(family)) continue;
1779
+ const weightSpec = (rule.style.getPropertyValue("font-weight") || "400").trim();
1780
+ const styleSpec = (rule.style.getPropertyValue("font-style") || "normal").trim();
1781
+ const stretchSpec = (rule.style.getPropertyValue("font-stretch") || "100%").trim();
1782
+ const srcRaw = (rule.style.getPropertyValue("src") || "").trim();
1783
+ const urange = (rule.style.getPropertyValue("unicode-range") || "").trim();
1784
+ if (!ctx.faceMatchesRequired(family, styleSpec, weightSpec, stretchSpec)) continue;
1785
+ const ranges = parseUnicodeRange(urange);
1786
+ if (!unicodeIntersects(ctx.usedCodepoints, ranges)) continue;
1787
+ const meta = {
1788
+ family,
1789
+ weightSpec,
1790
+ styleSpec,
1791
+ stretchSpec,
1792
+ unicodeRange: urange,
1793
+ srcRaw,
1794
+ srcUrls: extractSrcUrls(srcRaw, baseHref || location.href),
1795
+ href: baseHref || location.href
1796
+ };
1797
+ if (ctx.simpleExcluder && ctx.simpleExcluder(meta, ranges)) continue;
1798
+ if (/url\(/i.test(srcRaw)) {
1799
+ const inlinedSrc = await inlineUrlsInCssBlock(srcRaw, baseHref || location.href, ctx.useProxy);
1800
+ await emitFace(`@font-face{font-family:${family};src:${inlinedSrc};font-style:${styleSpec};font-weight:${weightSpec};font-stretch:${stretchSpec};${urange ? `unicode-range:${urange};` : ""}}`);
1801
+ } else {
1802
+ await emitFace(`@font-face{font-family:${family};src:${srcRaw};font-style:${styleSpec};font-weight:${weightSpec};font-stretch:${stretchSpec};${urange ? `unicode-range:${urange};` : ""}}`);
1803
+ }
1804
+ }
1805
+ }
1806
+ }
1447
1807
  async function embedCustomFonts({
1448
1808
  required,
1449
1809
  usedCodepoints,
@@ -1461,9 +1821,6 @@ async function embedCustomFonts({
1461
1821
  arr.push({ w: parseInt(w, 10), s, st: parseInt(st, 10) });
1462
1822
  requiredIndex.set(fam, arr);
1463
1823
  }
1464
- const IMPORT_RE = /@import\s+url\(["']?([^"')]+)["']?\)/g;
1465
- const URL_RE = /url\((["']?)([^"')]+)\1\)/g;
1466
- const FACE_RE = /@font-face[^{}]*\{[^}]*\}/g;
1467
1824
  function faceMatchesRequired(fam, styleSpec, weightSpec, stretchSpec) {
1468
1825
  if (!requiredIndex.has(fam)) return false;
1469
1826
  const need = requiredIndex.get(fam);
@@ -1489,188 +1846,23 @@ async function embedCustomFonts({
1489
1846
  const sOk = styleOK(normStyle(r.s));
1490
1847
  const tOk = r.st >= ts.min && r.st <= ts.max;
1491
1848
  const nearWeight = Math.abs(faceSingleW - r.w) <= 300;
1492
- if (nearWeight && sOk && tOk) {
1493
- return true;
1494
- }
1495
- }
1496
- }
1497
- return false;
1498
- }
1499
- function parseUnicodeRange(ur) {
1500
- if (!ur) return [];
1501
- const ranges = [];
1502
- const parts = ur.split(",").map((s) => s.trim()).filter(Boolean);
1503
- for (const p of parts) {
1504
- const m = p.match(/^U\+([0-9A-Fa-f?]+)(?:-([0-9A-Fa-f?]+))?$/);
1505
- if (!m) continue;
1506
- const a = m[1], b = m[2];
1507
- const expand = (hex) => {
1508
- if (!hex.includes("?")) return parseInt(hex, 16);
1509
- const min = parseInt(hex.replace(/\?/g, "0"), 16);
1510
- const max = parseInt(hex.replace(/\?/g, "F"), 16);
1511
- return [min, max];
1512
- };
1513
- if (b) {
1514
- const A = expand(a), B = expand(b);
1515
- const min = Array.isArray(A) ? A[0] : A;
1516
- const max = Array.isArray(B) ? B[1] : B;
1517
- ranges.push([Math.min(min, max), Math.max(min, max)]);
1518
- } else {
1519
- const X = expand(a);
1520
- if (Array.isArray(X)) ranges.push([X[0], X[1]]);
1521
- else ranges.push([X, X]);
1849
+ if (nearWeight && sOk && tOk) return true;
1522
1850
  }
1523
1851
  }
1524
- return ranges;
1525
- }
1526
- function unicodeIntersects(used, ranges) {
1527
- if (!ranges.length) return true;
1528
- if (!used || used.size === 0) return true;
1529
- for (const cp of used) {
1530
- for (const [a, b] of ranges) if (cp >= a && cp <= b) return true;
1531
- }
1532
1852
  return false;
1533
1853
  }
1534
- function extractSrcUrls(srcValue, baseHref) {
1535
- const urls = [];
1536
- if (!srcValue) return urls;
1537
- for (const m of srcValue.matchAll(URL_RE)) {
1538
- let u = (m[2] || "").trim();
1539
- if (!u || u.startsWith("data:")) continue;
1540
- if (!/^https?:/i.test(u)) {
1541
- try {
1542
- u = new URL(u, baseHref || location.href).href;
1543
- } catch {
1544
- }
1545
- }
1546
- urls.push(u);
1547
- }
1548
- return urls;
1549
- }
1550
- async function inlineUrlsInCssBlock(cssBlock, baseHref) {
1551
- let out = cssBlock;
1552
- for (const m of cssBlock.matchAll(URL_RE)) {
1553
- const raw = extractURL(m[0]);
1554
- if (!raw) continue;
1555
- let abs = raw;
1556
- if (!abs.startsWith("http") && !abs.startsWith("data:")) {
1557
- try {
1558
- abs = new URL(abs, baseHref || location.href).href;
1559
- } catch {
1560
- }
1561
- }
1562
- if (isIconFont2(abs)) continue;
1563
- if (cache.resource?.has(abs)) {
1564
- cache.font?.add(abs);
1565
- out = out.replace(m[0], `url(${cache.resource.get(abs)})`);
1566
- continue;
1567
- }
1568
- if (cache.font?.has(abs)) continue;
1569
- try {
1570
- const r = await snapFetch(abs, { as: "dataURL", useProxy, silent: true });
1571
- if (r.ok && typeof r.data === "string") {
1572
- const b64 = r.data;
1573
- cache.resource?.set(abs, b64);
1574
- cache.font?.add(abs);
1575
- out = out.replace(m[0], `url(${b64})`);
1576
- }
1577
- } catch {
1578
- console.warn("[snapDOM] Failed to fetch font resource:", abs);
1579
- }
1580
- }
1581
- return out;
1582
- }
1583
- function subsetFromRanges(ranges) {
1584
- if (!ranges.length) return null;
1585
- const hit = (a, b) => ranges.some(([x, y]) => !(y < a || x > b));
1586
- const latin = hit(0, 255) || hit(305, 305);
1587
- const latinExt = hit(256, 591) || hit(7680, 7935);
1588
- const greek = hit(880, 1023);
1589
- const cyr = hit(1024, 1279);
1590
- const viet = hit(7840, 7929) || hit(258, 259) || hit(416, 417) || hit(431, 432);
1591
- if (viet) return "vietnamese";
1592
- if (cyr) return "cyrillic";
1593
- if (greek) return "greek";
1594
- if (latinExt) return "latin-ext";
1595
- if (latin) return "latin";
1596
- return null;
1597
- }
1598
- function buildSimpleExcluder(ex = {}) {
1599
- const famSet = new Set((ex.families || []).map((s) => String(s).toLowerCase()));
1600
- const domSet = new Set((ex.domains || []).map((s) => String(s).toLowerCase()));
1601
- const subSet = new Set((ex.subsets || []).map((s) => String(s).toLowerCase()));
1602
- return (meta, parsedRanges) => {
1603
- if (famSet.size && famSet.has(meta.family.toLowerCase())) return true;
1604
- if (domSet.size) {
1605
- for (const u of meta.srcUrls) {
1606
- try {
1607
- if (domSet.has(new URL(u).host.toLowerCase())) return true;
1608
- } catch {
1609
- }
1610
- }
1611
- }
1612
- if (subSet.size) {
1613
- const label = subsetFromRanges(parsedRanges);
1614
- if (label && subSet.has(label)) return true;
1615
- }
1616
- return false;
1617
- };
1618
- }
1619
1854
  const simpleExcluder = buildSimpleExcluder(exclude);
1620
- function dedupeFontFaces(cssText) {
1621
- if (!cssText) return cssText;
1622
- const FACE_RE2 = /@font-face[^{}]*\{[^}]*\}/gi;
1623
- const seen = /* @__PURE__ */ new Set();
1624
- const out = [];
1625
- for (const block of cssText.match(FACE_RE2) || []) {
1626
- const familyRaw = block.match(/font-family:\s*([^;]+);/i)?.[1] || "";
1627
- const family = pickPrimaryFamily(familyRaw);
1628
- const weightSpec = (block.match(/font-weight:\s*([^;]+);/i)?.[1] || "400").trim();
1629
- const styleSpec = (block.match(/font-style:\s*([^;]+);/i)?.[1] || "normal").trim();
1630
- const stretchSpec = (block.match(/font-stretch:\s*([^;]+);/i)?.[1] || "100%").trim();
1631
- const urange = (block.match(/unicode-range:\s*([^;]+);/i)?.[1] || "").trim();
1632
- const srcRaw = (block.match(/src\s*:\s*([^;]+);/i)?.[1] || "").trim();
1633
- const urls = extractSrcUrls(srcRaw, location.href);
1634
- const srcPart = urls.length ? urls.map((u) => String(u).toLowerCase()).sort().join("|") : srcRaw.toLowerCase();
1635
- const key = [
1636
- String(family || "").toLowerCase(),
1637
- weightSpec,
1638
- styleSpec,
1639
- stretchSpec,
1640
- urange.toLowerCase(),
1641
- srcPart
1642
- ].join("|");
1643
- if (!seen.has(key)) {
1644
- seen.add(key);
1645
- out.push(block);
1646
- }
1647
- }
1648
- if (out.length === 0) return cssText;
1649
- let i = 0;
1650
- return cssText.replace(FACE_RE2, () => out[i++] || "");
1651
- }
1652
- function buildFontsCacheKey() {
1653
- const req = Array.from(required || []).sort().join("|");
1654
- const ex = exclude ? JSON.stringify({
1655
- families: (exclude.families || []).map((s) => String(s).toLowerCase()).sort(),
1656
- domains: (exclude.domains || []).map((s) => String(s).toLowerCase()).sort(),
1657
- subsets: (exclude.subsets || []).map((s) => String(s).toLowerCase()).sort()
1658
- }) : "";
1659
- const lf = (localFonts || []).map((f) => `${(f.family || "").toLowerCase()}::${f.weight || "normal"}::${f.style || "normal"}::${f.src || ""}`).sort().join("|");
1660
- const px = useProxy || "";
1661
- return `fonts-embed-css::req=${req}::ex=${ex}::lf=${lf}::px=${px}`;
1662
- }
1663
- const cacheKey = buildFontsCacheKey();
1855
+ const cacheKey = buildFontsCacheKey(required, exclude, localFonts, useProxy);
1664
1856
  if (cache.resource?.has(cacheKey)) {
1665
- const css = cache.resource.get(cacheKey);
1666
- return css;
1857
+ return cache.resource.get(cacheKey);
1667
1858
  }
1668
1859
  const requiredFamilies = familiesFromRequired(required);
1669
1860
  const importUrls = [];
1861
+ const IMPORT_ANY_RE_LOCAL = IMPORT_ANY_RE;
1670
1862
  for (const styleTag of document.querySelectorAll("style")) {
1671
1863
  const cssText = styleTag.textContent || "";
1672
- for (const m of cssText.matchAll(IMPORT_RE)) {
1673
- const u = m[1];
1864
+ for (const m of cssText.matchAll(IMPORT_ANY_RE_LOCAL)) {
1865
+ const u = (m[2] || m[4] || "").trim();
1674
1866
  if (!u || isIconFont2(u)) continue;
1675
1867
  const hasLink = !!document.querySelector(`link[rel="stylesheet"][href="${u}"]`);
1676
1868
  if (!hasLink) importUrls.push(u);
@@ -1717,65 +1909,53 @@ async function embedCustomFonts({
1717
1909
  cssText = res.data;
1718
1910
  if (isIconFont2(link.href)) continue;
1719
1911
  }
1912
+ cssText = await inlineImportsAndRewrite(cssText, link.href, useProxy);
1720
1913
  let facesOut = "";
1721
1914
  for (const face of cssText.match(FACE_RE) || []) {
1722
1915
  const famRaw = (face.match(/font-family:\s*([^;]+);/i)?.[1] || "").trim();
1723
1916
  const family = pickPrimaryFamily(famRaw);
1724
- if (!family || isIconFont2(family)) {
1725
- continue;
1726
- }
1917
+ if (!family || isIconFont2(family)) continue;
1727
1918
  const weightSpec = (face.match(/font-weight:\s*([^;]+);/i)?.[1] || "400").trim();
1728
1919
  const styleSpec = (face.match(/font-style:\s*([^;]+);/i)?.[1] || "normal").trim();
1729
1920
  const stretchSpec = (face.match(/font-stretch:\s*([^;]+);/i)?.[1] || "100%").trim();
1730
1921
  const urange = (face.match(/unicode-range:\s*([^;]+);/i)?.[1] || "").trim();
1731
1922
  const srcRaw = (face.match(/src\s*:\s*([^;]+);/i)?.[1] || "").trim();
1732
1923
  const srcUrls = extractSrcUrls(srcRaw, link.href);
1733
- if (!faceMatchesRequired(family, styleSpec, weightSpec, stretchSpec)) {
1734
- continue;
1735
- }
1924
+ if (!faceMatchesRequired(family, styleSpec, weightSpec, stretchSpec)) continue;
1736
1925
  const ranges = parseUnicodeRange(urange);
1737
- if (!unicodeIntersects(usedCodepoints, ranges)) {
1738
- continue;
1739
- }
1926
+ if (!unicodeIntersects(usedCodepoints, ranges)) continue;
1740
1927
  const meta = { family, weightSpec, styleSpec, stretchSpec, unicodeRange: urange, srcRaw, srcUrls, href: link.href };
1741
- if (exclude && simpleExcluder(meta, ranges)) {
1742
- continue;
1743
- }
1744
- const newFace = /url\(/i.test(srcRaw) ? await inlineUrlsInCssBlock(face, link.href) : face;
1745
- facesOut += newFace + "";
1928
+ if (exclude && simpleExcluder(meta, ranges)) continue;
1929
+ const newFace = /url\(/i.test(srcRaw) ? await inlineUrlsInCssBlock(face, link.href, useProxy) : face;
1930
+ facesOut += newFace;
1746
1931
  }
1747
- if (facesOut.trim()) finalCSS += facesOut + "";
1748
- } catch (e) {
1749
- console.warn("[snapdom] Failed to process stylesheet:", link.href);
1932
+ if (facesOut.trim()) finalCSS += facesOut;
1933
+ } catch {
1934
+ console.warn("[snapDOM] Failed to process stylesheet:", link.href);
1750
1935
  }
1751
1936
  }
1937
+ const ctx = {
1938
+ requiredIndex,
1939
+ usedCodepoints,
1940
+ faceMatchesRequired,
1941
+ simpleExcluder: exclude ? buildSimpleExcluder(exclude) : null,
1942
+ useProxy,
1943
+ visitedSheets: /* @__PURE__ */ new Set(),
1944
+ depth: 0
1945
+ };
1752
1946
  for (const sheet of document.styleSheets) {
1947
+ if (sheet.href && linkNodes.some((l) => l.href === sheet.href)) continue;
1753
1948
  try {
1754
- if (sheet.href && linkNodes.some((l) => l.href === sheet.href)) continue;
1755
- const rules = sheet.cssRules || [];
1756
- for (const rule of rules) {
1757
- if (rule.type !== CSSRule.FONT_FACE_RULE) continue;
1758
- const famRaw = (rule.style.getPropertyValue("font-family") || "").trim();
1759
- const family = pickPrimaryFamily(famRaw);
1760
- if (!family || isIconFont2(family)) continue;
1761
- const weightSpec = (rule.style.getPropertyValue("font-weight") || "400").trim();
1762
- const styleSpec = (rule.style.getPropertyValue("font-style") || "normal").trim();
1763
- const stretchSpec = (rule.style.getPropertyValue("font-stretch") || "100%").trim();
1764
- const srcRaw = (rule.style.getPropertyValue("src") || "").trim();
1765
- const urange = (rule.style.getPropertyValue("unicode-range") || "").trim();
1766
- const srcUrls = extractSrcUrls(srcRaw, sheet.href || location.href);
1767
- if (!faceMatchesRequired(family, styleSpec, weightSpec, stretchSpec)) continue;
1768
- const ranges = parseUnicodeRange(urange);
1769
- if (!unicodeIntersects(usedCodepoints, ranges)) continue;
1770
- const meta = { family, weightSpec, styleSpec, stretchSpec, unicodeRange: urange, srcRaw, srcUrls, href: sheet.href || location.href };
1771
- if (exclude && simpleExcluder(meta, ranges)) continue;
1772
- if (/url\(/i.test(srcRaw)) {
1773
- const inlinedSrc = await inlineUrlsInCssBlock(srcRaw, sheet.href || location.href);
1774
- finalCSS += `@font-face{font-family:${family};src:${inlinedSrc};font-style:${styleSpec};font-weight:${weightSpec};font-stretch:${stretchSpec};${urange ? `unicode-range:${urange};` : ""}}`;
1775
- } else {
1776
- finalCSS += `@font-face{font-family:${family};src:${srcRaw};font-style:${styleSpec};font-weight:${weightSpec};font-stretch:${stretchSpec};${urange ? `unicode-range:${urange};` : ""}}`;
1777
- }
1778
- }
1949
+ const rootHref = sheet.href || location.href;
1950
+ if (rootHref) ctx.visitedSheets.add(rootHref);
1951
+ await collectFacesFromSheet(
1952
+ sheet,
1953
+ rootHref,
1954
+ async (faceCss) => {
1955
+ finalCSS += faceCss;
1956
+ },
1957
+ ctx
1958
+ );
1779
1959
  } catch {
1780
1960
  }
1781
1961
  }
@@ -1804,7 +1984,7 @@ async function embedCustomFonts({
1804
1984
  continue;
1805
1985
  }
1806
1986
  } catch {
1807
- console.warn("[snapdom] Failed to fetch dynamic font src:", f._snapdomSrc);
1987
+ console.warn("[snapDOM] Failed to fetch dynamic font src:", f._snapdomSrc);
1808
1988
  continue;
1809
1989
  }
1810
1990
  }
@@ -1839,7 +2019,7 @@ async function embedCustomFonts({
1839
2019
  continue;
1840
2020
  }
1841
2021
  } catch {
1842
- console.warn("[snapdom] Failed to fetch localFonts src:", src);
2022
+ console.warn("[snapDOM] Failed to fetch localFonts src:", src);
1843
2023
  continue;
1844
2024
  }
1845
2025
  }
@@ -1928,7 +2108,7 @@ async function ensureFontsReady(families, warmupRepetitions = 2) {
1928
2108
  if (fams.length === 0) return;
1929
2109
  const warmupOnce = () => {
1930
2110
  const container = document.createElement("div");
1931
- container.style.cssText = `position: absolute !important;left: -9999px !important;top: 0 !important;opacity: 0 !important;pointer-events: none !important;contain: layout size style;`;
2111
+ container.style.cssText = "position:absolute!important;left:-9999px!important;top:0!important;opacity:0!important;pointer-events:none!important;contain:layout size style;";
1932
2112
  for (const fam of fams) {
1933
2113
  const span = document.createElement("span");
1934
2114
  span.textContent = "AaBbGg1234\xC1\xC9\xCD\xD3\xDA\xE7\xF1\u2014\u221E";
@@ -2279,9 +2459,6 @@ async function inlinePseudoElements(source, clone, sessionCache, options) {
2279
2459
  const fontSize = parseInt(style.fontSize) || 32;
2280
2460
  const fontWeight = parseInt(style.fontWeight) || false;
2281
2461
  const color = style.color || "#000";
2282
- const display = style.display;
2283
- const width = parseFloat(style.width);
2284
- const height = parseFloat(style.height);
2285
2462
  const borderStyle = style.borderStyle;
2286
2463
  const borderWidth = parseFloat(style.borderWidth);
2287
2464
  const transform = style.transform;
@@ -2289,14 +2466,13 @@ async function inlinePseudoElements(source, clone, sessionCache, options) {
2289
2466
  const hasExplicitContent = rawContent !== "none" && cleanContent !== "";
2290
2467
  const hasBg = bg && bg !== "none";
2291
2468
  const hasBgColor = bgColor && bgColor !== "transparent" && bgColor !== "rgba(0, 0, 0, 0)";
2292
- const hasBox2 = display !== "inline" && (width > 0 || height > 0);
2293
2469
  const hasBorder = borderStyle && borderStyle !== "none" && borderWidth > 0;
2294
2470
  const hasTransform = transform && transform !== "none";
2295
- const shouldRender = hasExplicitContent || hasBg || hasBgColor || hasBox2 || hasBorder || hasTransform;
2471
+ const shouldRender = hasExplicitContent || hasBg || hasBgColor || hasBorder || hasTransform;
2296
2472
  if (!shouldRender) {
2297
2473
  if (incs && incs.length && source.parentElement) {
2298
2474
  const map = __siblingCounters.get(source.parentElement) || /* @__PURE__ */ new Map();
2299
- for (const { name, num } of incs) {
2475
+ for (const { name } of incs) {
2300
2476
  if (!name) continue;
2301
2477
  const baseWithSibs = withSiblingOverrides(source, counterCtx);
2302
2478
  const derived = deriveCounterCtxForPseudo(source, getComputedStyle(source, pseudo), baseWithSibs);
@@ -2310,6 +2486,7 @@ async function inlinePseudoElements(source, clone, sessionCache, options) {
2310
2486
  const pseudoEl = document.createElement("span");
2311
2487
  pseudoEl.dataset.snapdomPseudo = pseudo;
2312
2488
  pseudoEl.style.verticalAlign = "middle";
2489
+ pseudoEl.style.pointerEvents = "none";
2313
2490
  const snapshot = snapshotComputedStyle(style);
2314
2491
  const key = getStyleKey(snapshot, "span");
2315
2492
  sessionCache.styleMap.set(pseudoEl, key);
@@ -2336,6 +2513,10 @@ async function inlinePseudoElements(source, clone, sessionCache, options) {
2336
2513
  } else if (!isIconFont22 && hasExplicitContent) {
2337
2514
  pseudoEl.textContent = cleanContent;
2338
2515
  }
2516
+ pseudoEl.style.background = "none";
2517
+ if ("mask" in pseudoEl.style) {
2518
+ pseudoEl.style.mask = "none";
2519
+ }
2339
2520
  if (hasBg) {
2340
2521
  try {
2341
2522
  const bgSplits = splitBackgroundImage(bg);
@@ -2347,7 +2528,7 @@ async function inlinePseudoElements(source, clone, sessionCache, options) {
2347
2528
  }
2348
2529
  if (hasBgColor) pseudoEl.style.backgroundColor = bgColor;
2349
2530
  const hasContent2 = pseudoEl.childNodes.length > 0 || pseudoEl.textContent?.trim() !== "";
2350
- const hasVisibleBox = hasContent2 || hasBg || hasBgColor || hasBox2 || hasBorder || hasTransform;
2531
+ const hasVisibleBox = hasContent2 || hasBg || hasBgColor || hasBorder || hasTransform;
2351
2532
  if (incs && incs.length && source.parentElement) {
2352
2533
  const map = __siblingCounters.get(source.parentElement) || /* @__PURE__ */ new Map();
2353
2534
  const baseWithSibs = withSiblingOverrides(source, counterCtx);
@@ -2426,6 +2607,102 @@ function inlineExternalDefsAndSymbols(rootElement) {
2426
2607
  });
2427
2608
  }
2428
2609
 
2610
+ // src/modules/changeCSS.js
2611
+ function freezeSticky(originalRoot, cloneRoot) {
2612
+ if (!originalRoot || !cloneRoot) return;
2613
+ const scrollTop = originalRoot.scrollTop || 0;
2614
+ if (!scrollTop) return;
2615
+ if (getComputedStyle(cloneRoot).position === "static") {
2616
+ cloneRoot.style.position = "relative";
2617
+ }
2618
+ const rootRect = originalRoot.getBoundingClientRect();
2619
+ const viewportH = originalRoot.clientHeight;
2620
+ const PH_ATTR = "data-snap-ph";
2621
+ const walker = document.createTreeWalker(originalRoot, NodeFilter.SHOW_ELEMENT);
2622
+ while (walker.nextNode()) {
2623
+ const el = (
2624
+ /** @type {HTMLElement} */
2625
+ walker.currentNode
2626
+ );
2627
+ const cs = getComputedStyle(el);
2628
+ const pos = cs.position;
2629
+ if (pos !== "sticky" && pos !== "-webkit-sticky") continue;
2630
+ const topInit = _toPx(cs.top);
2631
+ const bottomInit = _toPx(cs.bottom);
2632
+ if (topInit == null && bottomInit == null) continue;
2633
+ const path = _pathOf(el, originalRoot);
2634
+ const cloneEl = _findByPathIgnoringPlaceholders(cloneRoot, path, PH_ATTR);
2635
+ if (!cloneEl) continue;
2636
+ const elRect = el.getBoundingClientRect();
2637
+ const widthPx = elRect.width;
2638
+ const heightPx = elRect.height;
2639
+ const leftPx = elRect.left - rootRect.left;
2640
+ if (!(widthPx > 0 && heightPx > 0)) continue;
2641
+ if (!Number.isFinite(leftPx)) continue;
2642
+ const topAbsPx = topInit != null ? topInit + scrollTop : scrollTop + (viewportH - heightPx - /** bottomInit non-null */
2643
+ bottomInit);
2644
+ if (!Number.isFinite(topAbsPx)) continue;
2645
+ const zParsed = Number.parseInt(cs.zIndex, 10);
2646
+ const hasZ = Number.isFinite(zParsed);
2647
+ const overlayZ = hasZ ? Math.max(zParsed, 1) + 1 : 2;
2648
+ const placeholderZ = hasZ ? zParsed - 1 : 0;
2649
+ const ph = cloneEl.cloneNode(false);
2650
+ ph.setAttribute(PH_ATTR, "1");
2651
+ ph.style.position = "sticky";
2652
+ ph.style.left = `${leftPx}px`;
2653
+ ph.style.top = `${topAbsPx}px`;
2654
+ ph.style.width = `${widthPx}px`;
2655
+ ph.style.height = `${heightPx}px`;
2656
+ ph.style.visibility = "hidden";
2657
+ ph.style.zIndex = String(placeholderZ);
2658
+ ph.style.overflow = "hidden";
2659
+ ph.style.background = "transparent";
2660
+ ph.style.boxShadow = "none";
2661
+ ph.style.filter = "none";
2662
+ cloneEl.parentElement?.insertBefore(ph, cloneEl);
2663
+ cloneEl.style.position = "absolute";
2664
+ cloneEl.style.left = `${leftPx}px`;
2665
+ cloneEl.style.top = `${topAbsPx}px`;
2666
+ cloneEl.style.bottom = "auto";
2667
+ cloneEl.style.zIndex = String(overlayZ);
2668
+ cloneEl.style.pointerEvents = "none";
2669
+ }
2670
+ }
2671
+ function _toPx(v) {
2672
+ if (!v || v === "auto") return null;
2673
+ const n = Number.parseFloat(v);
2674
+ return Number.isFinite(n) ? n : null;
2675
+ }
2676
+ function _pathOf(el, root) {
2677
+ const path = [];
2678
+ for (let cur = el; cur && cur !== root; ) {
2679
+ const p = cur.parentElement;
2680
+ if (!p) break;
2681
+ path.push(Array.prototype.indexOf.call(p.children, cur));
2682
+ cur = p;
2683
+ }
2684
+ return path.reverse();
2685
+ }
2686
+ function _findByPathIgnoringPlaceholders(root, path, phAttr) {
2687
+ let cur = root;
2688
+ for (let i = 0; i < path.length; i++) {
2689
+ const kids = _childrenWithoutPlaceholders(cur, phAttr);
2690
+ cur = /** @type {HTMLElement|undefined} */
2691
+ kids[path[i]];
2692
+ if (!cur) return null;
2693
+ }
2694
+ return cur instanceof HTMLElement ? cur : null;
2695
+ }
2696
+ function _childrenWithoutPlaceholders(el, phAttr) {
2697
+ const out = [];
2698
+ const ch = el.children;
2699
+ for (let i = 0; i < ch.length; i++) {
2700
+ const c = ch[i];
2701
+ if (!c.hasAttribute(phAttr)) out.push(c);
2702
+ }
2703
+ return out;
2704
+ }
2705
+
2429
2706
  // src/core/prepare.js
2430
2707
  async function prepareClone(element, options = {}) {
2431
2708
  const sessionCache = {
@@ -2500,6 +2777,8 @@ async function prepareClone(element, options = {}) {
2500
2777
  cloneNode.appendChild(inner);
2501
2778
  }
2502
2779
  }
2780
+ const contentRoot = clone instanceof HTMLElement && clone.firstElementChild instanceof HTMLElement ? clone.firstElementChild : clone;
2781
+ freezeSticky(element, contentRoot);
2503
2782
  if (element === sessionCache.nodeMap.get(clone)) {
2504
2783
  const computed = sessionCache.styleCache.get(element) || window.getComputedStyle(element);
2505
2784
  sessionCache.styleCache.set(element, computed);
@@ -2875,18 +3154,49 @@ function isPlainTextContainer(el) {
2875
3154
  }
2876
3155
 
2877
3156
  // src/core/capture.js
3157
+ function stripRootShadows(originalEl, cloneRoot) {
3158
+ if (!originalEl || !cloneRoot || !cloneRoot.style) return;
3159
+ const cs = getComputedStyle(originalEl);
3160
+ try {
3161
+ cloneRoot.style.boxShadow = "none";
3162
+ } catch {
3163
+ }
3164
+ try {
3165
+ cloneRoot.style.textShadow = "none";
3166
+ } catch {
3167
+ }
3168
+ try {
3169
+ cloneRoot.style.outline = "none";
3170
+ } catch {
3171
+ }
3172
+ const f = cs.filter || "";
3173
+ const cleaned = f.replace(/\bblur\([^()]*\)\s*/gi, "").replace(/\bdrop-shadow\([^()]*\)\s*/gi, "").trim().replace(/\s+/g, " ");
3174
+ try {
3175
+ cloneRoot.style.filter = cleaned.length ? cleaned : "none";
3176
+ } catch {
3177
+ }
3178
+ }
2878
3179
  async function captureDOM(element, options) {
2879
3180
  if (!element) throw new Error("Element cannot be null or undefined");
2880
3181
  applyCachePolicy(options.cache);
2881
3182
  const fast = options.fast;
3183
+ const straighten = !!options.straighten;
3184
+ const noShadows = !!options.noShadows;
2882
3185
  let clone, classCSS, styleCache;
2883
3186
  let fontsCSS = "";
2884
3187
  let baseCSS = "";
2885
3188
  let dataURL;
2886
3189
  let svgString;
3190
+ let rootTransform2D = null;
2887
3191
  const undoClamp = lineClamp(element);
2888
3192
  try {
2889
3193
  ({ clone, classCSS, styleCache } = await prepareClone(element, options));
3194
+ if (straighten && clone) {
3195
+ rootTransform2D = normalizeRootTransforms(element, clone);
3196
+ }
3197
+ if (noShadows && clone) {
3198
+ stripRootShadows(element, clone);
3199
+ }
2890
3200
  } finally {
2891
3201
  undoClamp();
2892
3202
  }
@@ -2911,7 +3221,7 @@ async function captureDOM(element, options) {
2911
3221
  const families = new Set(
2912
3222
  Array.from(required).map((k) => String(k).split("__")[0]).filter(Boolean)
2913
3223
  );
2914
- await ensureFontsReady(families, 2);
3224
+ await ensureFontsReady(families, 1);
2915
3225
  }
2916
3226
  fontsCSS = await embedCustomFonts({
2917
3227
  required,
@@ -2959,60 +3269,7 @@ async function captureDOM(element, options) {
2959
3269
  b = Math.max(b, extY + Math.max(oy, 0));
2960
3270
  t = Math.max(t, extY + Math.max(-oy, 0));
2961
3271
  }
2962
- return {
2963
- bleed: { top: Math.ceil(t), right: Math.ceil(r), bottom: Math.ceil(b), left: Math.ceil(l) },
2964
- has: found
2965
- };
2966
- }
2967
- function parseBoxShadow(cs) {
2968
- const v = cs.boxShadow || "";
2969
- if (!v || v === "none") return { top: 0, right: 0, bottom: 0, left: 0 };
2970
- const parts = v.split(/\),(?=(?:[^()]*\([^()]*\))*[^()]*$)/).map((s) => s.trim());
2971
- let t = 0, r = 0, b2 = 0, l = 0;
2972
- for (const part of parts) {
2973
- const nums = part.match(/-?\d+(\.\d+)?px/g)?.map((n) => parseFloat(n)) || [];
2974
- if (nums.length < 2) continue;
2975
- const [ox2, oy2, blur = 0, spread = 0] = nums;
2976
- const extX = Math.abs(ox2) + blur + spread;
2977
- const extY = Math.abs(oy2) + blur + spread;
2978
- r = Math.max(r, extX + Math.max(ox2, 0));
2979
- l = Math.max(l, extX + Math.max(-ox2, 0));
2980
- b2 = Math.max(b2, extY + Math.max(oy2, 0));
2981
- t = Math.max(t, extY + Math.max(-oy2, 0));
2982
- }
2983
- return { top: Math.ceil(t), right: Math.ceil(r), bottom: Math.ceil(b2), left: Math.ceil(l) };
2984
- }
2985
- function parseFilterBlur(cs) {
2986
- const m = (cs.filter || "").match(/blur\(\s*([0-9.]+)px\s*\)/);
2987
- const b2 = m ? Math.ceil(parseFloat(m[1]) || 0) : 0;
2988
- return { top: b2, right: b2, bottom: b2, left: b2 };
2989
- }
2990
- function parseOutline(cs) {
2991
- if ((cs.outlineStyle || "none") === "none") return { top: 0, right: 0, bottom: 0, left: 0 };
2992
- const w2 = Math.ceil(parseFloat(cs.outlineWidth || "0") || 0);
2993
- return { top: w2, right: w2, bottom: w2, left: w2 };
2994
- }
2995
- function bboxWithOriginFull(w2, h2, M, ox2, oy2) {
2996
- const a2 = M.a, b2 = M.b, c2 = M.c, d2 = M.d, e2 = M.e || 0, f2 = M.f || 0;
2997
- function pt(x, y) {
2998
- let X = x - ox2, Y = y - oy2;
2999
- let X2 = a2 * X + c2 * Y, Y2 = b2 * X + d2 * Y;
3000
- X2 += ox2 + e2;
3001
- Y2 += oy2 + f2;
3002
- return [X2, Y2];
3003
- }
3004
- const P = [pt(0, 0), pt(w2, 0), pt(0, h2), pt(w2, h2)];
3005
- let minX2 = Infinity, minY2 = Infinity, maxX2 = -Infinity, maxY2 = -Infinity;
3006
- for (const [X, Y] of P) {
3007
- if (X < minX2) minX2 = X;
3008
- if (Y < minY2) minY2 = Y;
3009
- if (X > maxX2) maxX2 = X;
3010
- if (Y > maxY2) maxY2 = Y;
3011
- }
3012
- return { minX: minX2, minY: minY2, maxX: maxX2, maxY: maxY2, width: maxX2 - minX2, height: maxY2 - minY2 };
3013
- }
3014
- function hasTFBBox(el) {
3015
- return hasBBoxAffectingTransform(el);
3272
+ return { bleed: { top: Math.ceil(t), right: Math.ceil(r), bottom: Math.ceil(b), left: Math.ceil(l) }, has: found };
3016
3273
  }
3017
3274
  const rect = element.getBoundingClientRect();
3018
3275
  const w0 = Math.max(1, Math.ceil(element.offsetWidth || parseFloat(csEl.width) || rect.width || 1));
@@ -3041,28 +3298,38 @@ async function captureDOM(element, options) {
3041
3298
  h = h0;
3042
3299
  }
3043
3300
  let minX = 0, minY = 0, maxX = w0, maxY = h0;
3044
- if (hasTFBBox(element)) {
3045
- const baseTransform2 = csEl.transform && csEl.transform !== "none" ? csEl.transform : "";
3046
- const ind2 = readIndividualTransforms(element);
3047
- const TOTAL = readTotalTransformMatrix({
3048
- baseTransform: baseTransform2,
3049
- rotate: ind2.rotate || "0deg",
3050
- scale: ind2.scale,
3051
- translate: ind2.translate
3052
- });
3053
- const { ox: ox2, oy: oy2 } = parseTransformOriginPx(csEl, w0, h0);
3054
- const M = TOTAL.is2D ? TOTAL : new DOMMatrix(TOTAL.toString());
3055
- const bb = bboxWithOriginFull(w0, h0, M, ox2, oy2);
3056
- minX = bb.minX;
3057
- minY = bb.minY;
3058
- maxX = bb.maxX;
3059
- maxY = bb.maxY;
3301
+ if (straighten && rootTransform2D && Number.isFinite(rootTransform2D.a)) {
3302
+ const M2 = { a: rootTransform2D.a, b: rootTransform2D.b || 0, c: rootTransform2D.c || 0, d: rootTransform2D.d || 1, e: 0, f: 0 };
3303
+ const bb2 = bboxWithOriginFull(w0, h0, M2, 0, 0);
3304
+ minX = bb2.minX;
3305
+ minY = bb2.minY;
3306
+ maxX = bb2.maxX;
3307
+ maxY = bb2.maxY;
3308
+ } else {
3309
+ const useTFBBox = !straighten && hasTFBBox(element);
3310
+ if (useTFBBox) {
3311
+ const baseTransform2 = csEl.transform && csEl.transform !== "none" ? csEl.transform : "";
3312
+ const ind2 = readIndividualTransforms(element);
3313
+ const TOTAL = readTotalTransformMatrix({
3314
+ baseTransform: baseTransform2,
3315
+ rotate: ind2.rotate || "0deg",
3316
+ scale: ind2.scale,
3317
+ translate: ind2.translate
3318
+ });
3319
+ const { ox: ox2, oy: oy2 } = parseTransformOriginPx(csEl, w0, h0);
3320
+ const M = TOTAL.is2D ? TOTAL : new DOMMatrix(TOTAL.toString());
3321
+ const bb = bboxWithOriginFull(w0, h0, M, ox2, oy2);
3322
+ minX = bb.minX;
3323
+ minY = bb.minY;
3324
+ maxX = bb.maxX;
3325
+ maxY = bb.maxY;
3326
+ }
3060
3327
  }
3061
3328
  const bleedShadow = parseBoxShadow(csEl);
3062
3329
  const bleedBlur = parseFilterBlur(csEl);
3063
3330
  const bleedOutline = parseOutline(csEl);
3064
3331
  const drop = parseFilterDropShadows(csEl);
3065
- const bleed = {
3332
+ const bleed = noShadows ? { top: 0, right: 0, bottom: 0, left: 0 } : {
3066
3333
  top: bleedShadow.top + bleedBlur.top + bleedOutline.top + drop.bleed.top,
3067
3334
  right: bleedShadow.right + bleedBlur.right + bleedOutline.right + drop.bleed.right,
3068
3335
  bottom: bleedShadow.bottom + bleedBlur.bottom + bleedOutline.bottom + drop.bleed.bottom,
@@ -3077,7 +3344,9 @@ async function captureDOM(element, options) {
3077
3344
  const outW = Math.max(1, Math.round(vbW0 * (hasW || hasH ? w / w0 : 1)));
3078
3345
  const outH = Math.max(1, Math.round(vbH0 * (hasH || hasW ? h / h0 : 1)));
3079
3346
  const svgNS = "http://www.w3.org/2000/svg";
3080
- const pad = isSafari() ? 1 : 0;
3347
+ const basePad = isSafari() ? 1 : 0;
3348
+ const extraPad = straighten ? 1 : 0;
3349
+ const pad = basePad + extraPad;
3081
3350
  const fo = document.createElementNS(svgNS, "foreignObject");
3082
3351
  const vbMinX = Math.floor(minX);
3083
3352
  const vbMinY = Math.floor(minY);
@@ -3102,20 +3371,7 @@ async function captureDOM(element, options) {
3102
3371
  const vbW = vbW0 + pad * 2;
3103
3372
  const vbH = vbH0 + pad * 2;
3104
3373
  const wantsSize = hasW || hasH;
3105
- options.meta = {
3106
- w0,
3107
- // ancho natural del elemento
3108
- h0,
3109
- // alto natural
3110
- vbW,
3111
- // ancho del viewBox
3112
- vbH,
3113
- // alto del viewBox
3114
- targetW: w,
3115
- // ancho deseado según options.width
3116
- targetH: h
3117
- // alto deseado según options.height
3118
- };
3374
+ options.meta = { w0, h0, vbW, vbH, targetW: w, targetH: h };
3119
3375
  const svgOutW = isSafari() && wantsSize ? vbW : outW + pad * 2;
3120
3376
  const svgOutH = isSafari() && wantsSize ? vbH : outH + pad * 2;
3121
3377
  const svgHeader = `<svg xmlns="${svgNS}" width="${svgOutW}" height="${svgOutH}" viewBox="0 0 ${vbW} ${vbH}">`;
@@ -3129,6 +3385,115 @@ async function captureDOM(element, options) {
3129
3385
  if (sandbox && sandbox.style.position === "absolute") sandbox.remove();
3130
3386
  return dataURL;
3131
3387
  }
3388
+ function normalizeRootTransforms(originalEl, cloneRoot) {
3389
+ if (!originalEl || !cloneRoot || !cloneRoot.style) return null;
3390
+ const cs = getComputedStyle(originalEl);
3391
+ try {
3392
+ cloneRoot.style.transformOrigin = "0 0";
3393
+ } catch {
3394
+ }
3395
+ try {
3396
+ if ("translate" in cloneRoot.style) cloneRoot.style.translate = "none";
3397
+ if ("rotate" in cloneRoot.style) cloneRoot.style.rotate = "none";
3398
+ } catch {
3399
+ }
3400
+ const tr = cs.transform || "none";
3401
+ if (!tr || tr === "none") {
3402
+ try {
3403
+ const M = matrixFromComputed(originalEl);
3404
+ if (M.a === 1 && M.b === 0 && M.c === 0 && M.d === 1) {
3405
+ cloneRoot.style.transform = "none";
3406
+ return { a: 1, b: 0, c: 0, d: 1 };
3407
+ }
3408
+ } catch {
3409
+ }
3410
+ }
3411
+ const m2d = tr.match(/^matrix\(\s*([^)]+)\)$/i);
3412
+ if (m2d) {
3413
+ const nums = m2d[1].split(",").map((v) => parseFloat(v.trim()));
3414
+ if (nums.length === 6 && nums.every(Number.isFinite)) {
3415
+ const [a, b, c, d] = nums;
3416
+ const scaleX = Math.sqrt(a * a + b * b) || 0;
3417
+ let a1 = 0, b1 = 0, shear = 0, c2 = 0, d2 = 0, scaleY = 0;
3418
+ if (scaleX > 0) {
3419
+ a1 = a / scaleX;
3420
+ b1 = b / scaleX;
3421
+ shear = a1 * c + b1 * d;
3422
+ c2 = c - a1 * shear;
3423
+ d2 = d - b1 * shear;
3424
+ scaleY = Math.sqrt(c2 * c2 + d2 * d2) || 0;
3425
+ if (scaleY > 0) shear = shear / scaleY;
3426
+ else shear = 0;
3427
+ }
3428
+ const aP = scaleX;
3429
+ const bP = 0;
3430
+ const cP = shear * scaleY;
3431
+ const dP = scaleY;
3432
+ try {
3433
+ cloneRoot.style.transform = `matrix(${aP}, ${bP}, ${cP}, ${dP}, 0, 0)`;
3434
+ } catch {
3435
+ }
3436
+ return { a: aP, b: bP, c: cP, d: dP };
3437
+ }
3438
+ }
3439
+ try {
3440
+ const legacy = String(tr).trim();
3441
+ cloneRoot.style.transform = legacy + " translate(0px, 0px) rotate(0deg)";
3442
+ return null;
3443
+ } catch {
3444
+ return null;
3445
+ }
3446
+ }
3447
+ function parseBoxShadow(cs) {
3448
+ const v = cs.boxShadow || "";
3449
+ if (!v || v === "none") return { top: 0, right: 0, bottom: 0, left: 0 };
3450
+ const parts = v.split(/\),(?=(?:[^()]*\([^()]*\))*[^()]*$)/).map((s) => s.trim());
3451
+ let t = 0, r = 0, b2 = 0, l = 0;
3452
+ for (const part of parts) {
3453
+ const nums = part.match(/-?\d+(\.\d+)?px/g)?.map((n) => parseFloat(n)) || [];
3454
+ if (nums.length < 2) continue;
3455
+ const [ox2, oy2, blur = 0, spread = 0] = nums;
3456
+ const extX = Math.abs(ox2) + blur + spread;
3457
+ const extY = Math.abs(oy2) + blur + spread;
3458
+ r = Math.max(r, extX + Math.max(ox2, 0));
3459
+ l = Math.max(l, extX + Math.max(-ox2, 0));
3460
+ b2 = Math.max(b2, extY + Math.max(oy2, 0));
3461
+ t = Math.max(t, extY + Math.max(-oy2, 0));
3462
+ }
3463
+ return { top: Math.ceil(t), right: Math.ceil(r), bottom: Math.ceil(b2), left: Math.ceil(l) };
3464
+ }
3465
+ function parseFilterBlur(cs) {
3466
+ const m = (cs.filter || "").match(/blur\(\s*([0-9.]+)px\s*\)/);
3467
+ const b2 = m ? Math.ceil(parseFloat(m[1]) || 0) : 0;
3468
+ return { top: b2, right: b2, bottom: b2, left: b2 };
3469
+ }
3470
+ function parseOutline(cs) {
3471
+ if ((cs.outlineStyle || "none") === "none") return { top: 0, right: 0, bottom: 0, left: 0 };
3472
+ const w2 = Math.ceil(parseFloat(cs.outlineWidth || "0") || 0);
3473
+ return { top: w2, right: w2, bottom: w2, left: w2 };
3474
+ }
3475
+ function bboxWithOriginFull(w2, h2, M, ox2, oy2) {
3476
+ const a2 = M.a, b2 = M.b, c2 = M.c, d2 = M.d, e2 = M.e || 0, f2 = M.f || 0;
3477
+ function pt(x, y) {
3478
+ let X = x - ox2, Y = y - oy2;
3479
+ let X2 = a2 * X + c2 * Y, Y2 = b2 * X + d2 * Y;
3480
+ X2 += ox2 + e2;
3481
+ Y2 += oy2 + f2;
3482
+ return [X2, Y2];
3483
+ }
3484
+ const P = [pt(0, 0), pt(w2, 0), pt(0, h2), pt(w2, h2)];
3485
+ let minX2 = Infinity, minY2 = Infinity, maxX2 = -Infinity, maxY2 = -Infinity;
3486
+ for (const [X, Y] of P) {
3487
+ if (X < minX2) minX2 = X;
3488
+ if (Y < minY2) minY2 = Y;
3489
+ if (X > maxX2) maxX2 = X;
3490
+ if (Y > maxY2) maxY2 = Y;
3491
+ }
3492
+ return { minX: minX2, minY: minY2, maxX: maxX2, maxY: maxY2, width: maxX2 - minX2, height: maxY2 - minY2 };
3493
+ }
3494
+ function hasTFBBox(el) {
3495
+ return hasBBoxAffectingTransform(el);
3496
+ }
3132
3497
  function matrixFromComputed(el) {
3133
3498
  const tr = getComputedStyle(el).transform;
3134
3499
  if (!tr || tr === "none") return new DOMMatrix();
@@ -3281,20 +3646,20 @@ function createContext(options = {}) {
3281
3646
  excludeMode: options.excludeMode ?? "hide",
3282
3647
  filter: options.filter ?? null,
3283
3648
  filterMode: options.filterMode ?? "hide",
3649
+ // Placeholders
3284
3650
  placeholders: options.placeholders !== false,
3285
3651
  // default true
3286
- // Fuentes
3652
+ // Fonts
3287
3653
  embedFonts: options.embedFonts ?? false,
3288
3654
  iconFonts: Array.isArray(options.iconFonts) ? options.iconFonts : options.iconFonts ? [options.iconFonts] : [],
3289
3655
  localFonts: Array.isArray(options.localFonts) ? options.localFonts : [],
3290
3656
  excludeFonts: options.excludeFonts ?? void 0,
3291
3657
  fallbackURL: options.fallbackURL ?? void 0,
3292
- /** Cache policy (disabled|full|auto|soft). Default: "soft" */
3293
3658
  /** @type {CachePolicy} */
3294
3659
  cache: cachePolicy,
3295
- // Red
3660
+ // Network
3296
3661
  useProxy: typeof options.useProxy === "string" ? options.useProxy : "",
3297
- // Salida
3662
+ // Output
3298
3663
  width: options.width ?? null,
3299
3664
  height: options.height ?? null,
3300
3665
  format: resolvedFormat,
@@ -3302,8 +3667,11 @@ function createContext(options = {}) {
3302
3667
  quality: options.quality ?? 0.92,
3303
3668
  dpr: options.dpr ?? (window.devicePixelRatio || 1),
3304
3669
  backgroundColor: options.backgroundColor ?? (["jpg", "jpeg", "webp"].includes(resolvedFormat) ? "#ffffff" : null),
3305
- filename: options.filename ?? "snapDOM"
3306
- // Plugins (reservado para futura activación)
3670
+ filename: options.filename ?? "snapDOM",
3671
+ // NEW flags (user-friendly)
3672
+ straighten: options.straighten ?? false,
3673
+ noShadows: options.noShadows ?? false
3674
+ // Plugins (reservado)
3307
3675
  // plugins: normalizePlugins(...),
3308
3676
  };
3309
3677
  }
@@ -3493,9 +3861,20 @@ async function toImg(url, options) {
3493
3861
  const k = height / Math.max(1, refH);
3494
3862
  img.style.height = `${height}px`;
3495
3863
  img.style.width = `${Math.round(refW * k)}px`;
3496
- } else if (scale !== 1) {
3497
- img.style.width = `${Math.round(img.naturalWidth * scale)}px`;
3498
- img.style.height = `${Math.round(img.naturalHeight * scale)}px`;
3864
+ } else {
3865
+ const cssW = Math.round(img.naturalWidth * scale);
3866
+ const cssH = Math.round(img.naturalHeight * scale);
3867
+ img.style.width = `${cssW}px`;
3868
+ img.style.height = `${cssH}px`;
3869
+ if (typeof url === "string" && url.startsWith("data:image/svg+xml")) {
3870
+ try {
3871
+ const decoded = decodeURIComponent(url.split(",")[1]);
3872
+ const patched = decoded.replace(/width="[^"]*"/, `width="${cssW}"`).replace(/height="[^"]*"/, `height="${cssH}"`);
3873
+ url = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(patched)}`;
3874
+ img.src = url;
3875
+ } catch {
3876
+ }
3877
+ }
3499
3878
  }
3500
3879
  return img;
3501
3880
  }
@@ -3551,7 +3930,7 @@ async function snapdom(element, userOptions) {
3551
3930
  await safariWarmup(element, userOptions);
3552
3931
  console.log("Iteraci\xF3n n\xFAmero:", i);
3553
3932
  _safariWarmup = false;
3554
- } catch (error) {
3933
+ } catch {
3555
3934
  }
3556
3935
  }
3557
3936
  }
@@ -3559,7 +3938,7 @@ async function snapdom(element, userOptions) {
3559
3938
  if (!context.snap) {
3560
3939
  context.snap = {
3561
3940
  toPng: (el, opts) => snapdom.toPng(el, opts),
3562
- toImg: (el, opts) => snapdom.toImg(el, opts)
3941
+ toSvg: (el, opts) => snapdom.toSvg(el, opts)
3563
3942
  };
3564
3943
  }
3565
3944
  return snapdom.capture(element, context, INTERNAL_TOKEN);
@@ -3581,6 +3960,7 @@ snapdom.capture = async (el, context, _token) => {
3581
3960
  url,
3582
3961
  toRaw: () => url,
3583
3962
  toImg: (opts) => toImg(url, ensureContext(opts)),
3963
+ toSvg: (opts) => toImg(url, ensureContext(opts)),
3584
3964
  toCanvas: (opts) => toCanvas(url, ensureContext(opts)),
3585
3965
  toBlob: (opts) => toBlob(url, ensureContext(opts)),
3586
3966
  toPng: withFormat("png"),
@@ -3591,6 +3971,7 @@ snapdom.capture = async (el, context, _token) => {
3591
3971
  };
3592
3972
  snapdom.toRaw = (el, options) => snapdom(el, options).then((result) => result.toRaw());
3593
3973
  snapdom.toImg = (el, options) => snapdom(el, options).then((result) => result.toImg());
3974
+ snapdom.toSvg = (el, options) => snapdom(el, options).then((result) => result.toSvg());
3594
3975
  snapdom.toCanvas = (el, options) => snapdom(el, options).then((result) => result.toCanvas());
3595
3976
  snapdom.toBlob = (el, options) => snapdom(el, options).then((result) => result.toBlob());
3596
3977
  snapdom.toPng = (el, options) => snapdom(el, { ...options, format: "png" }).then((result) => result.toPng());
@@ -3729,7 +4110,7 @@ async function preCache(root = document, options = {}) {
3729
4110
  const families = new Set(
3730
4111
  Array.from(required).map((k) => String(k).split("__")[0]).filter(Boolean)
3731
4112
  );
3732
- await ensureFontsReady(families, 2);
4113
+ await ensureFontsReady(families, 3);
3733
4114
  }
3734
4115
  await embedCustomFonts({
3735
4116
  required,