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

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.
Files changed (37) hide show
  1. package/README.md +50 -9
  2. package/dist/modules/chunks/chunk-23Q2XEDO.js +7 -0
  3. package/dist/modules/chunks/chunk-2FBS3OGP.js +7 -0
  4. package/dist/modules/chunks/chunk-43NWAKSV.js +7 -0
  5. package/dist/modules/chunks/chunk-5EIHY65Z.js +7 -0
  6. package/dist/modules/chunks/chunk-6FO4Y3OG.js +10 -0
  7. package/dist/modules/chunks/chunk-AOOOETXS.js +7 -0
  8. package/dist/modules/chunks/chunk-DV3SVS7E.js +10 -0
  9. package/dist/modules/chunks/chunk-FPOOW4FJ.js +7 -0
  10. package/dist/modules/chunks/chunk-FQFF5KMG.js +7 -0
  11. package/dist/modules/chunks/chunk-GU6SHZKJ.js +7 -0
  12. package/dist/modules/chunks/chunk-JUQT3OWD.js +7 -0
  13. package/dist/modules/chunks/chunk-LO3HOQZX.js +8 -0
  14. package/dist/modules/chunks/chunk-MI3WCG7V.js +8 -0
  15. package/dist/modules/chunks/chunk-R6NINER4.js +7 -0
  16. package/dist/modules/chunks/chunk-TWGUMDQG.js +10 -0
  17. package/dist/modules/chunks/chunk-UI252PBJ.js +7 -0
  18. package/dist/modules/chunks/chunk-XUQSCYHL.js +10 -0
  19. package/dist/modules/chunks/chunk-ZHRUQIHZ.js +7 -0
  20. package/dist/modules/chunks/rasterize-NVVIIRN2.js +7 -0
  21. package/dist/modules/chunks/rasterize-XGEI2GKT.js +7 -0
  22. package/dist/modules/exporters/download.js +7 -0
  23. package/dist/modules/exporters/toBlob.js +7 -0
  24. package/dist/modules/exporters/toCanvas.js +7 -0
  25. package/dist/modules/exporters/toImg.js +7 -0
  26. package/dist/modules/exporters/toJpg.js +7 -0
  27. package/dist/modules/exporters/toPng.js +7 -0
  28. package/dist/modules/exporters/toSvg.js +7 -0
  29. package/dist/modules/exporters/toWebp.js +7 -0
  30. package/dist/modules/plugins.js +7 -0
  31. package/dist/modules/preCache.js +7 -0
  32. package/dist/modules/snapdom.js +7 -0
  33. package/dist/snapdom.js +13 -5030
  34. package/dist/snapdom.mjs +2284 -1886
  35. package/package.json +29 -24
  36. package/dist/snapdom.min.js +0 -8
  37. package/dist/snapdom.min.mjs +0 -8
package/dist/snapdom.mjs CHANGED
@@ -1,27 +1,20 @@
1
-
2
1
  /*
3
- * snapdom
4
- * v.2.0.0-dev.2
5
- * Author Juan Martin Muda
6
- * License MIT
7
- **/
8
-
2
+ * SnapDOM
3
+ * v2.0.0-dev.4
4
+ * Author: Juan Martin Muda
5
+ * License: MIT
6
+ */
7
+ var __defProp = Object.defineProperty;
8
+ var __getOwnPropNames = Object.getOwnPropertyNames;
9
+ var __esm = (fn, res) => function __init() {
10
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
11
+ };
12
+ var __export = (target, all) => {
13
+ for (var name in all)
14
+ __defProp(target, name, { get: all[name], enumerable: true });
15
+ };
9
16
 
10
17
  // src/core/cache.js
11
- var cache = {
12
- image: /* @__PURE__ */ new Map(),
13
- background: /* @__PURE__ */ new Map(),
14
- resource: /* @__PURE__ */ new Map(),
15
- defaultStyle: /* @__PURE__ */ new Map(),
16
- baseStyle: /* @__PURE__ */ new Map(),
17
- computedStyle: /* @__PURE__ */ new WeakMap(),
18
- font: /* @__PURE__ */ new Set(),
19
- session: {
20
- styleMap: /* @__PURE__ */ new Map(),
21
- styleCache: /* @__PURE__ */ new WeakMap(),
22
- nodeMap: /* @__PURE__ */ new Map()
23
- }
24
- };
25
18
  function applyCachePolicy(policy = "soft") {
26
19
  cache.session.__counterEpoch = (cache.session.__counterEpoch || 0) + 1;
27
20
  switch (policy) {
@@ -60,6 +53,25 @@ function applyCachePolicy(policy = "soft") {
60
53
  }
61
54
  }
62
55
  }
56
+ var cache;
57
+ var init_cache = __esm({
58
+ "src/core/cache.js"() {
59
+ cache = {
60
+ image: /* @__PURE__ */ new Map(),
61
+ background: /* @__PURE__ */ new Map(),
62
+ resource: /* @__PURE__ */ new Map(),
63
+ defaultStyle: /* @__PURE__ */ new Map(),
64
+ baseStyle: /* @__PURE__ */ new Map(),
65
+ computedStyle: /* @__PURE__ */ new WeakMap(),
66
+ font: /* @__PURE__ */ new Set(),
67
+ session: {
68
+ styleMap: /* @__PURE__ */ new Map(),
69
+ styleCache: /* @__PURE__ */ new WeakMap(),
70
+ nodeMap: /* @__PURE__ */ new Map()
71
+ }
72
+ };
73
+ }
74
+ });
63
75
 
64
76
  // src/utils/helpers.js
65
77
  function extractURL(value) {
@@ -96,6 +108,10 @@ function safeEncodeURI(uri) {
96
108
  return uri;
97
109
  }
98
110
  }
111
+ var init_helpers = __esm({
112
+ "src/utils/helpers.js"() {
113
+ }
114
+ });
99
115
 
100
116
  // src/modules/snapFetch.js
101
117
  function createSnapLogger(prefix = "[snapDOM]", { ttlMs = 5 * 6e4, maxEntries = 12 } = {}) {
@@ -124,9 +140,6 @@ function createSnapLogger(prefix = "[snapDOM]", { ttlMs = 5 * 6e4, maxEntries =
124
140
  }
125
141
  };
126
142
  }
127
- var snapLogger = createSnapLogger("[snapDOM]", { ttlMs: 3 * 6e4, maxEntries: 10 });
128
- var _inflight = /* @__PURE__ */ new Map();
129
- var _errorCache = /* @__PURE__ */ new Map();
130
143
  function isSpecialURL(url) {
131
144
  return /^data:|^blob:|^about:blank$/i.test(url);
132
145
  }
@@ -326,6 +339,15 @@ async function snapFetch(url, options = {}) {
326
339
  _inflight.set(key, p);
327
340
  return p;
328
341
  }
342
+ var snapLogger, _inflight, _errorCache;
343
+ var init_snapFetch = __esm({
344
+ "src/modules/snapFetch.js"() {
345
+ init_helpers();
346
+ snapLogger = createSnapLogger("[snapDOM]", { ttlMs: 3 * 6e4, maxEntries: 10 });
347
+ _inflight = /* @__PURE__ */ new Map();
348
+ _errorCache = /* @__PURE__ */ new Map();
349
+ }
350
+ });
329
351
 
330
352
  // src/utils/image.js
331
353
  async function inlineSingleBackgroundEntry(entry, options = {}) {
@@ -355,78 +377,15 @@ async function inlineSingleBackgroundEntry(entry, options = {}) {
355
377
  return "none";
356
378
  }
357
379
  }
380
+ var init_image = __esm({
381
+ "src/utils/image.js"() {
382
+ init_cache();
383
+ init_helpers();
384
+ init_snapFetch();
385
+ }
386
+ });
358
387
 
359
388
  // src/utils/css.js
360
- var NO_CAPTURE_TAGS = /* @__PURE__ */ new Set([
361
- "meta",
362
- "script",
363
- "noscript",
364
- "title",
365
- "link",
366
- "template"
367
- ]);
368
- var NO_DEFAULTS_TAGS = /* @__PURE__ */ new Set([
369
- // non-painting / head stuff
370
- "meta",
371
- "link",
372
- "style",
373
- "title",
374
- "noscript",
375
- "script",
376
- "template",
377
- // SVG whole namespace (safe for LeaderLine/presentation attrs)
378
- "g",
379
- "defs",
380
- "use",
381
- "marker",
382
- "mask",
383
- "clipPath",
384
- "pattern",
385
- "path",
386
- "polygon",
387
- "polyline",
388
- "line",
389
- "circle",
390
- "ellipse",
391
- "rect",
392
- "filter",
393
- "lineargradient",
394
- "radialgradient",
395
- "stop"
396
- ]);
397
- var commonTags = [
398
- "div",
399
- "span",
400
- "p",
401
- "a",
402
- "img",
403
- "ul",
404
- "li",
405
- "button",
406
- "input",
407
- "select",
408
- "textarea",
409
- "label",
410
- "section",
411
- "article",
412
- "header",
413
- "footer",
414
- "nav",
415
- "main",
416
- "aside",
417
- "h1",
418
- "h2",
419
- "h3",
420
- "h4",
421
- "h5",
422
- "h6",
423
- "table",
424
- "thead",
425
- "tbody",
426
- "tr",
427
- "td",
428
- "th"
429
- ];
430
389
  function precacheCommonTags() {
431
390
  for (let tag of commonTags) {
432
391
  const t = String(tag).toLowerCase();
@@ -473,26 +432,6 @@ function getDefaultStyleForTag(tagName) {
473
432
  cache.defaultStyle.set(tagName, defaults);
474
433
  return defaults;
475
434
  }
476
- var NO_PAINT_TOKEN = /(?:^|-)(animation|transition)(?:-|$)/i;
477
- var NO_PAINT_PREFIX = /^(--|view-timeline|scroll-timeline|animation-trigger|offset-|position-try|app-region|interactivity|overlay|view-transition|-webkit-locale|-webkit-user-(?:drag|modify)|-webkit-tap-highlight-color|-webkit-text-security)$/i;
478
- var NO_PAINT_EXACT = /* @__PURE__ */ new Set([
479
- // Interaction hints
480
- "cursor",
481
- "pointer-events",
482
- "touch-action",
483
- "user-select",
484
- // Printing/speech/reading-mode hints
485
- "print-color-adjust",
486
- "speak",
487
- "reading-flow",
488
- "reading-order",
489
- // Anchoring/container/timeline scopes (metadata for layout queries)
490
- "anchor-name",
491
- "anchor-scope",
492
- "container-name",
493
- "container-type",
494
- "timeline-scope"
495
- ]);
496
435
  function shouldIgnoreProp(prop) {
497
436
  const p = String(prop).toLowerCase();
498
437
  if (NO_PAINT_EXACT.has(p)) return true;
@@ -592,6 +531,102 @@ function splitBackgroundImage(bg) {
592
531
  parts.push(bg.slice(lastIndex).trim());
593
532
  return parts;
594
533
  }
534
+ var NO_CAPTURE_TAGS, NO_DEFAULTS_TAGS, commonTags, NO_PAINT_TOKEN, NO_PAINT_PREFIX, NO_PAINT_EXACT;
535
+ var init_css = __esm({
536
+ "src/utils/css.js"() {
537
+ init_cache();
538
+ NO_CAPTURE_TAGS = /* @__PURE__ */ new Set([
539
+ "meta",
540
+ "script",
541
+ "noscript",
542
+ "title",
543
+ "link",
544
+ "template"
545
+ ]);
546
+ NO_DEFAULTS_TAGS = /* @__PURE__ */ new Set([
547
+ // non-painting / head stuff
548
+ "meta",
549
+ "link",
550
+ "style",
551
+ "title",
552
+ "noscript",
553
+ "script",
554
+ "template",
555
+ // SVG whole namespace (safe for LeaderLine/presentation attrs)
556
+ "g",
557
+ "defs",
558
+ "use",
559
+ "marker",
560
+ "mask",
561
+ "clipPath",
562
+ "pattern",
563
+ "path",
564
+ "polygon",
565
+ "polyline",
566
+ "line",
567
+ "circle",
568
+ "ellipse",
569
+ "rect",
570
+ "filter",
571
+ "lineargradient",
572
+ "radialgradient",
573
+ "stop"
574
+ ]);
575
+ commonTags = [
576
+ "div",
577
+ "span",
578
+ "p",
579
+ "a",
580
+ "img",
581
+ "ul",
582
+ "li",
583
+ "button",
584
+ "input",
585
+ "select",
586
+ "textarea",
587
+ "label",
588
+ "section",
589
+ "article",
590
+ "header",
591
+ "footer",
592
+ "nav",
593
+ "main",
594
+ "aside",
595
+ "h1",
596
+ "h2",
597
+ "h3",
598
+ "h4",
599
+ "h5",
600
+ "h6",
601
+ "table",
602
+ "thead",
603
+ "tbody",
604
+ "tr",
605
+ "td",
606
+ "th"
607
+ ];
608
+ NO_PAINT_TOKEN = /(?:^|-)(animation|transition)(?:-|$)/i;
609
+ NO_PAINT_PREFIX = /^(--|view-timeline|scroll-timeline|animation-trigger|offset-|position-try|app-region|interactivity|overlay|view-transition|-webkit-locale|-webkit-user-(?:drag|modify)|-webkit-tap-highlight-color|-webkit-text-security)$/i;
610
+ NO_PAINT_EXACT = /* @__PURE__ */ new Set([
611
+ // Interaction hints
612
+ "cursor",
613
+ "pointer-events",
614
+ "touch-action",
615
+ "user-select",
616
+ // Printing/speech/reading-mode hints
617
+ "print-color-adjust",
618
+ "speak",
619
+ "reading-flow",
620
+ "reading-order",
621
+ // Anchoring/container/timeline scopes (metadata for layout queries)
622
+ "anchor-name",
623
+ "anchor-scope",
624
+ "container-name",
625
+ "container-type",
626
+ "timeline-scope"
627
+ ]);
628
+ }
629
+ });
595
630
 
596
631
  // src/utils/browser.js
597
632
  function idle(fn, { fast = false } = {}) {
@@ -618,915 +653,1392 @@ function isSafari() {
618
653
  const isIOSWebKit = /ipad|iphone|ipod/.test(uaLower) && isWebKit;
619
654
  return isSafariUA || isUIWebView || isWeChatUA || isBaiduUA || isIOSWebKit;
620
655
  }
656
+ var init_browser = __esm({
657
+ "src/utils/browser.js"() {
658
+ }
659
+ });
621
660
 
622
- // src/modules/styles.js
623
- var snapshotCache = /* @__PURE__ */ new WeakMap();
624
- var snapshotKeyCache = /* @__PURE__ */ new Map();
625
- var __epoch = 0;
626
- function bumpEpoch() {
627
- __epoch++;
661
+ // src/utils/index.js
662
+ var init_utils = __esm({
663
+ "src/utils/index.js"() {
664
+ init_image();
665
+ init_css();
666
+ init_browser();
667
+ init_helpers();
668
+ }
669
+ });
670
+
671
+ // src/exporters/toCanvas.js
672
+ var toCanvas_exports = {};
673
+ __export(toCanvas_exports, {
674
+ toCanvas: () => toCanvas
675
+ });
676
+ function isSvgDataURL(u) {
677
+ return typeof u === "string" && /^data:image\/svg\+xml/i.test(u);
628
678
  }
629
- var __wired = false;
630
- function setupInvalidationOnce(root = document.documentElement) {
631
- if (__wired) return;
632
- __wired = true;
633
- try {
634
- const domObs = new MutationObserver(() => bumpEpoch());
635
- domObs.observe(root, { subtree: true, childList: true, characterData: true, attributes: true });
636
- } catch {
679
+ function decodeSvgFromDataURL(u) {
680
+ const i = u.indexOf(",");
681
+ return i >= 0 ? decodeURIComponent(u.slice(i + 1)) : "";
682
+ }
683
+ function encodeSvgToDataURL(svgText) {
684
+ return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgText)}`;
685
+ }
686
+ function splitDecls(s) {
687
+ let parts = [], buf = "", depth = 0;
688
+ for (let i = 0; i < s.length; i++) {
689
+ const ch = s[i];
690
+ if (ch === "(") depth++;
691
+ if (ch === ")") depth = Math.max(0, depth - 1);
692
+ if (ch === ";" && depth === 0) {
693
+ parts.push(buf);
694
+ buf = "";
695
+ } else buf += ch;
637
696
  }
638
- try {
639
- const headObs = new MutationObserver(() => bumpEpoch());
640
- headObs.observe(document.head, { subtree: true, childList: true, characterData: true, attributes: true });
641
- } catch {
697
+ if (buf.trim()) parts.push(buf);
698
+ return parts.map((x) => x.trim()).filter(Boolean);
699
+ }
700
+ function boxShadowToDropShadow(value) {
701
+ const layers = [];
702
+ let buf = "", depth = 0;
703
+ for (let i = 0; i < value.length; i++) {
704
+ const ch = value[i];
705
+ if (ch === "(") depth++;
706
+ if (ch === ")") depth = Math.max(0, depth - 1);
707
+ if (ch === "," && depth === 0) {
708
+ layers.push(buf.trim());
709
+ buf = "";
710
+ } else buf += ch;
642
711
  }
643
- try {
644
- const f = document.fonts;
645
- if (f) {
646
- f.addEventListener?.("loadingdone", bumpEpoch);
647
- f.ready?.then(() => bumpEpoch()).catch(() => {
648
- });
649
- }
650
- } catch {
712
+ if (buf.trim()) layers.push(buf.trim());
713
+ const fns = [];
714
+ for (const layer of layers) {
715
+ if (/\binset\b/i.test(layer)) continue;
716
+ const nums = layer.match(/-?\d+(?:\.\d+)?px/gi) || [];
717
+ const [ox = "0px", oy = "0px", blur = "0px"] = nums;
718
+ let color = layer.replace(/-?\d+(?:\.\d+)?px/gi, "").replace(/\binset\b/ig, "").trim().replace(/\s{2,}/g, " ");
719
+ const hasColor = !!color && color !== ",";
720
+ fns.push(`drop-shadow(${ox} ${oy} ${blur}${hasColor ? ` ${color}` : ""})`);
651
721
  }
722
+ return fns.join(" ");
652
723
  }
653
- function snapshotComputedStyleFull(style, options = {}) {
654
- const out = {};
655
- const vis = style.getPropertyValue("visibility");
656
- for (let i = 0; i < style.length; i++) {
657
- const prop = style[i];
658
- let val = style.getPropertyValue(prop);
659
- if ((prop === "background-image" || prop === "content") && val.includes("url(") && !val.includes("data:")) {
660
- val = "none";
661
- }
662
- out[prop] = val;
663
- }
664
- const EXTRA_TEXT_DECORATION_PROPS = [
665
- "text-decoration-line",
666
- "text-decoration-color",
667
- "text-decoration-style",
668
- "text-decoration-thickness",
669
- "text-underline-offset",
670
- "text-decoration-skip-ink"
671
- ];
672
- for (const prop of EXTRA_TEXT_DECORATION_PROPS) {
673
- if (out[prop]) continue;
674
- try {
675
- const v = style.getPropertyValue(prop);
676
- if (v) out[prop] = v;
677
- } catch {
678
- }
724
+ function rewriteDeclList(list) {
725
+ const decls = splitDecls(list);
726
+ let filter = null, wfilter = null, box = null;
727
+ const rest = [];
728
+ for (const d of decls) {
729
+ const idx = d.indexOf(":");
730
+ if (idx < 0) continue;
731
+ const prop = d.slice(0, idx).trim().toLowerCase();
732
+ const val = d.slice(idx + 1).trim();
733
+ if (prop === "box-shadow") box = val;
734
+ else if (prop === "filter") filter = val;
735
+ else if (prop === "-webkit-filter") wfilter = val;
736
+ else rest.push([prop, val]);
679
737
  }
680
- if (options.embedFonts) {
681
- const EXTRA_FONT_PROPS = [
682
- "font-feature-settings",
683
- "font-variation-settings",
684
- "font-kerning",
685
- "font-variant",
686
- "font-variant-ligatures",
687
- "font-optical-sizing"
688
- ];
689
- for (const prop of EXTRA_FONT_PROPS) {
690
- if (out[prop]) continue;
691
- try {
692
- const v = style.getPropertyValue(prop);
693
- if (v) out[prop] = v;
694
- } catch {
695
- }
738
+ if (box) {
739
+ const ds = boxShadowToDropShadow(box);
740
+ if (ds) {
741
+ filter = filter ? `${filter} ${ds}` : ds;
742
+ wfilter = wfilter ? `${wfilter} ${ds}` : ds;
696
743
  }
697
744
  }
698
- if (vis === "hidden") out.opacity = "0";
699
- return out;
700
- }
701
- var __snapshotSig = /* @__PURE__ */ new WeakMap();
702
- function styleSignature(snap) {
703
- let sig = __snapshotSig.get(snap);
704
- if (sig) return sig;
705
- const entries = Object.entries(snap).sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0);
706
- sig = entries.map(([k, v]) => `${k}:${v}`).join(";");
707
- __snapshotSig.set(snap, sig);
708
- return sig;
745
+ const out = [...rest];
746
+ if (filter) out.push(["filter", filter]);
747
+ if (wfilter) out.push(["-webkit-filter", wfilter]);
748
+ return out.map(([k, v]) => `${k}:${v}`).join(";");
709
749
  }
710
- function getSnapshot(el, preStyle = null, options = {}) {
711
- const rec = snapshotCache.get(el);
712
- if (rec && rec.epoch === __epoch) return rec.snapshot;
713
- const style = preStyle || getComputedStyle(el);
714
- const snap = snapshotComputedStyleFull(style, options);
715
- stripHeightForWrappers(el, style, snap);
716
- snapshotCache.set(el, { epoch: __epoch, snapshot: snap });
717
- return snap;
750
+ function rewriteCssBlock(css) {
751
+ return css.replace(/([^{}]+)\{([^}]*)\}/g, (_m, sel, body) => `${sel}{${rewriteDeclList(body)}}`);
718
752
  }
719
- function _resolveCtx(sessionOrCtx, opts) {
720
- if (sessionOrCtx && sessionOrCtx.session && sessionOrCtx.persist) return sessionOrCtx;
721
- if (sessionOrCtx && (sessionOrCtx.styleMap || sessionOrCtx.styleCache || sessionOrCtx.nodeMap)) {
722
- return {
723
- session: sessionOrCtx,
724
- persist: {
725
- snapshotKeyCache,
726
- defaultStyle: cache.defaultStyle,
727
- baseStyle: cache.baseStyle,
728
- image: cache.image,
729
- resource: cache.resource,
730
- background: cache.background,
731
- font: cache.font
732
- },
733
- options: opts || {}
734
- };
735
- }
736
- return {
737
- session: cache.session,
738
- persist: {
739
- snapshotKeyCache,
740
- defaultStyle: cache.defaultStyle,
741
- baseStyle: cache.baseStyle,
742
- image: cache.image,
743
- resource: cache.resource,
744
- background: cache.background,
745
- font: cache.font
746
- },
747
- options: sessionOrCtx || opts || {}
748
- };
753
+ function rewriteSvgBoxShadowToDropShadow(svgText) {
754
+ svgText = svgText.replace(
755
+ /<style[^>]*>([\s\S]*?)<\/style>/gi,
756
+ (m, css) => m.replace(css, rewriteCssBlock(css))
757
+ );
758
+ svgText = svgText.replace(
759
+ /style=(['"])([\s\S]*?)\1/gi,
760
+ (m, q, body) => `style=${q}${rewriteDeclList(body)}${q}`
761
+ );
762
+ return svgText;
749
763
  }
750
- async function inlineAllStyles(source, clone, sessionOrCtx, opts) {
751
- if (source.tagName === "STYLE") return;
752
- const ctx = _resolveCtx(sessionOrCtx, opts);
753
- const resetMode = ctx.options && ctx.options.cache || "auto";
754
- if (resetMode !== "disabled") setupInvalidationOnce(document.documentElement);
755
- if (resetMode === "disabled" && !ctx.session.__bumpedForDisabled) {
756
- bumpEpoch();
757
- snapshotKeyCache.clear();
758
- ctx.session.__bumpedForDisabled = true;
759
- }
760
- if (NO_DEFAULTS_TAGS.has(source.tagName?.toLowerCase())) {
761
- const author = source.getAttribute?.("style");
762
- if (author) clone.setAttribute("style", author);
763
- }
764
- const { session, persist } = ctx;
765
- if (!session.styleCache.has(source)) {
766
- session.styleCache.set(source, getComputedStyle(source));
767
- }
768
- const pre = session.styleCache.get(source);
769
- const snap = getSnapshot(source, pre, ctx.options);
770
- const sig = styleSignature(snap);
771
- let key = persist.snapshotKeyCache.get(sig);
772
- if (!key) {
773
- const tag = source.tagName?.toLowerCase() || "div";
774
- key = getStyleKey(snap, tag);
775
- persist.snapshotKeyCache.set(sig, key);
764
+ function maybeConvertBoxShadowForSafari(url) {
765
+ if (!isSafari() || !isSvgDataURL(url)) return url;
766
+ try {
767
+ const svg = decodeSvgFromDataURL(url);
768
+ const fixed = rewriteSvgBoxShadowToDropShadow(svg);
769
+ return encodeSvgToDataURL(fixed);
770
+ } catch {
771
+ return url;
776
772
  }
777
- session.styleMap.set(clone, key);
778
- }
779
- function isReplaced(el) {
780
- return el instanceof HTMLImageElement || el instanceof HTMLCanvasElement || el instanceof HTMLVideoElement || el instanceof HTMLIFrameElement || el instanceof SVGElement || el instanceof HTMLObjectElement || el instanceof HTMLEmbedElement;
781
773
  }
782
- function hasBox(cs) {
783
- if (cs.backgroundImage && cs.backgroundImage !== "none") return true;
784
- if (cs.backgroundColor && cs.backgroundColor !== "rgba(0, 0, 0, 0)" && cs.backgroundColor !== "transparent") return true;
785
- if ((parseFloat(cs.borderTopWidth) || 0) > 0) return true;
786
- if ((parseFloat(cs.borderBottomWidth) || 0) > 0) return true;
787
- if ((parseFloat(cs.paddingTop) || 0) > 0) return true;
788
- if ((parseFloat(cs.paddingBottom) || 0) > 0) return true;
789
- const ob = cs.overflowBlock || cs.overflowY || "visible";
790
- return ob !== "visible";
791
- }
792
- function isFlexOrGridItem(el) {
793
- const p = el.parentElement;
794
- if (!p) return false;
795
- const pd = getComputedStyle(p).display || "";
796
- return pd.includes("flex") || pd.includes("grid");
797
- }
798
- function hasFlowFast(el, cs) {
799
- if (el.textContent && /\S/.test(el.textContent)) return true;
800
- const f = el.firstElementChild, l = el.lastElementChild;
801
- if (f && f.tagName === "BR" || l && l.tagName === "BR") return true;
802
- const sh = el.scrollHeight;
803
- if (sh === 0) return false;
804
- const pt = parseFloat(cs.paddingTop) || 0;
805
- const pb = parseFloat(cs.paddingBottom) || 0;
806
- return sh > pt + pb;
807
- }
808
- function stripHeightForWrappers(el, cs, snap) {
809
- if (el instanceof HTMLElement && el.style && el.style.height) return;
810
- const disp = cs.display || "";
811
- if (disp.includes("flex") || disp.includes("grid")) return;
812
- if (isReplaced(el)) return;
813
- const pos = cs.position;
814
- if (pos === "absolute" || pos === "fixed" || pos === "sticky") return;
815
- if (cs.transform !== "none") return;
816
- if (hasBox(cs)) return;
817
- if (isFlexOrGridItem(el)) return;
818
- if (!hasFlowFast(el, cs)) return;
819
- delete snap.height;
820
- delete snap["block-size"];
821
- }
822
-
823
- // src/modules/CSSVar.js
824
- var KEY_PROPS = ["fill", "stroke", "color", "background-color", "stop-color"];
825
- var __BASELINE_CACHE = /* @__PURE__ */ new Map();
826
- function getBaselineComputed(tagName, ns) {
827
- const key = ns + "::" + tagName.toLowerCase();
828
- let entry = __BASELINE_CACHE.get(key);
829
- if (entry) return entry;
830
- const doc = document;
831
- const el = ns === "http://www.w3.org/2000/svg" ? doc.createElementNS(ns, tagName) : doc.createElement(tagName);
832
- const holder = doc.createElement("div");
833
- holder.style.cssText = "position:absolute;left:-99999px;top:-99999px;contain:strict;display:block;";
834
- holder.appendChild(el);
835
- doc.documentElement.appendChild(holder);
836
- const cs = getComputedStyle(el);
837
- const base = {};
838
- for (const p of KEY_PROPS) {
839
- base[p] = cs.getPropertyValue(p) || "";
774
+ async function toCanvas(url, options) {
775
+ let { width: optW, height: optH, scale = 1, dpr = 1, meta = {}, backgroundColor } = options;
776
+ url = maybeConvertBoxShadowForSafari(url);
777
+ const img = new Image();
778
+ img.loading = "eager";
779
+ img.decoding = "sync";
780
+ img.crossOrigin = "anonymous";
781
+ img.src = url;
782
+ await img.decode();
783
+ const natW = img.naturalWidth;
784
+ const natH = img.naturalHeight;
785
+ const refW = Number.isFinite(meta.w0) ? meta.w0 : natW;
786
+ const refH = Number.isFinite(meta.h0) ? meta.h0 : natH;
787
+ let outW, outH;
788
+ const hasW = Number.isFinite(optW);
789
+ const hasH = Number.isFinite(optH);
790
+ if (hasW && hasH) {
791
+ outW = Math.max(1, optW);
792
+ outH = Math.max(1, optH);
793
+ } else if (hasW) {
794
+ const k = optW / Math.max(1, refW);
795
+ outW = optW;
796
+ outH = refH * k;
797
+ } else if (hasH) {
798
+ const k = optH / Math.max(1, refH);
799
+ outH = optH;
800
+ outW = refW * k;
801
+ } else {
802
+ outW = natW;
803
+ outH = natH;
840
804
  }
841
- holder.remove();
842
- __BASELINE_CACHE.set(key, base);
843
- return base;
844
- }
845
- function resolveCSSVars(sourceEl, cloneEl) {
846
- if (!(sourceEl instanceof Element) || !(cloneEl instanceof Element)) return;
847
- const styleAttr = sourceEl.getAttribute?.("style");
848
- let hasVar = !!(styleAttr && styleAttr.includes("var("));
849
- if (!hasVar && sourceEl.attributes?.length) {
850
- const attrs = sourceEl.attributes;
851
- for (let i = 0; i < attrs.length; i++) {
852
- const a = attrs[i];
853
- if (a && typeof a.value === "string" && a.value.includes("var(")) {
854
- hasVar = true;
855
- break;
856
- }
857
- }
805
+ outW = outW * scale;
806
+ outH = outH * scale;
807
+ const canvas = document.createElement("canvas");
808
+ canvas.width = outW * dpr;
809
+ canvas.height = outH * dpr;
810
+ canvas.style.width = `${outW}px`;
811
+ canvas.style.height = `${outH}px`;
812
+ const ctx = canvas.getContext("2d");
813
+ if (dpr !== 1) ctx.scale(dpr, dpr);
814
+ if (backgroundColor) {
815
+ ctx.save();
816
+ ctx.fillStyle = backgroundColor;
817
+ ctx.fillRect(0, 0, outW, outH);
818
+ ctx.restore();
858
819
  }
859
- let cs = null;
860
- if (hasVar) {
861
- try {
862
- cs = getComputedStyle(sourceEl);
863
- } catch {
864
- }
820
+ ctx.drawImage(img, 0, 0, outW, outH);
821
+ return canvas;
822
+ }
823
+ var init_toCanvas = __esm({
824
+ "src/exporters/toCanvas.js"() {
825
+ init_browser();
865
826
  }
866
- if (hasVar) {
867
- const author = sourceEl.style;
868
- if (author && author.length) {
869
- for (let i = 0; i < author.length; i++) {
870
- const prop = author[i];
871
- const val = author.getPropertyValue(prop);
872
- if (!val || !val.includes("var(")) continue;
873
- const resolved = cs && cs.getPropertyValue(prop);
874
- if (resolved) {
875
- try {
876
- cloneEl.style.setProperty(prop, resolved.trim(), author.getPropertyPriority(prop));
877
- } catch {
878
- }
879
- }
880
- }
881
- }
827
+ });
828
+
829
+ // src/modules/rasterize.js
830
+ var rasterize_exports = {};
831
+ __export(rasterize_exports, {
832
+ rasterize: () => rasterize
833
+ });
834
+ async function rasterize(url, options) {
835
+ const canvas = await toCanvas(url, options);
836
+ const img = new Image();
837
+ img.src = canvas.toDataURL(`image/${options.format}`, options.quality);
838
+ await img.decode();
839
+ img.style.width = `${canvas.width / options.dpr}px`;
840
+ img.style.height = `${canvas.height / options.dpr}px`;
841
+ return img;
842
+ }
843
+ var init_rasterize = __esm({
844
+ "src/modules/rasterize.js"() {
845
+ init_toCanvas();
882
846
  }
883
- if (hasVar && sourceEl.attributes?.length) {
884
- const attrs = sourceEl.attributes;
885
- for (let i = 0; i < attrs.length; i++) {
886
- const a = attrs[i];
887
- if (!a || typeof a.value !== "string" || !a.value.includes("var(")) continue;
888
- const propName = a.name;
889
- const resolved = cs && cs.getPropertyValue(propName);
890
- if (resolved) {
891
- try {
892
- cloneEl.style.setProperty(propName, resolved.trim());
893
- } catch {
894
- }
895
- }
896
- }
847
+ });
848
+
849
+ // src/exporters/toImg.js
850
+ var toImg_exports = {};
851
+ __export(toImg_exports, {
852
+ toImg: () => toImg,
853
+ toSvg: () => toImg
854
+ });
855
+ async function toImg(url, options) {
856
+ const { scale = 1, width, height, meta = {} } = options;
857
+ const hasW = Number.isFinite(width);
858
+ const hasH = Number.isFinite(height);
859
+ const wantsScale = Number.isFinite(scale) && scale !== 1 || hasW || hasH;
860
+ if (isSafari() && wantsScale) {
861
+ const pngUrl = await rasterize(url, { ...options, format: "png", quality: 1, meta });
862
+ return pngUrl;
897
863
  }
898
- if (!hasVar) {
899
- if (!cs) {
864
+ const img = new Image();
865
+ img.decoding = "sync";
866
+ img.loading = "eager";
867
+ img.src = url;
868
+ await img.decode();
869
+ if (hasW && hasH) {
870
+ img.style.width = `${width}px`;
871
+ img.style.height = `${height}px`;
872
+ } else if (hasW) {
873
+ const refW = Number.isFinite(meta.w0) ? meta.w0 : img.naturalWidth;
874
+ const refH = Number.isFinite(meta.h0) ? meta.h0 : img.naturalHeight;
875
+ const k = width / Math.max(1, refW);
876
+ img.style.width = `${width}px`;
877
+ img.style.height = `${Math.round(refH * k)}px`;
878
+ } else if (hasH) {
879
+ const refW = Number.isFinite(meta.w0) ? meta.w0 : img.naturalWidth;
880
+ const refH = Number.isFinite(meta.h0) ? meta.h0 : img.naturalHeight;
881
+ const k = height / Math.max(1, refH);
882
+ img.style.height = `${height}px`;
883
+ img.style.width = `${Math.round(refW * k)}px`;
884
+ } else {
885
+ const cssW = Math.round(img.naturalWidth * scale);
886
+ const cssH = Math.round(img.naturalHeight * scale);
887
+ img.style.width = `${cssW}px`;
888
+ img.style.height = `${cssH}px`;
889
+ if (typeof url === "string" && url.startsWith("data:image/svg+xml")) {
900
890
  try {
901
- cs = getComputedStyle(sourceEl);
891
+ const decoded = decodeURIComponent(url.split(",")[1]);
892
+ const patched = decoded.replace(/width="[^"]*"/, `width="${cssW}"`).replace(/height="[^"]*"/, `height="${cssH}"`);
893
+ url = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(patched)}`;
894
+ img.src = url;
902
895
  } catch {
903
- cs = null;
904
- }
905
- }
906
- if (!cs) return;
907
- const ns = sourceEl.namespaceURI || "html";
908
- const base = getBaselineComputed(sourceEl.tagName, ns);
909
- for (const prop of KEY_PROPS) {
910
- const v = cs.getPropertyValue(prop) || "";
911
- const b = base[prop] || "";
912
- if (v && v !== b) {
913
- try {
914
- cloneEl.style.setProperty(prop, v.trim());
915
- } catch {
916
- }
917
896
  }
918
897
  }
919
898
  }
899
+ return img;
920
900
  }
901
+ var init_toImg = __esm({
902
+ "src/exporters/toImg.js"() {
903
+ init_utils();
904
+ init_rasterize();
905
+ }
906
+ });
921
907
 
922
- // src/core/clone.js
923
- function idleCallback(childList, callback, fast) {
924
- return Promise.all(childList.map((child) => {
925
- return new Promise((resolve) => {
926
- function deal() {
927
- idle((deadline) => {
928
- const hasIdleBudget = deadline && typeof deadline.timeRemaining === "function" ? deadline.timeRemaining() > 0 : true;
929
- if (hasIdleBudget) {
930
- callback(child, resolve);
931
- } else {
932
- deal();
933
- }
934
- }, { fast });
935
- }
936
- deal();
937
- });
938
- }));
908
+ // src/exporters/toBlob.js
909
+ var toBlob_exports = {};
910
+ __export(toBlob_exports, {
911
+ toBlob: () => toBlob
912
+ });
913
+ async function toBlob(url, options) {
914
+ const type = options.type;
915
+ if (type === "svg") {
916
+ const svgText = decodeURIComponent(url.split(",")[1]);
917
+ return new Blob([svgText], { type: "image/svg+xml" });
918
+ }
919
+ const canvas = await toCanvas(url, options);
920
+ return new Promise(
921
+ (resolve) => canvas.toBlob((blob) => resolve(blob), `image/${type}`, options.quality)
922
+ );
939
923
  }
940
- function addNotSlottedRightmost(sel) {
941
- sel = sel.trim();
942
- if (!sel) return sel;
943
- if (/:not\(\s*\[data-sd-slotted\]\s*\)\s*$/.test(sel)) return sel;
944
- return `${sel}:not([data-sd-slotted])`;
945
- }
946
- function wrapWithScope(selectorList, scopeSelector, excludeSlotted = true) {
947
- return selectorList.split(",").map((s) => s.trim()).filter(Boolean).map((s) => {
948
- if (s.startsWith(":where(")) return s;
949
- if (s.startsWith("@")) return s;
950
- const body = excludeSlotted ? addNotSlottedRightmost(s) : s;
951
- return `:where(${scopeSelector} ${body})`;
952
- }).join(", ");
953
- }
954
- function rewriteShadowCSS(cssText, scopeSelector) {
955
- if (!cssText) return "";
956
- cssText = cssText.replace(/:host\(([^)]+)\)/g, (_, sel) => {
957
- return `:where(${scopeSelector}:is(${sel.trim()}))`;
958
- });
959
- cssText = cssText.replace(/:host\b/g, `:where(${scopeSelector})`);
960
- cssText = cssText.replace(/:host-context\(([^)]+)\)/g, (_, sel) => {
961
- return `:where(:where(${sel.trim()}) ${scopeSelector})`;
962
- });
963
- cssText = cssText.replace(/::slotted\(([^)]+)\)/g, (_, sel) => {
964
- return `:where(${scopeSelector} ${sel.trim()})`;
965
- });
966
- cssText = cssText.replace(/(^|})(\s*)([^@}{]+){/g, (_, brace, ws, selectorList) => {
967
- const wrapped = wrapWithScope(
968
- selectorList,
969
- scopeSelector,
970
- /*excludeSlotted*/
971
- true
972
- );
973
- return `${brace}${ws}${wrapped}{`;
974
- });
975
- return cssText;
976
- }
977
- function nextShadowScopeId(sessionCache) {
978
- sessionCache.shadowScopeSeq = (sessionCache.shadowScopeSeq || 0) + 1;
979
- return `s${sessionCache.shadowScopeSeq}`;
980
- }
981
- function extractShadowCSS(sr) {
982
- let css = "";
983
- try {
984
- sr.querySelectorAll("style").forEach((s) => {
985
- css += (s.textContent || "") + "\n";
986
- });
987
- const sheets = sr.adoptedStyleSheets || [];
988
- for (const sh of sheets) {
989
- try {
990
- if (sh && sh.cssRules) {
991
- for (const rule of sh.cssRules) css += rule.cssText + "\n";
992
- }
993
- } catch {
994
- }
995
- }
996
- } catch {
924
+ var init_toBlob = __esm({
925
+ "src/exporters/toBlob.js"() {
926
+ init_toCanvas();
997
927
  }
998
- return css;
928
+ });
929
+
930
+ // src/exporters/download.js
931
+ var download_exports = {};
932
+ __export(download_exports, {
933
+ download: () => download
934
+ });
935
+ async function download(url, options) {
936
+ options.dpr = 1;
937
+ if (options.format === "svg") {
938
+ const blob = await toBlob(url, { ...options, type: "svg" });
939
+ const objectURL = URL.createObjectURL(blob);
940
+ const a2 = document.createElement("a");
941
+ a2.href = objectURL;
942
+ a2.download = options.filename;
943
+ a2.click();
944
+ URL.revokeObjectURL(objectURL);
945
+ return;
946
+ }
947
+ const canvas = await toCanvas(url, options);
948
+ const a = document.createElement("a");
949
+ a.href = canvas.toDataURL(`image/${options.format}`, options.quality);
950
+ a.download = options.filename;
951
+ a.click();
999
952
  }
1000
- function injectScopedStyle(hostClone, cssText, scopeId) {
1001
- if (!cssText) return;
1002
- const style = document.createElement("style");
1003
- style.setAttribute("data-sd", scopeId);
1004
- style.textContent = cssText;
1005
- hostClone.insertBefore(style, hostClone.firstChild || null);
953
+ var init_download = __esm({
954
+ "src/exporters/download.js"() {
955
+ init_toBlob();
956
+ init_toCanvas();
957
+ }
958
+ });
959
+
960
+ // src/core/prepare.js
961
+ init_utils();
962
+
963
+ // src/modules/styles.js
964
+ init_utils();
965
+ init_cache();
966
+ var snapshotCache = /* @__PURE__ */ new WeakMap();
967
+ var snapshotKeyCache = /* @__PURE__ */ new Map();
968
+ var __epoch = 0;
969
+ function bumpEpoch() {
970
+ __epoch++;
1006
971
  }
1007
- function freezeImgSrcset(original, cloned) {
972
+ var __wired = false;
973
+ function setupInvalidationOnce(root = document.documentElement) {
974
+ if (__wired) return;
975
+ __wired = true;
1008
976
  try {
1009
- const chosen = original.currentSrc || original.src || "";
1010
- if (!chosen) return;
1011
- cloned.setAttribute("src", chosen);
1012
- cloned.removeAttribute("srcset");
1013
- cloned.removeAttribute("sizes");
1014
- cloned.loading = "eager";
1015
- cloned.decoding = "sync";
977
+ const domObs = new MutationObserver(() => bumpEpoch());
978
+ domObs.observe(root, { subtree: true, childList: true, characterData: true, attributes: true });
1016
979
  } catch {
1017
980
  }
1018
- }
1019
- function collectCustomPropsFromCSS(cssText) {
1020
- const out = /* @__PURE__ */ new Set();
1021
- if (!cssText) return out;
1022
- const re = /var\(\s*(--[A-Za-z0-9_-]+)\b/g;
1023
- let m;
1024
- while (m = re.exec(cssText)) out.add(m[1]);
1025
- return out;
1026
- }
1027
- function resolveCustomProp(el, name) {
1028
981
  try {
1029
- const cs = getComputedStyle(el);
1030
- let v = cs.getPropertyValue(name).trim();
1031
- if (v) return v;
982
+ const headObs = new MutationObserver(() => bumpEpoch());
983
+ headObs.observe(document.head, { subtree: true, childList: true, characterData: true, attributes: true });
1032
984
  } catch {
1033
985
  }
1034
986
  try {
1035
- const rootCS = getComputedStyle(document.documentElement);
1036
- let v = rootCS.getPropertyValue(name).trim();
1037
- if (v) return v;
987
+ const f = document.fonts;
988
+ if (f) {
989
+ f.addEventListener?.("loadingdone", bumpEpoch);
990
+ f.ready?.then(() => bumpEpoch()).catch(() => {
991
+ });
992
+ }
1038
993
  } catch {
1039
994
  }
1040
- return "";
1041
- }
1042
- function buildSeedCustomPropsRule(hostEl, names, scopeSelector) {
1043
- const decls = [];
1044
- for (const name of names) {
1045
- const val = resolveCustomProp(hostEl, name);
1046
- if (val) decls.push(`${name}: ${val};`);
1047
- }
1048
- if (!decls.length) return "";
1049
- return `${scopeSelector}{${decls.join("")}}
1050
- `;
1051
- }
1052
- function markSlottedSubtree(root) {
1053
- if (!root) return;
1054
- if (root.nodeType === Node.ELEMENT_NODE) {
1055
- root.setAttribute("data-sd-slotted", "");
1056
- }
1057
- if (root.querySelectorAll) {
1058
- root.querySelectorAll("*").forEach((el) => el.setAttribute("data-sd-slotted", ""));
1059
- }
1060
995
  }
1061
- async function getAccessibleIframeDocument(iframe, attempts = 3) {
1062
- const probe = () => {
1063
- try {
1064
- return iframe.contentDocument || iframe.contentWindow?.document || null;
1065
- } catch {
1066
- return null;
996
+ function snapshotComputedStyleFull(style, options = {}) {
997
+ const out = {};
998
+ const vis = style.getPropertyValue("visibility");
999
+ for (let i = 0; i < style.length; i++) {
1000
+ const prop = style[i];
1001
+ let val = style.getPropertyValue(prop);
1002
+ if ((prop === "background-image" || prop === "content") && val.includes("url(") && !val.includes("data:")) {
1003
+ val = "none";
1067
1004
  }
1068
- };
1069
- let doc = probe();
1070
- let i = 0;
1071
- while (i < attempts && (!doc || !doc.body && !doc.documentElement)) {
1072
- await new Promise((r) => setTimeout(r, 0));
1073
- doc = probe();
1074
- i++;
1075
- }
1076
- return doc && (doc.body || doc.documentElement) ? doc : null;
1077
- }
1078
- function measureContentBox(el) {
1079
- const rect = el.getBoundingClientRect();
1080
- let bl = 0, br = 0, bt = 0, bb = 0;
1081
- try {
1082
- const cs = getComputedStyle(el);
1083
- bl = parseFloat(cs.borderLeftWidth) || 0;
1084
- br = parseFloat(cs.borderRightWidth) || 0;
1085
- bt = parseFloat(cs.borderTopWidth) || 0;
1086
- bb = parseFloat(cs.borderBottomWidth) || 0;
1087
- } catch {
1005
+ out[prop] = val;
1088
1006
  }
1089
- const contentWidth = Math.max(0, Math.round(rect.width - (bl + br)));
1090
- const contentHeight = Math.max(0, Math.round(rect.height - (bt + bb)));
1091
- return { contentWidth, contentHeight, rect };
1092
- }
1093
- function pinIframeViewport(doc, w, h) {
1094
- const style = doc.createElement("style");
1095
- style.setAttribute("data-sd-iframe-pin", "");
1096
- style.textContent = `html, body {margin: 0 !important;padding: 0 !important;width: ${w}px !important;height: ${h}px !important;min-width: ${w}px !important;min-height: ${h}px !important;box-sizing: border-box !important;overflow: hidden !important;background-clip: border-box !important;}`;
1097
- (doc.head || doc.documentElement).appendChild(style);
1098
- return () => {
1007
+ const EXTRA_TEXT_DECORATION_PROPS = [
1008
+ "text-decoration-line",
1009
+ "text-decoration-color",
1010
+ "text-decoration-style",
1011
+ "text-decoration-thickness",
1012
+ "text-underline-offset",
1013
+ "text-decoration-skip-ink"
1014
+ ];
1015
+ for (const prop of EXTRA_TEXT_DECORATION_PROPS) {
1016
+ if (out[prop]) continue;
1099
1017
  try {
1100
- style.remove();
1018
+ const v = style.getPropertyValue(prop);
1019
+ if (v) out[prop] = v;
1101
1020
  } catch {
1102
1021
  }
1103
- };
1104
- }
1105
- async function rasterizeIframe(iframe, sessionCache, options) {
1106
- const doc = await getAccessibleIframeDocument(iframe, 3);
1107
- if (!doc) throw new Error("iframe document not accessible/ready");
1108
- const { contentWidth, contentHeight, rect } = measureContentBox(iframe);
1109
- const snap = options?.snap;
1110
- if (!snap || typeof snap.toPng !== "function") {
1111
- throw new Error("snapdom.toPng not available in iframe or window");
1112
1022
  }
1113
- const nested = { ...options, scale: 1 };
1114
- const unpin = pinIframeViewport(doc, contentWidth, contentHeight);
1115
- let imgEl;
1116
- try {
1117
- imgEl = await snap.toPng(doc.documentElement, nested);
1118
- } finally {
1119
- unpin();
1023
+ if (options.embedFonts) {
1024
+ const EXTRA_FONT_PROPS = [
1025
+ "font-feature-settings",
1026
+ "font-variation-settings",
1027
+ "font-kerning",
1028
+ "font-variant",
1029
+ "font-variant-ligatures",
1030
+ "font-optical-sizing"
1031
+ ];
1032
+ for (const prop of EXTRA_FONT_PROPS) {
1033
+ if (out[prop]) continue;
1034
+ try {
1035
+ const v = style.getPropertyValue(prop);
1036
+ if (v) out[prop] = v;
1037
+ } catch {
1038
+ }
1039
+ }
1120
1040
  }
1121
- imgEl.style.display = "block";
1122
- imgEl.style.width = `${contentWidth}px`;
1123
- imgEl.style.height = `${contentHeight}px`;
1124
- const wrapper = document.createElement("div");
1125
- sessionCache.nodeMap.set(wrapper, iframe);
1126
- inlineAllStyles(iframe, wrapper, sessionCache, options);
1127
- wrapper.style.overflow = "hidden";
1128
- wrapper.style.display = "block";
1129
- if (!wrapper.style.width) wrapper.style.width = `${Math.round(rect.width)}px`;
1130
- if (!wrapper.style.height) wrapper.style.height = `${Math.round(rect.height)}px`;
1131
- wrapper.appendChild(imgEl);
1132
- return wrapper;
1041
+ if (vis === "hidden") out.opacity = "0";
1042
+ return out;
1133
1043
  }
1134
- async function deepClone(node, sessionCache, options) {
1135
- if (!node) throw new Error("Invalid node");
1136
- const clonedAssignedNodes = /* @__PURE__ */ new Set();
1137
- let pendingSelectValue = null;
1138
- let pendingTextAreaValue = null;
1139
- if (node.nodeType === Node.ELEMENT_NODE) {
1140
- const tag = (node.localName || node.tagName || "").toLowerCase();
1141
- if (node.id === "snapdom-sandbox" || node.hasAttribute("data-snapdom-sandbox")) {
1142
- return null;
1143
- }
1144
- if (NO_CAPTURE_TAGS.has(tag)) {
1145
- return null;
1146
- }
1044
+ var __snapshotSig = /* @__PURE__ */ new WeakMap();
1045
+ function styleSignature(snap) {
1046
+ let sig = __snapshotSig.get(snap);
1047
+ if (sig) return sig;
1048
+ const entries = Object.entries(snap).sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0);
1049
+ sig = entries.map(([k, v]) => `${k}:${v}`).join(";");
1050
+ __snapshotSig.set(snap, sig);
1051
+ return sig;
1052
+ }
1053
+ function getSnapshot(el, preStyle = null, options = {}) {
1054
+ const rec = snapshotCache.get(el);
1055
+ if (rec && rec.epoch === __epoch) return rec.snapshot;
1056
+ const style = preStyle || getComputedStyle(el);
1057
+ const snap = snapshotComputedStyleFull(style, options);
1058
+ stripHeightForWrappers(el, style, snap);
1059
+ snapshotCache.set(el, { epoch: __epoch, snapshot: snap });
1060
+ return snap;
1061
+ }
1062
+ function _resolveCtx(sessionOrCtx, opts) {
1063
+ if (sessionOrCtx && sessionOrCtx.session && sessionOrCtx.persist) return sessionOrCtx;
1064
+ if (sessionOrCtx && (sessionOrCtx.styleMap || sessionOrCtx.styleCache || sessionOrCtx.nodeMap)) {
1065
+ return {
1066
+ session: sessionOrCtx,
1067
+ persist: {
1068
+ snapshotKeyCache,
1069
+ defaultStyle: cache.defaultStyle,
1070
+ baseStyle: cache.baseStyle,
1071
+ image: cache.image,
1072
+ resource: cache.resource,
1073
+ background: cache.background,
1074
+ font: cache.font
1075
+ },
1076
+ options: opts || {}
1077
+ };
1147
1078
  }
1148
- if (node.nodeType === Node.TEXT_NODE) {
1149
- return node.cloneNode(true);
1079
+ return {
1080
+ session: cache.session,
1081
+ persist: {
1082
+ snapshotKeyCache,
1083
+ defaultStyle: cache.defaultStyle,
1084
+ baseStyle: cache.baseStyle,
1085
+ image: cache.image,
1086
+ resource: cache.resource,
1087
+ background: cache.background,
1088
+ font: cache.font
1089
+ },
1090
+ options: sessionOrCtx || opts || {}
1091
+ };
1092
+ }
1093
+ async function inlineAllStyles(source, clone, sessionOrCtx, opts) {
1094
+ if (source.tagName === "STYLE") return;
1095
+ const ctx = _resolveCtx(sessionOrCtx, opts);
1096
+ const resetMode = ctx.options && ctx.options.cache || "auto";
1097
+ if (resetMode !== "disabled") setupInvalidationOnce(document.documentElement);
1098
+ if (resetMode === "disabled" && !ctx.session.__bumpedForDisabled) {
1099
+ bumpEpoch();
1100
+ snapshotKeyCache.clear();
1101
+ ctx.session.__bumpedForDisabled = true;
1150
1102
  }
1151
- if (node.nodeType !== Node.ELEMENT_NODE) {
1152
- return node.cloneNode(true);
1103
+ if (NO_DEFAULTS_TAGS.has(source.tagName?.toLowerCase())) {
1104
+ const author = source.getAttribute?.("style");
1105
+ if (author) clone.setAttribute("style", author);
1153
1106
  }
1154
- if (node.getAttribute("data-capture") === "exclude") {
1155
- if (options.excludeMode === "hide") {
1156
- const spacer = document.createElement("div");
1157
- const rect = node.getBoundingClientRect();
1158
- spacer.style.cssText = `display:inline-block;width:${rect.width}px;height:${rect.height}px;visibility:hidden;`;
1159
- return spacer;
1160
- } else if (options.excludeMode === "remove") {
1161
- return null;
1107
+ const { session, persist } = ctx;
1108
+ if (!session.styleCache.has(source)) {
1109
+ session.styleCache.set(source, getComputedStyle(source));
1110
+ }
1111
+ const pre = session.styleCache.get(source);
1112
+ const snap = getSnapshot(source, pre, ctx.options);
1113
+ const sig = styleSignature(snap);
1114
+ let key = persist.snapshotKeyCache.get(sig);
1115
+ if (!key) {
1116
+ const tag = source.tagName?.toLowerCase() || "div";
1117
+ key = getStyleKey(snap, tag);
1118
+ persist.snapshotKeyCache.set(sig, key);
1119
+ }
1120
+ session.styleMap.set(clone, key);
1121
+ }
1122
+ function isReplaced(el) {
1123
+ return el instanceof HTMLImageElement || el instanceof HTMLCanvasElement || el instanceof HTMLVideoElement || el instanceof HTMLIFrameElement || el instanceof SVGElement || el instanceof HTMLObjectElement || el instanceof HTMLEmbedElement;
1124
+ }
1125
+ function hasBox(cs) {
1126
+ if (cs.backgroundImage && cs.backgroundImage !== "none") return true;
1127
+ if (cs.backgroundColor && cs.backgroundColor !== "rgba(0, 0, 0, 0)" && cs.backgroundColor !== "transparent") return true;
1128
+ if ((parseFloat(cs.borderTopWidth) || 0) > 0) return true;
1129
+ if ((parseFloat(cs.borderBottomWidth) || 0) > 0) return true;
1130
+ if ((parseFloat(cs.paddingTop) || 0) > 0) return true;
1131
+ if ((parseFloat(cs.paddingBottom) || 0) > 0) return true;
1132
+ const ob = cs.overflowBlock || cs.overflowY || "visible";
1133
+ return ob !== "visible";
1134
+ }
1135
+ function isFlexOrGridItem(el) {
1136
+ const p = el.parentElement;
1137
+ if (!p) return false;
1138
+ const pd = getComputedStyle(p).display || "";
1139
+ return pd.includes("flex") || pd.includes("grid");
1140
+ }
1141
+ function hasFlowFast(el, cs) {
1142
+ if (el.textContent && /\S/.test(el.textContent)) return true;
1143
+ const f = el.firstElementChild, l = el.lastElementChild;
1144
+ if (f && f.tagName === "BR" || l && l.tagName === "BR") return true;
1145
+ const sh = el.scrollHeight;
1146
+ if (sh === 0) return false;
1147
+ const pt = parseFloat(cs.paddingTop) || 0;
1148
+ const pb = parseFloat(cs.paddingBottom) || 0;
1149
+ return sh > pt + pb;
1150
+ }
1151
+ function stripHeightForWrappers(el, cs, snap) {
1152
+ if (el instanceof HTMLElement && el.style && el.style.height) return;
1153
+ const tag = el.tagName && el.tagName.toLowerCase();
1154
+ if (!tag || tag !== "div" && tag !== "section" && tag !== "article" && tag !== "main" && tag !== "aside" && tag !== "header" && tag !== "footer" && tag !== "nav") {
1155
+ return;
1156
+ }
1157
+ const disp = cs.display || "";
1158
+ if (disp.includes("flex") || disp.includes("grid")) return;
1159
+ if (isReplaced(el)) return;
1160
+ const pos = cs.position;
1161
+ if (pos === "absolute" || pos === "fixed" || pos === "sticky") return;
1162
+ if (cs.transform !== "none") return;
1163
+ if (hasBox(cs)) return;
1164
+ if (isFlexOrGridItem(el)) return;
1165
+ const overflowX = cs.overflowX || cs.overflow || "visible";
1166
+ const overflowY = cs.overflowY || cs.overflow || "visible";
1167
+ if (overflowX !== "visible" || overflowY !== "visible") return;
1168
+ const clip = cs.clip;
1169
+ if (clip && clip !== "auto" && clip !== "rect(auto, auto, auto, auto)") return;
1170
+ if (cs.visibility === "hidden" || cs.opacity === "0") return;
1171
+ if (!hasFlowFast(el, cs)) return;
1172
+ delete snap.height;
1173
+ delete snap["block-size"];
1174
+ }
1175
+
1176
+ // src/core/clone.js
1177
+ init_css();
1178
+
1179
+ // src/modules/CSSVar.js
1180
+ var KEY_PROPS = ["fill", "stroke", "color", "background-color", "stop-color"];
1181
+ var __BASELINE_CACHE = /* @__PURE__ */ new Map();
1182
+ function getBaselineComputed(tagName, ns) {
1183
+ const key = ns + "::" + tagName.toLowerCase();
1184
+ let entry = __BASELINE_CACHE.get(key);
1185
+ if (entry) return entry;
1186
+ const doc = document;
1187
+ const el = ns === "http://www.w3.org/2000/svg" ? doc.createElementNS(ns, tagName) : doc.createElement(tagName);
1188
+ const holder = doc.createElement("div");
1189
+ holder.style.cssText = "position:absolute;left:-99999px;top:-99999px;contain:strict;display:block;";
1190
+ holder.appendChild(el);
1191
+ doc.documentElement.appendChild(holder);
1192
+ const cs = getComputedStyle(el);
1193
+ const base = {};
1194
+ for (const p of KEY_PROPS) {
1195
+ base[p] = cs.getPropertyValue(p) || "";
1196
+ }
1197
+ holder.remove();
1198
+ __BASELINE_CACHE.set(key, base);
1199
+ return base;
1200
+ }
1201
+ function resolveCSSVars(sourceEl, cloneEl) {
1202
+ if (!(sourceEl instanceof Element) || !(cloneEl instanceof Element)) return;
1203
+ const styleAttr = sourceEl.getAttribute?.("style");
1204
+ let hasVar = !!(styleAttr && styleAttr.includes("var("));
1205
+ if (!hasVar && sourceEl.attributes?.length) {
1206
+ const attrs = sourceEl.attributes;
1207
+ for (let i = 0; i < attrs.length; i++) {
1208
+ const a = attrs[i];
1209
+ if (a && typeof a.value === "string" && a.value.includes("var(")) {
1210
+ hasVar = true;
1211
+ break;
1212
+ }
1162
1213
  }
1163
1214
  }
1164
- if (options.exclude && Array.isArray(options.exclude)) {
1165
- for (const selector of options.exclude) {
1166
- try {
1167
- if (node.matches?.(selector)) {
1168
- if (options.excludeMode === "hide") {
1169
- const spacer = document.createElement("div");
1170
- const rect = node.getBoundingClientRect();
1171
- spacer.style.cssText = `display:inline-block;width:${rect.width}px;height:${rect.height}px;visibility:hidden;`;
1172
- return spacer;
1173
- } else if (options.excludeMode === "remove") {
1174
- return null;
1215
+ let cs = null;
1216
+ if (hasVar) {
1217
+ try {
1218
+ cs = getComputedStyle(sourceEl);
1219
+ } catch {
1220
+ }
1221
+ }
1222
+ if (hasVar) {
1223
+ const author = sourceEl.style;
1224
+ if (author && author.length) {
1225
+ for (let i = 0; i < author.length; i++) {
1226
+ const prop = author[i];
1227
+ const val = author.getPropertyValue(prop);
1228
+ if (!val || !val.includes("var(")) continue;
1229
+ const resolved = cs && cs.getPropertyValue(prop);
1230
+ if (resolved) {
1231
+ try {
1232
+ cloneEl.style.setProperty(prop, resolved.trim(), author.getPropertyPriority(prop));
1233
+ } catch {
1175
1234
  }
1176
1235
  }
1177
- } catch (err) {
1178
- console.warn(`Invalid selector in exclude option: ${selector}`, err);
1179
1236
  }
1180
1237
  }
1181
1238
  }
1182
- if (typeof options.filter === "function") {
1183
- try {
1184
- if (!options.filter(node)) {
1185
- if (options.filterMode === "hide") {
1186
- const spacer = document.createElement("div");
1187
- const rect = node.getBoundingClientRect();
1188
- spacer.style.cssText = `display:inline-block;width:${rect.width}px;height:${rect.height}px;visibility:hidden;`;
1189
- return spacer;
1190
- } else if (options.filterMode === "remove") {
1191
- return null;
1239
+ if (hasVar && sourceEl.attributes?.length) {
1240
+ const attrs = sourceEl.attributes;
1241
+ for (let i = 0; i < attrs.length; i++) {
1242
+ const a = attrs[i];
1243
+ if (!a || typeof a.value !== "string" || !a.value.includes("var(")) continue;
1244
+ const propName = a.name;
1245
+ const resolved = cs && cs.getPropertyValue(propName);
1246
+ if (resolved) {
1247
+ try {
1248
+ cloneEl.style.setProperty(propName, resolved.trim());
1249
+ } catch {
1192
1250
  }
1193
1251
  }
1194
- } catch (err) {
1195
- console.warn("Error in filter function:", err);
1196
1252
  }
1197
1253
  }
1198
- if (node.tagName === "IFRAME") {
1199
- let sameOrigin = false;
1200
- try {
1201
- sameOrigin = !!(node.contentDocument || node.contentWindow?.document);
1202
- } catch {
1203
- sameOrigin = false;
1204
- }
1205
- if (sameOrigin) {
1254
+ if (!hasVar) {
1255
+ if (!cs) {
1206
1256
  try {
1207
- const wrapper = await rasterizeIframe(node, sessionCache, options);
1208
- return wrapper;
1209
- } catch (err) {
1210
- console.warn("[SnapDOM] iframe rasterization failed, fallback:", err);
1257
+ cs = getComputedStyle(sourceEl);
1258
+ } catch {
1259
+ cs = null;
1211
1260
  }
1212
1261
  }
1213
- if (options.placeholders) {
1214
- const fallback = document.createElement("div");
1215
- fallback.style.cssText = `width:${node.offsetWidth}px;height:${node.offsetHeight}px;background-image:repeating-linear-gradient(45deg,#ddd,#ddd 5px,#f9f9f9 5px,#f9f9f9 10px);display:flex;align-items:center;justify-content:center;font-size:12px;color:#555;border:1px solid #aaa;`;
1216
- inlineAllStyles(node, fallback, sessionCache, options);
1217
- return fallback;
1218
- } else {
1219
- const rect = node.getBoundingClientRect();
1220
- const spacer = document.createElement("div");
1221
- spacer.style.cssText = `display:inline-block;width:${rect.width}px;height:${rect.height}px;visibility:hidden;`;
1222
- inlineAllStyles(node, spacer, sessionCache, options);
1223
- return spacer;
1224
- }
1225
- }
1226
- if (node.getAttribute("data-capture") === "placeholder") {
1227
- const clone2 = node.cloneNode(false);
1228
- sessionCache.nodeMap.set(clone2, node);
1229
- inlineAllStyles(node, clone2, sessionCache, options);
1230
- const placeholder = document.createElement("div");
1231
- placeholder.textContent = node.getAttribute("data-placeholder-text") || "";
1232
- placeholder.style.cssText = "color:#666;font-size:12px;text-align:center;line-height:1.4;padding:0.5em;box-sizing:border-box;";
1233
- clone2.appendChild(placeholder);
1234
- return clone2;
1235
- }
1236
- if (node.tagName === "CANVAS") {
1237
- let url = "";
1238
- try {
1239
- const ctx = node.getContext("2d", { willReadFrequently: true });
1240
- try {
1241
- ctx && ctx.getImageData(0, 0, 1, 1);
1242
- } catch {
1243
- }
1244
- await new Promise((r) => requestAnimationFrame(r));
1245
- url = node.toDataURL("image/png");
1246
- if (!url || url === "data:,") {
1262
+ if (!cs) return;
1263
+ const ns = sourceEl.namespaceURI || "html";
1264
+ const base = getBaselineComputed(sourceEl.tagName, ns);
1265
+ for (const prop of KEY_PROPS) {
1266
+ const v = cs.getPropertyValue(prop) || "";
1267
+ const b = base[prop] || "";
1268
+ if (v && v !== b) {
1247
1269
  try {
1248
- ctx && ctx.getImageData(0, 0, 1, 1);
1270
+ cloneEl.style.setProperty(prop, v.trim());
1249
1271
  } catch {
1250
1272
  }
1251
- await new Promise((r) => requestAnimationFrame(r));
1252
- url = node.toDataURL("image/png");
1253
- if (!url || url === "data:,") {
1254
- const scratch = document.createElement("canvas");
1255
- scratch.width = node.width;
1256
- scratch.height = node.height;
1257
- const sctx = scratch.getContext("2d");
1258
- if (sctx) {
1259
- sctx.drawImage(node, 0, 0);
1260
- url = scratch.toDataURL("image/png");
1261
- }
1262
- }
1263
1273
  }
1264
- } catch {
1265
- }
1266
- const img = document.createElement("img");
1267
- try {
1268
- img.decoding = "sync";
1269
- img.loading = "eager";
1270
- } catch {
1271
- }
1272
- if (url) img.src = url;
1273
- img.width = node.width;
1274
- img.height = node.height;
1275
- try {
1276
- const cs = getComputedStyle(node);
1277
- if (cs.width) img.style.width = cs.width;
1278
- if (cs.height) img.style.height = cs.height;
1279
- } catch {
1280
1274
  }
1281
- sessionCache.nodeMap.set(img, node);
1282
- inlineAllStyles(node, img, sessionCache, options);
1283
- return img;
1284
1275
  }
1285
- let clone;
1276
+ }
1277
+
1278
+ // src/utils/clone.helpers.js
1279
+ init_utils();
1280
+ init_cache();
1281
+ init_snapFetch();
1282
+ function idleCallback(childList, callback, fast) {
1283
+ return Promise.all(childList.map((child) => {
1284
+ return new Promise((resolve) => {
1285
+ function deal() {
1286
+ idle((deadline) => {
1287
+ const hasIdleBudget = deadline && typeof deadline.timeRemaining === "function" ? deadline.timeRemaining() > 0 : true;
1288
+ if (hasIdleBudget) {
1289
+ callback(child, resolve);
1290
+ } else {
1291
+ deal();
1292
+ }
1293
+ }, { fast });
1294
+ }
1295
+ deal();
1296
+ });
1297
+ }));
1298
+ }
1299
+ function addNotSlottedRightmost(sel) {
1300
+ sel = sel.trim();
1301
+ if (!sel) return sel;
1302
+ if (/:not\(\s*\[data-sd-slotted\]\s*\)\s*$/.test(sel)) return sel;
1303
+ return `${sel}:not([data-sd-slotted])`;
1304
+ }
1305
+ function wrapWithScope(selectorList, scopeSelector, excludeSlotted = true) {
1306
+ return selectorList.split(",").map((s) => s.trim()).filter(Boolean).map((s) => {
1307
+ if (s.startsWith(":where(")) return s;
1308
+ if (s.startsWith("@")) return s;
1309
+ const body = excludeSlotted ? addNotSlottedRightmost(s) : s;
1310
+ return `:where(${scopeSelector} ${body})`;
1311
+ }).join(", ");
1312
+ }
1313
+ function rewriteShadowCSS(cssText, scopeSelector) {
1314
+ if (!cssText) return "";
1315
+ cssText = cssText.replace(/:host\(([^)]+)\)/g, (_, sel) => {
1316
+ return `:where(${scopeSelector}:is(${sel.trim()}))`;
1317
+ });
1318
+ cssText = cssText.replace(/:host\b/g, `:where(${scopeSelector})`);
1319
+ cssText = cssText.replace(/:host-context\(([^)]+)\)/g, (_, sel) => {
1320
+ return `:where(:where(${sel.trim()}) ${scopeSelector})`;
1321
+ });
1322
+ cssText = cssText.replace(/::slotted\(([^)]+)\)/g, (_, sel) => {
1323
+ return `:where(${scopeSelector} ${sel.trim()})`;
1324
+ });
1325
+ cssText = cssText.replace(/(^|})(\s*)([^@}{]+){/g, (_, brace, ws, selectorList) => {
1326
+ const wrapped = wrapWithScope(
1327
+ selectorList,
1328
+ scopeSelector,
1329
+ /*excludeSlotted*/
1330
+ true
1331
+ );
1332
+ return `${brace}${ws}${wrapped}{`;
1333
+ });
1334
+ return cssText;
1335
+ }
1336
+ function nextShadowScopeId(sessionCache) {
1337
+ sessionCache.shadowScopeSeq = (sessionCache.shadowScopeSeq || 0) + 1;
1338
+ return `s${sessionCache.shadowScopeSeq}`;
1339
+ }
1340
+ function extractShadowCSS(sr) {
1341
+ let css = "";
1286
1342
  try {
1287
- clone = node.cloneNode(false);
1288
- resolveCSSVars(node, clone);
1289
- sessionCache.nodeMap.set(clone, node);
1290
- if (node.tagName === "IMG") {
1291
- freezeImgSrcset(node, clone);
1343
+ sr.querySelectorAll("style").forEach((s) => {
1344
+ css += (s.textContent || "") + "\n";
1345
+ });
1346
+ const sheets = sr.adoptedStyleSheets || [];
1347
+ for (const sh of sheets) {
1292
1348
  try {
1293
- const rect = node.getBoundingClientRect();
1294
- let w = Math.round(rect.width || 0);
1295
- let h = Math.round(rect.height || 0);
1296
- if (!w || !h) {
1297
- const computed = window.getComputedStyle(node);
1298
- const cssW = parseFloat(computed.width) || 0;
1299
- const cssH = parseFloat(computed.height) || 0;
1300
- const attrW = parseInt(node.getAttribute("width") || "", 10) || 0;
1301
- const attrH = parseInt(node.getAttribute("height") || "", 10) || 0;
1302
- const propW = node.width || node.naturalWidth || 0;
1303
- const propH = node.height || node.naturalHeight || 0;
1304
- w = Math.round(w || cssW || attrW || propW || 0);
1305
- h = Math.round(h || cssH || attrH || propH || 0);
1349
+ if (sh && sh.cssRules) {
1350
+ for (const rule of sh.cssRules) css += rule.cssText + "\n";
1306
1351
  }
1307
- if (w) clone.dataset.snapdomWidth = String(w);
1308
- if (h) clone.dataset.snapdomHeight = String(h);
1309
1352
  } catch {
1310
1353
  }
1311
1354
  }
1312
- } catch (err) {
1313
- console.error("[Snapdom] Failed to clone node:", node, err);
1314
- throw err;
1355
+ } catch {
1315
1356
  }
1316
- if (node instanceof HTMLTextAreaElement) {
1317
- const rect = node.getBoundingClientRect();
1318
- clone.style.width = `${rect.width}px`;
1319
- clone.style.height = `${rect.height}px`;
1357
+ return css;
1358
+ }
1359
+ function injectScopedStyle(hostClone, cssText, scopeId) {
1360
+ if (!cssText) return;
1361
+ const style = document.createElement("style");
1362
+ style.setAttribute("data-sd", scopeId);
1363
+ style.textContent = cssText;
1364
+ hostClone.insertBefore(style, hostClone.firstChild || null);
1365
+ }
1366
+ function freezeImgSrcset(original, cloned) {
1367
+ try {
1368
+ const chosen = original.currentSrc || original.src || "";
1369
+ if (!chosen) return;
1370
+ cloned.setAttribute("src", chosen);
1371
+ cloned.removeAttribute("srcset");
1372
+ cloned.removeAttribute("sizes");
1373
+ cloned.loading = "eager";
1374
+ cloned.decoding = "sync";
1375
+ } catch {
1320
1376
  }
1321
- if (node instanceof HTMLInputElement) {
1322
- clone.value = node.value;
1323
- clone.setAttribute("value", node.value);
1324
- if (node.checked !== void 0) {
1325
- clone.checked = node.checked;
1326
- if (node.checked) clone.setAttribute("checked", "");
1327
- if (node.indeterminate) clone.indeterminate = node.indeterminate;
1328
- }
1377
+ }
1378
+ function collectCustomPropsFromCSS(cssText) {
1379
+ const out = /* @__PURE__ */ new Set();
1380
+ if (!cssText) return out;
1381
+ const re = /var\(\s*(--[A-Za-z0-9_-]+)\b/g;
1382
+ let m;
1383
+ while (m = re.exec(cssText)) out.add(m[1]);
1384
+ return out;
1385
+ }
1386
+ function resolveCustomProp(el, name) {
1387
+ try {
1388
+ const cs = getComputedStyle(el);
1389
+ let v = cs.getPropertyValue(name).trim();
1390
+ if (v) return v;
1391
+ } catch {
1329
1392
  }
1330
- if (node instanceof HTMLSelectElement) {
1331
- pendingSelectValue = node.value;
1393
+ try {
1394
+ const rootCS = getComputedStyle(document.documentElement);
1395
+ let v = rootCS.getPropertyValue(name).trim();
1396
+ if (v) return v;
1397
+ } catch {
1332
1398
  }
1333
- if (node instanceof HTMLTextAreaElement) {
1334
- pendingTextAreaValue = node.value;
1399
+ return "";
1400
+ }
1401
+ function buildSeedCustomPropsRule(hostEl, names, scopeSelector) {
1402
+ const decls = [];
1403
+ for (const name of names) {
1404
+ const val = resolveCustomProp(hostEl, name);
1405
+ if (val) decls.push(`${name}: ${val};`);
1335
1406
  }
1336
- inlineAllStyles(node, clone, sessionCache, options);
1337
- if (node.shadowRoot) {
1338
- let callback2 = function(child, resolve) {
1339
- if (child.nodeType === Node.ELEMENT_NODE && child.tagName === "STYLE") {
1340
- return resolve(null);
1341
- } else {
1342
- deepClone(child, sessionCache, options).then((clonedChild) => {
1343
- resolve(clonedChild || null);
1344
- }).catch(() => {
1345
- resolve(null);
1346
- });
1347
- }
1348
- };
1407
+ if (!decls.length) return "";
1408
+ return `${scopeSelector}{${decls.join("")}}
1409
+ `;
1410
+ }
1411
+ function markSlottedSubtree(root) {
1412
+ if (!root) return;
1413
+ if (root.nodeType === Node.ELEMENT_NODE) {
1414
+ root.setAttribute("data-sd-slotted", "");
1415
+ }
1416
+ if (root.querySelectorAll) {
1417
+ root.querySelectorAll("*").forEach((el) => el.setAttribute("data-sd-slotted", ""));
1418
+ }
1419
+ }
1420
+ async function getAccessibleIframeDocument(iframe, attempts = 3) {
1421
+ const probe = () => {
1349
1422
  try {
1350
- const slots = node.shadowRoot.querySelectorAll("slot");
1351
- for (const s of slots) {
1352
- let assigned = [];
1353
- try {
1354
- assigned = s.assignedNodes?.({ flatten: true }) || s.assignedNodes?.() || [];
1355
- } catch {
1356
- assigned = s.assignedNodes?.() || [];
1357
- }
1358
- for (const an of assigned) clonedAssignedNodes.add(an);
1359
- }
1360
- } catch {
1361
- }
1362
- const scopeId = nextShadowScopeId(sessionCache);
1363
- const scopeSelector = `[data-sd="${scopeId}"]`;
1364
- try {
1365
- clone.setAttribute("data-sd", scopeId);
1423
+ return iframe.contentDocument || iframe.contentWindow?.document || null;
1366
1424
  } catch {
1425
+ return null;
1367
1426
  }
1368
- const rawCSS = extractShadowCSS(node.shadowRoot);
1369
- const rewritten = rewriteShadowCSS(rawCSS, scopeSelector);
1370
- const neededVars = collectCustomPropsFromCSS(rawCSS);
1371
- const seed = buildSeedCustomPropsRule(node, neededVars, scopeSelector);
1372
- injectScopedStyle(clone, seed + rewritten, scopeId);
1373
- const shadowFrag = document.createDocumentFragment();
1374
- const cloneList2 = await idleCallback(Array.from(node.shadowRoot.childNodes), callback2, options.fast);
1375
- shadowFrag.append(...cloneList2.filter((clonedChild) => !!clonedChild));
1376
- clone.appendChild(shadowFrag);
1377
- }
1378
- if (node.tagName === "SLOT") {
1379
- let callback2 = function(child, resolve) {
1380
- deepClone(child, sessionCache, options).then((clonedChild) => {
1381
- if (clonedChild) {
1382
- markSlottedSubtree(clonedChild);
1383
- }
1384
- resolve(clonedChild || null);
1385
- }).catch(() => {
1386
- resolve(null);
1387
- });
1388
- };
1389
- const assigned = node.assignedNodes?.({ flatten: true }) || [];
1390
- const nodesToClone = assigned.length > 0 ? assigned : Array.from(node.childNodes);
1391
- const fragment = document.createDocumentFragment();
1392
- const cloneList2 = await idleCallback(Array.from(nodesToClone), callback2, options.fast);
1393
- fragment.append(...cloneList2.filter((clonedChild) => !!clonedChild));
1394
- return fragment;
1395
- }
1396
- function callback(child, resolve) {
1397
- if (clonedAssignedNodes.has(child)) return resolve(null);
1398
- deepClone(child, sessionCache, options).then((clonedChild) => {
1399
- resolve(clonedChild || null);
1400
- }).catch(() => {
1401
- resolve(null);
1402
- });
1403
- }
1404
- const cloneList = await idleCallback(Array.from(node.childNodes), callback, options.fast);
1405
- clone.append(...cloneList.filter((clonedChild) => !!clonedChild));
1406
- if (pendingSelectValue !== null && clone instanceof HTMLSelectElement) {
1407
- clone.value = pendingSelectValue;
1408
- for (const opt of clone.options) {
1409
- if (opt.value === pendingSelectValue) {
1410
- opt.setAttribute("selected", "");
1411
- } else {
1412
- opt.removeAttribute("selected");
1413
- }
1414
- }
1415
- }
1416
- if (pendingTextAreaValue !== null && clone instanceof HTMLTextAreaElement) {
1417
- clone.textContent = pendingTextAreaValue;
1418
- }
1419
- return clone;
1420
- }
1421
-
1422
- // src/modules/iconFonts.js
1423
- var defaultIconFonts = [
1424
- // /uicons/i,
1425
- /font\s*awesome/i,
1426
- /material\s*icons/i,
1427
- /ionicons/i,
1428
- /glyphicons/i,
1429
- /feather/i,
1430
- /bootstrap\s*icons/i,
1431
- /remix\s*icons/i,
1432
- /heroicons/i,
1433
- /layui/i,
1434
- /lucide/i
1435
- ];
1436
- var ICON_FONT_URLS = Object.assign({
1437
- materialIconsFilled: "https://fonts.gstatic.com/s/materialicons/v48/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.woff2",
1438
- materialIconsOutlined: "https://fonts.gstatic.com/s/materialiconsoutlined/v110/gok-H7zzDkdnRel8-DQ6KAXJ69wP1tGnf4ZGhUcel5euIg.woff2",
1439
- materialIconsRound: "https://fonts.gstatic.com/s/materialiconsround/v109/LDItaoyNOAY6Uewc665JcIzCKsKc_M9flwmPq_HTTw.woff2",
1440
- materialIconsSharp: "https://fonts.gstatic.com/s/materialiconssharp/v110/oPWQ_lt5nv4pWNJpghLP75WiFR4kLh3kvmvRImcycg.woff2"
1441
- }, typeof window !== "undefined" && window.__SNAPDOM_ICON_FONTS__ || {});
1442
- var userIconFonts = [];
1443
- function extendIconFonts(fonts) {
1444
- const list = Array.isArray(fonts) ? fonts : [fonts];
1445
- for (const f of list) {
1446
- if (f instanceof RegExp) userIconFonts.push(f);
1447
- else if (typeof f === "string") userIconFonts.push(new RegExp(f, "i"));
1448
- else console.warn("[snapdom] Ignored invalid iconFont value:", f);
1427
+ };
1428
+ let doc = probe();
1429
+ let i = 0;
1430
+ while (i < attempts && (!doc || !doc.body && !doc.documentElement)) {
1431
+ await new Promise((r) => setTimeout(r, 0));
1432
+ doc = probe();
1433
+ i++;
1449
1434
  }
1435
+ return doc && (doc.body || doc.documentElement) ? doc : null;
1450
1436
  }
1451
- function isIconFont2(input) {
1452
- const text = typeof input === "string" ? input : "";
1453
- const candidates = [...defaultIconFonts, ...userIconFonts];
1454
- for (const rx of candidates) {
1455
- if (rx instanceof RegExp && rx.test(text)) return true;
1437
+ function measureContentBox(el) {
1438
+ const rect = el.getBoundingClientRect();
1439
+ let bl = 0, br = 0, bt = 0, bb = 0;
1440
+ try {
1441
+ const cs = getComputedStyle(el);
1442
+ bl = parseFloat(cs.borderLeftWidth) || 0;
1443
+ br = parseFloat(cs.borderRightWidth) || 0;
1444
+ bt = parseFloat(cs.borderTopWidth) || 0;
1445
+ bb = parseFloat(cs.borderBottomWidth) || 0;
1446
+ } catch {
1456
1447
  }
1457
- if (/icon/i.test(text) || /glyph/i.test(text) || /symbols/i.test(text) || /feather/i.test(text) || /fontawesome/i.test(text)) return true;
1458
- return false;
1459
- }
1460
- function isMaterialFamily(family = "") {
1461
- const s = String(family).toLowerCase();
1462
- return /\bmaterial\s*icons\b/.test(s) || /\bmaterial\s*symbols\b/.test(s);
1448
+ const contentWidth = Math.max(0, Math.round(rect.width - (bl + br)));
1449
+ const contentHeight = Math.max(0, Math.round(rect.height - (bt + bb)));
1450
+ return { contentWidth, contentHeight, rect };
1463
1451
  }
1464
- var loadedCanvasFamilies = /* @__PURE__ */ new Map();
1465
- function parseAxes(variation = "") {
1466
- const out = /* @__PURE__ */ Object.create(null);
1467
- const v = String(variation || "");
1468
- const rx = /['"]?\s*([A-Za-z]{3,4})\s*['"]?\s*([+-]?\d+(?:\.\d+)?)\s*/g;
1469
- let m;
1470
- while (m = rx.exec(v)) out[m[1].toUpperCase()] = Number(m[2]);
1471
- return out;
1452
+ function pinIframeViewport(doc, w, h) {
1453
+ const style = doc.createElement("style");
1454
+ style.setAttribute("data-sd-iframe-pin", "");
1455
+ style.textContent = `html, body {margin: 0 !important;padding: 0 !important;width: ${w}px !important;height: ${h}px !important;min-width: ${w}px !important;min-height: ${h}px !important;box-sizing: border-box !important;overflow: hidden !important;background-clip: border-box !important;}`;
1456
+ (doc.head || doc.documentElement).appendChild(style);
1457
+ return () => {
1458
+ try {
1459
+ style.remove();
1460
+ } catch {
1461
+ }
1462
+ };
1472
1463
  }
1473
- async function ensureLigatureCanvasFont(cssFamily, className, axes) {
1474
- const fam = String(cssFamily || "");
1475
- const lowerFam = fam.toLowerCase();
1476
- const cls = String(className || "").toLowerCase();
1477
- if (/\bmaterial\s*icons\b/.test(lowerFam) && !/\bsymbols\b/.test(lowerFam)) {
1478
- return { familyForMeasure: fam, familyForCanvas: fam };
1464
+ async function rasterizeIframe(iframe, sessionCache, options) {
1465
+ const doc = await getAccessibleIframeDocument(iframe, 3);
1466
+ if (!doc) throw new Error("iframe document not accessible/ready");
1467
+ const { contentWidth, contentHeight, rect } = measureContentBox(iframe);
1468
+ const snap = options?.snap;
1469
+ if (!snap || typeof snap.toPng !== "function") {
1470
+ throw new Error("snapdom.toPng not available in iframe or window");
1479
1471
  }
1480
- const isSymbols = /\bmaterial\s*symbols\b/.test(lowerFam);
1481
- if (!isSymbols) {
1482
- return { familyForMeasure: fam, familyForCanvas: fam };
1472
+ const nested = { ...options, scale: 1 };
1473
+ const unpin = pinIframeViewport(doc, contentWidth, contentHeight);
1474
+ let imgEl;
1475
+ try {
1476
+ imgEl = await snap.toPng(doc.documentElement, nested);
1477
+ } finally {
1478
+ unpin();
1483
1479
  }
1484
- const FILL = axes && (axes.FILL ?? axes.fill);
1485
- let style = "outlined";
1486
- if (/\brounded\b/.test(cls) || /\bround\b/.test(cls)) style = "rounded";
1487
- else if (/\bsharp\b/.test(cls)) style = "sharp";
1488
- else if (/\boutlined\b/.test(cls)) style = "outlined";
1489
- const filled = FILL === 1;
1490
- let pick = null;
1491
- if (filled) {
1492
- if (style === "outlined" && ICON_FONT_URLS.materialIconsFilled) {
1493
- pick = { url: ICON_FONT_URLS.materialIconsFilled, alias: "snapdom-mi-filled" };
1494
- } else if (style === "rounded" && ICON_FONT_URLS.materialIconsRound) {
1495
- pick = { url: ICON_FONT_URLS.materialIconsRound, alias: "snapdom-mi-round" };
1496
- } else if (style === "sharp" && ICON_FONT_URLS.materialIconsSharp) {
1497
- pick = { url: ICON_FONT_URLS.materialIconsSharp, alias: "snapdom-mi-sharp" };
1480
+ imgEl.style.display = "block";
1481
+ imgEl.style.width = `${contentWidth}px`;
1482
+ imgEl.style.height = `${contentHeight}px`;
1483
+ const wrapper = document.createElement("div");
1484
+ sessionCache.nodeMap.set(wrapper, iframe);
1485
+ inlineAllStyles(iframe, wrapper, sessionCache, options);
1486
+ wrapper.style.overflow = "hidden";
1487
+ wrapper.style.display = "block";
1488
+ if (!wrapper.style.width) wrapper.style.width = `${Math.round(rect.width)}px`;
1489
+ if (!wrapper.style.height) wrapper.style.height = `${Math.round(rect.height)}px`;
1490
+ wrapper.appendChild(imgEl);
1491
+ return wrapper;
1492
+ }
1493
+ var _blobToDataUrlCache = /* @__PURE__ */ new Map();
1494
+ async function blobUrlToDataUrl(blobUrl) {
1495
+ if (cache.resource?.has(blobUrl)) return cache.resource.get(blobUrl);
1496
+ if (_blobToDataUrlCache.has(blobUrl)) return _blobToDataUrlCache.get(blobUrl);
1497
+ const p = (async () => {
1498
+ const r = await snapFetch(blobUrl, { as: "dataURL", silent: true });
1499
+ if (!r.ok || typeof r.data !== "string") {
1500
+ throw new Error(`[snapDOM] Failed to read blob URL: ${blobUrl}`);
1498
1501
  }
1502
+ cache.resource?.set(blobUrl, r.data);
1503
+ return r.data;
1504
+ })();
1505
+ _blobToDataUrlCache.set(blobUrl, p);
1506
+ try {
1507
+ const data = await p;
1508
+ _blobToDataUrlCache.set(blobUrl, data);
1509
+ return data;
1510
+ } catch (e) {
1511
+ _blobToDataUrlCache.delete(blobUrl);
1512
+ throw e;
1499
1513
  }
1500
- if (!pick) {
1501
- return { familyForMeasure: fam, familyForCanvas: fam };
1502
- }
1503
- if (!loadedCanvasFamilies.has(pick.alias)) {
1514
+ }
1515
+ var BLOB_URL_RE = /\bblob:[^)"'\s]+/g;
1516
+ async function replaceBlobUrlsInCssText(cssText) {
1517
+ if (!cssText || cssText.indexOf("blob:") === -1) return cssText;
1518
+ const uniques = Array.from(new Set(cssText.match(BLOB_URL_RE) || []));
1519
+ if (uniques.length === 0) return cssText;
1520
+ let out = cssText;
1521
+ for (const u of uniques) {
1504
1522
  try {
1505
- const ff = new FontFace(pick.alias, `url(${pick.url})`, { style: "normal", weight: "400" });
1506
- document.fonts.add(ff);
1507
- await ff.load();
1508
- loadedCanvasFamilies.set(pick.alias, true);
1523
+ const d = await blobUrlToDataUrl(u);
1524
+ out = out.split(u).join(d);
1509
1525
  } catch {
1510
- return { familyForMeasure: fam, familyForCanvas: fam };
1511
1526
  }
1512
1527
  }
1513
- const quoted = `"${pick.alias}"`;
1514
- return { familyForMeasure: quoted, familyForCanvas: quoted };
1528
+ return out;
1515
1529
  }
1516
- async function ensureMaterialFontsReady(family = "Material Icons", px = 24) {
1517
- try {
1518
- await Promise.all([
1519
- document.fonts.load(`400 ${px}px "${String(family).replace(/["']/g, "")}"`),
1520
- document.fonts.ready
1521
- ]);
1522
- } catch {
1523
- }
1530
+ function isBlobUrl(u) {
1531
+ return typeof u === "string" && u.startsWith("blob:");
1524
1532
  }
1525
- function resolvePaintColor(cs) {
1526
- let fill = cs.getPropertyValue("-webkit-text-fill-color")?.trim() || "";
1527
- const isTransparent = /^transparent$/i.test(fill) || /rgba?\(\s*0\s*,\s*0\s*,\s*0\s*,\s*0\s*\)/i.test(fill);
1528
- if (fill && !isTransparent && fill.toLowerCase() !== "currentcolor") return fill;
1529
- const c = cs.color?.trim();
1533
+ function parseSrcset(srcset) {
1534
+ return (srcset || "").split(",").map((s) => s.trim()).filter(Boolean).map((item) => {
1535
+ const m = item.match(/^(\S+)(\s+.+)?$/);
1536
+ return m ? { url: m[1], desc: m[2] || "" } : null;
1537
+ }).filter(Boolean);
1538
+ }
1539
+ function stringifySrcset(parts) {
1540
+ return parts.map((p) => p.desc ? `${p.url} ${p.desc.trim()}` : p.url).join(", ");
1541
+ }
1542
+ async function resolveBlobUrlsInTree(root) {
1543
+ if (!root) return;
1544
+ const imgs = root.querySelectorAll ? root.querySelectorAll("img") : [];
1545
+ for (const img of imgs) {
1546
+ try {
1547
+ const srcAttr = img.getAttribute("src");
1548
+ const effective = srcAttr || img.currentSrc || "";
1549
+ if (isBlobUrl(effective)) {
1550
+ const data = await blobUrlToDataUrl(effective);
1551
+ img.setAttribute("src", data);
1552
+ }
1553
+ const srcset = img.getAttribute("srcset");
1554
+ if (srcset && srcset.includes("blob:")) {
1555
+ const parts = parseSrcset(srcset);
1556
+ let changed = false;
1557
+ for (const p of parts) {
1558
+ if (isBlobUrl(p.url)) {
1559
+ try {
1560
+ p.url = await blobUrlToDataUrl(p.url);
1561
+ changed = true;
1562
+ } catch {
1563
+ }
1564
+ }
1565
+ }
1566
+ if (changed) img.setAttribute("srcset", stringifySrcset(parts));
1567
+ }
1568
+ } catch {
1569
+ }
1570
+ }
1571
+ const svgImages = root.querySelectorAll ? root.querySelectorAll("image") : [];
1572
+ for (const node of svgImages) {
1573
+ try {
1574
+ const XLINK_NS = "http://www.w3.org/1999/xlink";
1575
+ const href = node.getAttribute("href") || node.getAttributeNS?.(XLINK_NS, "href");
1576
+ if (isBlobUrl(href)) {
1577
+ const d = await blobUrlToDataUrl(href);
1578
+ node.setAttribute("href", d);
1579
+ node.removeAttributeNS?.(XLINK_NS, "href");
1580
+ }
1581
+ } catch {
1582
+ }
1583
+ }
1584
+ const styled = root.querySelectorAll ? root.querySelectorAll("[style*='blob:']") : [];
1585
+ for (const el of styled) {
1586
+ try {
1587
+ const styleText = el.getAttribute("style");
1588
+ if (styleText && styleText.includes("blob:")) {
1589
+ const replaced = await replaceBlobUrlsInCssText(styleText);
1590
+ el.setAttribute("style", replaced);
1591
+ }
1592
+ } catch {
1593
+ }
1594
+ }
1595
+ const styleTags = root.querySelectorAll ? root.querySelectorAll("style") : [];
1596
+ for (const s of styleTags) {
1597
+ try {
1598
+ const css = s.textContent || "";
1599
+ if (css.includes("blob:")) {
1600
+ s.textContent = await replaceBlobUrlsInCssText(css);
1601
+ }
1602
+ } catch {
1603
+ }
1604
+ }
1605
+ const urlAttrs = ["poster"];
1606
+ for (const attr of urlAttrs) {
1607
+ const nodes = root.querySelectorAll ? root.querySelectorAll(`[${attr}^='blob:']`) : [];
1608
+ for (const n of nodes) {
1609
+ try {
1610
+ const u = n.getAttribute(attr);
1611
+ if (isBlobUrl(u)) {
1612
+ n.setAttribute(attr, await blobUrlToDataUrl(u));
1613
+ }
1614
+ } catch {
1615
+ }
1616
+ }
1617
+ }
1618
+ }
1619
+
1620
+ // src/core/clone.js
1621
+ async function deepClone(node, sessionCache, options) {
1622
+ if (!node) throw new Error("Invalid node");
1623
+ const clonedAssignedNodes = /* @__PURE__ */ new Set();
1624
+ let pendingSelectValue = null;
1625
+ let pendingTextAreaValue = null;
1626
+ if (node.nodeType === Node.ELEMENT_NODE) {
1627
+ const tag = (node.localName || node.tagName || "").toLowerCase();
1628
+ if (node.id === "snapdom-sandbox" || node.hasAttribute("data-snapdom-sandbox")) {
1629
+ return null;
1630
+ }
1631
+ if (NO_CAPTURE_TAGS.has(tag)) {
1632
+ return null;
1633
+ }
1634
+ }
1635
+ if (node.nodeType === Node.TEXT_NODE) {
1636
+ return node.cloneNode(true);
1637
+ }
1638
+ if (node.nodeType !== Node.ELEMENT_NODE) {
1639
+ return node.cloneNode(true);
1640
+ }
1641
+ if (node.getAttribute("data-capture") === "exclude") {
1642
+ if (options.excludeMode === "hide") {
1643
+ const spacer = document.createElement("div");
1644
+ const rect = node.getBoundingClientRect();
1645
+ spacer.style.cssText = `display:inline-block;width:${rect.width}px;height:${rect.height}px;visibility:hidden;`;
1646
+ return spacer;
1647
+ } else if (options.excludeMode === "remove") {
1648
+ return null;
1649
+ }
1650
+ }
1651
+ if (options.exclude && Array.isArray(options.exclude)) {
1652
+ for (const selector of options.exclude) {
1653
+ try {
1654
+ if (node.matches?.(selector)) {
1655
+ if (options.excludeMode === "hide") {
1656
+ const spacer = document.createElement("div");
1657
+ const rect = node.getBoundingClientRect();
1658
+ spacer.style.cssText = `display:inline-block;width:${rect.width}px;height:${rect.height}px;visibility:hidden;`;
1659
+ return spacer;
1660
+ } else if (options.excludeMode === "remove") {
1661
+ return null;
1662
+ }
1663
+ }
1664
+ } catch (err) {
1665
+ console.warn(`Invalid selector in exclude option: ${selector}`, err);
1666
+ }
1667
+ }
1668
+ }
1669
+ if (typeof options.filter === "function") {
1670
+ try {
1671
+ if (!options.filter(node)) {
1672
+ if (options.filterMode === "hide") {
1673
+ const spacer = document.createElement("div");
1674
+ const rect = node.getBoundingClientRect();
1675
+ spacer.style.cssText = `display:inline-block;width:${rect.width}px;height:${rect.height}px;visibility:hidden;`;
1676
+ return spacer;
1677
+ } else if (options.filterMode === "remove") {
1678
+ return null;
1679
+ }
1680
+ }
1681
+ } catch (err) {
1682
+ console.warn("Error in filter function:", err);
1683
+ }
1684
+ }
1685
+ if (node.tagName === "IFRAME") {
1686
+ let sameOrigin = false;
1687
+ try {
1688
+ sameOrigin = !!(node.contentDocument || node.contentWindow?.document);
1689
+ } catch {
1690
+ sameOrigin = false;
1691
+ }
1692
+ if (sameOrigin) {
1693
+ try {
1694
+ const wrapper = await rasterizeIframe(node, sessionCache, options);
1695
+ return wrapper;
1696
+ } catch (err) {
1697
+ console.warn("[SnapDOM] iframe rasterization failed, fallback:", err);
1698
+ }
1699
+ }
1700
+ if (options.placeholders) {
1701
+ const fallback = document.createElement("div");
1702
+ fallback.style.cssText = `width:${node.offsetWidth}px;height:${node.offsetHeight}px;background-image:repeating-linear-gradient(45deg,#ddd,#ddd 5px,#f9f9f9 5px,#f9f9f9 10px);display:flex;align-items:center;justify-content:center;font-size:12px;color:#555;border:1px solid #aaa;`;
1703
+ inlineAllStyles(node, fallback, sessionCache, options);
1704
+ return fallback;
1705
+ } else {
1706
+ const rect = node.getBoundingClientRect();
1707
+ const spacer = document.createElement("div");
1708
+ spacer.style.cssText = `display:inline-block;width:${rect.width}px;height:${rect.height}px;visibility:hidden;`;
1709
+ inlineAllStyles(node, spacer, sessionCache, options);
1710
+ return spacer;
1711
+ }
1712
+ }
1713
+ if (node.getAttribute("data-capture") === "placeholder") {
1714
+ const clone2 = node.cloneNode(false);
1715
+ sessionCache.nodeMap.set(clone2, node);
1716
+ inlineAllStyles(node, clone2, sessionCache, options);
1717
+ const placeholder = document.createElement("div");
1718
+ placeholder.textContent = node.getAttribute("data-placeholder-text") || "";
1719
+ placeholder.style.cssText = "color:#666;font-size:12px;text-align:center;line-height:1.4;padding:0.5em;box-sizing:border-box;";
1720
+ clone2.appendChild(placeholder);
1721
+ return clone2;
1722
+ }
1723
+ if (node.tagName === "CANVAS") {
1724
+ let url = "";
1725
+ try {
1726
+ const ctx = node.getContext("2d", { willReadFrequently: true });
1727
+ try {
1728
+ ctx && ctx.getImageData(0, 0, 1, 1);
1729
+ } catch {
1730
+ }
1731
+ await new Promise((r) => requestAnimationFrame(r));
1732
+ url = node.toDataURL("image/png");
1733
+ if (!url || url === "data:,") {
1734
+ try {
1735
+ ctx && ctx.getImageData(0, 0, 1, 1);
1736
+ } catch {
1737
+ }
1738
+ await new Promise((r) => requestAnimationFrame(r));
1739
+ url = node.toDataURL("image/png");
1740
+ if (!url || url === "data:,") {
1741
+ const scratch = document.createElement("canvas");
1742
+ scratch.width = node.width;
1743
+ scratch.height = node.height;
1744
+ const sctx = scratch.getContext("2d");
1745
+ if (sctx) {
1746
+ sctx.drawImage(node, 0, 0);
1747
+ url = scratch.toDataURL("image/png");
1748
+ }
1749
+ }
1750
+ }
1751
+ } catch {
1752
+ }
1753
+ const img = document.createElement("img");
1754
+ try {
1755
+ img.decoding = "sync";
1756
+ img.loading = "eager";
1757
+ } catch {
1758
+ }
1759
+ if (url) img.src = url;
1760
+ img.width = node.width;
1761
+ img.height = node.height;
1762
+ try {
1763
+ const cs = getComputedStyle(node);
1764
+ if (cs.width) img.style.width = cs.width;
1765
+ if (cs.height) img.style.height = cs.height;
1766
+ } catch {
1767
+ }
1768
+ sessionCache.nodeMap.set(img, node);
1769
+ inlineAllStyles(node, img, sessionCache, options);
1770
+ return img;
1771
+ }
1772
+ let clone;
1773
+ try {
1774
+ clone = node.cloneNode(false);
1775
+ resolveCSSVars(node, clone);
1776
+ sessionCache.nodeMap.set(clone, node);
1777
+ if (node.tagName === "IMG") {
1778
+ freezeImgSrcset(node, clone);
1779
+ try {
1780
+ const rect = node.getBoundingClientRect();
1781
+ let w = Math.round(rect.width || 0);
1782
+ let h = Math.round(rect.height || 0);
1783
+ if (!w || !h) {
1784
+ const computed = window.getComputedStyle(node);
1785
+ const cssW = parseFloat(computed.width) || 0;
1786
+ const cssH = parseFloat(computed.height) || 0;
1787
+ const attrW = parseInt(node.getAttribute("width") || "", 10) || 0;
1788
+ const attrH = parseInt(node.getAttribute("height") || "", 10) || 0;
1789
+ const propW = node.width || node.naturalWidth || 0;
1790
+ const propH = node.height || node.naturalHeight || 0;
1791
+ w = Math.round(w || cssW || attrW || propW || 0);
1792
+ h = Math.round(h || cssH || attrH || propH || 0);
1793
+ }
1794
+ if (w) clone.dataset.snapdomWidth = String(w);
1795
+ if (h) clone.dataset.snapdomHeight = String(h);
1796
+ } catch {
1797
+ }
1798
+ try {
1799
+ const authored = node.getAttribute("style") || "";
1800
+ const cs = window.getComputedStyle(node);
1801
+ const usesPercentOrAuto = (prop) => {
1802
+ const a = authored.match(new RegExp(`${prop}\\s*:\\s*([^;]+)`, "i"));
1803
+ const v = a ? a[1].trim() : cs.getPropertyValue(prop);
1804
+ return /%|auto/i.test(String(v || ""));
1805
+ };
1806
+ const w = parseInt(clone.dataset.snapdomWidth || "0", 10);
1807
+ const h = parseInt(clone.dataset.snapdomHeight || "0", 10);
1808
+ const needFreezeW = usesPercentOrAuto("width") || !w;
1809
+ const needFreezeH = usesPercentOrAuto("height") || !h;
1810
+ if (needFreezeW && w) clone.style.width = `${w}px`;
1811
+ if (needFreezeH && h) clone.style.height = `${h}px`;
1812
+ if (w) clone.style.minWidth = `${w}px`;
1813
+ if (h) clone.style.minHeight = `${h}px`;
1814
+ } catch {
1815
+ }
1816
+ }
1817
+ } catch (err) {
1818
+ console.error("[Snapdom] Failed to clone node:", node, err);
1819
+ throw err;
1820
+ }
1821
+ if (node instanceof HTMLTextAreaElement) {
1822
+ const rect = node.getBoundingClientRect();
1823
+ clone.style.width = `${rect.width}px`;
1824
+ clone.style.height = `${rect.height}px`;
1825
+ }
1826
+ if (node instanceof HTMLInputElement) {
1827
+ clone.value = node.value;
1828
+ clone.setAttribute("value", node.value);
1829
+ if (node.checked !== void 0) {
1830
+ clone.checked = node.checked;
1831
+ if (node.checked) clone.setAttribute("checked", "");
1832
+ if (node.indeterminate) clone.indeterminate = node.indeterminate;
1833
+ }
1834
+ }
1835
+ if (node instanceof HTMLSelectElement) {
1836
+ pendingSelectValue = node.value;
1837
+ }
1838
+ if (node instanceof HTMLTextAreaElement) {
1839
+ pendingTextAreaValue = node.value;
1840
+ }
1841
+ inlineAllStyles(node, clone, sessionCache, options);
1842
+ if (node.shadowRoot) {
1843
+ let callback2 = function(child, resolve) {
1844
+ if (child.nodeType === Node.ELEMENT_NODE && child.tagName === "STYLE") {
1845
+ return resolve(null);
1846
+ } else {
1847
+ deepClone(child, sessionCache, options).then((clonedChild) => {
1848
+ resolve(clonedChild || null);
1849
+ }).catch(() => {
1850
+ resolve(null);
1851
+ });
1852
+ }
1853
+ };
1854
+ try {
1855
+ const slots = node.shadowRoot.querySelectorAll("slot");
1856
+ for (const s of slots) {
1857
+ let assigned = [];
1858
+ try {
1859
+ assigned = s.assignedNodes?.({ flatten: true }) || s.assignedNodes?.() || [];
1860
+ } catch {
1861
+ assigned = s.assignedNodes?.() || [];
1862
+ }
1863
+ for (const an of assigned) clonedAssignedNodes.add(an);
1864
+ }
1865
+ } catch {
1866
+ }
1867
+ const scopeId = nextShadowScopeId(sessionCache);
1868
+ const scopeSelector = `[data-sd="${scopeId}"]`;
1869
+ try {
1870
+ clone.setAttribute("data-sd", scopeId);
1871
+ } catch {
1872
+ }
1873
+ const rawCSS = extractShadowCSS(node.shadowRoot);
1874
+ const rewritten = rewriteShadowCSS(rawCSS, scopeSelector);
1875
+ const neededVars = collectCustomPropsFromCSS(rawCSS);
1876
+ const seed = buildSeedCustomPropsRule(node, neededVars, scopeSelector);
1877
+ injectScopedStyle(clone, seed + rewritten, scopeId);
1878
+ const shadowFrag = document.createDocumentFragment();
1879
+ const cloneList2 = await idleCallback(Array.from(node.shadowRoot.childNodes), callback2, options.fast);
1880
+ shadowFrag.append(...cloneList2.filter((clonedChild) => !!clonedChild));
1881
+ clone.appendChild(shadowFrag);
1882
+ }
1883
+ if (node.tagName === "SLOT") {
1884
+ let callback2 = function(child, resolve) {
1885
+ deepClone(child, sessionCache, options).then((clonedChild) => {
1886
+ if (clonedChild) {
1887
+ markSlottedSubtree(clonedChild);
1888
+ }
1889
+ resolve(clonedChild || null);
1890
+ }).catch(() => {
1891
+ resolve(null);
1892
+ });
1893
+ };
1894
+ const assigned = node.assignedNodes?.({ flatten: true }) || [];
1895
+ const nodesToClone = assigned.length > 0 ? assigned : Array.from(node.childNodes);
1896
+ const fragment = document.createDocumentFragment();
1897
+ const cloneList2 = await idleCallback(Array.from(nodesToClone), callback2, options.fast);
1898
+ fragment.append(...cloneList2.filter((clonedChild) => !!clonedChild));
1899
+ return fragment;
1900
+ }
1901
+ function callback(child, resolve) {
1902
+ if (clonedAssignedNodes.has(child)) return resolve(null);
1903
+ deepClone(child, sessionCache, options).then((clonedChild) => {
1904
+ resolve(clonedChild || null);
1905
+ }).catch(() => {
1906
+ resolve(null);
1907
+ });
1908
+ }
1909
+ const cloneList = await idleCallback(Array.from(node.childNodes), callback, options.fast);
1910
+ clone.append(...cloneList.filter((clonedChild) => !!clonedChild));
1911
+ if (pendingSelectValue !== null && clone instanceof HTMLSelectElement) {
1912
+ clone.value = pendingSelectValue;
1913
+ for (const opt of clone.options) {
1914
+ if (opt.value === pendingSelectValue) {
1915
+ opt.setAttribute("selected", "");
1916
+ } else {
1917
+ opt.removeAttribute("selected");
1918
+ }
1919
+ }
1920
+ }
1921
+ if (pendingTextAreaValue !== null && clone instanceof HTMLTextAreaElement) {
1922
+ clone.textContent = pendingTextAreaValue;
1923
+ }
1924
+ return clone;
1925
+ }
1926
+
1927
+ // src/modules/pseudo.js
1928
+ init_utils();
1929
+
1930
+ // src/modules/fonts.js
1931
+ init_helpers();
1932
+ init_cache();
1933
+
1934
+ // src/modules/iconFonts.js
1935
+ var defaultIconFonts = [
1936
+ // /uicons/i,
1937
+ /font\s*awesome/i,
1938
+ /material\s*icons/i,
1939
+ /ionicons/i,
1940
+ /glyphicons/i,
1941
+ /feather/i,
1942
+ /bootstrap\s*icons/i,
1943
+ /remix\s*icons/i,
1944
+ /heroicons/i,
1945
+ /layui/i,
1946
+ /lucide/i
1947
+ ];
1948
+ var ICON_FONT_URLS = Object.assign({
1949
+ materialIconsFilled: "https://fonts.gstatic.com/s/materialicons/v48/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.woff2",
1950
+ materialIconsOutlined: "https://fonts.gstatic.com/s/materialiconsoutlined/v110/gok-H7zzDkdnRel8-DQ6KAXJ69wP1tGnf4ZGhUcel5euIg.woff2",
1951
+ materialIconsRound: "https://fonts.gstatic.com/s/materialiconsround/v109/LDItaoyNOAY6Uewc665JcIzCKsKc_M9flwmPq_HTTw.woff2",
1952
+ materialIconsSharp: "https://fonts.gstatic.com/s/materialiconssharp/v110/oPWQ_lt5nv4pWNJpghLP75WiFR4kLh3kvmvRImcycg.woff2"
1953
+ }, typeof window !== "undefined" && window.__SNAPDOM_ICON_FONTS__ || {});
1954
+ var userIconFonts = [];
1955
+ function extendIconFonts(fonts) {
1956
+ const list = Array.isArray(fonts) ? fonts : [fonts];
1957
+ for (const f of list) {
1958
+ if (f instanceof RegExp) userIconFonts.push(f);
1959
+ else if (typeof f === "string") userIconFonts.push(new RegExp(f, "i"));
1960
+ else console.warn("[snapdom] Ignored invalid iconFont value:", f);
1961
+ }
1962
+ }
1963
+ function isIconFont2(input) {
1964
+ const text = typeof input === "string" ? input : "";
1965
+ const candidates = [...defaultIconFonts, ...userIconFonts];
1966
+ for (const rx of candidates) {
1967
+ if (rx instanceof RegExp && rx.test(text)) return true;
1968
+ }
1969
+ if (/icon/i.test(text) || /glyph/i.test(text) || /symbols/i.test(text) || /feather/i.test(text) || /fontawesome/i.test(text)) return true;
1970
+ return false;
1971
+ }
1972
+ function isMaterialFamily(family = "") {
1973
+ const s = String(family).toLowerCase();
1974
+ return /\bmaterial\s*icons\b/.test(s) || /\bmaterial\s*symbols\b/.test(s);
1975
+ }
1976
+ var loadedCanvasFamilies = /* @__PURE__ */ new Map();
1977
+ function parseAxes(variation = "") {
1978
+ const out = /* @__PURE__ */ Object.create(null);
1979
+ const v = String(variation || "");
1980
+ const rx = /['"]?\s*([A-Za-z]{3,4})\s*['"]?\s*([+-]?\d+(?:\.\d+)?)\s*/g;
1981
+ let m;
1982
+ while (m = rx.exec(v)) out[m[1].toUpperCase()] = Number(m[2]);
1983
+ return out;
1984
+ }
1985
+ async function ensureLigatureCanvasFont(cssFamily, className, axes) {
1986
+ const fam = String(cssFamily || "");
1987
+ const lowerFam = fam.toLowerCase();
1988
+ const cls = String(className || "").toLowerCase();
1989
+ if (/\bmaterial\s*icons\b/.test(lowerFam) && !/\bsymbols\b/.test(lowerFam)) {
1990
+ return { familyForMeasure: fam, familyForCanvas: fam };
1991
+ }
1992
+ const isSymbols = /\bmaterial\s*symbols\b/.test(lowerFam);
1993
+ if (!isSymbols) {
1994
+ return { familyForMeasure: fam, familyForCanvas: fam };
1995
+ }
1996
+ const FILL = axes && (axes.FILL ?? axes.fill);
1997
+ let style = "outlined";
1998
+ if (/\brounded\b/.test(cls) || /\bround\b/.test(cls)) style = "rounded";
1999
+ else if (/\bsharp\b/.test(cls)) style = "sharp";
2000
+ else if (/\boutlined\b/.test(cls)) style = "outlined";
2001
+ const filled = FILL === 1;
2002
+ let pick = null;
2003
+ if (filled) {
2004
+ if (style === "outlined" && ICON_FONT_URLS.materialIconsFilled) {
2005
+ pick = { url: ICON_FONT_URLS.materialIconsFilled, alias: "snapdom-mi-filled" };
2006
+ } else if (style === "rounded" && ICON_FONT_URLS.materialIconsRound) {
2007
+ pick = { url: ICON_FONT_URLS.materialIconsRound, alias: "snapdom-mi-round" };
2008
+ } else if (style === "sharp" && ICON_FONT_URLS.materialIconsSharp) {
2009
+ pick = { url: ICON_FONT_URLS.materialIconsSharp, alias: "snapdom-mi-sharp" };
2010
+ }
2011
+ }
2012
+ if (!pick) {
2013
+ return { familyForMeasure: fam, familyForCanvas: fam };
2014
+ }
2015
+ if (!loadedCanvasFamilies.has(pick.alias)) {
2016
+ try {
2017
+ const ff = new FontFace(pick.alias, `url(${pick.url})`, { style: "normal", weight: "400" });
2018
+ document.fonts.add(ff);
2019
+ await ff.load();
2020
+ loadedCanvasFamilies.set(pick.alias, true);
2021
+ } catch {
2022
+ return { familyForMeasure: fam, familyForCanvas: fam };
2023
+ }
2024
+ }
2025
+ const quoted = `"${pick.alias}"`;
2026
+ return { familyForMeasure: quoted, familyForCanvas: quoted };
2027
+ }
2028
+ async function ensureMaterialFontsReady(family = "Material Icons", px = 24) {
2029
+ try {
2030
+ await Promise.all([
2031
+ document.fonts.load(`400 ${px}px "${String(family).replace(/["']/g, "")}"`),
2032
+ document.fonts.ready
2033
+ ]);
2034
+ } catch {
2035
+ }
2036
+ }
2037
+ function resolvePaintColor(cs) {
2038
+ let fill = cs.getPropertyValue("-webkit-text-fill-color")?.trim() || "";
2039
+ const isTransparent = /^transparent$/i.test(fill) || /rgba?\(\s*0\s*,\s*0\s*,\s*0\s*,\s*0\s*\)/i.test(fill);
2040
+ if (fill && !isTransparent && fill.toLowerCase() !== "currentcolor") return fill;
2041
+ const c = cs.color?.trim();
1530
2042
  return c && c !== "inherit" ? c : "#000";
1531
2043
  }
1532
2044
  async function materialIconToImage(ligatureText, {
@@ -1630,6 +2142,7 @@ async function ligatureIconToImage(cloneRoot, sourceRoot) {
1630
2142
  }
1631
2143
 
1632
2144
  // src/modules/fonts.js
2145
+ init_snapFetch();
1633
2146
  async function iconToImage(unicodeChar, fontFamily, fontWeight, fontSize = 32, color = "#000") {
1634
2147
  fontFamily = fontFamily.replace(/^['"]+|['"]+$/g, "");
1635
2148
  const dpr = window.devicePixelRatio || 1;
@@ -2115,6 +2628,18 @@ async function embedCustomFonts({
2115
2628
  if (nearWeight && sOk && tOk) return true;
2116
2629
  }
2117
2630
  }
2631
+ if (!faceIsRange && ss.kind === "normal") {
2632
+ const hasItalicRequest = need.some((r) => normStyle(r.s) !== "normal");
2633
+ if (hasItalicRequest) {
2634
+ for (const r of need) {
2635
+ const nearWeight = Math.abs(faceSingleW - r.w) <= 300;
2636
+ const stretchOK = r.st >= ts.min && r.st <= ts.max;
2637
+ if (nearWeight && stretchOK) {
2638
+ return true;
2639
+ }
2640
+ }
2641
+ }
2642
+ }
2118
2643
  return false;
2119
2644
  }
2120
2645
  const simpleExcluder = buildSimpleExcluder(exclude);
@@ -2399,6 +2924,7 @@ async function ensureFontsReady(families, warmupRepetitions = 2) {
2399
2924
  }
2400
2925
 
2401
2926
  // src/modules/counter.js
2927
+ init_cache();
2402
2928
  function hasCounters(input) {
2403
2929
  return /\bcounter\s*\(|\bcounters\s*\(/.test(input || "");
2404
2930
  }
@@ -2595,6 +3121,154 @@ function resolveCountersInContent(raw, node, ctx) {
2595
3121
  }
2596
3122
 
2597
3123
  // src/modules/pseudo.js
3124
+ init_snapFetch();
3125
+ init_cache();
3126
+ var __preflightMemo = /* @__PURE__ */ new WeakMap();
3127
+ var CSS_RULE_SCAN_BUDGET = 300;
3128
+ function preflightWithFp(doc, sessionCache) {
3129
+ const fp = styleFingerprint(doc);
3130
+ if (!sessionCache) return shouldProcessPseudos(doc);
3131
+ if (sessionCache.__pseudoPreflightFp !== fp) {
3132
+ sessionCache.__pseudoPreflight = shouldProcessPseudos(doc);
3133
+ sessionCache.__pseudoPreflightFp = fp;
3134
+ }
3135
+ return !!sessionCache.__pseudoPreflight;
3136
+ }
3137
+ function safeRules(sheet) {
3138
+ try {
3139
+ return sheet && sheet.cssRules ? sheet.cssRules : null;
3140
+ } catch {
3141
+ return null;
3142
+ }
3143
+ }
3144
+ function styleFingerprint(doc) {
3145
+ const nodes = doc.querySelectorAll('style,link[rel~="stylesheet"]');
3146
+ let fp = `n:${nodes.length}|`;
3147
+ let totalRules = 0;
3148
+ for (let i = 0; i < nodes.length; i++) {
3149
+ const n = nodes[i];
3150
+ if (n.tagName === "STYLE") {
3151
+ const len = n.textContent ? n.textContent.length : 0;
3152
+ fp += `S${len}|`;
3153
+ const sheet = (
3154
+ /** @type {HTMLStyleElement} */
3155
+ n.sheet
3156
+ );
3157
+ const rules = sheet ? safeRules(sheet) : null;
3158
+ if (rules) totalRules += rules.length;
3159
+ } else {
3160
+ const href = n.getAttribute("href") || "";
3161
+ const media = n.getAttribute("media") || "all";
3162
+ fp += `L${href}|m:${media}|`;
3163
+ const sheet = (
3164
+ /** @type {HTMLLinkElement} */
3165
+ n.sheet
3166
+ );
3167
+ const rules = sheet ? safeRules(sheet) : null;
3168
+ if (rules) totalRules += rules.length;
3169
+ }
3170
+ }
3171
+ const ass = (
3172
+ /** @type {any} */
3173
+ doc.adoptedStyleSheets
3174
+ );
3175
+ fp += `ass:${Array.isArray(ass) ? ass.length : 0}|tr:${totalRules}`;
3176
+ return fp;
3177
+ }
3178
+ function sheetHasNeedles(sheet, needles, state) {
3179
+ const rules = safeRules(sheet);
3180
+ if (!rules) return false;
3181
+ for (let i = 0; i < rules.length; i++) {
3182
+ if (state.budget <= 0) return false;
3183
+ const rule = rules[i];
3184
+ const css = rule && rule.cssText ? rule.cssText : "";
3185
+ state.budget--;
3186
+ for (const k of needles) {
3187
+ if (css.includes(k)) return true;
3188
+ }
3189
+ if (rule && rule.cssRules && rule.cssRules.length) {
3190
+ for (let j = 0; j < rule.cssRules.length && state.budget > 0; j++) {
3191
+ const inner = rule.cssRules[j];
3192
+ const innerCss = inner && inner.cssText ? inner.cssText : "";
3193
+ state.budget--;
3194
+ for (const k of needles) {
3195
+ if (innerCss.includes(k)) return true;
3196
+ }
3197
+ }
3198
+ }
3199
+ if (state.budget <= 0) return false;
3200
+ }
3201
+ return false;
3202
+ }
3203
+ function shouldProcessPseudos(doc = document) {
3204
+ const fp = styleFingerprint(doc);
3205
+ const memo = __preflightMemo.get(doc);
3206
+ if (memo && memo.fingerprint === fp) return memo.result;
3207
+ const NEEDLES = [
3208
+ // double-colon
3209
+ "::before",
3210
+ "::after",
3211
+ "::first-letter",
3212
+ // single-colon robustness
3213
+ ":before",
3214
+ ":after",
3215
+ ":first-letter",
3216
+ // counters
3217
+ "counter(",
3218
+ "counters(",
3219
+ "counter-increment",
3220
+ "counter-reset"
3221
+ ];
3222
+ const styleEls = doc.querySelectorAll("style");
3223
+ for (let i = 0; i < styleEls.length; i++) {
3224
+ const t = styleEls[i].textContent || "";
3225
+ for (const k of NEEDLES) if (t.includes(k)) {
3226
+ __preflightMemo.set(doc, { fingerprint: fp, result: true });
3227
+ return true;
3228
+ }
3229
+ }
3230
+ const ass = (
3231
+ /** @type {any} */
3232
+ doc.adoptedStyleSheets
3233
+ );
3234
+ if (Array.isArray(ass) && ass.length) {
3235
+ const state = { budget: CSS_RULE_SCAN_BUDGET };
3236
+ try {
3237
+ for (const sheet of ass) {
3238
+ if (sheetHasNeedles(sheet, NEEDLES, state)) {
3239
+ __preflightMemo.set(doc, { fingerprint: fp, result: true });
3240
+ return true;
3241
+ }
3242
+ }
3243
+ } catch {
3244
+ }
3245
+ }
3246
+ {
3247
+ const nodes = doc.querySelectorAll('style,link[rel~="stylesheet"]');
3248
+ const state = { budget: CSS_RULE_SCAN_BUDGET };
3249
+ for (let i = 0; i < nodes.length && state.budget > 0; i++) {
3250
+ const n = nodes[i];
3251
+ let sheet = null;
3252
+ if (n.tagName === "STYLE") {
3253
+ sheet = /** @type {HTMLStyleElement} */
3254
+ n.sheet || null;
3255
+ } else {
3256
+ sheet = /** @type {HTMLLinkElement} */
3257
+ n.sheet || null;
3258
+ }
3259
+ if (sheet && sheetHasNeedles(sheet, NEEDLES, state)) {
3260
+ __preflightMemo.set(doc, { fingerprint: fp, result: true });
3261
+ return true;
3262
+ }
3263
+ }
3264
+ }
3265
+ if (doc.querySelector('[style*="counter("], [style*="counters("]')) {
3266
+ __preflightMemo.set(doc, { fingerprint: fp, result: true });
3267
+ return true;
3268
+ }
3269
+ __preflightMemo.set(doc, { fingerprint: fp, result: false });
3270
+ return false;
3271
+ }
2598
3272
  var __siblingCounters = /* @__PURE__ */ new WeakMap();
2599
3273
  var __pseudoEpoch = -1;
2600
3274
  function unquoteDoubleStrings2(s) {
@@ -2693,6 +3367,10 @@ function resolvePseudoContentAndIncs(node, pseudo, baseCtx) {
2693
3367
  }
2694
3368
  async function inlinePseudoElements(source, clone, sessionCache, options) {
2695
3369
  if (!(source instanceof Element) || !(clone instanceof Element)) return;
3370
+ const doc = source.ownerDocument || document;
3371
+ if (!preflightWithFp(doc, sessionCache)) {
3372
+ return;
3373
+ }
2696
3374
  const epoch = cache?.session?.__counterEpoch ?? 0;
2697
3375
  if (__pseudoEpoch !== epoch) {
2698
3376
  __siblingCounters = /* @__PURE__ */ new WeakMap();
@@ -2709,7 +3387,7 @@ async function inlinePseudoElements(source, clone, sessionCache, options) {
2709
3387
  for (const pseudo of ["::before", "::after", "::first-letter"]) {
2710
3388
  try {
2711
3389
  const style = getStyle(source, pseudo);
2712
- if (!style || typeof style[Symbol.iterator] !== "function") continue;
3390
+ if (!style) continue;
2713
3391
  const isEmptyPseudo = style.content === "none" && style.backgroundImage === "none" && style.backgroundColor === "transparent" && (style.borderStyle === "none" || parseFloat(style.borderWidth) === 0) && (!style.transform || style.transform === "none") && style.display === "inline";
2714
3392
  if (isEmptyPseudo) continue;
2715
3393
  if (pseudo === "::first-letter") {
@@ -2736,7 +3414,8 @@ async function inlinePseudoElements(source, clone, sessionCache, options) {
2736
3414
  clone.insertBefore(span, restNode);
2737
3415
  continue;
2738
3416
  }
2739
- const rawContent = style.content;
3417
+ const rawContent = style.content ?? "";
3418
+ const isNoExplicitContent = rawContent === "" || rawContent === "none" || rawContent === "normal";
2740
3419
  const { text: cleanContent, incs } = resolvePseudoContentAndIncs(source, pseudo, counterCtx);
2741
3420
  const bg = style.backgroundImage;
2742
3421
  const bgColor = style.backgroundColor;
@@ -2748,7 +3427,7 @@ async function inlinePseudoElements(source, clone, sessionCache, options) {
2748
3427
  const borderWidth = parseFloat(style.borderWidth);
2749
3428
  const transform = style.transform;
2750
3429
  const isIconFont22 = isIconFont2(fontFamily);
2751
- const hasExplicitContent = rawContent !== "none" && cleanContent !== "";
3430
+ const hasExplicitContent = !isNoExplicitContent && cleanContent !== "";
2752
3431
  const hasBg = bg && bg !== "none";
2753
3432
  const hasBgColor = bgColor && bgColor !== "transparent" && bgColor !== "rgba(0, 0, 0, 0)";
2754
3433
  const hasBorder = borderStyle && borderStyle !== "none" && borderWidth > 0;
@@ -2770,8 +3449,6 @@ async function inlinePseudoElements(source, clone, sessionCache, options) {
2770
3449
  }
2771
3450
  const pseudoEl = document.createElement("span");
2772
3451
  pseudoEl.dataset.snapdomPseudo = pseudo;
2773
- pseudoEl.style.display = "inline";
2774
- pseudoEl.style.verticalAlign = "baseline";
2775
3452
  pseudoEl.style.pointerEvents = "none";
2776
3453
  const snapshot = snapshotComputedStyle(style);
2777
3454
  const key = getStyleKey(snapshot, "span");
@@ -2877,31 +3554,55 @@ function inlineExternalDefsAndSymbols(element, lookupRoot) {
2877
3554
  "marker-end"
2878
3555
  ];
2879
3556
  const cssEscape = (s) => window.CSS && CSS.escape ? CSS.escape(s) : s.replace(/[^a-zA-Z0-9_-]/g, "\\$&");
3557
+ const XLINK_NS = "http://www.w3.org/1999/xlink";
3558
+ const getHrefAttr = (el) => {
3559
+ if (!el || !el.getAttribute) return null;
3560
+ let href = el.getAttribute("href") || el.getAttribute("xlink:href") || (typeof el.getAttributeNS === "function" ? el.getAttributeNS(XLINK_NS, "href") : null);
3561
+ if (href) return href;
3562
+ const attrs = el.attributes;
3563
+ if (!attrs) return null;
3564
+ for (let i = 0; i < attrs.length; i++) {
3565
+ const a = attrs[i];
3566
+ if (!a || !a.name) continue;
3567
+ if (a.name === "href") return a.value;
3568
+ const idx = a.name.indexOf(":");
3569
+ if (idx !== -1 && a.name.slice(idx + 1) === "href") {
3570
+ return a.value;
3571
+ }
3572
+ }
3573
+ return null;
3574
+ };
2880
3575
  const globalExistingIds = new Set(
2881
3576
  Array.from(element.querySelectorAll("[id]")).map((n) => n.id)
2882
3577
  );
2883
3578
  const neededIds = /* @__PURE__ */ new Set();
2884
3579
  let sawAnyReference = false;
2885
- const addUrlIdsFromValue = (val) => {
3580
+ const addUrlIdsFromValue = (val, queueForResolve = null) => {
2886
3581
  if (!val) return;
2887
3582
  URL_ID_RE.lastIndex = 0;
2888
3583
  let m;
2889
3584
  while (m = URL_ID_RE.exec(val)) {
2890
3585
  sawAnyReference = true;
2891
3586
  const id = (m[1] || "").trim();
2892
- if (id && !globalExistingIds.has(id)) neededIds.add(id);
3587
+ if (!id) continue;
3588
+ if (!globalExistingIds.has(id)) {
3589
+ neededIds.add(id);
3590
+ if (queueForResolve && !queueForResolve.has(id)) {
3591
+ queueForResolve.add(id);
3592
+ }
3593
+ }
2893
3594
  }
2894
3595
  };
2895
3596
  const collectReferencesInSvg = (rootSvg) => {
2896
- const uses = rootSvg.querySelectorAll('use[href^="#"], use[xlink\\:href^="#"]');
3597
+ const uses = rootSvg.querySelectorAll("use");
2897
3598
  for (const u of uses) {
2898
- const href = u.getAttribute("href") || u.getAttribute("xlink:href");
3599
+ const href = getHrefAttr(u);
2899
3600
  if (!href || !href.startsWith("#")) continue;
2900
3601
  sawAnyReference = true;
2901
3602
  const id = href.slice(1).trim();
2902
3603
  if (id && !globalExistingIds.has(id)) neededIds.add(id);
2903
3604
  }
2904
- let query = '*[style*="url("],*[fill^="url("], *[stroke^="url("],*[filter^="url("], *[clip-path^="url("],*[mask^="url("],*[marker^="url("], *[marker-start^="url("],*[marker-mid^="url("], *[marker-end^="url("]';
3605
+ const query = '*[style*="url("],*[fill^="url("], *[stroke^="url("],*[filter^="url("],*[clip-path^="url("],*[mask^="url("],*[marker^="url("],*[marker-start^="url("],*[marker-mid^="url("],*[marker-end^="url("]';
2905
3606
  const candidates = rootSvg.querySelectorAll(query);
2906
3607
  for (const el of candidates) {
2907
3608
  addUrlIdsFromValue(el.getAttribute("style") || "");
@@ -2952,21 +3653,26 @@ function inlineExternalDefsAndSymbols(element, lookupRoot) {
2952
3653
  globalExistingIds.add(id);
2953
3654
  const walk = [clone, ...clone.querySelectorAll("*")];
2954
3655
  for (const node of walk) {
2955
- const h = node.getAttribute?.("href") || node.getAttribute?.("xlink:href");
2956
- if (h && h.startsWith("#")) {
2957
- const ref = h.slice(1).trim();
2958
- if (ref && !globalExistingIds.has(ref) && !inlined.has(ref)) queued.add(ref);
3656
+ const href = getHrefAttr(node);
3657
+ if (href && href.startsWith("#")) {
3658
+ const ref = href.slice(1).trim();
3659
+ if (ref && !globalExistingIds.has(ref) && !inlined.has(ref)) {
3660
+ queued.add(ref);
3661
+ }
2959
3662
  }
2960
3663
  const style = node.getAttribute?.("style") || "";
2961
- if (style) addUrlIdsFromValue(style);
3664
+ if (style) addUrlIdsFromValue(style, queued);
2962
3665
  for (const a of URL_ATTRS) {
2963
3666
  const v = node.getAttribute?.(a);
2964
- if (v) addUrlIdsFromValue(v);
3667
+ if (v) addUrlIdsFromValue(v, queued);
2965
3668
  }
2966
3669
  }
2967
3670
  }
2968
3671
  }
2969
3672
 
3673
+ // src/core/prepare.js
3674
+ init_cache();
3675
+
2970
3676
  // src/modules/changeCSS.js
2971
3677
  function freezeSticky(originalRoot, cloneRoot) {
2972
3678
  if (!originalRoot || !cloneRoot) return;
@@ -3063,6 +3769,20 @@ function _childrenWithoutPlaceholders(el, phAttr) {
3063
3769
  return out;
3064
3770
  }
3065
3771
 
3772
+ // src/utils/prepare.helpers.js
3773
+ function stabilizeLayout(element) {
3774
+ const style = getComputedStyle(element);
3775
+ const outlineStyle = style.outlineStyle;
3776
+ const outlineWidth = style.outlineWidth;
3777
+ const borderStyle = style.borderStyle;
3778
+ const borderWidth = style.borderWidth;
3779
+ const outlineVisible = outlineStyle !== "none" && parseFloat(outlineWidth) > 0;
3780
+ const borderAbsent = borderStyle === "none" || parseFloat(borderWidth) === 0;
3781
+ if (outlineVisible && borderAbsent) {
3782
+ element.style.border = `${outlineWidth} solid transparent`;
3783
+ }
3784
+ }
3785
+
3066
3786
  // src/core/prepare.js
3067
3787
  async function prepareClone(element, options = {}) {
3068
3788
  const sessionCache = {
@@ -3091,218 +3811,92 @@ async function prepareClone(element, options = {}) {
3091
3811
  console.warn("inlinePseudoElements failed:", e);
3092
3812
  }
3093
3813
  await resolveBlobUrlsInTree(clone);
3094
- try {
3095
- const styleNodes = clone.querySelectorAll("style[data-sd]");
3096
- for (const s of styleNodes) {
3097
- shadowScopedCSS += s.textContent || "";
3098
- s.remove();
3099
- }
3100
- } catch {
3101
- }
3102
- const keyToClass = generateCSSClasses(sessionCache.styleMap);
3103
- classCSS = Array.from(keyToClass.entries()).map(([key, className]) => `.${className}{${key}}`).join("");
3104
- classCSS = shadowScopedCSS + classCSS;
3105
- for (const [node, key] of sessionCache.styleMap.entries()) {
3106
- if (node.tagName === "STYLE") continue;
3107
- if (node.getRootNode && node.getRootNode() instanceof ShadowRoot) {
3108
- node.setAttribute("style", key.replace(/;/g, "; "));
3109
- continue;
3110
- }
3111
- const className = keyToClass.get(key);
3112
- if (className) node.classList.add(className);
3113
- const bgImage = node.style?.backgroundImage;
3114
- const hasIcon = node.dataset?.snapdomHasIcon;
3115
- if (bgImage && bgImage !== "none") node.style.backgroundImage = bgImage;
3116
- if (hasIcon) {
3117
- node.style.verticalAlign = "middle";
3118
- node.style.display = "inline";
3119
- }
3120
- }
3121
- for (const [cloneNode, originalNode] of sessionCache.nodeMap.entries()) {
3122
- const scrollX = originalNode.scrollLeft;
3123
- const scrollY = originalNode.scrollTop;
3124
- const hasScroll = scrollX || scrollY;
3125
- if (hasScroll && cloneNode instanceof HTMLElement) {
3126
- cloneNode.style.overflow = "hidden";
3127
- cloneNode.style.scrollbarWidth = "none";
3128
- cloneNode.style.msOverflowStyle = "none";
3129
- const inner = document.createElement("div");
3130
- inner.style.transform = `translate(${-scrollX}px, ${-scrollY}px)`;
3131
- inner.style.willChange = "transform";
3132
- inner.style.display = "inline-block";
3133
- inner.style.width = "100%";
3134
- while (cloneNode.firstChild) {
3135
- inner.appendChild(cloneNode.firstChild);
3136
- }
3137
- cloneNode.appendChild(inner);
3138
- }
3139
- }
3140
- const contentRoot = clone instanceof HTMLElement && clone.firstElementChild instanceof HTMLElement ? clone.firstElementChild : clone;
3141
- freezeSticky(element, contentRoot);
3142
- if (element === sessionCache.nodeMap.get(clone)) {
3143
- const computed = sessionCache.styleCache.get(element) || window.getComputedStyle(element);
3144
- sessionCache.styleCache.set(element, computed);
3145
- const transform = stripTranslate(computed.transform);
3146
- clone.style.margin = "0";
3147
- clone.style.top = "auto";
3148
- clone.style.left = "auto";
3149
- clone.style.right = "auto";
3150
- clone.style.bottom = "auto";
3151
- clone.style.animation = "none";
3152
- clone.style.transition = "none";
3153
- clone.style.willChange = "auto";
3154
- clone.style.float = "none";
3155
- clone.style.clear = "none";
3156
- clone.style.transform = transform || "";
3157
- }
3158
- for (const [cloneNode, originalNode] of sessionCache.nodeMap.entries()) {
3159
- if (originalNode.tagName === "PRE") {
3160
- cloneNode.style.marginTop = "0";
3161
- cloneNode.style.marginBlockStart = "0";
3162
- }
3163
- }
3164
- return { clone, classCSS, styleCache: sessionCache.styleCache };
3165
- }
3166
- function stabilizeLayout(element) {
3167
- const style = getComputedStyle(element);
3168
- const outlineStyle = style.outlineStyle;
3169
- const outlineWidth = style.outlineWidth;
3170
- const borderStyle = style.borderStyle;
3171
- const borderWidth = style.borderWidth;
3172
- const outlineVisible = outlineStyle !== "none" && parseFloat(outlineWidth) > 0;
3173
- const borderAbsent = borderStyle === "none" || parseFloat(borderWidth) === 0;
3174
- if (outlineVisible && borderAbsent) {
3175
- element.style.border = `${outlineWidth} solid transparent`;
3176
- }
3177
- }
3178
- var _blobToDataUrlCache = /* @__PURE__ */ new Map();
3179
- async function blobUrlToDataUrl(blobUrl) {
3180
- if (cache.resource?.has(blobUrl)) return cache.resource.get(blobUrl);
3181
- if (_blobToDataUrlCache.has(blobUrl)) return _blobToDataUrlCache.get(blobUrl);
3182
- const p = (async () => {
3183
- const r = await snapFetch(blobUrl, { as: "dataURL", silent: true });
3184
- if (!r.ok || typeof r.data !== "string") {
3185
- throw new Error(`[snapDOM] Failed to read blob URL: ${blobUrl}`);
3186
- }
3187
- cache.resource?.set(blobUrl, r.data);
3188
- return r.data;
3189
- })();
3190
- _blobToDataUrlCache.set(blobUrl, p);
3191
- try {
3192
- const data = await p;
3193
- _blobToDataUrlCache.set(blobUrl, data);
3194
- return data;
3195
- } catch (e) {
3196
- _blobToDataUrlCache.delete(blobUrl);
3197
- throw e;
3198
- }
3199
- }
3200
- var BLOB_URL_RE = /\bblob:[^)"'\s]+/g;
3201
- async function replaceBlobUrlsInCssText(cssText) {
3202
- if (!cssText || cssText.indexOf("blob:") === -1) return cssText;
3203
- const uniques = Array.from(new Set(cssText.match(BLOB_URL_RE) || []));
3204
- if (uniques.length === 0) return cssText;
3205
- let out = cssText;
3206
- for (const u of uniques) {
3207
- try {
3208
- const d = await blobUrlToDataUrl(u);
3209
- out = out.split(u).join(d);
3210
- } catch {
3211
- }
3212
- }
3213
- return out;
3214
- }
3215
- function isBlobUrl(u) {
3216
- return typeof u === "string" && u.startsWith("blob:");
3217
- }
3218
- function parseSrcset(srcset) {
3219
- return (srcset || "").split(",").map((s) => s.trim()).filter(Boolean).map((item) => {
3220
- const m = item.match(/^(\S+)(\s+.+)?$/);
3221
- return m ? { url: m[1], desc: m[2] || "" } : null;
3222
- }).filter(Boolean);
3223
- }
3224
- function stringifySrcset(parts) {
3225
- return parts.map((p) => p.desc ? `${p.url} ${p.desc.trim()}` : p.url).join(", ");
3226
- }
3227
- async function resolveBlobUrlsInTree(root) {
3228
- if (!root) return;
3229
- const imgs = root.querySelectorAll ? root.querySelectorAll("img") : [];
3230
- for (const img of imgs) {
3231
- try {
3232
- const srcAttr = img.getAttribute("src");
3233
- const effective = srcAttr || img.currentSrc || "";
3234
- if (isBlobUrl(effective)) {
3235
- const data = await blobUrlToDataUrl(effective);
3236
- img.setAttribute("src", data);
3237
- }
3238
- const srcset = img.getAttribute("srcset");
3239
- if (srcset && srcset.includes("blob:")) {
3240
- const parts = parseSrcset(srcset);
3241
- let changed = false;
3242
- for (const p of parts) {
3243
- if (isBlobUrl(p.url)) {
3244
- try {
3245
- p.url = await blobUrlToDataUrl(p.url);
3246
- changed = true;
3247
- } catch {
3248
- }
3249
- }
3250
- }
3251
- if (changed) img.setAttribute("srcset", stringifySrcset(parts));
3252
- }
3253
- } catch {
3254
- }
3255
- }
3256
- const svgImages = root.querySelectorAll ? root.querySelectorAll("image") : [];
3257
- for (const node of svgImages) {
3258
- try {
3259
- const XLINK_NS = "http://www.w3.org/1999/xlink";
3260
- const href = node.getAttribute("href") || node.getAttributeNS?.(XLINK_NS, "href");
3261
- if (isBlobUrl(href)) {
3262
- const d = await blobUrlToDataUrl(href);
3263
- node.setAttribute("href", d);
3264
- node.removeAttributeNS?.(XLINK_NS, "href");
3265
- }
3266
- } catch {
3814
+ try {
3815
+ const styleNodes = clone.querySelectorAll("style[data-sd]");
3816
+ for (const s of styleNodes) {
3817
+ shadowScopedCSS += s.textContent || "";
3818
+ s.remove();
3267
3819
  }
3820
+ } catch {
3268
3821
  }
3269
- const styled = root.querySelectorAll ? root.querySelectorAll("[style*='blob:']") : [];
3270
- for (const el of styled) {
3271
- try {
3272
- const styleText = el.getAttribute("style");
3273
- if (styleText && styleText.includes("blob:")) {
3274
- const replaced = await replaceBlobUrlsInCssText(styleText);
3275
- el.setAttribute("style", replaced);
3276
- }
3277
- } catch {
3822
+ const keyToClass = generateCSSClasses(sessionCache.styleMap);
3823
+ classCSS = Array.from(keyToClass.entries()).map(([key, className]) => `.${className}{${key}}`).join("");
3824
+ classCSS = shadowScopedCSS + classCSS;
3825
+ for (const [node, key] of sessionCache.styleMap.entries()) {
3826
+ if (node.tagName === "STYLE") continue;
3827
+ if (node.getRootNode && node.getRootNode() instanceof ShadowRoot) {
3828
+ node.setAttribute("style", key.replace(/;/g, "; "));
3829
+ continue;
3830
+ }
3831
+ const className = keyToClass.get(key);
3832
+ if (className) node.classList.add(className);
3833
+ const bgImage = node.style?.backgroundImage;
3834
+ const hasIcon = node.dataset?.snapdomHasIcon;
3835
+ if (bgImage && bgImage !== "none") node.style.backgroundImage = bgImage;
3836
+ if (hasIcon) {
3837
+ node.style.verticalAlign = "middle";
3838
+ node.style.display = "inline";
3278
3839
  }
3279
3840
  }
3280
- const styleTags = root.querySelectorAll ? root.querySelectorAll("style") : [];
3281
- for (const s of styleTags) {
3282
- try {
3283
- const css = s.textContent || "";
3284
- if (css.includes("blob:")) {
3285
- s.textContent = await replaceBlobUrlsInCssText(css);
3841
+ for (const [cloneNode, originalNode] of sessionCache.nodeMap.entries()) {
3842
+ const scrollX = originalNode.scrollLeft;
3843
+ const scrollY = originalNode.scrollTop;
3844
+ const hasScroll = scrollX || scrollY;
3845
+ if (hasScroll && cloneNode instanceof HTMLElement) {
3846
+ cloneNode.style.overflow = "hidden";
3847
+ cloneNode.style.scrollbarWidth = "none";
3848
+ cloneNode.style.msOverflowStyle = "none";
3849
+ const inner = document.createElement("div");
3850
+ inner.style.transform = `translate(${-scrollX}px, ${-scrollY}px)`;
3851
+ inner.style.willChange = "transform";
3852
+ inner.style.display = "inline-block";
3853
+ inner.style.width = "100%";
3854
+ while (cloneNode.firstChild) {
3855
+ inner.appendChild(cloneNode.firstChild);
3286
3856
  }
3287
- } catch {
3857
+ cloneNode.appendChild(inner);
3288
3858
  }
3289
3859
  }
3290
- const urlAttrs = ["poster"];
3291
- for (const attr of urlAttrs) {
3292
- const nodes = root.querySelectorAll ? root.querySelectorAll(`[${attr}^='blob:']`) : [];
3293
- for (const n of nodes) {
3294
- try {
3295
- const u = n.getAttribute(attr);
3296
- if (isBlobUrl(u)) {
3297
- n.setAttribute(attr, await blobUrlToDataUrl(u));
3298
- }
3299
- } catch {
3300
- }
3860
+ const contentRoot = clone instanceof HTMLElement && clone.firstElementChild instanceof HTMLElement ? clone.firstElementChild : clone;
3861
+ freezeSticky(element, contentRoot);
3862
+ if (element === sessionCache.nodeMap.get(clone)) {
3863
+ const computed = sessionCache.styleCache.get(element) || window.getComputedStyle(element);
3864
+ sessionCache.styleCache.set(element, computed);
3865
+ const transform = stripTranslate(computed.transform);
3866
+ clone.style.margin = "0";
3867
+ clone.style.top = "auto";
3868
+ clone.style.left = "auto";
3869
+ clone.style.right = "auto";
3870
+ clone.style.bottom = "auto";
3871
+ clone.style.animation = "none";
3872
+ clone.style.transition = "none";
3873
+ clone.style.willChange = "auto";
3874
+ clone.style.float = "none";
3875
+ clone.style.clear = "none";
3876
+ clone.style.transform = transform || "";
3877
+ }
3878
+ for (const [cloneNode, originalNode] of sessionCache.nodeMap.entries()) {
3879
+ if (originalNode.tagName === "PRE") {
3880
+ cloneNode.style.marginTop = "0";
3881
+ cloneNode.style.marginBlockStart = "0";
3301
3882
  }
3302
3883
  }
3884
+ return { clone, classCSS, styleCache: sessionCache.styleCache };
3303
3885
  }
3304
3886
 
3305
3887
  // src/modules/images.js
3888
+ init_snapFetch();
3889
+ function extractImageDimensions(img) {
3890
+ const dsW = parseInt(img.dataset?.snapdomWidth || "", 10) || 0;
3891
+ const dsH = parseInt(img.dataset?.snapdomHeight || "", 10) || 0;
3892
+ const attrW = parseInt(img.getAttribute("width") || "", 10) || 0;
3893
+ const attrH = parseInt(img.getAttribute("height") || "", 10) || 0;
3894
+ const styleW = parseFloat(img.style?.width || "") || 0;
3895
+ const styleH = parseFloat(img.style?.height || "") || 0;
3896
+ const w = dsW || styleW || attrW || img.width || img.naturalWidth || 100;
3897
+ const h = dsH || styleH || attrH || img.height || img.naturalHeight || 100;
3898
+ return { width: w, height: h };
3899
+ }
3306
3900
  async function inlineImages(clone, options = {}) {
3307
3901
  const imgs = Array.from(clone.querySelectorAll("img"));
3308
3902
  const processImg = async (img) => {
@@ -3321,41 +3915,30 @@ async function inlineImages(clone, options = {}) {
3321
3915
  if (!img.height) img.height = img.naturalHeight || 100;
3322
3916
  return;
3323
3917
  }
3918
+ const { width: fbW, height: fbH } = extractImageDimensions(img);
3324
3919
  const { fallbackURL } = options || {};
3325
3920
  if (fallbackURL) {
3326
3921
  try {
3327
- const dsW = parseInt(img.dataset?.snapdomWidth || "", 10) || 0;
3328
- const dsH = parseInt(img.dataset?.snapdomHeight || "", 10) || 0;
3329
- const attrW = parseInt(img.getAttribute("width") || "", 10) || 0;
3330
- const attrH = parseInt(img.getAttribute("height") || "", 10) || 0;
3331
- const styleW = parseFloat(img.style?.width || "") || 0;
3332
- const styleH = parseFloat(img.style?.height || "") || 0;
3333
- const fbW = dsW || styleW || attrW || img.width || void 0;
3334
- const fbH = dsH || styleH || attrH || img.height || void 0;
3335
3922
  const fallbackUrl = typeof fallbackURL === "function" ? await fallbackURL({ width: fbW, height: fbH, src, element: img }) : fallbackURL;
3336
3923
  if (fallbackUrl) {
3337
3924
  const fallbackData = await snapFetch(fallbackUrl, { as: "dataURL", useProxy: options.useProxy });
3338
3925
  img.src = fallbackData.data;
3339
- if (!img.width && fbW) img.width = fbW;
3340
- if (!img.height && fbH) img.height = fbH;
3341
- if (!img.width) img.width = img.naturalWidth || 100;
3342
- if (!img.height) img.height = img.naturalHeight || 100;
3926
+ if (!img.width) img.width = fbW;
3927
+ if (!img.height) img.height = fbH;
3343
3928
  return;
3344
3929
  }
3345
3930
  } catch {
3346
3931
  }
3347
3932
  }
3348
- const w = img.width || img.naturalWidth || 100;
3349
- const h = img.height || img.naturalHeight || 100;
3350
3933
  if (options.placeholders !== false) {
3351
3934
  const fallback = document.createElement("div");
3352
3935
  fallback.style.cssText = [
3353
- `width:${w}px`,
3354
- `height:${h}px`,
3936
+ `width:${fbW}px`,
3937
+ `height:${fbH}px`,
3355
3938
  "background:#ccc",
3356
3939
  "display:inline-block",
3357
3940
  "text-align:center",
3358
- `line-height:${h}px`,
3941
+ `line-height:${fbH}px`,
3359
3942
  "color:#666",
3360
3943
  "font-size:12px",
3361
3944
  "overflow:hidden"
@@ -3364,7 +3947,7 @@ async function inlineImages(clone, options = {}) {
3364
3947
  img.replaceWith(fallback);
3365
3948
  } else {
3366
3949
  const spacer = document.createElement("div");
3367
- spacer.style.cssText = `display:inline-block;width:${w}px;height:${h}px;visibility:hidden;`;
3950
+ spacer.style.cssText = `display:inline-block;width:${fbW}px;height:${fbH}px;visibility:hidden;`;
3368
3951
  img.replaceWith(spacer);
3369
3952
  }
3370
3953
  };
@@ -3375,6 +3958,7 @@ async function inlineImages(clone, options = {}) {
3375
3958
  }
3376
3959
 
3377
3960
  // src/modules/background.js
3961
+ init_utils();
3378
3962
  async function inlineBackgroundImages(source, clone, styleCache, options = {}) {
3379
3963
  const queue = [[source, clone]];
3380
3964
  const URL_PROPS = [
@@ -3472,6 +4056,10 @@ async function inlineBackgroundImages(source, clone, styleCache, options = {}) {
3472
4056
  }
3473
4057
  }
3474
4058
 
4059
+ // src/core/capture.js
4060
+ init_utils();
4061
+ init_cache();
4062
+
3475
4063
  // src/modules/lineClamp.js
3476
4064
  function lineClamp(el) {
3477
4065
  if (!el) return () => {
@@ -3604,7 +4192,7 @@ function attachSessionPlugins(context, localDefs, force = false) {
3604
4192
  return context;
3605
4193
  }
3606
4194
 
3607
- // src/core/capture.js
4195
+ // src/utils/capture.helpers.js
3608
4196
  function stripRootShadows(originalEl, cloneRoot) {
3609
4197
  if (!originalEl || !cloneRoot || !cloneRoot.style) return;
3610
4198
  const cs = getComputedStyle(originalEl);
@@ -3633,268 +4221,43 @@ function removeAllComments(root) {
3633
4221
  while (it.nextNode()) toRemove.push(it.currentNode);
3634
4222
  for (const n of toRemove) n.remove();
3635
4223
  }
3636
- function sanitizeAttributesForXHTML(root, opts = {}) {
3637
- const { stripFrameworkDirectives = true } = opts;
3638
- const ALLOWED_PREFIXES = /* @__PURE__ */ new Set(["xml", "xlink"]);
3639
- const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
3640
- while (walker.nextNode()) {
3641
- const el = walker.currentNode;
3642
- for (const attr of Array.from(el.attributes)) {
3643
- const name = attr.name;
3644
- if (name.includes("@")) {
3645
- el.removeAttribute(name);
3646
- continue;
3647
- }
3648
- if (name.includes(":")) {
3649
- const prefix = name.split(":", 1)[0];
3650
- if (!ALLOWED_PREFIXES.has(prefix)) {
3651
- el.removeAttribute(name);
3652
- continue;
3653
- }
3654
- }
3655
- if (!stripFrameworkDirectives) continue;
3656
- if (name.startsWith("x-") || // Alpine
3657
- name.startsWith("v-") || // Vue
3658
- name.startsWith(":") || // Vue/Alpine shorthand
3659
- name.startsWith("on:") || // Svelte
3660
- name.startsWith("bind:") || // Svelte
3661
- name.startsWith("let:") || // Svelte
3662
- name.startsWith("class:")) {
3663
- el.removeAttribute(name);
3664
- continue;
3665
- }
3666
- }
3667
- }
3668
- }
3669
- function sanitizeCloneForXHTML(root, opts = {}) {
3670
- if (!root) return;
3671
- sanitizeAttributesForXHTML(root, opts);
3672
- removeAllComments(root);
3673
- }
3674
- async function captureDOM(element, options) {
3675
- if (!element) throw new Error("Element cannot be null or undefined");
3676
- applyCachePolicy(options.cache);
3677
- const fast = options.fast;
3678
- const outerTransforms = options.outerTransforms !== false;
3679
- const outerShadows = !!options.outerShadows;
3680
- let state = { element, options, plugins: options.plugins };
3681
- let clone, classCSS, styleCache;
3682
- let fontsCSS = "";
3683
- let baseCSS = "";
3684
- let dataURL;
3685
- let svgString;
3686
- let rootTransform2D = null;
3687
- await runHook("beforeSnap", state);
3688
- await runHook("beforeClone", state);
3689
- const undoClamp = lineClamp(state.element);
3690
- try {
3691
- ({ clone, classCSS, styleCache } = await prepareClone(state.element, state.options));
3692
- if (!outerTransforms && clone) {
3693
- rootTransform2D = normalizeRootTransforms(state.element, clone);
3694
- }
3695
- if (!outerShadows && clone) {
3696
- stripRootShadows(state.element, clone);
3697
- }
3698
- } finally {
3699
- undoClamp();
3700
- }
3701
- state = { clone, classCSS, styleCache, ...state };
3702
- await runHook("afterClone", state);
3703
- sanitizeCloneForXHTML(state.clone);
3704
- if (state.options?.excludeMode === "remove") {
3705
- try {
3706
- shrinkAutoSizeBoxes(state.element, state.clone, state.styleCache);
3707
- } catch (e) {
3708
- console.warn("[snapdom] shrink pass failed:", e);
3709
- }
3710
- }
3711
- try {
3712
- await ligatureIconToImage(state.clone, state.element);
3713
- } catch {
3714
- }
3715
- await new Promise((resolve) => {
3716
- idle(async () => {
3717
- await inlineImages(state.clone, state.options);
3718
- resolve();
3719
- }, { fast });
3720
- });
3721
- await new Promise((resolve) => {
3722
- idle(async () => {
3723
- await inlineBackgroundImages(state.element, state.clone, state.styleCache, state.options);
3724
- resolve();
3725
- }, { fast });
3726
- });
3727
- if (options.embedFonts) {
3728
- await new Promise((resolve) => {
3729
- idle(async () => {
3730
- const required = collectUsedFontVariants(state.element);
3731
- const usedCodepoints = collectUsedCodepoints(state.element);
3732
- if (isSafari()) {
3733
- const families = new Set(
3734
- Array.from(required).map((k) => String(k).split("__")[0]).filter(Boolean)
3735
- );
3736
- await ensureFontsReady(families, 1);
3737
- }
3738
- fontsCSS = await embedCustomFonts({
3739
- required,
3740
- usedCodepoints,
3741
- preCached: false,
3742
- exclude: state.options.excludeFonts,
3743
- useProxy: state.options.useProxy
3744
- });
3745
- resolve();
3746
- }, { fast });
3747
- });
3748
- }
3749
- const usedTags = collectUsedTagNames(state.clone).sort();
3750
- const tagKey = usedTags.join(",");
3751
- if (cache.baseStyle.has(tagKey)) {
3752
- baseCSS = cache.baseStyle.get(tagKey);
3753
- } else {
3754
- await new Promise((resolve) => {
3755
- idle(() => {
3756
- baseCSS = generateDedupedBaseCSS(usedTags);
3757
- cache.baseStyle.set(tagKey, baseCSS);
3758
- resolve();
3759
- }, { fast });
3760
- });
3761
- }
3762
- state = { fontsCSS, baseCSS, ...state };
3763
- await runHook("beforeRender", state);
3764
- await new Promise((resolve) => {
3765
- idle(() => {
3766
- const csEl = getComputedStyle(state.element);
3767
- const rect = state.element.getBoundingClientRect();
3768
- let w0 = Math.max(1, limitDecimals(state.element.offsetWidth || parseFloat(csEl.width) || rect.width || 1));
3769
- let h0 = Math.max(1, limitDecimals(state.element.offsetHeight || parseFloat(csEl.height) || rect.height || 1));
3770
- if (state.options?.excludeMode === "remove") {
3771
- const hEst = estimateKeptHeight(state.element, state.options);
3772
- const EPS = 1;
3773
- if (Number.isFinite(hEst) && hEst > 0) {
3774
- h0 = Math.max(1, Math.min(h0, limitDecimals(hEst + EPS)));
3775
- }
3776
- }
3777
- const coerceNum = (v, def = NaN) => {
3778
- const n = typeof v === "string" ? parseFloat(v) : v;
3779
- return Number.isFinite(n) ? n : def;
3780
- };
3781
- const optW = coerceNum(state.options.width);
3782
- const optH = coerceNum(state.options.height);
3783
- let w = w0, h = h0;
3784
- const hasW = Number.isFinite(optW);
3785
- const hasH = Number.isFinite(optH);
3786
- const aspect0 = h0 > 0 ? w0 / h0 : 1;
3787
- if (hasW && hasH) {
3788
- w = Math.max(1, limitDecimals(optW));
3789
- h = Math.max(1, limitDecimals(optH));
3790
- } else if (hasW) {
3791
- w = Math.max(1, limitDecimals(optW));
3792
- h = Math.max(1, limitDecimals(w / (aspect0 || 1)));
3793
- } else if (hasH) {
3794
- h = Math.max(1, limitDecimals(optH));
3795
- w = Math.max(1, limitDecimals(h * (aspect0 || 1)));
3796
- } else {
3797
- w = w0;
3798
- h = h0;
4224
+ function sanitizeAttributesForXHTML(root, opts = {}) {
4225
+ const { stripFrameworkDirectives = true } = opts;
4226
+ const ALLOWED_PREFIXES = /* @__PURE__ */ new Set(["xml", "xlink"]);
4227
+ const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
4228
+ while (walker.nextNode()) {
4229
+ const el = walker.currentNode;
4230
+ for (const attr of Array.from(el.attributes)) {
4231
+ const name = attr.name;
4232
+ if (name.includes("@")) {
4233
+ el.removeAttribute(name);
4234
+ continue;
3799
4235
  }
3800
- let minX = 0, minY = 0, maxX = w0, maxY = h0;
3801
- if (!outerTransforms && rootTransform2D && Number.isFinite(rootTransform2D.a)) {
3802
- const M2 = {
3803
- a: rootTransform2D.a,
3804
- b: rootTransform2D.b || 0,
3805
- c: rootTransform2D.c || 0,
3806
- d: rootTransform2D.d || 1,
3807
- e: 0,
3808
- f: 0
3809
- };
3810
- const bb2 = bboxWithOriginFull(w0, h0, M2, 0, 0);
3811
- minX = limitDecimals(bb2.minX);
3812
- minY = limitDecimals(bb2.minY);
3813
- maxX = limitDecimals(bb2.maxX);
3814
- maxY = limitDecimals(bb2.maxY);
3815
- } else {
3816
- const useTFBBox = outerTransforms && hasTFBBox(state.element);
3817
- if (useTFBBox) {
3818
- const baseTransform2 = csEl.transform && csEl.transform !== "none" ? csEl.transform : "";
3819
- const ind2 = readIndividualTransforms(state.element);
3820
- const TOTAL = readTotalTransformMatrix({
3821
- baseTransform: baseTransform2,
3822
- rotate: ind2.rotate || "0deg",
3823
- scale: ind2.scale,
3824
- translate: ind2.translate
3825
- });
3826
- const { ox: ox2, oy: oy2 } = parseTransformOriginPx(csEl, w0, h0);
3827
- const M = TOTAL.is2D ? TOTAL : new DOMMatrix(TOTAL.toString());
3828
- const bb = bboxWithOriginFull(w0, h0, M, ox2, oy2);
3829
- minX = limitDecimals(bb.minX);
3830
- minY = limitDecimals(bb.minY);
3831
- maxX = limitDecimals(bb.maxX);
3832
- maxY = limitDecimals(bb.maxY);
4236
+ if (name.includes(":")) {
4237
+ const prefix = name.split(":", 1)[0];
4238
+ if (!ALLOWED_PREFIXES.has(prefix)) {
4239
+ el.removeAttribute(name);
4240
+ continue;
3833
4241
  }
3834
4242
  }
3835
- const bleedShadow = parseBoxShadow(csEl);
3836
- const bleedBlur = parseFilterBlur(csEl);
3837
- const bleedOutline = parseOutline(csEl);
3838
- const drop = parseFilterDropShadows(csEl);
3839
- const bleed = !outerShadows ? { top: 0, right: 0, bottom: 0, left: 0 } : {
3840
- top: limitDecimals(bleedShadow.top + bleedBlur.top + bleedOutline.top + drop.bleed.top),
3841
- right: limitDecimals(bleedShadow.right + bleedBlur.right + bleedOutline.right + drop.bleed.right),
3842
- bottom: limitDecimals(bleedShadow.bottom + bleedBlur.bottom + bleedOutline.bottom + drop.bleed.bottom),
3843
- left: limitDecimals(bleedShadow.left + bleedBlur.left + bleedOutline.left + drop.bleed.left)
3844
- };
3845
- minX = limitDecimals(minX - bleed.left);
3846
- minY = limitDecimals(minY - bleed.top);
3847
- maxX = limitDecimals(maxX + bleed.right);
3848
- maxY = limitDecimals(maxY + bleed.bottom);
3849
- const vbW0 = Math.max(1, limitDecimals(maxX - minX));
3850
- const vbH0 = Math.max(1, limitDecimals(maxY - minY));
3851
- const scaleW = hasW || hasH ? limitDecimals(w / w0) : 1;
3852
- const scaleH = hasH || hasW ? limitDecimals(h / h0) : 1;
3853
- const outW = Math.max(1, limitDecimals(vbW0 * scaleW));
3854
- const outH = Math.max(1, limitDecimals(vbH0 * scaleH));
3855
- const svgNS = "http://www.w3.org/2000/svg";
3856
- const basePad = isSafari() ? 1 : 0;
3857
- const extraPad = !outerTransforms ? 1 : 0;
3858
- const pad = limitDecimals(basePad + extraPad);
3859
- const fo = document.createElementNS(svgNS, "foreignObject");
3860
- const vbMinX = limitDecimals(minX);
3861
- const vbMinY = limitDecimals(minY);
3862
- fo.setAttribute("x", String(limitDecimals(-(vbMinX - pad))));
3863
- fo.setAttribute("y", String(limitDecimals(-(vbMinY - pad))));
3864
- fo.setAttribute("width", String(limitDecimals(w0 + pad * 2)));
3865
- fo.setAttribute("height", String(limitDecimals(h0 + pad * 2)));
3866
- fo.style.overflow = "visible";
3867
- const styleTag = document.createElement("style");
3868
- styleTag.textContent = state.baseCSS + state.fontsCSS + "svg{overflow:visible;} foreignObject{overflow:visible;}" + state.classCSS;
3869
- fo.appendChild(styleTag);
3870
- const container = document.createElement("div");
3871
- container.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
3872
- container.style.width = `${limitDecimals(w0)}px`;
3873
- container.style.height = `${limitDecimals(h0)}px`;
3874
- container.style.overflow = "visible";
3875
- state.clone.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
3876
- container.appendChild(state.clone);
3877
- fo.appendChild(container);
3878
- const serializer = new XMLSerializer();
3879
- const foString = serializer.serializeToString(fo);
3880
- const vbW = limitDecimals(vbW0 + pad * 2);
3881
- const vbH = limitDecimals(vbH0 + pad * 2);
3882
- const wantsSize = hasW || hasH;
3883
- options.meta = { w0, h0, vbW, vbH, targetW: w, targetH: h };
3884
- const svgOutW = isSafari() && wantsSize ? vbW : limitDecimals(outW + pad * 2);
3885
- const svgOutH = isSafari() && wantsSize ? vbH : limitDecimals(outH + pad * 2);
3886
- const svgHeader = `<svg xmlns="${svgNS}" width="${svgOutW}" height="${svgOutH}" viewBox="0 0 ${vbW} ${vbH}">`;
3887
- const svgFooter = "</svg>";
3888
- svgString = svgHeader + foString + svgFooter;
3889
- dataURL = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgString)}`;
3890
- state = { svgString, dataURL, ...state };
3891
- resolve();
3892
- }, { fast });
3893
- });
3894
- await runHook("afterRender", state);
3895
- const sandbox = document.getElementById("snapdom-sandbox");
3896
- if (sandbox && sandbox.style.position === "absolute") sandbox.remove();
3897
- return state.dataURL;
4243
+ if (!stripFrameworkDirectives) continue;
4244
+ if (name.startsWith("x-") || // Alpine
4245
+ name.startsWith("v-") || // Vue
4246
+ name.startsWith(":") || // Vue/Alpine shorthand
4247
+ name.startsWith("on:") || // Svelte
4248
+ name.startsWith("bind:") || // Svelte
4249
+ name.startsWith("let:") || // Svelte
4250
+ name.startsWith("class:")) {
4251
+ el.removeAttribute(name);
4252
+ continue;
4253
+ }
4254
+ }
4255
+ }
4256
+ }
4257
+ function sanitizeCloneForXHTML(root, opts = {}) {
4258
+ if (!root) return;
4259
+ sanitizeAttributesForXHTML(root, opts);
4260
+ removeAllComments(root);
3898
4261
  }
3899
4262
  function authorHasExplicitSize(el) {
3900
4263
  try {
@@ -3947,7 +4310,83 @@ function shrinkAutoSizeBoxes(sourceRoot, cloneRoot, styleCache = /* @__PURE__ */
3947
4310
  }
3948
4311
  walk(sourceRoot, cloneRoot);
3949
4312
  }
4313
+ function isInNormalFlow(el) {
4314
+ const cs = getComputedStyle(el);
4315
+ if (cs.display === "none") return false;
4316
+ if (cs.position === "absolute" || cs.position === "fixed" || cs.position === "sticky") return false;
4317
+ if ((cs.cssFloat || cs.float || "none") !== "none") return false;
4318
+ if (cs.transform && cs.transform !== "none") return false;
4319
+ return true;
4320
+ }
4321
+ function willBeExcluded(el, options) {
4322
+ if (!(el instanceof Element)) return false;
4323
+ if (el.getAttribute("data-capture") === "exclude" && options?.excludeMode === "remove") return true;
4324
+ if (Array.isArray(options?.exclude)) {
4325
+ for (const sel of options.exclude) {
4326
+ try {
4327
+ if (el.matches(sel)) return options.excludeMode === "remove";
4328
+ } catch {
4329
+ }
4330
+ }
4331
+ }
4332
+ return false;
4333
+ }
4334
+ function estimateKeptHeight(container, options) {
4335
+ const csC = getComputedStyle(container);
4336
+ const rC = container.getBoundingClientRect();
4337
+ let minTop = Infinity;
4338
+ let maxBottom = -Infinity;
4339
+ let found = false;
4340
+ const kids = Array.from(container.children);
4341
+ for (const k of kids) {
4342
+ if (willBeExcluded(k, options)) continue;
4343
+ if (!isInNormalFlow(k)) continue;
4344
+ const rk = k.getBoundingClientRect();
4345
+ const top = rk.top - rC.top;
4346
+ const bottom = rk.bottom - rC.top;
4347
+ if (bottom <= top) continue;
4348
+ if (top < minTop) minTop = top;
4349
+ if (bottom > maxBottom) maxBottom = bottom;
4350
+ found = true;
4351
+ }
4352
+ const contentSpan = found ? Math.max(0, maxBottom - minTop) : 0;
4353
+ const bt = parseFloat(csC.borderTopWidth) || 0;
4354
+ const bb = parseFloat(csC.borderBottomWidth) || 0;
4355
+ const pt = parseFloat(csC.paddingTop) || 0;
4356
+ const pb = parseFloat(csC.paddingBottom) || 0;
4357
+ return bt + bb + pt + pb + contentSpan;
4358
+ }
3950
4359
  var limitDecimals = (v, n = 3) => Number.isFinite(v) ? Math.round(v * 10 ** n) / 10 ** n : v;
4360
+
4361
+ // src/utils/transforms.helpers.js
4362
+ function parseBoxShadow(cs) {
4363
+ const v = cs.boxShadow || "";
4364
+ if (!v || v === "none") return { top: 0, right: 0, bottom: 0, left: 0 };
4365
+ const parts = v.split(/\),(?=(?:[^()]*\([^()]*\))*[^()]*$)/).map((s) => s.trim());
4366
+ let t = 0, r = 0, b2 = 0, l = 0;
4367
+ for (const part of parts) {
4368
+ const nums = part.match(/-?\d+(\.\d+)?px/g)?.map((n) => parseFloat(n)) || [];
4369
+ if (nums.length < 2) continue;
4370
+ const [ox2, oy2, blur = 0, spread = 0] = nums;
4371
+ const extX = Math.abs(ox2) + blur + spread;
4372
+ const extY = Math.abs(oy2) + blur + spread;
4373
+ r = Math.max(r, extX + Math.max(ox2, 0));
4374
+ l = Math.max(l, extX + Math.max(-ox2, 0));
4375
+ b2 = Math.max(b2, extY + Math.max(oy2, 0));
4376
+ t = Math.max(t, extY + Math.max(-oy2, 0));
4377
+ }
4378
+ return { top: Math.ceil(t), right: Math.ceil(r), bottom: Math.ceil(b2), left: Math.ceil(l) };
4379
+ }
4380
+ function parseFilterBlur(cs) {
4381
+ const m = (cs.filter || "").match(/blur\(\s*([0-9.]+)px\s*\)/);
4382
+ const b2 = m ? Math.ceil(parseFloat(m[1]) || 0) : 0;
4383
+ return { top: b2, right: b2, bottom: b2, left: b2 };
4384
+ }
4385
+ function parseOutline(cs) {
4386
+ if ((cs.outlineStyle || "none") === "none") return { top: 0, right: 0, bottom: 0, left: 0 };
4387
+ const w2 = Math.ceil(parseFloat(cs.outlineWidth || "0") || 0);
4388
+ return { top: w2, right: w2, bottom: w2, left: w2 };
4389
+ }
3951
4390
  function parseFilterDropShadows(cs) {
3952
4391
  const raw = `${cs.filter || ""} ${cs.webkitFilter || ""}`.trim();
3953
4392
  if (!raw || raw === "none") {
@@ -4036,34 +4475,6 @@ function normalizeRootTransforms(originalEl, cloneRoot) {
4036
4475
  return null;
4037
4476
  }
4038
4477
  }
4039
- function parseBoxShadow(cs) {
4040
- const v = cs.boxShadow || "";
4041
- if (!v || v === "none") return { top: 0, right: 0, bottom: 0, left: 0 };
4042
- const parts = v.split(/\),(?=(?:[^()]*\([^()]*\))*[^()]*$)/).map((s) => s.trim());
4043
- let t = 0, r = 0, b2 = 0, l = 0;
4044
- for (const part of parts) {
4045
- const nums = part.match(/-?\d+(\.\d+)?px/g)?.map((n) => parseFloat(n)) || [];
4046
- if (nums.length < 2) continue;
4047
- const [ox2, oy2, blur = 0, spread = 0] = nums;
4048
- const extX = Math.abs(ox2) + blur + spread;
4049
- const extY = Math.abs(oy2) + blur + spread;
4050
- r = Math.max(r, extX + Math.max(ox2, 0));
4051
- l = Math.max(l, extX + Math.max(-ox2, 0));
4052
- b2 = Math.max(b2, extY + Math.max(oy2, 0));
4053
- t = Math.max(t, extY + Math.max(-oy2, 0));
4054
- }
4055
- return { top: Math.ceil(t), right: Math.ceil(r), bottom: Math.ceil(b2), left: Math.ceil(l) };
4056
- }
4057
- function parseFilterBlur(cs) {
4058
- const m = (cs.filter || "").match(/blur\(\s*([0-9.]+)px\s*\)/);
4059
- const b2 = m ? Math.ceil(parseFloat(m[1]) || 0) : 0;
4060
- return { top: b2, right: b2, bottom: b2, left: b2 };
4061
- }
4062
- function parseOutline(cs) {
4063
- if ((cs.outlineStyle || "none") === "none") return { top: 0, right: 0, bottom: 0, left: 0 };
4064
- const w2 = Math.ceil(parseFloat(cs.outlineWidth || "0") || 0);
4065
- return { top: w2, right: w2, bottom: w2, left: w2 };
4066
- }
4067
4478
  function bboxWithOriginFull(w2, h2, M, ox2, oy2) {
4068
4479
  const a2 = M.a, b2 = M.b, c2 = M.c, d2 = M.d, e2 = M.e || 0, f2 = M.f || 0;
4069
4480
  function pt(x, y) {
@@ -4083,17 +4494,24 @@ function bboxWithOriginFull(w2, h2, M, ox2, oy2) {
4083
4494
  }
4084
4495
  return { minX: minX2, minY: minY2, maxX: maxX2, maxY: maxY2, width: maxX2 - minX2, height: maxY2 - minY2 };
4085
4496
  }
4086
- function hasTFBBox(el) {
4087
- return hasBBoxAffectingTransform(el);
4088
- }
4089
- function matrixFromComputed(el) {
4090
- const tr = getComputedStyle(el).transform;
4091
- if (!tr || tr === "none") return new DOMMatrix();
4092
- try {
4093
- return new DOMMatrix(tr);
4094
- } catch {
4095
- return new WebKitCSSMatrix(tr);
4096
- }
4497
+ function parseTransformOriginPx(cs, w, h) {
4498
+ const raw = (cs.transformOrigin || "0 0").trim().split(/\s+/);
4499
+ const [oxRaw, oyRaw] = [raw[0] || "0", raw[1] || "0"];
4500
+ const toPx = (token, size) => {
4501
+ const t = token.toLowerCase();
4502
+ if (t === "left" || t === "top") return 0;
4503
+ if (t === "center") return size / 2;
4504
+ if (t === "right") return size;
4505
+ if (t === "bottom") return size;
4506
+ if (t.endsWith("px")) return parseFloat(t) || 0;
4507
+ if (t.endsWith("%")) return (parseFloat(t) || 0) * size / 100;
4508
+ if (/^-?\d+(\.\d+)?$/.test(t)) return parseFloat(t) || 0;
4509
+ return 0;
4510
+ };
4511
+ return {
4512
+ ox: toPx(oxRaw, w),
4513
+ oy: toPx(oyRaw, h)
4514
+ };
4097
4515
  }
4098
4516
  function readIndividualTransforms(el) {
4099
4517
  const out = { rotate: "0deg", scale: null, translate: null };
@@ -4133,438 +4551,373 @@ function readIndividualTransforms(el) {
4133
4551
  }
4134
4552
  const tr = safeGet("translate");
4135
4553
  if (tr) {
4136
- const tx = "x" in tr && "value" in tr.x ? tr.x.value : Array.isArray(tr) ? tr[0]?.value : 0;
4137
- const ty = "y" in tr && "value" in tr.y ? tr.y.value : Array.isArray(tr) ? tr[1]?.value : 0;
4138
- const ux = "x" in tr && tr.x?.unit ? tr.x.unit : "px";
4139
- const uy = "y" in tr && tr.y?.unit ? tr.y.unit : "px";
4140
- out.translate = `${tx}${ux} ${ty}${uy}`;
4141
- } else {
4142
- const cs2 = getComputedStyle(el);
4143
- out.translate = cs2.translate && cs2.translate !== "none" ? cs2.translate : null;
4144
- }
4145
- return out;
4146
- }
4147
- const cs = getComputedStyle(el);
4148
- out.rotate = cs.rotate && cs.rotate !== "none" ? cs.rotate : "0deg";
4149
- out.scale = cs.scale && cs.scale !== "none" ? cs.scale : null;
4150
- out.translate = cs.translate && cs.translate !== "none" ? cs.translate : null;
4151
- return out;
4152
- }
4153
- function hasBBoxAffectingTransform(el) {
4154
- const cs = getComputedStyle(el);
4155
- const t = cs.transform || "none";
4156
- const hasMatrix = t !== "none" && !/^matrix\(\s*1\s*,\s*0\s*,\s*0\s*,\s*1\s*,\s*0\s*,\s*0\s*\)$/i.test(t);
4157
- if (hasMatrix) return true;
4158
- const r = cs.rotate && cs.rotate !== "none" && cs.rotate !== "0deg";
4159
- const s = cs.scale && cs.scale !== "none" && cs.scale !== "1";
4160
- const tr = cs.translate && cs.translate !== "none" && cs.translate !== "0px 0px";
4161
- return Boolean(r || s || tr);
4162
- }
4163
- function parseTransformOriginPx(cs, w, h) {
4164
- const raw = (cs.transformOrigin || "0 0").trim().split(/\s+/);
4165
- const [oxRaw, oyRaw] = [raw[0] || "0", raw[1] || "0"];
4166
- const toPx = (token, size) => {
4167
- const t = token.toLowerCase();
4168
- if (t === "left" || t === "top") return 0;
4169
- if (t === "center") return size / 2;
4170
- if (t === "right") return size;
4171
- if (t === "bottom") return size;
4172
- if (t.endsWith("px")) return parseFloat(t) || 0;
4173
- if (t.endsWith("%")) return (parseFloat(t) || 0) * size / 100;
4174
- if (/^-?\d+(\.\d+)?$/.test(t)) return parseFloat(t) || 0;
4175
- return 0;
4176
- };
4177
- return {
4178
- ox: toPx(oxRaw, w),
4179
- oy: toPx(oyRaw, h)
4180
- };
4181
- }
4182
- var __measureHost = null;
4183
- function getMeasureHost() {
4184
- if (__measureHost) return __measureHost;
4185
- const n = document.createElement("div");
4186
- n.id = "snapdom-measure-slot";
4187
- n.setAttribute("aria-hidden", "true");
4188
- Object.assign(n.style, {
4189
- position: "absolute",
4190
- left: "-99999px",
4191
- top: "0px",
4192
- width: "0px",
4193
- height: "0px",
4194
- overflow: "hidden",
4195
- opacity: "0",
4196
- pointerEvents: "none",
4197
- contain: "size layout style"
4198
- });
4199
- document.documentElement.appendChild(n);
4200
- __measureHost = n;
4201
- return n;
4202
- }
4203
- function readTotalTransformMatrix(t) {
4204
- const host = getMeasureHost();
4205
- const tmp = document.createElement("div");
4206
- tmp.style.transformOrigin = "0 0";
4207
- if (t.baseTransform) tmp.style.transform = t.baseTransform;
4208
- if (t.rotate) tmp.style.rotate = t.rotate;
4209
- if (t.scale) tmp.style.scale = t.scale;
4210
- if (t.translate) tmp.style.translate = t.translate;
4211
- host.appendChild(tmp);
4212
- const M = matrixFromComputed(tmp);
4213
- host.removeChild(tmp);
4214
- return M;
4215
- }
4216
- function isInNormalFlow(el) {
4217
- const cs = getComputedStyle(el);
4218
- if (cs.display === "none") return false;
4219
- if (cs.position === "absolute" || cs.position === "fixed" || cs.position === "sticky") return false;
4220
- if ((cs.cssFloat || cs.float || "none") !== "none") return false;
4221
- if (cs.transform && cs.transform !== "none") return false;
4222
- return true;
4223
- }
4224
- function willBeExcluded(el, options) {
4225
- if (!(el instanceof Element)) return false;
4226
- if (el.getAttribute("data-capture") === "exclude" && options?.excludeMode === "remove") return true;
4227
- if (Array.isArray(options?.exclude)) {
4228
- for (const sel of options.exclude) {
4229
- try {
4230
- if (el.matches(sel)) return options.excludeMode === "remove";
4231
- } catch {
4232
- }
4233
- }
4234
- }
4235
- return false;
4236
- }
4237
- function estimateKeptHeight(container, options) {
4238
- const csC = getComputedStyle(container);
4239
- const rC = container.getBoundingClientRect();
4240
- let minTop = Infinity;
4241
- let maxBottom = -Infinity;
4242
- let found = false;
4243
- const kids = Array.from(container.children);
4244
- for (const k of kids) {
4245
- if (willBeExcluded(k, options)) continue;
4246
- if (!isInNormalFlow(k)) continue;
4247
- const rk = k.getBoundingClientRect();
4248
- const top = rk.top - rC.top;
4249
- const bottom = rk.bottom - rC.top;
4250
- if (bottom <= top) continue;
4251
- if (top < minTop) minTop = top;
4252
- if (bottom > maxBottom) maxBottom = bottom;
4253
- found = true;
4254
- }
4255
- const contentSpan = found ? Math.max(0, maxBottom - minTop) : 0;
4256
- const bt = parseFloat(csC.borderTopWidth) || 0;
4257
- const bb = parseFloat(csC.borderBottomWidth) || 0;
4258
- const pt = parseFloat(csC.paddingTop) || 0;
4259
- const pb = parseFloat(csC.paddingBottom) || 0;
4260
- return bt + bb + pt + pb + contentSpan;
4261
- }
4262
-
4263
- // src/core/context.js
4264
- function normalizeCachePolicy(v) {
4265
- if (typeof v === "string") {
4266
- const s = v.toLowerCase().trim();
4267
- if (s === "disabled" || s === "full" || s === "auto" || s === "soft") return (
4268
- /** @type {CachePolicy} */
4269
- s
4270
- );
4271
- }
4272
- return "soft";
4273
- }
4274
- function createContext(options = {}) {
4275
- let resolvedFormat = options.format ?? "png";
4276
- if (resolvedFormat === "jpg") resolvedFormat = "jpeg";
4277
- const cachePolicy = normalizeCachePolicy(options.cache);
4278
- return {
4279
- // Debug & perf
4280
- debug: options.debug ?? false,
4281
- fast: options.fast ?? true,
4282
- scale: options.scale ?? 1,
4283
- // DOM filters
4284
- exclude: options.exclude ?? [],
4285
- excludeMode: options.excludeMode ?? "hide",
4286
- filter: options.filter ?? null,
4287
- filterMode: options.filterMode ?? "hide",
4288
- // Placeholders
4289
- placeholders: options.placeholders !== false,
4290
- // default true
4291
- // Fonts
4292
- embedFonts: options.embedFonts ?? false,
4293
- iconFonts: Array.isArray(options.iconFonts) ? options.iconFonts : options.iconFonts ? [options.iconFonts] : [],
4294
- localFonts: Array.isArray(options.localFonts) ? options.localFonts : [],
4295
- excludeFonts: options.excludeFonts ?? void 0,
4296
- fallbackURL: options.fallbackURL ?? void 0,
4297
- /** @type {CachePolicy} */
4298
- cache: cachePolicy,
4299
- // Network
4300
- useProxy: typeof options.useProxy === "string" ? options.useProxy : "",
4301
- // Output
4302
- width: options.width ?? null,
4303
- height: options.height ?? null,
4304
- format: resolvedFormat,
4305
- type: options.type ?? "svg",
4306
- quality: options.quality ?? 0.92,
4307
- dpr: options.dpr ?? (window.devicePixelRatio || 1),
4308
- backgroundColor: options.backgroundColor ?? (["jpeg", "webp"].includes(resolvedFormat) ? "#ffffff" : null),
4309
- filename: options.filename ?? "snapDOM",
4310
- // NEW flags (user-friendly)
4311
- outerTransforms: options.outerTransforms ?? true,
4312
- outerShadows: options.outerShadows ?? false
4313
- // Plugins (reservado)
4314
- // plugins: normalizePlugins(...),
4315
- };
4316
- }
4317
-
4318
- // src/exporters/toCanvas.js
4319
- function isSvgDataURL(u) {
4320
- return typeof u === "string" && /^data:image\/svg\+xml/i.test(u);
4321
- }
4322
- function decodeSvgFromDataURL(u) {
4323
- const i = u.indexOf(",");
4324
- return i >= 0 ? decodeURIComponent(u.slice(i + 1)) : "";
4325
- }
4326
- function encodeSvgToDataURL(svgText) {
4327
- return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgText)}`;
4328
- }
4329
- function splitDecls(s) {
4330
- let parts = [], buf = "", depth = 0;
4331
- for (let i = 0; i < s.length; i++) {
4332
- const ch = s[i];
4333
- if (ch === "(") depth++;
4334
- if (ch === ")") depth = Math.max(0, depth - 1);
4335
- if (ch === ";" && depth === 0) {
4336
- parts.push(buf);
4337
- buf = "";
4338
- } else buf += ch;
4339
- }
4340
- if (buf.trim()) parts.push(buf);
4341
- return parts.map((x) => x.trim()).filter(Boolean);
4342
- }
4343
- function boxShadowToDropShadow(value) {
4344
- const layers = [];
4345
- let buf = "", depth = 0;
4346
- for (let i = 0; i < value.length; i++) {
4347
- const ch = value[i];
4348
- if (ch === "(") depth++;
4349
- if (ch === ")") depth = Math.max(0, depth - 1);
4350
- if (ch === "," && depth === 0) {
4351
- layers.push(buf.trim());
4352
- buf = "";
4353
- } else buf += ch;
4354
- }
4355
- if (buf.trim()) layers.push(buf.trim());
4356
- const fns = [];
4357
- for (const layer of layers) {
4358
- if (/\binset\b/i.test(layer)) continue;
4359
- const nums = layer.match(/-?\d+(?:\.\d+)?px/gi) || [];
4360
- const [ox = "0px", oy = "0px", blur = "0px"] = nums;
4361
- let color = layer.replace(/-?\d+(?:\.\d+)?px/gi, "").replace(/\binset\b/ig, "").trim().replace(/\s{2,}/g, " ");
4362
- const hasColor = !!color && color !== ",";
4363
- fns.push(`drop-shadow(${ox} ${oy} ${blur}${hasColor ? ` ${color}` : ""})`);
4364
- }
4365
- return fns.join(" ");
4366
- }
4367
- function rewriteDeclList(list) {
4368
- const decls = splitDecls(list);
4369
- let filter = null, wfilter = null, box = null;
4370
- const rest = [];
4371
- for (const d of decls) {
4372
- const idx = d.indexOf(":");
4373
- if (idx < 0) continue;
4374
- const prop = d.slice(0, idx).trim().toLowerCase();
4375
- const val = d.slice(idx + 1).trim();
4376
- if (prop === "box-shadow") box = val;
4377
- else if (prop === "filter") filter = val;
4378
- else if (prop === "-webkit-filter") wfilter = val;
4379
- else rest.push([prop, val]);
4380
- }
4381
- if (box) {
4382
- const ds = boxShadowToDropShadow(box);
4383
- if (ds) {
4384
- filter = filter ? `${filter} ${ds}` : ds;
4385
- wfilter = wfilter ? `${wfilter} ${ds}` : ds;
4554
+ const tx = "x" in tr && "value" in tr.x ? tr.x.value : Array.isArray(tr) ? tr[0]?.value : 0;
4555
+ const ty = "y" in tr && "value" in tr.y ? tr.y.value : Array.isArray(tr) ? tr[1]?.value : 0;
4556
+ const ux = "x" in tr && tr.x?.unit ? tr.x.unit : "px";
4557
+ const uy = "y" in tr && tr.y?.unit ? tr.y.unit : "px";
4558
+ out.translate = `${tx}${ux} ${ty}${uy}`;
4559
+ } else {
4560
+ const cs2 = getComputedStyle(el);
4561
+ out.translate = cs2.translate && cs2.translate !== "none" ? cs2.translate : null;
4386
4562
  }
4563
+ return out;
4387
4564
  }
4388
- const out = [...rest];
4389
- if (filter) out.push(["filter", filter]);
4390
- if (wfilter) out.push(["-webkit-filter", wfilter]);
4391
- return out.map(([k, v]) => `${k}:${v}`).join(";");
4565
+ const cs = getComputedStyle(el);
4566
+ out.rotate = cs.rotate && cs.rotate !== "none" ? cs.rotate : "0deg";
4567
+ out.scale = cs.scale && cs.scale !== "none" ? cs.scale : null;
4568
+ out.translate = cs.translate && cs.translate !== "none" ? cs.translate : null;
4569
+ return out;
4392
4570
  }
4393
- function rewriteCssBlock(css) {
4394
- return css.replace(/([^{}]+)\{([^}]*)\}/g, (_m, sel, body) => `${sel}{${rewriteDeclList(body)}}`);
4571
+ var __measureHost = null;
4572
+ function getMeasureHost() {
4573
+ if (__measureHost) return __measureHost;
4574
+ const n = document.createElement("div");
4575
+ n.id = "snapdom-measure-slot";
4576
+ n.setAttribute("aria-hidden", "true");
4577
+ Object.assign(n.style, {
4578
+ position: "absolute",
4579
+ left: "-99999px",
4580
+ top: "0px",
4581
+ width: "0px",
4582
+ height: "0px",
4583
+ overflow: "hidden",
4584
+ opacity: "0",
4585
+ pointerEvents: "none",
4586
+ contain: "size layout style"
4587
+ });
4588
+ document.documentElement.appendChild(n);
4589
+ __measureHost = n;
4590
+ return n;
4395
4591
  }
4396
- function rewriteSvgBoxShadowToDropShadow(svgText) {
4397
- svgText = svgText.replace(
4398
- /<style[^>]*>([\s\S]*?)<\/style>/gi,
4399
- (m, css) => m.replace(css, rewriteCssBlock(css))
4400
- );
4401
- svgText = svgText.replace(
4402
- /style=(['"])([\s\S]*?)\1/gi,
4403
- (m, q, body) => `style=${q}${rewriteDeclList(body)}${q}`
4404
- );
4405
- return svgText;
4592
+ function readTotalTransformMatrix(t) {
4593
+ const host = getMeasureHost();
4594
+ const tmp = document.createElement("div");
4595
+ tmp.style.transformOrigin = "0 0";
4596
+ if (t.baseTransform) tmp.style.transform = t.baseTransform;
4597
+ if (t.rotate) tmp.style.rotate = t.rotate;
4598
+ if (t.scale) tmp.style.scale = t.scale;
4599
+ if (t.translate) tmp.style.translate = t.translate;
4600
+ host.appendChild(tmp);
4601
+ const M = matrixFromComputed(tmp);
4602
+ host.removeChild(tmp);
4603
+ return M;
4406
4604
  }
4407
- function maybeConvertBoxShadowForSafari(url) {
4408
- if (!isSafari() || !isSvgDataURL(url)) return url;
4605
+ function hasBBoxAffectingTransform(el) {
4606
+ const cs = getComputedStyle(el);
4607
+ const t = cs.transform || "none";
4608
+ 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);
4609
+ if (hasMatrix) return true;
4610
+ const r = cs.rotate && cs.rotate !== "none" && cs.rotate !== "0deg";
4611
+ const s = cs.scale && cs.scale !== "none" && cs.scale !== "1";
4612
+ const tr = cs.translate && cs.translate !== "none" && cs.translate !== "0px 0px";
4613
+ return Boolean(r || s || tr);
4614
+ }
4615
+ function matrixFromComputed(el) {
4616
+ const tr = getComputedStyle(el).transform;
4617
+ if (!tr || tr === "none") return new DOMMatrix();
4409
4618
  try {
4410
- const svg = decodeSvgFromDataURL(url);
4411
- const fixed = rewriteSvgBoxShadowToDropShadow(svg);
4412
- return encodeSvgToDataURL(fixed);
4619
+ return new DOMMatrix(tr);
4413
4620
  } catch {
4414
- return url;
4415
- }
4416
- }
4417
- async function toCanvas(url, options) {
4418
- let { width: optW, height: optH, scale = 1, dpr = 1, meta = {}, backgroundColor } = options;
4419
- url = maybeConvertBoxShadowForSafari(url);
4420
- const img = new Image();
4421
- img.loading = "eager";
4422
- img.decoding = "sync";
4423
- img.crossOrigin = "anonymous";
4424
- img.src = url;
4425
- await img.decode();
4426
- const natW = img.naturalWidth;
4427
- const natH = img.naturalHeight;
4428
- const refW = Number.isFinite(meta.w0) ? meta.w0 : natW;
4429
- const refH = Number.isFinite(meta.h0) ? meta.h0 : natH;
4430
- let outW, outH;
4431
- const hasW = Number.isFinite(optW);
4432
- const hasH = Number.isFinite(optH);
4433
- if (hasW && hasH) {
4434
- outW = Math.max(1, optW);
4435
- outH = Math.max(1, optH);
4436
- } else if (hasW) {
4437
- const k = optW / Math.max(1, refW);
4438
- outW = optW;
4439
- outH = refH * k;
4440
- } else if (hasH) {
4441
- const k = optH / Math.max(1, refH);
4442
- outH = optH;
4443
- outW = refW * k;
4444
- } else {
4445
- outW = natW;
4446
- outH = natH;
4447
- }
4448
- outW = outW * scale;
4449
- outH = outH * scale;
4450
- const canvas = document.createElement("canvas");
4451
- canvas.width = outW * dpr;
4452
- canvas.height = outH * dpr;
4453
- canvas.style.width = `${outW}px`;
4454
- canvas.style.height = `${outH}px`;
4455
- const ctx = canvas.getContext("2d");
4456
- if (dpr !== 1) ctx.scale(dpr, dpr);
4457
- if (backgroundColor) {
4458
- ctx.save();
4459
- ctx.fillStyle = backgroundColor;
4460
- ctx.fillRect(0, 0, outW, outH);
4461
- ctx.restore();
4621
+ return new WebKitCSSMatrix(tr);
4462
4622
  }
4463
- ctx.drawImage(img, 0, 0, outW, outH);
4464
- return canvas;
4465
- }
4466
-
4467
- // src/modules/rasterize.js
4468
- async function rasterize(url, options) {
4469
- const canvas = await toCanvas(url, options);
4470
- const img = new Image();
4471
- img.src = canvas.toDataURL(`image/${options.format}`, options.quality);
4472
- await img.decode();
4473
- img.style.width = `${canvas.width / options.dpr}px`;
4474
- img.style.height = `${canvas.height / options.dpr}px`;
4475
- return img;
4476
4623
  }
4477
4624
 
4478
- // src/exporters/toImg.js
4479
- async function toImg(url, options) {
4480
- const { scale = 1, width, height, meta = {} } = options;
4481
- const hasW = Number.isFinite(width);
4482
- const hasH = Number.isFinite(height);
4483
- const wantsScale = Number.isFinite(scale) && scale !== 1 || hasW || hasH;
4484
- if (isSafari() && wantsScale) {
4485
- const pngUrl = await rasterize(url, { ...options, format: "png", quality: 1, meta });
4486
- return pngUrl;
4625
+ // src/core/capture.js
4626
+ async function captureDOM(element, options) {
4627
+ if (!element) throw new Error("Element cannot be null or undefined");
4628
+ applyCachePolicy(options.cache);
4629
+ const fast = options.fast;
4630
+ const outerTransforms = options.outerTransforms !== false;
4631
+ const outerShadows = !!options.outerShadows;
4632
+ let state = { element, options, plugins: options.plugins };
4633
+ let clone, classCSS, styleCache;
4634
+ let fontsCSS = "";
4635
+ let baseCSS = "";
4636
+ let dataURL;
4637
+ let svgString;
4638
+ let rootTransform2D = null;
4639
+ await runHook("beforeSnap", state);
4640
+ await runHook("beforeClone", state);
4641
+ const undoClamp = lineClamp(state.element);
4642
+ try {
4643
+ ({ clone, classCSS, styleCache } = await prepareClone(state.element, state.options));
4644
+ if (!outerTransforms && clone) {
4645
+ rootTransform2D = normalizeRootTransforms(state.element, clone);
4646
+ }
4647
+ if (!outerShadows && clone) {
4648
+ stripRootShadows(state.element, clone);
4649
+ }
4650
+ } finally {
4651
+ undoClamp();
4487
4652
  }
4488
- const img = new Image();
4489
- img.decoding = "sync";
4490
- img.loading = "eager";
4491
- img.src = url;
4492
- await img.decode();
4493
- if (hasW && hasH) {
4494
- img.style.width = `${width}px`;
4495
- img.style.height = `${height}px`;
4496
- } else if (hasW) {
4497
- const refW = Number.isFinite(meta.w0) ? meta.w0 : img.naturalWidth;
4498
- const refH = Number.isFinite(meta.h0) ? meta.h0 : img.naturalHeight;
4499
- const k = width / Math.max(1, refW);
4500
- img.style.width = `${width}px`;
4501
- img.style.height = `${Math.round(refH * k)}px`;
4502
- } else if (hasH) {
4503
- const refW = Number.isFinite(meta.w0) ? meta.w0 : img.naturalWidth;
4504
- const refH = Number.isFinite(meta.h0) ? meta.h0 : img.naturalHeight;
4505
- const k = height / Math.max(1, refH);
4506
- img.style.height = `${height}px`;
4507
- img.style.width = `${Math.round(refW * k)}px`;
4508
- } else {
4509
- const cssW = Math.round(img.naturalWidth * scale);
4510
- const cssH = Math.round(img.naturalHeight * scale);
4511
- img.style.width = `${cssW}px`;
4512
- img.style.height = `${cssH}px`;
4513
- if (typeof url === "string" && url.startsWith("data:image/svg+xml")) {
4514
- try {
4515
- const decoded = decodeURIComponent(url.split(",")[1]);
4516
- const patched = decoded.replace(/width="[^"]*"/, `width="${cssW}"`).replace(/height="[^"]*"/, `height="${cssH}"`);
4517
- url = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(patched)}`;
4518
- img.src = url;
4519
- } catch {
4520
- }
4653
+ state = { clone, classCSS, styleCache, ...state };
4654
+ await runHook("afterClone", state);
4655
+ sanitizeCloneForXHTML(state.clone);
4656
+ if (state.options?.excludeMode === "remove") {
4657
+ try {
4658
+ shrinkAutoSizeBoxes(state.element, state.clone, state.styleCache);
4659
+ } catch (e) {
4660
+ console.warn("[snapdom] shrink pass failed:", e);
4521
4661
  }
4522
4662
  }
4523
- return img;
4524
- }
4525
-
4526
- // src/exporters/toBlob.js
4527
- async function toBlob(url, options) {
4528
- const type = options.type;
4529
- if (type === "svg") {
4530
- const svgText = decodeURIComponent(url.split(",")[1]);
4531
- return new Blob([svgText], { type: "image/svg+xml" });
4663
+ try {
4664
+ await ligatureIconToImage(state.clone, state.element);
4665
+ } catch {
4666
+ }
4667
+ await new Promise((resolve) => {
4668
+ idle(async () => {
4669
+ await inlineImages(state.clone, state.options);
4670
+ resolve();
4671
+ }, { fast });
4672
+ });
4673
+ await new Promise((resolve) => {
4674
+ idle(async () => {
4675
+ await inlineBackgroundImages(state.element, state.clone, state.styleCache, state.options);
4676
+ resolve();
4677
+ }, { fast });
4678
+ });
4679
+ if (options.embedFonts) {
4680
+ await new Promise((resolve) => {
4681
+ idle(async () => {
4682
+ const required = collectUsedFontVariants(state.element);
4683
+ const usedCodepoints = collectUsedCodepoints(state.element);
4684
+ if (isSafari()) {
4685
+ const families = new Set(
4686
+ Array.from(required).map((k) => String(k).split("__")[0]).filter(Boolean)
4687
+ );
4688
+ await ensureFontsReady(families, 1);
4689
+ }
4690
+ fontsCSS = await embedCustomFonts({
4691
+ required,
4692
+ usedCodepoints,
4693
+ preCached: false,
4694
+ exclude: state.options.excludeFonts,
4695
+ useProxy: state.options.useProxy
4696
+ });
4697
+ resolve();
4698
+ }, { fast });
4699
+ });
4700
+ }
4701
+ const usedTags = collectUsedTagNames(state.clone).sort();
4702
+ const tagKey = usedTags.join(",");
4703
+ if (cache.baseStyle.has(tagKey)) {
4704
+ baseCSS = cache.baseStyle.get(tagKey);
4705
+ } else {
4706
+ await new Promise((resolve) => {
4707
+ idle(() => {
4708
+ baseCSS = generateDedupedBaseCSS(usedTags);
4709
+ cache.baseStyle.set(tagKey, baseCSS);
4710
+ resolve();
4711
+ }, { fast });
4712
+ });
4532
4713
  }
4533
- const canvas = await toCanvas(url, options);
4534
- return new Promise(
4535
- (resolve) => canvas.toBlob((blob) => resolve(blob), `image/${type}`, options.quality)
4536
- );
4714
+ state = { fontsCSS, baseCSS, ...state };
4715
+ await runHook("beforeRender", state);
4716
+ await new Promise((resolve) => {
4717
+ idle(() => {
4718
+ const csEl = getComputedStyle(state.element);
4719
+ const rect = state.element.getBoundingClientRect();
4720
+ let w0 = Math.max(1, limitDecimals(state.element.offsetWidth || parseFloat(csEl.width) || rect.width || 1));
4721
+ let h0 = Math.max(1, limitDecimals(state.element.offsetHeight || parseFloat(csEl.height) || rect.height || 1));
4722
+ if (state.options?.excludeMode === "remove") {
4723
+ const hEst = estimateKeptHeight(state.element, state.options);
4724
+ const EPS = 1;
4725
+ if (Number.isFinite(hEst) && hEst > 0) {
4726
+ h0 = Math.max(1, Math.min(h0, limitDecimals(hEst + EPS)));
4727
+ }
4728
+ }
4729
+ const coerceNum = (v, def = NaN) => {
4730
+ const n = typeof v === "string" ? parseFloat(v) : v;
4731
+ return Number.isFinite(n) ? n : def;
4732
+ };
4733
+ const optW = coerceNum(state.options.width);
4734
+ const optH = coerceNum(state.options.height);
4735
+ let w = w0, h = h0;
4736
+ const hasW = Number.isFinite(optW);
4737
+ const hasH = Number.isFinite(optH);
4738
+ const aspect0 = h0 > 0 ? w0 / h0 : 1;
4739
+ if (hasW && hasH) {
4740
+ w = Math.max(1, limitDecimals(optW));
4741
+ h = Math.max(1, limitDecimals(optH));
4742
+ } else if (hasW) {
4743
+ w = Math.max(1, limitDecimals(optW));
4744
+ h = Math.max(1, limitDecimals(w / (aspect0 || 1)));
4745
+ } else if (hasH) {
4746
+ h = Math.max(1, limitDecimals(optH));
4747
+ w = Math.max(1, limitDecimals(h * (aspect0 || 1)));
4748
+ } else {
4749
+ w = w0;
4750
+ h = h0;
4751
+ }
4752
+ let minX = 0, minY = 0, maxX = w0, maxY = h0;
4753
+ if (!outerTransforms && rootTransform2D && Number.isFinite(rootTransform2D.a)) {
4754
+ const M2 = {
4755
+ a: rootTransform2D.a,
4756
+ b: rootTransform2D.b || 0,
4757
+ c: rootTransform2D.c || 0,
4758
+ d: rootTransform2D.d || 1,
4759
+ e: 0,
4760
+ f: 0
4761
+ };
4762
+ const bb2 = bboxWithOriginFull(w0, h0, M2, 0, 0);
4763
+ minX = limitDecimals(bb2.minX);
4764
+ minY = limitDecimals(bb2.minY);
4765
+ maxX = limitDecimals(bb2.maxX);
4766
+ maxY = limitDecimals(bb2.maxY);
4767
+ } else {
4768
+ const useTFBBox = outerTransforms && hasTFBBox(state.element);
4769
+ if (useTFBBox) {
4770
+ const baseTransform2 = csEl.transform && csEl.transform !== "none" ? csEl.transform : "";
4771
+ const ind2 = readIndividualTransforms(state.element);
4772
+ const TOTAL = readTotalTransformMatrix({
4773
+ baseTransform: baseTransform2,
4774
+ rotate: ind2.rotate || "0deg",
4775
+ scale: ind2.scale,
4776
+ translate: ind2.translate
4777
+ });
4778
+ const { ox: ox2, oy: oy2 } = parseTransformOriginPx(csEl, w0, h0);
4779
+ const M = TOTAL.is2D ? TOTAL : new DOMMatrix(TOTAL.toString());
4780
+ const bb = bboxWithOriginFull(w0, h0, M, ox2, oy2);
4781
+ minX = limitDecimals(bb.minX);
4782
+ minY = limitDecimals(bb.minY);
4783
+ maxX = limitDecimals(bb.maxX);
4784
+ maxY = limitDecimals(bb.maxY);
4785
+ }
4786
+ }
4787
+ const bleedShadow = parseBoxShadow(csEl);
4788
+ const bleedBlur = parseFilterBlur(csEl);
4789
+ const bleedOutline = parseOutline(csEl);
4790
+ const drop = parseFilterDropShadows(csEl);
4791
+ const bleed = !outerShadows ? { top: 0, right: 0, bottom: 0, left: 0 } : {
4792
+ top: limitDecimals(bleedShadow.top + bleedBlur.top + bleedOutline.top + drop.bleed.top),
4793
+ right: limitDecimals(bleedShadow.right + bleedBlur.right + bleedOutline.right + drop.bleed.right),
4794
+ bottom: limitDecimals(bleedShadow.bottom + bleedBlur.bottom + bleedOutline.bottom + drop.bleed.bottom),
4795
+ left: limitDecimals(bleedShadow.left + bleedBlur.left + bleedOutline.left + drop.bleed.left)
4796
+ };
4797
+ minX = limitDecimals(minX - bleed.left);
4798
+ minY = limitDecimals(minY - bleed.top);
4799
+ maxX = limitDecimals(maxX + bleed.right);
4800
+ maxY = limitDecimals(maxY + bleed.bottom);
4801
+ const vbW0 = Math.max(1, limitDecimals(maxX - minX));
4802
+ const vbH0 = Math.max(1, limitDecimals(maxY - minY));
4803
+ const scaleW = hasW || hasH ? limitDecimals(w / w0) : 1;
4804
+ const scaleH = hasH || hasW ? limitDecimals(h / h0) : 1;
4805
+ const outW = Math.max(1, limitDecimals(vbW0 * scaleW));
4806
+ const outH = Math.max(1, limitDecimals(vbH0 * scaleH));
4807
+ const svgNS = "http://www.w3.org/2000/svg";
4808
+ const basePad = isSafari() ? 1 : 0;
4809
+ const extraPad = !outerTransforms ? 1 : 0;
4810
+ const pad = limitDecimals(basePad + extraPad);
4811
+ const fo = document.createElementNS(svgNS, "foreignObject");
4812
+ const vbMinX = limitDecimals(minX);
4813
+ const vbMinY = limitDecimals(minY);
4814
+ fo.setAttribute("x", String(limitDecimals(-(vbMinX - pad))));
4815
+ fo.setAttribute("y", String(limitDecimals(-(vbMinY - pad))));
4816
+ fo.setAttribute("width", String(limitDecimals(w0 + pad * 2)));
4817
+ fo.setAttribute("height", String(limitDecimals(h0 + pad * 2)));
4818
+ fo.style.overflow = "visible";
4819
+ const styleTag = document.createElement("style");
4820
+ styleTag.textContent = state.baseCSS + state.fontsCSS + "svg{overflow:visible;} foreignObject{overflow:visible;}" + state.classCSS;
4821
+ fo.appendChild(styleTag);
4822
+ const container = document.createElement("div");
4823
+ container.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
4824
+ container.style.width = `${limitDecimals(w0)}px`;
4825
+ container.style.height = `${limitDecimals(h0)}px`;
4826
+ container.style.overflow = "visible";
4827
+ state.clone.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
4828
+ container.appendChild(state.clone);
4829
+ fo.appendChild(container);
4830
+ const serializer = new XMLSerializer();
4831
+ const foString = serializer.serializeToString(fo);
4832
+ const vbW = limitDecimals(vbW0 + pad * 2);
4833
+ const vbH = limitDecimals(vbH0 + pad * 2);
4834
+ const wantsSize = hasW || hasH;
4835
+ options.meta = { w0, h0, vbW, vbH, targetW: w, targetH: h };
4836
+ const svgOutW = isSafari() && wantsSize ? vbW : limitDecimals(outW + pad * 2);
4837
+ const svgOutH = isSafari() && wantsSize ? vbH : limitDecimals(outH + pad * 2);
4838
+ const svgHeader = `<svg xmlns="${svgNS}" width="${svgOutW}" height="${svgOutH}" viewBox="0 0 ${vbW} ${vbH}">`;
4839
+ const svgFooter = "</svg>";
4840
+ svgString = svgHeader + foString + svgFooter;
4841
+ dataURL = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgString)}`;
4842
+ state = { svgString, dataURL, ...state };
4843
+ resolve();
4844
+ }, { fast });
4845
+ });
4846
+ await runHook("afterRender", state);
4847
+ const sandbox = document.getElementById("snapdom-sandbox");
4848
+ if (sandbox && sandbox.style.position === "absolute") sandbox.remove();
4849
+ return state.dataURL;
4850
+ }
4851
+ function hasTFBBox(el) {
4852
+ return hasBBoxAffectingTransform(el);
4537
4853
  }
4538
4854
 
4539
- // src/exporters/download.js
4540
- async function download(url, options) {
4541
- options.dpr = 1;
4542
- if (options.format === "svg") {
4543
- const blob = await toBlob(url, { ...options, type: "svg" });
4544
- const objectURL = URL.createObjectURL(blob);
4545
- const a2 = document.createElement("a");
4546
- a2.href = objectURL;
4547
- a2.download = options.filename;
4548
- a2.click();
4549
- URL.revokeObjectURL(objectURL);
4550
- return;
4855
+ // src/core/context.js
4856
+ function normalizeCachePolicy(v) {
4857
+ if (typeof v === "string") {
4858
+ const s = v.toLowerCase().trim();
4859
+ if (s === "disabled" || s === "full" || s === "auto" || s === "soft") return (
4860
+ /** @type {CachePolicy} */
4861
+ s
4862
+ );
4551
4863
  }
4552
- const canvas = await toCanvas(url, options);
4553
- const a = document.createElement("a");
4554
- a.href = canvas.toDataURL(`image/${options.format}`, options.quality);
4555
- a.download = options.filename;
4556
- a.click();
4864
+ return "soft";
4865
+ }
4866
+ function createContext(options = {}) {
4867
+ let resolvedFormat = options.format ?? "png";
4868
+ if (resolvedFormat === "jpg") resolvedFormat = "jpeg";
4869
+ const cachePolicy = normalizeCachePolicy(options.cache);
4870
+ return {
4871
+ // Debug & perf
4872
+ debug: options.debug ?? false,
4873
+ fast: options.fast ?? true,
4874
+ scale: options.scale ?? 1,
4875
+ // DOM filters
4876
+ exclude: options.exclude ?? [],
4877
+ excludeMode: options.excludeMode ?? "hide",
4878
+ filter: options.filter ?? null,
4879
+ filterMode: options.filterMode ?? "hide",
4880
+ // Placeholders
4881
+ placeholders: options.placeholders !== false,
4882
+ // default true
4883
+ // Fonts
4884
+ embedFonts: options.embedFonts ?? false,
4885
+ iconFonts: Array.isArray(options.iconFonts) ? options.iconFonts : options.iconFonts ? [options.iconFonts] : [],
4886
+ localFonts: Array.isArray(options.localFonts) ? options.localFonts : [],
4887
+ excludeFonts: options.excludeFonts ?? void 0,
4888
+ fallbackURL: options.fallbackURL ?? void 0,
4889
+ /** @type {CachePolicy} */
4890
+ cache: cachePolicy,
4891
+ // Network
4892
+ useProxy: typeof options.useProxy === "string" ? options.useProxy : "",
4893
+ // Output
4894
+ width: options.width ?? null,
4895
+ height: options.height ?? null,
4896
+ format: resolvedFormat,
4897
+ type: options.type ?? "svg",
4898
+ quality: options.quality ?? 0.92,
4899
+ dpr: options.dpr ?? (window.devicePixelRatio || 1),
4900
+ backgroundColor: options.backgroundColor ?? (["jpeg", "webp"].includes(resolvedFormat) ? "#ffffff" : null),
4901
+ filename: options.filename ?? "snapDOM",
4902
+ // NEW flags (user-friendly)
4903
+ outerTransforms: options.outerTransforms ?? true,
4904
+ outerShadows: options.outerShadows ?? false
4905
+ // Plugins (reservado)
4906
+ // plugins: normalizePlugins(...),
4907
+ };
4557
4908
  }
4558
4909
 
4559
4910
  // src/api/snapdom.js
4560
- snapdom.plugins = (...defs) => {
4911
+ init_browser();
4912
+ function plugins(...defs) {
4561
4913
  registerPlugins(...defs);
4562
4914
  return snapdom;
4563
- };
4915
+ }
4916
+ var snapdom = Object.assign(main, { plugins });
4564
4917
  var INTERNAL_TOKEN = Symbol("snapdom.internal");
4565
4918
  var INTERNAL_EXPORT_TOKEN = Symbol("snapdom.internal.silent");
4566
4919
  var _safariWarmup = false;
4567
- async function snapdom(element, userOptions) {
4920
+ async function main(element, userOptions) {
4568
4921
  if (!element) throw new Error("Element cannot be null or undefined");
4569
4922
  const context = createContext(userOptions);
4570
4923
  attachSessionPlugins(context, userOptions && userOptions.plugins);
@@ -4572,7 +4925,6 @@ async function snapdom(element, userOptions) {
4572
4925
  for (let i = 0; i < 3; i++) {
4573
4926
  try {
4574
4927
  await safariWarmup(element, userOptions);
4575
- console.log("safariWarmup:", i);
4576
4928
  _safariWarmup = false;
4577
4929
  } catch {
4578
4930
  }
@@ -4587,32 +4939,76 @@ async function snapdom(element, userOptions) {
4587
4939
  }
4588
4940
  return snapdom.capture(element, context, INTERNAL_TOKEN);
4589
4941
  }
4590
- snapdom.plugins = (...defs) => {
4591
- registerPlugins(...defs);
4592
- return snapdom;
4593
- };
4594
4942
  snapdom.capture = async (el, context, _token) => {
4595
4943
  if (_token !== INTERNAL_TOKEN) throw new Error("[snapdom.capture] is internal. Use snapdom(...) instead.");
4596
4944
  const url = await captureDOM(el, context);
4597
4945
  const coreExports = {
4598
- img: async (ctx, opts) => toImg(url, { ...ctx, ...opts || {} }),
4599
- svg: async (ctx, opts) => toImg(url, { ...ctx, ...opts || {} }),
4600
- canvas: async (ctx, opts) => toCanvas(url, { ...ctx, ...opts || {} }),
4601
- blob: async (ctx, opts) => toBlob(url, { ...ctx, ...opts || {} }),
4602
- png: async (ctx, opts) => rasterize(url, { ...ctx, ...opts || {}, format: "png" }),
4603
- jpeg: async (ctx, opts) => rasterize(url, { ...ctx, ...opts || {}, format: "jpeg" }),
4604
- webp: async (ctx, opts) => rasterize(url, { ...ctx, ...opts || {}, format: "webp" }),
4605
- download: async (ctx, opts) => download(url, { ...ctx, ...opts || {} })
4946
+ img: async (ctx, opts) => {
4947
+ const { toImg: toImg2 } = await Promise.resolve().then(() => (init_toImg(), toImg_exports));
4948
+ return toImg2(url, { ...ctx, ...opts || {} });
4949
+ },
4950
+ svg: async (ctx, opts) => {
4951
+ const { toSvg } = await Promise.resolve().then(() => (init_toImg(), toImg_exports));
4952
+ return toSvg(url, { ...ctx, ...opts || {} });
4953
+ },
4954
+ canvas: async (ctx, opts) => {
4955
+ const { toCanvas: toCanvas2 } = await Promise.resolve().then(() => (init_toCanvas(), toCanvas_exports));
4956
+ return toCanvas2(url, { ...ctx, ...opts || {} });
4957
+ },
4958
+ blob: async (ctx, opts) => {
4959
+ const { toBlob: toBlob2 } = await Promise.resolve().then(() => (init_toBlob(), toBlob_exports));
4960
+ return toBlob2(url, { ...ctx, ...opts || {} });
4961
+ },
4962
+ png: async (ctx, opts) => {
4963
+ const { rasterize: rasterize2 } = await Promise.resolve().then(() => (init_rasterize(), rasterize_exports));
4964
+ return rasterize2(url, { ...ctx, ...opts || {}, format: "png" });
4965
+ },
4966
+ jpeg: async (ctx, opts) => {
4967
+ const { rasterize: rasterize2 } = await Promise.resolve().then(() => (init_rasterize(), rasterize_exports));
4968
+ return rasterize2(url, { ...ctx, ...opts || {}, format: "jpeg" });
4969
+ },
4970
+ webp: async (ctx, opts) => {
4971
+ const { rasterize: rasterize2 } = await Promise.resolve().then(() => (init_rasterize(), rasterize_exports));
4972
+ return rasterize2(url, { ...ctx, ...opts || {}, format: "webp" });
4973
+ },
4974
+ download: async (ctx, opts) => {
4975
+ const { download: download2 } = await Promise.resolve().then(() => (init_download(), download_exports));
4976
+ return download2(url, { ...ctx, ...opts || {} });
4977
+ }
4606
4978
  };
4607
4979
  const _pluginExports = {
4608
- svg: (opts) => toImg(url, { ...context, ...opts || {}, [INTERNAL_EXPORT_TOKEN]: true }),
4609
- canvas: (opts) => toCanvas(url, { ...context, ...opts || {}, [INTERNAL_EXPORT_TOKEN]: true }),
4610
- png: (opts) => rasterize(url, { ...context, ...opts || {}, format: "png", [INTERNAL_EXPORT_TOKEN]: true }),
4611
- jpeg: (opts) => rasterize(url, { ...context, ...opts || {}, format: "jpeg", [INTERNAL_EXPORT_TOKEN]: true }),
4612
- jpg: (opts) => rasterize(url, { ...context, ...opts || {}, format: "jpeg", [INTERNAL_EXPORT_TOKEN]: true }),
4613
- webp: (opts) => rasterize(url, { ...context, ...opts || {}, format: "webp", [INTERNAL_EXPORT_TOKEN]: true }),
4614
- blob: (opts) => toBlob(url, { ...context, ...opts || {}, [INTERNAL_EXPORT_TOKEN]: true }),
4615
- img: (opts) => toImg(url, { ...context, ...opts || {}, [INTERNAL_EXPORT_TOKEN]: true })
4980
+ svg: async (opts) => {
4981
+ const { toSvg } = await Promise.resolve().then(() => (init_toImg(), toImg_exports));
4982
+ return toSvg(url, { ...context, ...opts || {}, [INTERNAL_EXPORT_TOKEN]: true });
4983
+ },
4984
+ canvas: async (opts) => {
4985
+ const { toCanvas: toCanvas2 } = await Promise.resolve().then(() => (init_toCanvas(), toCanvas_exports));
4986
+ return toCanvas2(url, { ...context, ...opts || {}, [INTERNAL_EXPORT_TOKEN]: true });
4987
+ },
4988
+ png: async (opts) => {
4989
+ const { rasterize: rasterize2 } = await Promise.resolve().then(() => (init_rasterize(), rasterize_exports));
4990
+ return rasterize2(url, { ...context, ...opts || {}, format: "png", [INTERNAL_EXPORT_TOKEN]: true });
4991
+ },
4992
+ jpeg: async (opts) => {
4993
+ const { rasterize: rasterize2 } = await Promise.resolve().then(() => (init_rasterize(), rasterize_exports));
4994
+ return rasterize2(url, { ...context, ...opts || {}, format: "jpeg", [INTERNAL_EXPORT_TOKEN]: true });
4995
+ },
4996
+ jpg: async (opts) => {
4997
+ const { rasterize: rasterize2 } = await Promise.resolve().then(() => (init_rasterize(), rasterize_exports));
4998
+ return rasterize2(url, { ...context, ...opts || {}, format: "jpeg", [INTERNAL_EXPORT_TOKEN]: true });
4999
+ },
5000
+ webp: async (opts) => {
5001
+ const { rasterize: rasterize2 } = await Promise.resolve().then(() => (init_rasterize(), rasterize_exports));
5002
+ return rasterize2(url, { ...context, ...opts || {}, format: "webp", [INTERNAL_EXPORT_TOKEN]: true });
5003
+ },
5004
+ blob: async (opts) => {
5005
+ const { toBlob: toBlob2 } = await Promise.resolve().then(() => (init_toBlob(), toBlob_exports));
5006
+ return toBlob2(url, { ...context, ...opts || {}, [INTERNAL_EXPORT_TOKEN]: true });
5007
+ },
5008
+ img: async (opts) => {
5009
+ const { toImg: toImg2 } = await Promise.resolve().then(() => (init_toImg(), toImg_exports));
5010
+ return toImg2(url, { ...context, ...opts || {}, [INTERNAL_EXPORT_TOKEN]: true });
5011
+ }
4616
5012
  };
4617
5013
  const _defineCtx = { ...context, export: { url }, exports: _pluginExports };
4618
5014
  const providedMaps = await runAll("defineExports", _defineCtx);
@@ -4659,10 +5055,9 @@ snapdom.capture = async (el, context, _token) => {
4659
5055
  toBlob: (opts) => runExport("blob", opts),
4660
5056
  toPng: (opts) => runExport("png", opts),
4661
5057
  toJpg: (opts) => runExport("jpg", opts),
4662
- // ← alias requerido por los tests
5058
+ // alias requerido por tests
4663
5059
  toWebp: (opts) => runExport("webp", opts),
4664
5060
  download: (opts) => runExport("download", opts)
4665
- // ← método directo (no toDownload)
4666
5061
  };
4667
5062
  for (const key of Object.keys(exportsMap)) {
4668
5063
  const helper = "to" + key.charAt(0).toUpperCase() + key.slice(1);
@@ -4752,6 +5147,9 @@ function hasBackgroundOrMask(el) {
4752
5147
  }
4753
5148
 
4754
5149
  // src/api/preCache.js
5150
+ init_utils();
5151
+ init_snapFetch();
5152
+ init_cache();
4755
5153
  async function preCache(root = document, options = {}) {
4756
5154
  const {
4757
5155
  embedFonts = true,