@zumer/snapdom 2.0.0-dev.3 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/snapdom.mjs CHANGED
@@ -1,27 +1,20 @@
1
-
2
1
  /*
3
- * snapdom
4
- * v.2.0.0-dev.3
5
- * Author Juan Martin Muda
6
- * License MIT
7
- **/
8
-
2
+ * SnapDOM
3
+ * v2.0.0
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,845 +653,1187 @@ 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/utils/clone.helpers.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
- var _blobToDataUrlCache = /* @__PURE__ */ new Map();
1135
- async function blobUrlToDataUrl(blobUrl) {
1136
- if (cache.resource?.has(blobUrl)) return cache.resource.get(blobUrl);
1137
- if (_blobToDataUrlCache.has(blobUrl)) return _blobToDataUrlCache.get(blobUrl);
1138
- const p = (async () => {
1139
- const r = await snapFetch(blobUrl, { as: "dataURL", silent: true });
1140
- if (!r.ok || typeof r.data !== "string") {
1141
- throw new Error(`[snapDOM] Failed to read blob URL: ${blobUrl}`);
1142
- }
1143
- cache.resource?.set(blobUrl, r.data);
1144
- return r.data;
1145
- })();
1146
- _blobToDataUrlCache.set(blobUrl, p);
1147
- try {
1148
- const data = await p;
1149
- _blobToDataUrlCache.set(blobUrl, data);
1150
- return data;
1151
- } catch (e) {
1152
- _blobToDataUrlCache.delete(blobUrl);
1153
- throw e;
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
+ };
1154
1078
  }
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
+ };
1155
1092
  }
1156
- var BLOB_URL_RE = /\bblob:[^)"'\s]+/g;
1157
- async function replaceBlobUrlsInCssText(cssText) {
1158
- if (!cssText || cssText.indexOf("blob:") === -1) return cssText;
1159
- const uniques = Array.from(new Set(cssText.match(BLOB_URL_RE) || []));
1160
- if (uniques.length === 0) return cssText;
1161
- let out = cssText;
1162
- for (const u of uniques) {
1163
- try {
1164
- const d = await blobUrlToDataUrl(u);
1165
- out = out.split(u).join(d);
1166
- } catch {
1167
- }
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;
1168
1102
  }
1169
- return out;
1103
+ if (NO_DEFAULTS_TAGS.has(source.tagName?.toLowerCase())) {
1104
+ const author = source.getAttribute?.("style");
1105
+ if (author) clone.setAttribute("style", author);
1106
+ }
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);
1170
1121
  }
1171
- function isBlobUrl(u) {
1172
- return typeof u === "string" && u.startsWith("blob:");
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;
1173
1124
  }
1174
- function parseSrcset(srcset) {
1175
- return (srcset || "").split(",").map((s) => s.trim()).filter(Boolean).map((item) => {
1176
- const m = item.match(/^(\S+)(\s+.+)?$/);
1177
- return m ? { url: m[1], desc: m[2] || "" } : null;
1178
- }).filter(Boolean);
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";
1179
1134
  }
1180
- function stringifySrcset(parts) {
1181
- return parts.map((p) => p.desc ? `${p.url} ${p.desc.trim()}` : p.url).join(", ");
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");
1182
1140
  }
1183
- async function resolveBlobUrlsInTree(root) {
1184
- if (!root) return;
1185
- const imgs = root.querySelectorAll ? root.querySelectorAll("img") : [];
1186
- for (const img of imgs) {
1187
- try {
1188
- const srcAttr = img.getAttribute("src");
1189
- const effective = srcAttr || img.currentSrc || "";
1190
- if (isBlobUrl(effective)) {
1191
- const data = await blobUrlToDataUrl(effective);
1192
- img.setAttribute("src", data);
1193
- }
1194
- const srcset = img.getAttribute("srcset");
1195
- if (srcset && srcset.includes("blob:")) {
1196
- const parts = parseSrcset(srcset);
1197
- let changed = false;
1198
- for (const p of parts) {
1199
- if (isBlobUrl(p.url)) {
1200
- try {
1201
- p.url = await blobUrlToDataUrl(p.url);
1202
- changed = true;
1203
- } catch {
1204
- }
1205
- }
1206
- }
1207
- if (changed) img.setAttribute("srcset", stringifySrcset(parts));
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;
1208
1212
  }
1209
- } catch {
1210
1213
  }
1211
1214
  }
1212
- const svgImages = root.querySelectorAll ? root.querySelectorAll("image") : [];
1213
- for (const node of svgImages) {
1215
+ let cs = null;
1216
+ if (hasVar) {
1214
1217
  try {
1215
- const XLINK_NS = "http://www.w3.org/1999/xlink";
1216
- const href = node.getAttribute("href") || node.getAttributeNS?.(XLINK_NS, "href");
1217
- if (isBlobUrl(href)) {
1218
- const d = await blobUrlToDataUrl(href);
1219
- node.setAttribute("href", d);
1220
- node.removeAttributeNS?.(XLINK_NS, "href");
1221
- }
1218
+ cs = getComputedStyle(sourceEl);
1222
1219
  } catch {
1223
1220
  }
1224
1221
  }
1225
- const styled = root.querySelectorAll ? root.querySelectorAll("[style*='blob:']") : [];
1226
- for (const el of styled) {
1227
- try {
1228
- const styleText = el.getAttribute("style");
1229
- if (styleText && styleText.includes("blob:")) {
1230
- const replaced = await replaceBlobUrlsInCssText(styleText);
1231
- el.setAttribute("style", replaced);
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 {
1234
+ }
1235
+ }
1232
1236
  }
1233
- } catch {
1234
1237
  }
1235
1238
  }
1236
- const styleTags = root.querySelectorAll ? root.querySelectorAll("style") : [];
1237
- for (const s of styleTags) {
1238
- try {
1239
- const css = s.textContent || "";
1240
- if (css.includes("blob:")) {
1241
- s.textContent = await replaceBlobUrlsInCssText(css);
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 {
1250
+ }
1242
1251
  }
1243
- } catch {
1244
1252
  }
1245
1253
  }
1246
- const urlAttrs = ["poster"];
1247
- for (const attr of urlAttrs) {
1248
- const nodes = root.querySelectorAll ? root.querySelectorAll(`[${attr}^='blob:']`) : [];
1249
- for (const n of nodes) {
1254
+ if (!hasVar) {
1255
+ if (!cs) {
1250
1256
  try {
1251
- const u = n.getAttribute(attr);
1252
- if (isBlobUrl(u)) {
1253
- n.setAttribute(attr, await blobUrlToDataUrl(u));
1254
- }
1257
+ cs = getComputedStyle(sourceEl);
1255
1258
  } catch {
1259
+ cs = null;
1260
+ }
1261
+ }
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) {
1269
+ try {
1270
+ cloneEl.style.setProperty(prop, v.trim());
1271
+ } catch {
1272
+ }
1256
1273
  }
1257
1274
  }
1258
1275
  }
1259
1276
  }
1260
1277
 
1261
- // src/core/clone.js
1262
- async function deepClone(node, sessionCache, options) {
1263
- if (!node) throw new Error("Invalid node");
1264
- const clonedAssignedNodes = /* @__PURE__ */ new Set();
1265
- let pendingSelectValue = null;
1266
- let pendingTextAreaValue = null;
1267
- if (node.nodeType === Node.ELEMENT_NODE) {
1268
- const tag = (node.localName || node.tagName || "").toLowerCase();
1269
- if (node.id === "snapdom-sandbox" || node.hasAttribute("data-snapdom-sandbox")) {
1270
- return null;
1271
- }
1272
- if (NO_CAPTURE_TAGS.has(tag)) {
1273
- return null;
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 = "";
1342
+ try {
1343
+ sr.querySelectorAll("style").forEach((s) => {
1344
+ css += (s.textContent || "") + "\n";
1345
+ });
1346
+ const sheets = sr.adoptedStyleSheets || [];
1347
+ for (const sh of sheets) {
1348
+ try {
1349
+ if (sh && sh.cssRules) {
1350
+ for (const rule of sh.cssRules) css += rule.cssText + "\n";
1351
+ }
1352
+ } catch {
1353
+ }
1274
1354
  }
1355
+ } catch {
1275
1356
  }
1276
- if (node.nodeType === Node.TEXT_NODE) {
1277
- return node.cloneNode(true);
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 {
1278
1376
  }
1279
- if (node.nodeType !== Node.ELEMENT_NODE) {
1280
- return node.cloneNode(true);
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 {
1281
1392
  }
1282
- if (node.getAttribute("data-capture") === "exclude") {
1283
- if (options.excludeMode === "hide") {
1284
- const spacer = document.createElement("div");
1285
- const rect = node.getBoundingClientRect();
1286
- spacer.style.cssText = `display:inline-block;width:${rect.width}px;height:${rect.height}px;visibility:hidden;`;
1287
- return spacer;
1288
- } else if (options.excludeMode === "remove") {
1289
- return null;
1290
- }
1393
+ try {
1394
+ const rootCS = getComputedStyle(document.documentElement);
1395
+ let v = rootCS.getPropertyValue(name).trim();
1396
+ if (v) return v;
1397
+ } catch {
1291
1398
  }
1292
- if (options.exclude && Array.isArray(options.exclude)) {
1293
- for (const selector of options.exclude) {
1294
- try {
1295
- if (node.matches?.(selector)) {
1296
- if (options.excludeMode === "hide") {
1297
- const spacer = document.createElement("div");
1298
- const rect = node.getBoundingClientRect();
1299
- spacer.style.cssText = `display:inline-block;width:${rect.width}px;height:${rect.height}px;visibility:hidden;`;
1300
- return spacer;
1301
- } else if (options.excludeMode === "remove") {
1302
- return null;
1303
- }
1304
- }
1305
- } catch (err) {
1306
- console.warn(`Invalid selector in exclude option: ${selector}`, err);
1307
- }
1308
- }
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};`);
1309
1406
  }
1310
- if (typeof options.filter === "function") {
1311
- try {
1312
- if (!options.filter(node)) {
1313
- if (options.filterMode === "hide") {
1314
- const spacer = document.createElement("div");
1315
- const rect = node.getBoundingClientRect();
1316
- spacer.style.cssText = `display:inline-block;width:${rect.width}px;height:${rect.height}px;visibility:hidden;`;
1317
- return spacer;
1318
- } else if (options.filterMode === "remove") {
1319
- return null;
1320
- }
1321
- }
1322
- } catch (err) {
1323
- console.warn("Error in filter function:", err);
1324
- }
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", "");
1325
1415
  }
1326
- if (node.tagName === "IFRAME") {
1327
- let sameOrigin = false;
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 = () => {
1328
1422
  try {
1329
- sameOrigin = !!(node.contentDocument || node.contentWindow?.document);
1423
+ return iframe.contentDocument || iframe.contentWindow?.document || null;
1330
1424
  } catch {
1331
- sameOrigin = false;
1332
- }
1333
- if (sameOrigin) {
1334
- try {
1335
- const wrapper = await rasterizeIframe(node, sessionCache, options);
1336
- return wrapper;
1337
- } catch (err) {
1338
- console.warn("[SnapDOM] iframe rasterization failed, fallback:", err);
1339
- }
1425
+ return null;
1340
1426
  }
1341
- if (options.placeholders) {
1342
- const fallback = document.createElement("div");
1343
- 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;`;
1344
- inlineAllStyles(node, fallback, sessionCache, options);
1345
- return fallback;
1346
- } else {
1347
- const rect = node.getBoundingClientRect();
1348
- const spacer = document.createElement("div");
1349
- spacer.style.cssText = `display:inline-block;width:${rect.width}px;height:${rect.height}px;visibility:hidden;`;
1350
- inlineAllStyles(node, spacer, sessionCache, options);
1351
- return spacer;
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++;
1434
+ }
1435
+ return doc && (doc.body || doc.documentElement) ? doc : null;
1436
+ }
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 {
1447
+ }
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 };
1451
+ }
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 {
1352
1461
  }
1462
+ };
1463
+ }
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");
1353
1471
  }
1354
- if (node.getAttribute("data-capture") === "placeholder") {
1355
- const clone2 = node.cloneNode(false);
1356
- sessionCache.nodeMap.set(clone2, node);
1357
- inlineAllStyles(node, clone2, sessionCache, options);
1358
- const placeholder = document.createElement("div");
1359
- placeholder.textContent = node.getAttribute("data-placeholder-text") || "";
1360
- placeholder.style.cssText = "color:#666;font-size:12px;text-align:center;line-height:1.4;padding:0.5em;box-sizing:border-box;";
1361
- clone2.appendChild(placeholder);
1362
- return clone2;
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();
1363
1479
  }
1364
- if (node.tagName === "CANVAS") {
1365
- let url = "";
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}`);
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;
1513
+ }
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) {
1366
1522
  try {
1367
- const ctx = node.getContext("2d", { willReadFrequently: true });
1368
- try {
1369
- ctx && ctx.getImageData(0, 0, 1, 1);
1370
- } catch {
1523
+ const d = await blobUrlToDataUrl(u);
1524
+ out = out.split(u).join(d);
1525
+ } catch {
1526
+ }
1527
+ }
1528
+ return out;
1529
+ }
1530
+ function isBlobUrl(u) {
1531
+ return typeof u === "string" && u.startsWith("blob:");
1532
+ }
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);
1371
1552
  }
1372
- await new Promise((r) => requestAnimationFrame(r));
1373
- url = node.toDataURL("image/png");
1374
- if (!url || url === "data:,") {
1375
- try {
1376
- ctx && ctx.getImageData(0, 0, 1, 1);
1377
- } catch {
1378
- }
1379
- await new Promise((r) => requestAnimationFrame(r));
1380
- url = node.toDataURL("image/png");
1381
- if (!url || url === "data:,") {
1382
- const scratch = document.createElement("canvas");
1383
- scratch.width = node.width;
1384
- scratch.height = node.height;
1385
- const sctx = scratch.getContext("2d");
1386
- if (sctx) {
1387
- sctx.drawImage(node, 0, 0);
1388
- url = scratch.toDataURL("image/png");
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
+ }
1389
1564
  }
1390
1565
  }
1566
+ if (changed) img.setAttribute("srcset", stringifySrcset(parts));
1391
1567
  }
1392
1568
  } catch {
1393
1569
  }
1394
- const img = document.createElement("img");
1570
+ }
1571
+ const svgImages = root.querySelectorAll ? root.querySelectorAll("image") : [];
1572
+ for (const node of svgImages) {
1395
1573
  try {
1396
- img.decoding = "sync";
1397
- img.loading = "eager";
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
+ }
1398
1581
  } catch {
1399
1582
  }
1400
- if (url) img.src = url;
1401
- img.width = node.width;
1402
- img.height = node.height;
1583
+ }
1584
+ const styled = root.querySelectorAll ? root.querySelectorAll("[style*='blob:']") : [];
1585
+ for (const el of styled) {
1403
1586
  try {
1404
- const cs = getComputedStyle(node);
1405
- if (cs.width) img.style.width = cs.width;
1406
- if (cs.height) img.style.height = cs.height;
1587
+ const styleText = el.getAttribute("style");
1588
+ if (styleText && styleText.includes("blob:")) {
1589
+ const replaced = await replaceBlobUrlsInCssText(styleText);
1590
+ el.setAttribute("style", replaced);
1591
+ }
1407
1592
  } catch {
1408
1593
  }
1409
- sessionCache.nodeMap.set(img, node);
1410
- inlineAllStyles(node, img, sessionCache, options);
1411
- return img;
1412
1594
  }
1413
- let clone;
1414
- try {
1415
- clone = node.cloneNode(false);
1416
- resolveCSSVars(node, clone);
1417
- sessionCache.nodeMap.set(clone, node);
1418
- if (node.tagName === "IMG") {
1419
- freezeImgSrcset(node, clone);
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) {
1420
1609
  try {
1421
- const rect = node.getBoundingClientRect();
1422
- let w = Math.round(rect.width || 0);
1423
- let h = Math.round(rect.height || 0);
1424
- if (!w || !h) {
1425
- const computed = window.getComputedStyle(node);
1426
- const cssW = parseFloat(computed.width) || 0;
1427
- const cssH = parseFloat(computed.height) || 0;
1428
- const attrW = parseInt(node.getAttribute("width") || "", 10) || 0;
1429
- const attrH = parseInt(node.getAttribute("height") || "", 10) || 0;
1430
- const propW = node.width || node.naturalWidth || 0;
1431
- const propH = node.height || node.naturalHeight || 0;
1432
- w = Math.round(w || cssW || attrW || propW || 0);
1433
- h = Math.round(h || cssH || attrH || propH || 0);
1610
+ const u = n.getAttribute(attr);
1611
+ if (isBlobUrl(u)) {
1612
+ n.setAttribute(attr, await blobUrlToDataUrl(u));
1434
1613
  }
1435
- if (w) clone.dataset.snapdomWidth = String(w);
1436
- if (h) clone.dataset.snapdomHeight = String(h);
1437
1614
  } catch {
1438
1615
  }
1439
1616
  }
1440
- } catch (err) {
1441
- console.error("[Snapdom] Failed to clone node:", node, err);
1442
- throw err;
1443
- }
1444
- if (node instanceof HTMLTextAreaElement) {
1445
- const rect = node.getBoundingClientRect();
1446
- clone.style.width = `${rect.width}px`;
1447
- clone.style.height = `${rect.height}px`;
1448
- }
1449
- if (node instanceof HTMLInputElement) {
1450
- clone.value = node.value;
1451
- clone.setAttribute("value", node.value);
1452
- if (node.checked !== void 0) {
1453
- clone.checked = node.checked;
1454
- if (node.checked) clone.setAttribute("checked", "");
1455
- if (node.indeterminate) clone.indeterminate = node.indeterminate;
1456
- }
1457
1617
  }
1458
- if (node instanceof HTMLSelectElement) {
1459
- pendingSelectValue = node.value;
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;
1460
1837
  }
1461
1838
  if (node instanceof HTMLTextAreaElement) {
1462
1839
  pendingTextAreaValue = node.value;
@@ -1547,6 +1924,13 @@ async function deepClone(node, sessionCache, options) {
1547
1924
  return clone;
1548
1925
  }
1549
1926
 
1927
+ // src/modules/pseudo.js
1928
+ init_utils();
1929
+
1930
+ // src/modules/fonts.js
1931
+ init_helpers();
1932
+ init_cache();
1933
+
1550
1934
  // src/modules/iconFonts.js
1551
1935
  var defaultIconFonts = [
1552
1936
  // /uicons/i,
@@ -1758,6 +2142,7 @@ async function ligatureIconToImage(cloneRoot, sourceRoot) {
1758
2142
  }
1759
2143
 
1760
2144
  // src/modules/fonts.js
2145
+ init_snapFetch();
1761
2146
  async function iconToImage(unicodeChar, fontFamily, fontWeight, fontSize = 32, color = "#000") {
1762
2147
  fontFamily = fontFamily.replace(/^['"]+|['"]+$/g, "");
1763
2148
  const dpr = window.devicePixelRatio || 1;
@@ -2243,6 +2628,18 @@ async function embedCustomFonts({
2243
2628
  if (nearWeight && sOk && tOk) return true;
2244
2629
  }
2245
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
+ }
2246
2643
  return false;
2247
2644
  }
2248
2645
  const simpleExcluder = buildSimpleExcluder(exclude);
@@ -2527,6 +2924,7 @@ async function ensureFontsReady(families, warmupRepetitions = 2) {
2527
2924
  }
2528
2925
 
2529
2926
  // src/modules/counter.js
2927
+ init_cache();
2530
2928
  function hasCounters(input) {
2531
2929
  return /\bcounter\s*\(|\bcounters\s*\(/.test(input || "");
2532
2930
  }
@@ -2723,6 +3121,8 @@ function resolveCountersInContent(raw, node, ctx) {
2723
3121
  }
2724
3122
 
2725
3123
  // src/modules/pseudo.js
3124
+ init_snapFetch();
3125
+ init_cache();
2726
3126
  var __preflightMemo = /* @__PURE__ */ new WeakMap();
2727
3127
  var CSS_RULE_SCAN_BUDGET = 300;
2728
3128
  function preflightWithFp(doc, sessionCache) {
@@ -3154,31 +3554,55 @@ function inlineExternalDefsAndSymbols(element, lookupRoot) {
3154
3554
  "marker-end"
3155
3555
  ];
3156
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
+ };
3157
3575
  const globalExistingIds = new Set(
3158
3576
  Array.from(element.querySelectorAll("[id]")).map((n) => n.id)
3159
3577
  );
3160
3578
  const neededIds = /* @__PURE__ */ new Set();
3161
3579
  let sawAnyReference = false;
3162
- const addUrlIdsFromValue = (val) => {
3580
+ const addUrlIdsFromValue = (val, queueForResolve = null) => {
3163
3581
  if (!val) return;
3164
3582
  URL_ID_RE.lastIndex = 0;
3165
3583
  let m;
3166
3584
  while (m = URL_ID_RE.exec(val)) {
3167
3585
  sawAnyReference = true;
3168
3586
  const id = (m[1] || "").trim();
3169
- 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
+ }
3170
3594
  }
3171
3595
  };
3172
3596
  const collectReferencesInSvg = (rootSvg) => {
3173
- const uses = rootSvg.querySelectorAll('use[href^="#"], use[xlink\\:href^="#"]');
3597
+ const uses = rootSvg.querySelectorAll("use");
3174
3598
  for (const u of uses) {
3175
- const href = u.getAttribute("href") || u.getAttribute("xlink:href");
3599
+ const href = getHrefAttr(u);
3176
3600
  if (!href || !href.startsWith("#")) continue;
3177
3601
  sawAnyReference = true;
3178
3602
  const id = href.slice(1).trim();
3179
3603
  if (id && !globalExistingIds.has(id)) neededIds.add(id);
3180
3604
  }
3181
- 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("]';
3182
3606
  const candidates = rootSvg.querySelectorAll(query);
3183
3607
  for (const el of candidates) {
3184
3608
  addUrlIdsFromValue(el.getAttribute("style") || "");
@@ -3229,21 +3653,26 @@ function inlineExternalDefsAndSymbols(element, lookupRoot) {
3229
3653
  globalExistingIds.add(id);
3230
3654
  const walk = [clone, ...clone.querySelectorAll("*")];
3231
3655
  for (const node of walk) {
3232
- const h = node.getAttribute?.("href") || node.getAttribute?.("xlink:href");
3233
- if (h && h.startsWith("#")) {
3234
- const ref = h.slice(1).trim();
3235
- 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
+ }
3236
3662
  }
3237
3663
  const style = node.getAttribute?.("style") || "";
3238
- if (style) addUrlIdsFromValue(style);
3664
+ if (style) addUrlIdsFromValue(style, queued);
3239
3665
  for (const a of URL_ATTRS) {
3240
3666
  const v = node.getAttribute?.(a);
3241
- if (v) addUrlIdsFromValue(v);
3667
+ if (v) addUrlIdsFromValue(v, queued);
3242
3668
  }
3243
3669
  }
3244
3670
  }
3245
3671
  }
3246
3672
 
3673
+ // src/core/prepare.js
3674
+ init_cache();
3675
+
3247
3676
  // src/modules/changeCSS.js
3248
3677
  function freezeSticky(originalRoot, cloneRoot) {
3249
3678
  if (!originalRoot || !cloneRoot) return;
@@ -3456,6 +3885,7 @@ async function prepareClone(element, options = {}) {
3456
3885
  }
3457
3886
 
3458
3887
  // src/modules/images.js
3888
+ init_snapFetch();
3459
3889
  function extractImageDimensions(img) {
3460
3890
  const dsW = parseInt(img.dataset?.snapdomWidth || "", 10) || 0;
3461
3891
  const dsH = parseInt(img.dataset?.snapdomHeight || "", 10) || 0;
@@ -3528,6 +3958,7 @@ async function inlineImages(clone, options = {}) {
3528
3958
  }
3529
3959
 
3530
3960
  // src/modules/background.js
3961
+ init_utils();
3531
3962
  async function inlineBackgroundImages(source, clone, styleCache, options = {}) {
3532
3963
  const queue = [[source, clone]];
3533
3964
  const URL_PROPS = [
@@ -3625,6 +4056,10 @@ async function inlineBackgroundImages(source, clone, styleCache, options = {}) {
3625
4056
  }
3626
4057
  }
3627
4058
 
4059
+ // src/core/capture.js
4060
+ init_utils();
4061
+ init_cache();
4062
+
3628
4063
  // src/modules/lineClamp.js
3629
4064
  function lineClamp(el) {
3630
4065
  if (!el) return () => {
@@ -4260,468 +4695,229 @@ async function captureDOM(element, options) {
4260
4695
  useProxy: state.options.useProxy
4261
4696
  });
4262
4697
  resolve();
4263
- }, { fast });
4264
- });
4265
- }
4266
- const usedTags = collectUsedTagNames(state.clone).sort();
4267
- const tagKey = usedTags.join(",");
4268
- if (cache.baseStyle.has(tagKey)) {
4269
- baseCSS = cache.baseStyle.get(tagKey);
4270
- } else {
4271
- await new Promise((resolve) => {
4272
- idle(() => {
4273
- baseCSS = generateDedupedBaseCSS(usedTags);
4274
- cache.baseStyle.set(tagKey, baseCSS);
4275
- resolve();
4276
- }, { fast });
4277
- });
4278
- }
4279
- state = { fontsCSS, baseCSS, ...state };
4280
- await runHook("beforeRender", state);
4281
- await new Promise((resolve) => {
4282
- idle(() => {
4283
- const csEl = getComputedStyle(state.element);
4284
- const rect = state.element.getBoundingClientRect();
4285
- let w0 = Math.max(1, limitDecimals(state.element.offsetWidth || parseFloat(csEl.width) || rect.width || 1));
4286
- let h0 = Math.max(1, limitDecimals(state.element.offsetHeight || parseFloat(csEl.height) || rect.height || 1));
4287
- if (state.options?.excludeMode === "remove") {
4288
- const hEst = estimateKeptHeight(state.element, state.options);
4289
- const EPS = 1;
4290
- if (Number.isFinite(hEst) && hEst > 0) {
4291
- h0 = Math.max(1, Math.min(h0, limitDecimals(hEst + EPS)));
4292
- }
4293
- }
4294
- const coerceNum = (v, def = NaN) => {
4295
- const n = typeof v === "string" ? parseFloat(v) : v;
4296
- return Number.isFinite(n) ? n : def;
4297
- };
4298
- const optW = coerceNum(state.options.width);
4299
- const optH = coerceNum(state.options.height);
4300
- let w = w0, h = h0;
4301
- const hasW = Number.isFinite(optW);
4302
- const hasH = Number.isFinite(optH);
4303
- const aspect0 = h0 > 0 ? w0 / h0 : 1;
4304
- if (hasW && hasH) {
4305
- w = Math.max(1, limitDecimals(optW));
4306
- h = Math.max(1, limitDecimals(optH));
4307
- } else if (hasW) {
4308
- w = Math.max(1, limitDecimals(optW));
4309
- h = Math.max(1, limitDecimals(w / (aspect0 || 1)));
4310
- } else if (hasH) {
4311
- h = Math.max(1, limitDecimals(optH));
4312
- w = Math.max(1, limitDecimals(h * (aspect0 || 1)));
4313
- } else {
4314
- w = w0;
4315
- h = h0;
4316
- }
4317
- let minX = 0, minY = 0, maxX = w0, maxY = h0;
4318
- if (!outerTransforms && rootTransform2D && Number.isFinite(rootTransform2D.a)) {
4319
- const M2 = {
4320
- a: rootTransform2D.a,
4321
- b: rootTransform2D.b || 0,
4322
- c: rootTransform2D.c || 0,
4323
- d: rootTransform2D.d || 1,
4324
- e: 0,
4325
- f: 0
4326
- };
4327
- const bb2 = bboxWithOriginFull(w0, h0, M2, 0, 0);
4328
- minX = limitDecimals(bb2.minX);
4329
- minY = limitDecimals(bb2.minY);
4330
- maxX = limitDecimals(bb2.maxX);
4331
- maxY = limitDecimals(bb2.maxY);
4332
- } else {
4333
- const useTFBBox = outerTransforms && hasTFBBox(state.element);
4334
- if (useTFBBox) {
4335
- const baseTransform2 = csEl.transform && csEl.transform !== "none" ? csEl.transform : "";
4336
- const ind2 = readIndividualTransforms(state.element);
4337
- const TOTAL = readTotalTransformMatrix({
4338
- baseTransform: baseTransform2,
4339
- rotate: ind2.rotate || "0deg",
4340
- scale: ind2.scale,
4341
- translate: ind2.translate
4342
- });
4343
- const { ox: ox2, oy: oy2 } = parseTransformOriginPx(csEl, w0, h0);
4344
- const M = TOTAL.is2D ? TOTAL : new DOMMatrix(TOTAL.toString());
4345
- const bb = bboxWithOriginFull(w0, h0, M, ox2, oy2);
4346
- minX = limitDecimals(bb.minX);
4347
- minY = limitDecimals(bb.minY);
4348
- maxX = limitDecimals(bb.maxX);
4349
- maxY = limitDecimals(bb.maxY);
4350
- }
4351
- }
4352
- const bleedShadow = parseBoxShadow(csEl);
4353
- const bleedBlur = parseFilterBlur(csEl);
4354
- const bleedOutline = parseOutline(csEl);
4355
- const drop = parseFilterDropShadows(csEl);
4356
- const bleed = !outerShadows ? { top: 0, right: 0, bottom: 0, left: 0 } : {
4357
- top: limitDecimals(bleedShadow.top + bleedBlur.top + bleedOutline.top + drop.bleed.top),
4358
- right: limitDecimals(bleedShadow.right + bleedBlur.right + bleedOutline.right + drop.bleed.right),
4359
- bottom: limitDecimals(bleedShadow.bottom + bleedBlur.bottom + bleedOutline.bottom + drop.bleed.bottom),
4360
- left: limitDecimals(bleedShadow.left + bleedBlur.left + bleedOutline.left + drop.bleed.left)
4361
- };
4362
- minX = limitDecimals(minX - bleed.left);
4363
- minY = limitDecimals(minY - bleed.top);
4364
- maxX = limitDecimals(maxX + bleed.right);
4365
- maxY = limitDecimals(maxY + bleed.bottom);
4366
- const vbW0 = Math.max(1, limitDecimals(maxX - minX));
4367
- const vbH0 = Math.max(1, limitDecimals(maxY - minY));
4368
- const scaleW = hasW || hasH ? limitDecimals(w / w0) : 1;
4369
- const scaleH = hasH || hasW ? limitDecimals(h / h0) : 1;
4370
- const outW = Math.max(1, limitDecimals(vbW0 * scaleW));
4371
- const outH = Math.max(1, limitDecimals(vbH0 * scaleH));
4372
- const svgNS = "http://www.w3.org/2000/svg";
4373
- const basePad = isSafari() ? 1 : 0;
4374
- const extraPad = !outerTransforms ? 1 : 0;
4375
- const pad = limitDecimals(basePad + extraPad);
4376
- const fo = document.createElementNS(svgNS, "foreignObject");
4377
- const vbMinX = limitDecimals(minX);
4378
- const vbMinY = limitDecimals(minY);
4379
- fo.setAttribute("x", String(limitDecimals(-(vbMinX - pad))));
4380
- fo.setAttribute("y", String(limitDecimals(-(vbMinY - pad))));
4381
- fo.setAttribute("width", String(limitDecimals(w0 + pad * 2)));
4382
- fo.setAttribute("height", String(limitDecimals(h0 + pad * 2)));
4383
- fo.style.overflow = "visible";
4384
- const styleTag = document.createElement("style");
4385
- styleTag.textContent = state.baseCSS + state.fontsCSS + "svg{overflow:visible;} foreignObject{overflow:visible;}" + state.classCSS;
4386
- fo.appendChild(styleTag);
4387
- const container = document.createElement("div");
4388
- container.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
4389
- container.style.width = `${limitDecimals(w0)}px`;
4390
- container.style.height = `${limitDecimals(h0)}px`;
4391
- container.style.overflow = "visible";
4392
- state.clone.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
4393
- container.appendChild(state.clone);
4394
- fo.appendChild(container);
4395
- const serializer = new XMLSerializer();
4396
- const foString = serializer.serializeToString(fo);
4397
- const vbW = limitDecimals(vbW0 + pad * 2);
4398
- const vbH = limitDecimals(vbH0 + pad * 2);
4399
- const wantsSize = hasW || hasH;
4400
- options.meta = { w0, h0, vbW, vbH, targetW: w, targetH: h };
4401
- const svgOutW = isSafari() && wantsSize ? vbW : limitDecimals(outW + pad * 2);
4402
- const svgOutH = isSafari() && wantsSize ? vbH : limitDecimals(outH + pad * 2);
4403
- const svgHeader = `<svg xmlns="${svgNS}" width="${svgOutW}" height="${svgOutH}" viewBox="0 0 ${vbW} ${vbH}">`;
4404
- const svgFooter = "</svg>";
4405
- svgString = svgHeader + foString + svgFooter;
4406
- dataURL = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgString)}`;
4407
- state = { svgString, dataURL, ...state };
4408
- resolve();
4409
- }, { fast });
4410
- });
4411
- await runHook("afterRender", state);
4412
- const sandbox = document.getElementById("snapdom-sandbox");
4413
- if (sandbox && sandbox.style.position === "absolute") sandbox.remove();
4414
- return state.dataURL;
4415
- }
4416
- function hasTFBBox(el) {
4417
- return hasBBoxAffectingTransform(el);
4418
- }
4419
-
4420
- // src/core/context.js
4421
- function normalizeCachePolicy(v) {
4422
- if (typeof v === "string") {
4423
- const s = v.toLowerCase().trim();
4424
- if (s === "disabled" || s === "full" || s === "auto" || s === "soft") return (
4425
- /** @type {CachePolicy} */
4426
- s
4427
- );
4428
- }
4429
- return "soft";
4430
- }
4431
- function createContext(options = {}) {
4432
- let resolvedFormat = options.format ?? "png";
4433
- if (resolvedFormat === "jpg") resolvedFormat = "jpeg";
4434
- const cachePolicy = normalizeCachePolicy(options.cache);
4435
- return {
4436
- // Debug & perf
4437
- debug: options.debug ?? false,
4438
- fast: options.fast ?? true,
4439
- scale: options.scale ?? 1,
4440
- // DOM filters
4441
- exclude: options.exclude ?? [],
4442
- excludeMode: options.excludeMode ?? "hide",
4443
- filter: options.filter ?? null,
4444
- filterMode: options.filterMode ?? "hide",
4445
- // Placeholders
4446
- placeholders: options.placeholders !== false,
4447
- // default true
4448
- // Fonts
4449
- embedFonts: options.embedFonts ?? false,
4450
- iconFonts: Array.isArray(options.iconFonts) ? options.iconFonts : options.iconFonts ? [options.iconFonts] : [],
4451
- localFonts: Array.isArray(options.localFonts) ? options.localFonts : [],
4452
- excludeFonts: options.excludeFonts ?? void 0,
4453
- fallbackURL: options.fallbackURL ?? void 0,
4454
- /** @type {CachePolicy} */
4455
- cache: cachePolicy,
4456
- // Network
4457
- useProxy: typeof options.useProxy === "string" ? options.useProxy : "",
4458
- // Output
4459
- width: options.width ?? null,
4460
- height: options.height ?? null,
4461
- format: resolvedFormat,
4462
- type: options.type ?? "svg",
4463
- quality: options.quality ?? 0.92,
4464
- dpr: options.dpr ?? (window.devicePixelRatio || 1),
4465
- backgroundColor: options.backgroundColor ?? (["jpeg", "webp"].includes(resolvedFormat) ? "#ffffff" : null),
4466
- filename: options.filename ?? "snapDOM",
4467
- // NEW flags (user-friendly)
4468
- outerTransforms: options.outerTransforms ?? true,
4469
- outerShadows: options.outerShadows ?? false
4470
- // Plugins (reservado)
4471
- // plugins: normalizePlugins(...),
4472
- };
4473
- }
4474
-
4475
- // src/exporters/toCanvas.js
4476
- function isSvgDataURL(u) {
4477
- return typeof u === "string" && /^data:image\/svg\+xml/i.test(u);
4478
- }
4479
- function decodeSvgFromDataURL(u) {
4480
- const i = u.indexOf(",");
4481
- return i >= 0 ? decodeURIComponent(u.slice(i + 1)) : "";
4482
- }
4483
- function encodeSvgToDataURL(svgText) {
4484
- return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgText)}`;
4485
- }
4486
- function splitDecls(s) {
4487
- let parts = [], buf = "", depth = 0;
4488
- for (let i = 0; i < s.length; i++) {
4489
- const ch = s[i];
4490
- if (ch === "(") depth++;
4491
- if (ch === ")") depth = Math.max(0, depth - 1);
4492
- if (ch === ";" && depth === 0) {
4493
- parts.push(buf);
4494
- buf = "";
4495
- } else buf += ch;
4496
- }
4497
- if (buf.trim()) parts.push(buf);
4498
- return parts.map((x) => x.trim()).filter(Boolean);
4499
- }
4500
- function boxShadowToDropShadow(value) {
4501
- const layers = [];
4502
- let buf = "", depth = 0;
4503
- for (let i = 0; i < value.length; i++) {
4504
- const ch = value[i];
4505
- if (ch === "(") depth++;
4506
- if (ch === ")") depth = Math.max(0, depth - 1);
4507
- if (ch === "," && depth === 0) {
4508
- layers.push(buf.trim());
4509
- buf = "";
4510
- } else buf += ch;
4511
- }
4512
- if (buf.trim()) layers.push(buf.trim());
4513
- const fns = [];
4514
- for (const layer of layers) {
4515
- if (/\binset\b/i.test(layer)) continue;
4516
- const nums = layer.match(/-?\d+(?:\.\d+)?px/gi) || [];
4517
- const [ox = "0px", oy = "0px", blur = "0px"] = nums;
4518
- let color = layer.replace(/-?\d+(?:\.\d+)?px/gi, "").replace(/\binset\b/ig, "").trim().replace(/\s{2,}/g, " ");
4519
- const hasColor = !!color && color !== ",";
4520
- fns.push(`drop-shadow(${ox} ${oy} ${blur}${hasColor ? ` ${color}` : ""})`);
4521
- }
4522
- return fns.join(" ");
4523
- }
4524
- function rewriteDeclList(list) {
4525
- const decls = splitDecls(list);
4526
- let filter = null, wfilter = null, box = null;
4527
- const rest = [];
4528
- for (const d of decls) {
4529
- const idx = d.indexOf(":");
4530
- if (idx < 0) continue;
4531
- const prop = d.slice(0, idx).trim().toLowerCase();
4532
- const val = d.slice(idx + 1).trim();
4533
- if (prop === "box-shadow") box = val;
4534
- else if (prop === "filter") filter = val;
4535
- else if (prop === "-webkit-filter") wfilter = val;
4536
- else rest.push([prop, val]);
4537
- }
4538
- if (box) {
4539
- const ds = boxShadowToDropShadow(box);
4540
- if (ds) {
4541
- filter = filter ? `${filter} ${ds}` : ds;
4542
- wfilter = wfilter ? `${wfilter} ${ds}` : ds;
4543
- }
4544
- }
4545
- const out = [...rest];
4546
- if (filter) out.push(["filter", filter]);
4547
- if (wfilter) out.push(["-webkit-filter", wfilter]);
4548
- return out.map(([k, v]) => `${k}:${v}`).join(";");
4549
- }
4550
- function rewriteCssBlock(css) {
4551
- return css.replace(/([^{}]+)\{([^}]*)\}/g, (_m, sel, body) => `${sel}{${rewriteDeclList(body)}}`);
4552
- }
4553
- function rewriteSvgBoxShadowToDropShadow(svgText) {
4554
- svgText = svgText.replace(
4555
- /<style[^>]*>([\s\S]*?)<\/style>/gi,
4556
- (m, css) => m.replace(css, rewriteCssBlock(css))
4557
- );
4558
- svgText = svgText.replace(
4559
- /style=(['"])([\s\S]*?)\1/gi,
4560
- (m, q, body) => `style=${q}${rewriteDeclList(body)}${q}`
4561
- );
4562
- return svgText;
4563
- }
4564
- function maybeConvertBoxShadowForSafari(url) {
4565
- if (!isSafari() || !isSvgDataURL(url)) return url;
4566
- try {
4567
- const svg = decodeSvgFromDataURL(url);
4568
- const fixed = rewriteSvgBoxShadowToDropShadow(svg);
4569
- return encodeSvgToDataURL(fixed);
4570
- } catch {
4571
- return url;
4572
- }
4573
- }
4574
- async function toCanvas(url, options) {
4575
- let { width: optW, height: optH, scale = 1, dpr = 1, meta = {}, backgroundColor } = options;
4576
- url = maybeConvertBoxShadowForSafari(url);
4577
- const img = new Image();
4578
- img.loading = "eager";
4579
- img.decoding = "sync";
4580
- img.crossOrigin = "anonymous";
4581
- img.src = url;
4582
- await img.decode();
4583
- const natW = img.naturalWidth;
4584
- const natH = img.naturalHeight;
4585
- const refW = Number.isFinite(meta.w0) ? meta.w0 : natW;
4586
- const refH = Number.isFinite(meta.h0) ? meta.h0 : natH;
4587
- let outW, outH;
4588
- const hasW = Number.isFinite(optW);
4589
- const hasH = Number.isFinite(optH);
4590
- if (hasW && hasH) {
4591
- outW = Math.max(1, optW);
4592
- outH = Math.max(1, optH);
4593
- } else if (hasW) {
4594
- const k = optW / Math.max(1, refW);
4595
- outW = optW;
4596
- outH = refH * k;
4597
- } else if (hasH) {
4598
- const k = optH / Math.max(1, refH);
4599
- outH = optH;
4600
- outW = refW * k;
4601
- } else {
4602
- outW = natW;
4603
- outH = natH;
4604
- }
4605
- outW = outW * scale;
4606
- outH = outH * scale;
4607
- const canvas = document.createElement("canvas");
4608
- canvas.width = outW * dpr;
4609
- canvas.height = outH * dpr;
4610
- canvas.style.width = `${outW}px`;
4611
- canvas.style.height = `${outH}px`;
4612
- const ctx = canvas.getContext("2d");
4613
- if (dpr !== 1) ctx.scale(dpr, dpr);
4614
- if (backgroundColor) {
4615
- ctx.save();
4616
- ctx.fillStyle = backgroundColor;
4617
- ctx.fillRect(0, 0, outW, outH);
4618
- ctx.restore();
4619
- }
4620
- ctx.drawImage(img, 0, 0, outW, outH);
4621
- return canvas;
4622
- }
4623
-
4624
- // src/modules/rasterize.js
4625
- async function rasterize(url, options) {
4626
- const canvas = await toCanvas(url, options);
4627
- const img = new Image();
4628
- img.src = canvas.toDataURL(`image/${options.format}`, options.quality);
4629
- await img.decode();
4630
- img.style.width = `${canvas.width / options.dpr}px`;
4631
- img.style.height = `${canvas.height / options.dpr}px`;
4632
- return img;
4633
- }
4634
-
4635
- // src/exporters/toImg.js
4636
- async function toImg(url, options) {
4637
- const { scale = 1, width, height, meta = {} } = options;
4638
- const hasW = Number.isFinite(width);
4639
- const hasH = Number.isFinite(height);
4640
- const wantsScale = Number.isFinite(scale) && scale !== 1 || hasW || hasH;
4641
- if (isSafari() && wantsScale) {
4642
- const pngUrl = await rasterize(url, { ...options, format: "png", quality: 1, meta });
4643
- return pngUrl;
4644
- }
4645
- const img = new Image();
4646
- img.decoding = "sync";
4647
- img.loading = "eager";
4648
- img.src = url;
4649
- await img.decode();
4650
- if (hasW && hasH) {
4651
- img.style.width = `${width}px`;
4652
- img.style.height = `${height}px`;
4653
- } else if (hasW) {
4654
- const refW = Number.isFinite(meta.w0) ? meta.w0 : img.naturalWidth;
4655
- const refH = Number.isFinite(meta.h0) ? meta.h0 : img.naturalHeight;
4656
- const k = width / Math.max(1, refW);
4657
- img.style.width = `${width}px`;
4658
- img.style.height = `${Math.round(refH * k)}px`;
4659
- } else if (hasH) {
4660
- const refW = Number.isFinite(meta.w0) ? meta.w0 : img.naturalWidth;
4661
- const refH = Number.isFinite(meta.h0) ? meta.h0 : img.naturalHeight;
4662
- const k = height / Math.max(1, refH);
4663
- img.style.height = `${height}px`;
4664
- img.style.width = `${Math.round(refW * k)}px`;
4665
- } else {
4666
- const cssW = Math.round(img.naturalWidth * scale);
4667
- const cssH = Math.round(img.naturalHeight * scale);
4668
- img.style.width = `${cssW}px`;
4669
- img.style.height = `${cssH}px`;
4670
- if (typeof url === "string" && url.startsWith("data:image/svg+xml")) {
4671
- try {
4672
- const decoded = decodeURIComponent(url.split(",")[1]);
4673
- const patched = decoded.replace(/width="[^"]*"/, `width="${cssW}"`).replace(/height="[^"]*"/, `height="${cssH}"`);
4674
- url = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(patched)}`;
4675
- img.src = url;
4676
- } catch {
4677
- }
4678
- }
4698
+ }, { fast });
4699
+ });
4679
4700
  }
4680
- return img;
4681
- }
4682
-
4683
- // src/exporters/toBlob.js
4684
- async function toBlob(url, options) {
4685
- const type = options.type;
4686
- if (type === "svg") {
4687
- const svgText = decodeURIComponent(url.split(",")[1]);
4688
- return new Blob([svgText], { type: "image/svg+xml" });
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
+ });
4689
4713
  }
4690
- const canvas = await toCanvas(url, options);
4691
- return new Promise(
4692
- (resolve) => canvas.toBlob((blob) => resolve(blob), `image/${type}`, options.quality)
4693
- );
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);
4694
4853
  }
4695
4854
 
4696
- // src/exporters/download.js
4697
- async function download(url, options) {
4698
- options.dpr = 1;
4699
- if (options.format === "svg") {
4700
- const blob = await toBlob(url, { ...options, type: "svg" });
4701
- const objectURL = URL.createObjectURL(blob);
4702
- const a2 = document.createElement("a");
4703
- a2.href = objectURL;
4704
- a2.download = options.filename;
4705
- a2.click();
4706
- URL.revokeObjectURL(objectURL);
4707
- 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
+ );
4708
4863
  }
4709
- const canvas = await toCanvas(url, options);
4710
- const a = document.createElement("a");
4711
- a.href = canvas.toDataURL(`image/${options.format}`, options.quality);
4712
- a.download = options.filename;
4713
- 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
+ };
4714
4908
  }
4715
4909
 
4716
4910
  // src/api/snapdom.js
4717
- snapdom.plugins = (...defs) => {
4911
+ init_browser();
4912
+ function plugins(...defs) {
4718
4913
  registerPlugins(...defs);
4719
4914
  return snapdom;
4720
- };
4915
+ }
4916
+ var snapdom = Object.assign(main, { plugins });
4721
4917
  var INTERNAL_TOKEN = Symbol("snapdom.internal");
4722
4918
  var INTERNAL_EXPORT_TOKEN = Symbol("snapdom.internal.silent");
4723
4919
  var _safariWarmup = false;
4724
- async function snapdom(element, userOptions) {
4920
+ async function main(element, userOptions) {
4725
4921
  if (!element) throw new Error("Element cannot be null or undefined");
4726
4922
  const context = createContext(userOptions);
4727
4923
  attachSessionPlugins(context, userOptions && userOptions.plugins);
@@ -4729,7 +4925,6 @@ async function snapdom(element, userOptions) {
4729
4925
  for (let i = 0; i < 3; i++) {
4730
4926
  try {
4731
4927
  await safariWarmup(element, userOptions);
4732
- console.log("safariWarmup:", i);
4733
4928
  _safariWarmup = false;
4734
4929
  } catch {
4735
4930
  }
@@ -4744,32 +4939,76 @@ async function snapdom(element, userOptions) {
4744
4939
  }
4745
4940
  return snapdom.capture(element, context, INTERNAL_TOKEN);
4746
4941
  }
4747
- snapdom.plugins = (...defs) => {
4748
- registerPlugins(...defs);
4749
- return snapdom;
4750
- };
4751
4942
  snapdom.capture = async (el, context, _token) => {
4752
4943
  if (_token !== INTERNAL_TOKEN) throw new Error("[snapdom.capture] is internal. Use snapdom(...) instead.");
4753
4944
  const url = await captureDOM(el, context);
4754
4945
  const coreExports = {
4755
- img: async (ctx, opts) => toImg(url, { ...ctx, ...opts || {} }),
4756
- svg: async (ctx, opts) => toImg(url, { ...ctx, ...opts || {} }),
4757
- canvas: async (ctx, opts) => toCanvas(url, { ...ctx, ...opts || {} }),
4758
- blob: async (ctx, opts) => toBlob(url, { ...ctx, ...opts || {} }),
4759
- png: async (ctx, opts) => rasterize(url, { ...ctx, ...opts || {}, format: "png" }),
4760
- jpeg: async (ctx, opts) => rasterize(url, { ...ctx, ...opts || {}, format: "jpeg" }),
4761
- webp: async (ctx, opts) => rasterize(url, { ...ctx, ...opts || {}, format: "webp" }),
4762
- 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
+ }
4763
4978
  };
4764
4979
  const _pluginExports = {
4765
- svg: (opts) => toImg(url, { ...context, ...opts || {}, [INTERNAL_EXPORT_TOKEN]: true }),
4766
- canvas: (opts) => toCanvas(url, { ...context, ...opts || {}, [INTERNAL_EXPORT_TOKEN]: true }),
4767
- png: (opts) => rasterize(url, { ...context, ...opts || {}, format: "png", [INTERNAL_EXPORT_TOKEN]: true }),
4768
- jpeg: (opts) => rasterize(url, { ...context, ...opts || {}, format: "jpeg", [INTERNAL_EXPORT_TOKEN]: true }),
4769
- jpg: (opts) => rasterize(url, { ...context, ...opts || {}, format: "jpeg", [INTERNAL_EXPORT_TOKEN]: true }),
4770
- webp: (opts) => rasterize(url, { ...context, ...opts || {}, format: "webp", [INTERNAL_EXPORT_TOKEN]: true }),
4771
- blob: (opts) => toBlob(url, { ...context, ...opts || {}, [INTERNAL_EXPORT_TOKEN]: true }),
4772
- 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
+ }
4773
5012
  };
4774
5013
  const _defineCtx = { ...context, export: { url }, exports: _pluginExports };
4775
5014
  const providedMaps = await runAll("defineExports", _defineCtx);
@@ -4816,10 +5055,9 @@ snapdom.capture = async (el, context, _token) => {
4816
5055
  toBlob: (opts) => runExport("blob", opts),
4817
5056
  toPng: (opts) => runExport("png", opts),
4818
5057
  toJpg: (opts) => runExport("jpg", opts),
4819
- // ← alias requerido por los tests
5058
+ // alias requerido por tests
4820
5059
  toWebp: (opts) => runExport("webp", opts),
4821
5060
  download: (opts) => runExport("download", opts)
4822
- // ← método directo (no toDownload)
4823
5061
  };
4824
5062
  for (const key of Object.keys(exportsMap)) {
4825
5063
  const helper = "to" + key.charAt(0).toUpperCase() + key.slice(1);
@@ -4909,6 +5147,9 @@ function hasBackgroundOrMask(el) {
4909
5147
  }
4910
5148
 
4911
5149
  // src/api/preCache.js
5150
+ init_utils();
5151
+ init_snapFetch();
5152
+ init_cache();
4912
5153
  async function preCache(root = document, options = {}) {
4913
5154
  const {
4914
5155
  embedFonts = true,