@zumer/snapdom 0.9.9 → 1.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/README.md CHANGED
@@ -1,38 +1,31 @@
1
1
  # snapDOM
2
2
 
3
-
4
3
  <p align="center">
5
4
  <a href="http://zumerlab.github.io/orbit-docs">
6
5
  <img src="https://raw.githubusercontent.com/zumerlab/snapdom/main/docs/assets/hero.png" width="80%">
7
6
  </a>
8
7
  </p>
9
8
 
10
-
11
9
  <p align="center">
12
10
  <a href="https://www.npmjs.com/package/@zumer/snapdom"><img src="https://img.shields.io/github/package-json/v/zumerlab/snapdom"></a>
13
11
  </p>
14
12
 
13
+ **snapDOM** is a fast and accurate DOM-to-image capture tool built for **Zumly**, a zoom-based view transition framework.
15
14
 
15
+ It captures any HTML element as a scalable SVG image, preserving styles, fonts, background images, pseudo-elements, and even shadow DOM. It also supports export to raster image formats and canvas.
16
16
 
17
- **snapDOM** is a fast and accurate DOM capture tool to images developed for **Zumly**, a WIP framework that enables zoom-based view transitions.
18
-
19
- It converts any HTML element into a scalable SVG image, preserving styles, fonts, backgrounds, shadow DOM content, pseudo-elements, and more.
20
-
21
-
22
- - 📸 Full DOM capture
23
- - 🎨 Embedded styles, pseudo-elements, and fonts
24
- - 🖼️ Export to SVG, PNG, JPG, WebP, or `canvas`
25
- - ⚡ Ultra fast, no dependencies
26
- - 📦 100% based on standard Web APIs
17
+ * 📸 Full DOM capture
18
+ * 🎨 Embedded styles, pseudo-elements, and fonts
19
+ * 🖼️ Export to SVG, PNG, JPG, WebP, or `canvas`
20
+ * ⚡ Ultra fast, no dependencies
21
+ * 📦 100% based on standard Web APIs
27
22
 
28
23
  ## Demo
29
24
 
30
- https://zumerlab.github.io/snapdom/
25
+ [https://zumerlab.github.io/snapdom/](https://zumerlab.github.io/snapdom/)
31
26
 
32
27
  ## Installation
33
28
 
34
- You can use **snapDOM** via **NPM**, **CDN**, **script tag**, or by **importing as a module**.
35
-
36
29
  ### NPM / Yarn
37
30
 
38
31
  ```sh
@@ -55,15 +48,13 @@ yarn add @zumer/snapdom
55
48
  <script src="snapdom.js"></script>
56
49
  ```
57
50
 
58
- The global object `snapdom` will be available.
59
-
60
51
  ### ES Module
61
52
 
62
- ```javascript
53
+ ```js
63
54
  import { snapdom } from './snapdom.mjs';
64
55
  ```
65
56
 
66
- ### Script Tag (Type Module)
57
+ ### Module via CDN
67
58
 
68
59
  ```html
69
60
  <script type="module">
@@ -71,70 +62,101 @@ import { snapdom } from './snapdom.mjs';
71
62
  </script>
72
63
  ```
73
64
 
74
- Now you can call `snapdom(el)`, `snapdom.toPng(el)`, etc., directly in your JavaScript.
75
-
76
65
  ## Basic usage
77
66
 
78
- ```javascript
79
- // Capture an element as <img> tag
80
- const el = document.querySelector("#myElement")
81
- const img = await snapdom.toImg(el);
67
+ ### Reusable result (recommended)
82
68
 
69
+ ```js
70
+ const el = document.querySelector('#target');
71
+ const result = await snapdom(el, { scale: 2 });
72
+
73
+ const img = await result.toPng();
83
74
  document.body.appendChild(img);
75
+
76
+ await result.download({ format: 'jpg', filename: 'my-capture' });
77
+ ```
78
+
79
+ ### One-step shortcuts
80
+
81
+ ```js
82
+ const el = document.querySelector('#target');
83
+ const png = await snapdom.toPng(el);
84
+ document.body.appendChild(png);
85
+
86
+ const blob = await snapdom.toBlob(el);
84
87
  ```
85
88
 
86
89
  ## API
87
90
 
88
- ### Note: API may evolve until v1.0.0!
91
+ ### `snapdom(el, options?)`
92
+
93
+ Returns an object with reusable export methods:
89
94
 
90
- The main API is exposed as `snapdom` and offers multiple capture methods:
95
+ ```js
96
+ {
97
+ url: string;
98
+ toRaw(): string;
99
+ toImg(): Promise<HTMLImageElement>;
100
+ toCanvas(): Promise<HTMLCanvasElement>;
101
+ toBlob(): Promise<Blob>;
102
+ toPng(): Promise<HTMLImageElement>;
103
+ toJpg(options?): Promise<HTMLImageElement>;
104
+ toWebp(options?): Promise<HTMLImageElement>;
105
+ download(options?): Promise<void>;
106
+ }
107
+ ```
108
+
109
+ ### Shortcut methods
91
110
 
92
- | Method | Description |
93
- | :------------------------------- | :------------------------------------------------------------- |
94
- | `snapdom(el, options?)` | Captures as SVG Data URL |
95
- | `snapdom.toImg(el, options?)` | Captures as `HTMLImageElement` (SVG) |
96
- | `snapdom.toCanvas(el, options?)` | Captures as `HTMLCanvasElement` |
97
- | `snapdom.toPng(el, options?)` | Captures as PNG image (`Image`) |
98
- | `snapdom.toJpg(el, options?)` | Captures as JPG image (`Image`) |
99
- | `snapdom.toWebp(el, options?)` | Captures as WebP image (`Image`) |
100
- | `snapdom.toBlob(el, options?)` | Captures as SVG `Blob` |
101
- | `preCache(root?, options?)` | Preload resources for faster and more accurate captures |
111
+ | Method | Description |
112
+ | ------------------------------ | ------------------------------------- |
113
+ | `snapdom.toImg(el, options?)` | Returns an `HTMLImageElement` |
114
+ | `snapdom.toCanvas(el, options?) ` | Returns a `Canvas` |
115
+ | `snapdom.toBlob(el, options?)` | Returns an SVG `Blob` |
116
+ | `snapdom.toPng(el, options?)` | Returns a PNG image |
117
+ | `snapdom.toJpg(el, options?)` | Returns a JPG image |
118
+ | `snapdom.toWebp(el, options?)` | Returns a WebP image |
119
+ | `snapdom.download(el, options?) ` | Triggers download in specified format |
102
120
 
103
- ---
121
+ ## Options
104
122
 
105
- ### Options
123
+ All capture methods accept an `options` object:
106
124
 
107
- You can pass an `options` object to control the behavior of all capture methods:
125
+ | Option | Type | Default | Description |
126
+ | ----------------- | ------- | -------- | ------------------------------------------ |
127
+ | `compress` | boolean | `true` | Removes redundant styles |
128
+ | `fast` | boolean | `true` | Skips idle delay for faster results |
129
+ | `embedFonts` | boolean | `false` | Inlines fonts (icon fonts always embedded) |
130
+ | `scale` | number | `1` | Output scale multiplier |
131
+ | `backgroundColor` | string | `"#fff"` | Fallback color for JPG/WebP |
132
+ | `quality` | number | `1` | Quality for JPG/WebP (0 to 1) |
108
133
 
109
- | Option | Type | Default | Description |
110
- | ----------------- | --------- | -------- | ----------------------------------------------------------------------------------- |
111
- | `compress` | `boolean` | `true` | Removes default styles to reduce size and deduplicate CSS where possible. |
112
- | `fast` | `boolean` | `true` | Skips `requestIdleCallback` delays and executes preparation immediately. |
113
- | `embedFonts` | `boolean` | `false` | Inlines external fonts as Data URLs (except icon fonts, which are always embedded). |
114
- | `scale` | `number` | `1` | Multiplies the output resolution without affecting layout size. |
115
- | `backgroundColor` | `string` | `"#fff"` | Used when exporting to JPG or WebP formats (which don't support transparency). |
116
- | `quality` | `number` | `1` | Compression quality for JPG/WebP, between `0` and `1`. |
134
+ ### Download options
135
+
136
+ ```js
137
+ {
138
+ format?: "svg" | "png" | "jpg" | "jpeg" | "webp"; // default: "png"
139
+ filename?: string; // default: "capture"
140
+ backgroundColor?: string; // optional override
141
+ }
142
+ ```
117
143
 
118
- ---
119
144
 
120
145
  ### `preCache()` – Optional helper
121
146
 
122
- The `preCache()` function can be used to load external resources (like images and fonts) in advance.
147
+ The `preCache()` function can be used to load external resources (like images and fonts) in advance. It is specially useful when the element to capure is big and complex.
123
148
 
124
149
  ```js
125
150
  import { preCache } from '@zumer/snapdom';
126
151
 
127
- await preCache(document.body, {
128
- embedFonts: true,
129
- preWarm: true
130
- });
152
+ await preCache(document.body);
131
153
  ```
132
154
 
133
155
  ```js
134
156
  import { snapdom, preCache } from './snapdom.mjs';
135
157
  window.addEventListener('load', async () => {
136
158
  await preCache();
137
- console.log('📦 Resources preloaded');
159
+ console.log('📦 Resources preloaded');
138
160
  });
139
161
  ```
140
162
 
@@ -142,50 +164,22 @@ import { snapdom, preCache } from './snapdom.mjs';
142
164
 
143
165
  * `embedFonts` *(boolean, default: true)* — Inlines non-icon fonts during preload.
144
166
  * `reset` *(boolean, default: false)* — Clears all existing internal caches.
145
- * `preWarm` *(boolean, default: true)* — Runs a fake capture in memory to warm up styles, fonts and layout detection.
146
-
147
- ## Special features
148
-
149
- * **Shadow DOM**: Captures content inside Web Components and `shadowRoot`.
150
- * **Pseudo-elements**: Captures `::before` and `::after`, including background images.
151
- * **Backgrounds and images**: Inlines external images as Data URLs.
152
- * **Fonts**: Replicates applied font families without requiring external font files.
153
- * **Icon fonts**: Captures icon fonts like **Font Awesome** and **Material Icons**.
154
- * **Placeholder and Exclusion**:
155
-
156
- * `data-capture="exclude"`: Skips an element while preserving layout space.
157
- * `data-capture="placeholder"` + `data-placeholder-text="Text"`: Replaces an element with placeholder text.
158
167
 
159
- **Now with improved fidelity and even faster performance.**
160
168
 
161
- ## Full example
169
+ ## Features
162
170
 
163
- ```html
164
- <div id="captureMe">
165
- <h1 style="color: tomato;">Hello World!</h1>
166
- <p>This content will be captured.</p>
167
- </div>
168
-
169
- <button id="captureBtn">Capture as img</button>
170
-
171
- <script type="module">
172
- import { snapdom } from './snapdom.mjs';
173
-
174
- const button = document.getElementById('captureBtn');
175
- button.addEventListener('click', async () => {
176
- const target = document.getElementById('captureMe');
177
- const img = await snapdom.toJpg(target, { quality: 0.5});
178
- document.body.appendChild(img);
179
- });
180
- </script>
181
- ```
171
+ * Captures **shadow DOM** and Web Components
172
+ * Supports `::before` and `::after` pseudo-elements
173
+ * Inlines background images and fonts
174
+ * Handles **Font Awesome**, **Material Icons**, and more
175
+ * `data-capture="exclude"` to ignore an element
176
+ * `data-capture="placeholder"` with `data-placeholder-text` for masked replacements
182
177
 
183
178
  ## Limitations
184
179
 
185
- * External images must be CORS-accessible.
186
- * Iframes are not captured.
187
-
188
- ### Please if you find a bug **open an issue**.
180
+ * External images must be CORS-accessible
181
+ * Iframes are not supported
182
+ * When WebP format is used on Safari, it will fallback to PNG rendering.
189
183
 
190
184
  ## Benchmarks
191
185
 
@@ -221,7 +215,7 @@ npm run test:benchmark
221
215
 
222
216
  They execute in **headless Chromium** using real DOM nodes.
223
217
 
218
+
224
219
  ## License
225
220
 
226
221
  MIT © Zumerlab
227
-
package/dist/snapdom.js CHANGED
@@ -1,7 +1,7 @@
1
1
 
2
2
  /*
3
3
  * snapdom
4
- * v.0.9.9
4
+ * v.1.0.0
5
5
  * Author Juan Martin Muda
6
6
  * License MIT
7
7
  **/
@@ -247,6 +247,9 @@
247
247
  }
248
248
  return snap;
249
249
  }
250
+ function isSafari() {
251
+ return /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
252
+ }
250
253
 
251
254
  // src/modules/styles.js
252
255
  function inlineAllStyles(source, clone, styleMap, cache, compress) {
@@ -291,6 +294,9 @@
291
294
  img.src = dataURL;
292
295
  img.width = node.width;
293
296
  img.height = node.height;
297
+ img.style.display = "inline-block";
298
+ img.style.width = `${node.width}px`;
299
+ img.style.height = `${node.height}px`;
294
300
  return img;
295
301
  }
296
302
  if (node.nodeType === Node.TEXT_NODE) {
@@ -596,14 +602,12 @@
596
602
  await new Promise((resolve) => {
597
603
  idle(() => {
598
604
  const rect = element.getBoundingClientRect();
599
- const w = rect.width * scale;
600
- const h = rect.height * scale;
601
- if (scale !== 1) {
602
- clone.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
603
- clone.style.transform = `scale(${scale})`;
604
- clone.style.transformOrigin = "top left";
605
- clone.style.width = `${rect.width}px`;
606
- clone.style.height = `${rect.height}px`;
605
+ const w = Math.ceil(rect.width);
606
+ const h = Math.ceil(rect.height);
607
+ clone.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
608
+ clone.style.transformOrigin = "top left";
609
+ if (scale !== 1 && isSafari()) {
610
+ clone.style.scale = `${scale}`;
607
611
  }
608
612
  const svgNS = "http://www.w3.org/2000/svg";
609
613
  const fo = document.createElementNS(svgNS, "foreignObject");
@@ -628,68 +632,122 @@
628
632
  }
629
633
 
630
634
  // src/api/snapdom.js
631
- async function snapdom(element, options = {}) {
632
- if (!element) {
633
- throw new Error("Element cannot be null or undefined");
635
+ async function toImg(url, { dpr = 1, scale = 1 }) {
636
+ const img = new Image();
637
+ img.src = url;
638
+ await img.decode();
639
+ if (isSafari) {
640
+ img.width = img.width * scale;
641
+ img.height = img.height * scale;
642
+ } else {
643
+ img.width = img.width / scale;
644
+ img.height = img.height / scale;
634
645
  }
635
- return await captureDOM(element, options);
646
+ return img;
636
647
  }
637
- snapdom.toImg = async (el, options = {}) => {
638
- const url = await snapdom(el, options);
648
+ async function toCanvas(url, { dpr = 1, scale = 1 }) {
639
649
  const img = new Image();
640
650
  img.src = url;
641
651
  await img.decode();
642
- return img;
643
- };
644
- snapdom.toCanvas = async (el, options = {}) => {
645
- const img = await snapdom.toImg(el, options);
646
652
  const canvas = document.createElement("canvas");
647
- canvas.width = img.width;
648
- canvas.height = img.height;
649
- canvas.getContext("2d").drawImage(img, 0, 0);
653
+ const width = img.width * scale * dpr;
654
+ const height = img.height * scale * dpr;
655
+ canvas.width = Math.ceil(width);
656
+ canvas.height = Math.ceil(height);
657
+ const ctx = canvas.getContext("2d");
658
+ ctx.scale(scale * dpr, scale * dpr);
659
+ ctx.drawImage(img, 0, 0);
650
660
  return canvas;
651
- };
652
- snapdom.toPng = async (el, options = {}) => {
653
- const canvas = await snapdom.toCanvas(el, options);
654
- const img = new Image();
655
- img.src = canvas.toDataURL("image/png");
656
- await img.decode();
657
- return img;
658
- };
659
- snapdom.toJpg = async (el, options = {}) => {
660
- const { backgroundColor = "#fff", quality = 1 } = options;
661
- const canvas = await snapdom.toCanvas(el, options);
662
- const temp = document.createElement("canvas");
663
- temp.width = canvas.width;
664
- temp.height = canvas.height;
665
- const ctx = temp.getContext("2d");
666
- ctx.fillStyle = backgroundColor;
667
- ctx.fillRect(0, 0, temp.width, temp.height);
668
- ctx.drawImage(canvas, 0, 0);
669
- const img = new Image();
670
- img.src = temp.toDataURL("image/jpeg", quality);
671
- await img.decode();
672
- return img;
673
- };
674
- snapdom.toWebp = async (el, options = {}) => {
675
- const { backgroundColor = "#fff", quality = 1 } = options;
676
- const canvas = await snapdom.toCanvas(el, options);
661
+ }
662
+ async function toBlob(url) {
663
+ const svgText = decodeURIComponent(url.split(",")[1]);
664
+ return new Blob([svgText], { type: "image/svg+xml" });
665
+ }
666
+ async function createBackground(url, { dpr = 1, scale = 1 }, backgroundColor) {
667
+ const baseCanvas = await toCanvas(url, { dpr, scale });
668
+ if (!backgroundColor) return baseCanvas;
677
669
  const temp = document.createElement("canvas");
678
- temp.width = canvas.width;
679
- temp.height = canvas.height;
670
+ temp.width = baseCanvas.width;
671
+ temp.height = baseCanvas.height;
680
672
  const ctx = temp.getContext("2d");
681
673
  ctx.fillStyle = backgroundColor;
682
674
  ctx.fillRect(0, 0, temp.width, temp.height);
683
- ctx.drawImage(canvas, 0, 0);
675
+ ctx.drawImage(baseCanvas, 0, 0);
676
+ return temp;
677
+ }
678
+ async function toRasterImg(url, { dpr = 1, scale = 1, backgroundColor = "#fff", quality }, format = "png") {
679
+ const canvas = await createBackground(url, { dpr, scale }, backgroundColor);
684
680
  const img = new Image();
685
- img.src = temp.toDataURL("image/webp", quality);
681
+ img.src = canvas.toDataURL(`image/${format}`, quality);
686
682
  await img.decode();
683
+ img.style.width = `${canvas.width / dpr}px`;
684
+ img.style.height = `${canvas.height / dpr}px`;
687
685
  return img;
686
+ }
687
+ async function download(url, { dpr = 1, scale = 1, backgroundColor = "#fff", format = "png", filename = "capture" } = {}) {
688
+ if (format === "svg") {
689
+ const blob = await toBlob(url);
690
+ const objectURL = URL.createObjectURL(blob);
691
+ const a2 = document.createElement("a");
692
+ a2.href = objectURL;
693
+ a2.download = `${filename}.svg`;
694
+ a2.click();
695
+ URL.revokeObjectURL(objectURL);
696
+ return;
697
+ }
698
+ const defaultBg = ["jpg", "jpeg", "webp"].includes(format) ? "#fff" : void 0;
699
+ const finalBg = backgroundColor ?? defaultBg;
700
+ const canvas = await createBackground(url, { dpr, scale }, finalBg);
701
+ const mime = {
702
+ jpg: "image/jpeg",
703
+ jpeg: "image/jpeg",
704
+ png: "image/png",
705
+ webp: "image/webp"
706
+ }[format] || "image/png";
707
+ const dataURL = canvas.toDataURL(mime);
708
+ const a = document.createElement("a");
709
+ a.href = dataURL;
710
+ a.download = `${filename}.${format}`;
711
+ a.click();
712
+ }
713
+ async function snapdom(element, options = {}) {
714
+ options = { scale: 1, ...options };
715
+ if (!element) throw new Error("Element cannot be null or undefined");
716
+ return await snapdom.capture(element, options);
717
+ }
718
+ snapdom.capture = async (el, options = {}) => {
719
+ const url = await captureDOM(el, options);
720
+ const dpr = window.devicePixelRatio || 1;
721
+ const scale = options.scale || 1;
722
+ return {
723
+ url,
724
+ options,
725
+ toRaw: () => url,
726
+ toImg: () => toImg(url, { dpr, scale }),
727
+ toCanvas: () => toCanvas(url, { dpr, scale }),
728
+ toBlob: () => toBlob(url),
729
+ toPng: () => toRasterImg(url, { dpr, scale }, "png"),
730
+ toJpg: (options2) => toRasterImg(url, { dpr, scale, ...options2 }, "jpeg"),
731
+ toWebp: (options2) => toRasterImg(url, { dpr, scale, ...options2 }, "webp"),
732
+ download: ({ format = "png", filename = "capture", backgroundColor } = {}) => download(url, { dpr, scale, backgroundColor, format, filename })
733
+ };
688
734
  };
689
- snapdom.toBlob = async (el, options = {}) => {
690
- const dataUrl = await snapdom(el, options);
691
- const svgText = decodeURIComponent(dataUrl.split(",")[1]);
692
- return new Blob([svgText], { type: "image/svg+xml" });
735
+ snapdom.toRaw = async (el, options) => (await snapdom.capture(el, options)).toRaw();
736
+ snapdom.toImg = async (el, options) => (await snapdom.capture(el, options)).toImg();
737
+ snapdom.toCanvas = async (el, options) => (await snapdom.capture(el, options)).toCanvas();
738
+ snapdom.toBlob = async (el, options) => (await snapdom.capture(el, options)).toBlob();
739
+ snapdom.toPng = async (el, options) => (await snapdom.capture(el, options)).toPng(options);
740
+ snapdom.toJpg = async (el, options) => (await snapdom.capture(el, options)).toJpg(options);
741
+ snapdom.toWebp = async (el, options) => (await snapdom.capture(el, options)).toWebp(options);
742
+ snapdom.download = async (el, options = {}) => {
743
+ const {
744
+ format = "png",
745
+ filename = "capture",
746
+ backgroundColor,
747
+ ...rest
748
+ } = options;
749
+ const capture = await snapdom.capture(el, rest);
750
+ return await capture.download({ format, filename, backgroundColor });
693
751
  };
694
752
 
695
753
  // src/api/preCache.js
@@ -734,32 +792,5 @@
734
792
  await embedCustomFonts({ ignoreIconFonts: true, preCached: true });
735
793
  }
736
794
  await Promise.all(promises);
737
- if (preWarm) {
738
- const usedTags = collectUsedTagNames(root).sort();
739
- const tagKey = usedTags.join(",");
740
- if (!baseCSSCache.has(tagKey)) {
741
- const css = generateDedupedBaseCSS(usedTags);
742
- baseCSSCache.set(tagKey, css);
743
- }
744
- const clone = root.cloneNode(false);
745
- await inlinePseudoElements(
746
- root,
747
- clone,
748
- /* @__PURE__ */ new Map(),
749
- /* @__PURE__ */ new WeakMap(),
750
- /* compress */
751
- false
752
- );
753
- }
754
- if (preWarm) {
755
- try {
756
- await prepareClone(
757
- root,
758
- /* compress: */
759
- options.preWarm
760
- );
761
- } catch (e) {
762
- }
763
- }
764
795
  }
765
796
  })();
@@ -1,3 +1,3 @@
1
- (()=>{var S=new Map,E=new Map,N=new Map,$=new Map,v=new Map,P=new WeakMap;var ee=["div","span","p","a","img","ul","li","button","input","select","textarea","label","section","article","header","footer","nav","main","aside","h1","h2","h3","h4","h5","h6","svg","path","circle","rect","line","g","table","thead","tbody","tr","td","th"];function z(){for(let e of ee)K(e)}function K(e){if($.has(e))return $.get(e);if(new Set(["script","style","meta","link","noscript","template"]).has(e)){let r={};return $.set(e,r),r}let o=document.getElementById("snapdom-sandbox");o||(o=document.createElement("div"),o.id="snapdom-sandbox",o.style.position="absolute",o.style.left="-9999px",o.style.top="-9999px",o.style.width="0",o.style.height="0",o.style.overflow="hidden",document.body.appendChild(o));let t=document.createElement(e);t.style.all="initial",o.appendChild(t);let s=getComputedStyle(t),a={};for(let r of s)a[r]=s.getPropertyValue(r);return o.removeChild(t),$.set(e,a),a}function D(e,n,o=!1){let t=[],s=K(n);for(let[a,r]of Object.entries(e))if(!o)r&&t.push(`${a}:${r}`);else{let c=s[a];r&&r!==c&&t.push(`${a}:${r}`)}return t.sort().join(";")}function M(e){let n=new Set;return e.nodeType!==Node.ELEMENT_NODE&&e.nodeType!==Node.DOCUMENT_FRAGMENT_NODE?[]:(e.tagName&&n.add(e.tagName.toLowerCase()),typeof e.querySelectorAll=="function"&&e.querySelectorAll("*").forEach(o=>n.add(o.tagName.toLowerCase())),Array.from(n))}function F(e){let n=new Map;for(let t of e){let s=$.get(t);if(!s)continue;let a=Object.entries(s).map(([r,c])=>`${r}:${c};`).sort().join("");n.has(a)||n.set(a,[]),n.get(a).push(t)}let o="";for(let[t,s]of n.entries())o+=`${s.join(",")} { ${t} }
2
- `;return o}function H(e){let n=new Set(e.values()),o=new Map,t=1;for(let s of n)o.set(s,`c${t++}`);return o}function k(e,{fast:n=!1}={}){if(n)return e();"requestIdleCallback"in window?requestIdleCallback(e,{timeout:50}):setTimeout(e,1)}function A(e,n=null){if(!(e instanceof Element))return window.getComputedStyle(e,n);let o=P.get(e);if(o||(o=new Map,P.set(e,o)),!o.has(n)){let t=window.getComputedStyle(e,n);o.set(n,t)}return o.get(n)}function Y(e){let n=e.replace(/^['"]|['"]$/g,"");if(n.startsWith("\\"))try{return String.fromCharCode(parseInt(n.replace("\\",""),16))}catch{return n}return n}function X(e){let n=e.match(/url\(["']?([^"')]+)["']?\)/);return n?n[1]:null}function B(e){return[/font\s*awesome/i,/material\s*icons/i,/ionicons/i,/glyphicons/i,/feather/i,/bootstrap\s*icons/i,/remix\s*icons/i,/heroicons/i].some(o=>o.test(e))}function C(e,n=3e3){return S.has(e)?Promise.resolve(S.get(e)):new Promise((o,t)=>{let s=setTimeout(()=>{t(new Error("Image load timed out"))},n),a=new Image;a.crossOrigin="anonymous",a.onload=async()=>{clearTimeout(s);try{await a.decode();let r=document.createElement("canvas");r.width=a.width,r.height=a.height,r.getContext("2d").drawImage(a,0,0);try{let i=r.toDataURL("image/png");S.set(e,i),o(i)}catch{t(new Error("CORS restrictions prevented image capture"))}}catch(r){t(r)}},a.onerror=r=>{clearTimeout(s),t(new Error("Failed to load image: "+(r.message||"Unknown error")))},a.src=e})}function O(e){let n={};for(let o of e)n[o]=e.getPropertyValue(o);return n}function j(e,n,o,t,s){if(e.tagName==="STYLE")return;t.has(e)||t.set(e,A(e));let a=t.get(e),r=O(a),c=e.tagName?.toLowerCase()||"div",i=D(r,c,s);o.set(n,i)}function W(e,n,o,t,s){if(e.nodeType===Node.ELEMENT_NODE&&e.getAttribute("data-capture")==="exclude"){let c=document.createElement("div"),i=e.getBoundingClientRect();return c.style.cssText=`display: inline-block; width: ${i.width}px; height: ${i.height}px; visibility: hidden;`,c}if(e.tagName==="IFRAME"){let c=document.createElement("div");return c.textContent="",c.style.cssText=`width: ${e.offsetWidth}px; height: ${e.offsetHeight}px; background: 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;`,c}if(e.nodeType===Node.ELEMENT_NODE&&e.getAttribute("data-capture")==="placeholder"){let c=e.cloneNode(!1);t.set(c,e),j(e,c,n,o,s);let i=document.createElement("div");return i.textContent=e.getAttribute("data-placeholder-text")||"",i.style.cssText="color: #666;font-size: 12px;text-align: center;line-height: 1.4;padding: 0.5em;box-sizing: border-box;",c.appendChild(i),c}if(e.tagName==="CANVAS"){let c=e.toDataURL(),i=document.createElement("img");return i.src=c,i.width=e.width,i.height=e.height,i}if(e.nodeType===Node.TEXT_NODE){if(!e.textContent.trim())return null;if(e.parentElement?.shadowRoot){let i=e.parentElement.tagName.toLowerCase();if(customElements.get(i))return null}return e.cloneNode(!0)}if(e.nodeType!==Node.ELEMENT_NODE)return e.cloneNode(!0);let a=e.cloneNode(!1);t.set(a,e),j(e,a,n,o,s);let r=document.createDocumentFragment();if(e.childNodes.forEach(c=>{let i=W(c,n,o,t,s);i&&r.appendChild(i)}),a.appendChild(r),e.shadowRoot){let c=Array.from(e.shadowRoot.children).filter(m=>m.tagName!=="STYLE").map(m=>W(m,n,o,t)).filter(Boolean),i=document.createDocumentFragment();c.forEach(m=>i.appendChild(m)),a.appendChild(i)}return a}async function G(e,n,o,t=32,s="#000"){n=n.replace(/^['"]+|['"]+$/g,""),await document.fonts.ready,await document.fonts.load(`${t}px "${n}"`);let a=document.createElement("canvas");a.width=t,a.height=t;let r=a.getContext("2d");return r.font=o?`${o} ${t}px "${n}"`:` ${t}px "${n}"`,r.textAlign="center",r.textBaseline="middle",r.fillStyle=s,r.fillText(e,a.width/2,a.height/2),a.toDataURL()}async function q({ignoreIconFonts:e=!0,preCached:n=!1}){let o=Array.from(document.querySelectorAll('link[rel="stylesheet"]')).filter(s=>s.href),t="";for(let s of o)try{let r=await(await fetch(s.href)).text();if(e&&(B(s.href)||B(r))){console.log("\u23ED\uFE0F Ignorando icon font CSS:",s.href);continue}let c=/url\(([^)]+)\)/g,i=await Promise.all(Array.from(r.matchAll(c)).map(async l=>{let d=l[1].replace(/["']/g,"");if(d.startsWith("http")||(d=new URL(d,s.href).href),e&&B(d))return console.log("\u23ED\uFE0F Ignorando icon font URL:",d),null;if(N.has(d))return{original:l[0],inlined:`url(${N.get(d)})`};try{let y=await(await fetch(d)).blob(),f=await new Promise(h=>{let u=new FileReader;u.onload=()=>h(u.result),u.readAsDataURL(y)});return N.set(d,f),{original:l[0],inlined:`url(${f})`}}catch{return console.warn("\u274C No pude fetch font",d),null}})),m=r;for(let l of i)l&&(m=m.replace(l.original,l.inlined));t+=m+`
3
- `}catch{console.warn("\u274C No pude fetch CSS",s.href)}if(t&&n){let s=document.createElement("style");s.setAttribute("data-snapdom","embedFonts"),s.textContent=t,document.head.appendChild(s)}return t}async function R(e,n,o,t,s){if(!(e instanceof Element)||!(n instanceof Element))return;for(let c of["::before","::after"])try{let i=A(e,c);if(!i)continue;let m=i.getPropertyValue("content"),l=i.getPropertyValue("background-image"),d=m&&m!=="none"&&m!=='""'&&m!=="''",g=l&&l.startsWith("url(");if(d||g){let y=i.getPropertyValue("font-family"),f=parseInt(i.getPropertyValue("font-size"))||32,h=parseInt(i.getPropertyValue("font-weight"))||!1,u=i.getPropertyValue("color")||"#000",w=document.createElement("span");w.dataset.snapdomPseudo=c;let L=O(i),I=D(L,"span",s);o.set(w,I);let U=y&&/font.*awesome|material|bootstrap|glyphicons|ionicons|remixicon|simple-line-icons|octicons|feather|typicons|weathericons/i.test(y),x=Y(m);if(U&&x.length===1){let b=document.createElement("img");b.src=await G(x,y,h,f,u),b.style="display:block;width:100%;height:100%;object-fit:contain;",w.appendChild(b)}else if(x.startsWith("url(")){let b=x.match(/url\(["']?([^"')]+)["']?\)/);if(b?.[1])try{let T=document.createElement("img"),_=await C(b[1]);T.src=_,T.style="display:block;width:100%;height:100%;object-fit:contain;",w.appendChild(T)}catch(T){console.error(`[snapdom] Error in pseudo ${c} for`,e,T)}}else x&&x!=="none"&&(w.textContent=x);c==="::before"?n.insertBefore(w,n.firstChild):n.appendChild(w)}}catch(i){console.warn(`[snapdom] Failed to capture ${c} for`,e,i)}let a=Array.from(e.children),r=Array.from(n.children).filter(c=>!c.dataset.snapdomPseudo);for(let c=0;c<Math.min(a.length,r.length);c++)await R(a[c],r[c],o,t,s)}async function V(e,n=!1){let o=new Map,t=new WeakMap,s=new WeakMap,a;try{a=W(e,o,t,s,n)}catch(i){throw console.warn("deepClone failed:",i),i}try{await R(e,a,o,t,n)}catch(i){console.warn("inlinePseudoElements failed:",i)}let r=H(o),c=Array.from(r.entries()).map(([i,m])=>`.${m}{${i}}`).join("");for(let[i,m]of o.entries()){if(i.tagName==="STYLE")continue;let l=r.get(m);l&&i.classList.add(l);let d=i.style?.backgroundImage;i.removeAttribute("style"),d&&d!=="none"&&(i.style.backgroundImage=d)}return{clone:a,classCSS:c,styleCache:t}}async function J(e){let n=Array.from(e.querySelectorAll("img")),o=async t=>{let s=t.src;try{let a=await C(s);t.src=a,t.width||(t.width=t.naturalWidth||100),t.height||(t.height=t.naturalHeight||100)}catch{let a=document.createElement("div");a.style=`width: ${t.width||100}px; height: ${t.height||100}px; background: #ccc; display: inline-block; text-align: center; line-height: ${t.height||100}px; color: #666; font-size: 12px;`,a.innerText="img",t.replaceWith(a)}};for(let t=0;t<n.length;t+=4){let s=n.slice(t,t+4).map(o);await Promise.allSettled(s)}}async function Q(e,n,o){let t=[[e,n]];for(;t.length;){let[s,a]=t.shift(),r=o.get(s)||A(s);o.has(s)||o.set(s,r);let c=r.getPropertyValue("background-image");if(c&&c.includes("url(")){let l=c.match(/url\(["']?([^"')]+)["']?\)/);if(l?.[1])try{let d=l[1],g;E.has(d)?g=E.get(d):(g=await C(d),E.set(d,g)),a.style.backgroundImage=`url(${g})`}catch{a.style.backgroundImage="none"}}let i=Array.from(s.children),m=Array.from(a.children);for(let l=0;l<Math.min(i.length,m.length);l++)t.push([i[l],m[l]])}}async function Z(e,n={}){if(!e)throw new Error("Element cannot be null or undefined");let{compress:o=!0,embedFonts:t=!1,fast:s=!0,scale:a=1}=n,r,c,i,m="",l="",d,g;if({clone:r,classCSS:c,styleCache:i}=await V(e,o),await new Promise(f=>{k(async()=>{await J(r),f()},{fast:s})}),await new Promise(f=>{k(async()=>{await Q(e,r,i),f()},{fast:s})}),t&&await new Promise(f=>{k(async()=>{m=await q({ignoreIconFonts:!0}),f()},{fast:s})}),o){let f=M(r).sort(),h=f.join(",");v.has(h)?l=v.get(h):await new Promise(u=>{k(()=>{l=F(f),v.set(h,l),u()},{fast:s})})}await new Promise(f=>{k(()=>{let h=e.getBoundingClientRect(),u=h.width*a,w=h.height*a;a!==1&&(r.setAttribute("xmlns","http://www.w3.org/1999/xhtml"),r.style.transform=`scale(${a})`,r.style.transformOrigin="top left",r.style.width=`${h.width}px`,r.style.height=`${h.height}px`);let L="http://www.w3.org/2000/svg",I=document.createElementNS(L,"foreignObject");I.setAttribute("width","100%"),I.setAttribute("height","100%");let U=document.createElement("style");U.textContent=l+m+"svg{overflow:visible;}"+c,I.appendChild(U),I.appendChild(r);let b=new XMLSerializer().serializeToString(I);g=`<svg xmlns="${L}" width="${u}" height="${w}" viewBox="0 0 ${u} ${w}">`+b+"</svg>",d=`data:image/svg+xml;charset=utf-8,${encodeURIComponent(g)}`,f()},{fast:s})});let y=document.getElementById("snapdom-sandbox");return y&&y.style.position==="absolute"&&y.remove(),d}async function p(e,n={}){if(!e)throw new Error("Element cannot be null or undefined");return await Z(e,n)}p.toImg=async(e,n={})=>{let o=await p(e,n),t=new Image;return t.src=o,await t.decode(),t};p.toCanvas=async(e,n={})=>{let o=await p.toImg(e,n),t=document.createElement("canvas");return t.width=o.width,t.height=o.height,t.getContext("2d").drawImage(o,0,0),t};p.toPng=async(e,n={})=>{let o=await p.toCanvas(e,n),t=new Image;return t.src=o.toDataURL("image/png"),await t.decode(),t};p.toJpg=async(e,n={})=>{let{backgroundColor:o="#fff",quality:t=1}=n,s=await p.toCanvas(e,n),a=document.createElement("canvas");a.width=s.width,a.height=s.height;let r=a.getContext("2d");r.fillStyle=o,r.fillRect(0,0,a.width,a.height),r.drawImage(s,0,0);let c=new Image;return c.src=a.toDataURL("image/jpeg",t),await c.decode(),c};p.toWebp=async(e,n={})=>{let{backgroundColor:o="#fff",quality:t=1}=n,s=await p.toCanvas(e,n),a=document.createElement("canvas");a.width=s.width,a.height=s.height;let r=a.getContext("2d");r.fillStyle=o,r.fillRect(0,0,a.width,a.height),r.drawImage(s,0,0);let c=new Image;return c.src=a.toDataURL("image/webp",t),await c.decode(),c};p.toBlob=async(e,n={})=>{let o=await p(e,n),t=decodeURIComponent(o.split(",")[1]);return new Blob([t],{type:"image/svg+xml"})};async function te(e=document,n={}){let{embedFonts:o=!0,reset:t=!1,preWarm:s=!0}=n;if(t){S.clear(),E.clear(),N.clear(),v.clear(),P.clear();return}await document.fonts.ready,z();let a=[],r=[];e?.querySelectorAll&&(a=Array.from(e.querySelectorAll("img[src]")),r=Array.from(e.querySelectorAll("*")));let c=[];for(let i of a){let m=i.src;S.has(m)||c.push(C(m).then(l=>S.set(m,l)).catch(()=>{}))}for(let i of r){let m=getComputedStyle(i).backgroundImage,l=X(m);l&&!E.has(l)&&c.push(C(l).then(d=>E.set(l,d)).catch(()=>{}))}if(o&&await q({ignoreIconFonts:!0,preCached:!0}),await Promise.all(c),s){let i=M(e).sort(),m=i.join(",");if(!v.has(m)){let d=F(i);v.set(m,d)}let l=e.cloneNode(!1);await R(e,l,new Map,new WeakMap,!1)}if(s)try{await V(e,n.preWarm)}catch{}}})();
1
+ (()=>{var S=new Map,E=new Map,$=new Map,k=new Map,T=new Map,R=new WeakMap;var rt=["div","span","p","a","img","ul","li","button","input","select","textarea","label","section","article","header","footer","nav","main","aside","h1","h2","h3","h4","h5","h6","svg","path","circle","rect","line","g","table","thead","tbody","tr","td","th"];function _(){for(let t of rt)z(t)}function z(t){if(k.has(t))return k.get(t);if(new Set(["script","style","meta","link","noscript","template"]).has(t)){let s={};return k.set(t,s),s}let o=document.getElementById("snapdom-sandbox");o||(o=document.createElement("div"),o.id="snapdom-sandbox",o.style.position="absolute",o.style.left="-9999px",o.style.top="-9999px",o.style.width="0",o.style.height="0",o.style.overflow="hidden",document.body.appendChild(o));let n=document.createElement(t);n.style.all="initial",o.appendChild(n);let a=getComputedStyle(n),r={};for(let s of a)r[s]=a.getPropertyValue(s);return o.removeChild(n),k.set(t,r),r}function U(t,e,o=!1){let n=[],a=z(e);for(let[r,s]of Object.entries(t))if(!o)s&&n.push(`${r}:${s}`);else{let c=a[r];s&&s!==c&&n.push(`${r}:${s}`)}return n.sort().join(";")}function H(t){let e=new Set;return t.nodeType!==Node.ELEMENT_NODE&&t.nodeType!==Node.DOCUMENT_FRAGMENT_NODE?[]:(t.tagName&&e.add(t.tagName.toLowerCase()),typeof t.querySelectorAll=="function"&&t.querySelectorAll("*").forEach(o=>e.add(o.tagName.toLowerCase())),Array.from(e))}function K(t){let e=new Map;for(let n of t){let a=k.get(n);if(!a)continue;let r=Object.entries(a).map(([s,c])=>`${s}:${c};`).sort().join("");e.has(r)||e.set(r,[]),e.get(r).push(n)}let o="";for(let[n,a]of e.entries())o+=`${a.join(",")} { ${n} }
2
+ `;return o}function J(t){let e=new Set(t.values()),o=new Map,n=1;for(let a of e)o.set(a,`c${n++}`);return o}function N(t,{fast:e=!1}={}){if(e)return t();"requestIdleCallback"in window?requestIdleCallback(t,{timeout:50}):setTimeout(t,1)}function A(t,e=null){if(!(t instanceof Element))return window.getComputedStyle(t,e);let o=R.get(t);if(o||(o=new Map,R.set(t,o)),!o.has(e)){let n=window.getComputedStyle(t,e);o.set(e,n)}return o.get(e)}function Y(t){let e=t.replace(/^['"]|['"]$/g,"");if(e.startsWith("\\"))try{return String.fromCharCode(parseInt(e.replace("\\",""),16))}catch{return e}return e}function X(t){let e=t.match(/url\(["']?([^"')]+)["']?\)/);return e?e[1]:null}function M(t){return[/font\s*awesome/i,/material\s*icons/i,/ionicons/i,/glyphicons/i,/feather/i,/bootstrap\s*icons/i,/remix\s*icons/i,/heroicons/i].some(o=>o.test(t))}function C(t,e=3e3){return S.has(t)?Promise.resolve(S.get(t)):new Promise((o,n)=>{let a=setTimeout(()=>{n(new Error("Image load timed out"))},e),r=new Image;r.crossOrigin="anonymous",r.onload=async()=>{clearTimeout(a);try{await r.decode();let s=document.createElement("canvas");s.width=r.width,s.height=r.height,s.getContext("2d").drawImage(r,0,0);try{let i=s.toDataURL("image/png");S.set(t,i),o(i)}catch{n(new Error("CORS restrictions prevented image capture"))}}catch(s){n(s)}},r.onerror=s=>{clearTimeout(a),n(new Error("Failed to load image: "+(s.message||"Unknown error")))},r.src=t})}function B(t){let e={};for(let o of t)e[o]=t.getPropertyValue(o);return e}function F(){return/^((?!chrome|android).)*safari/i.test(navigator.userAgent)}function O(t,e,o,n,a){if(t.tagName==="STYLE")return;n.has(t)||n.set(t,A(t));let r=n.get(t),s=B(r),c=t.tagName?.toLowerCase()||"div",i=U(s,c,a);o.set(e,i)}function D(t,e,o,n,a){if(t.nodeType===Node.ELEMENT_NODE&&t.getAttribute("data-capture")==="exclude"){let c=document.createElement("div"),i=t.getBoundingClientRect();return c.style.cssText=`display: inline-block; width: ${i.width}px; height: ${i.height}px; visibility: hidden;`,c}if(t.tagName==="IFRAME"){let c=document.createElement("div");return c.textContent="",c.style.cssText=`width: ${t.offsetWidth}px; height: ${t.offsetHeight}px; background: 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;`,c}if(t.nodeType===Node.ELEMENT_NODE&&t.getAttribute("data-capture")==="placeholder"){let c=t.cloneNode(!1);n.set(c,t),O(t,c,e,o,a);let i=document.createElement("div");return i.textContent=t.getAttribute("data-placeholder-text")||"",i.style.cssText="color: #666;font-size: 12px;text-align: center;line-height: 1.4;padding: 0.5em;box-sizing: border-box;",c.appendChild(i),c}if(t.tagName==="CANVAS"){let c=t.toDataURL(),i=document.createElement("img");return i.src=c,i.width=t.width,i.height=t.height,i.style.display="inline-block",i.style.width=`${t.width}px`,i.style.height=`${t.height}px`,i}if(t.nodeType===Node.TEXT_NODE){if(!t.textContent.trim())return null;if(t.parentElement?.shadowRoot){let i=t.parentElement.tagName.toLowerCase();if(customElements.get(i))return null}return t.cloneNode(!0)}if(t.nodeType!==Node.ELEMENT_NODE)return t.cloneNode(!0);let r=t.cloneNode(!1);n.set(r,t),O(t,r,e,o,a);let s=document.createDocumentFragment();if(t.childNodes.forEach(c=>{let i=D(c,e,o,n,a);i&&s.appendChild(i)}),r.appendChild(s),t.shadowRoot){let c=Array.from(t.shadowRoot.children).filter(d=>d.tagName!=="STYLE").map(d=>D(d,e,o,n)).filter(Boolean),i=document.createDocumentFragment();c.forEach(d=>i.appendChild(d)),r.appendChild(i)}return r}async function G(t,e,o,n=32,a="#000"){e=e.replace(/^['"]+|['"]+$/g,""),await document.fonts.ready,await document.fonts.load(`${n}px "${e}"`);let r=document.createElement("canvas");r.width=n,r.height=n;let s=r.getContext("2d");return s.font=o?`${o} ${n}px "${e}"`:` ${n}px "${e}"`,s.textAlign="center",s.textBaseline="middle",s.fillStyle=a,s.fillText(t,r.width/2,r.height/2),r.toDataURL()}async function j({ignoreIconFonts:t=!0,preCached:e=!1}){let o=Array.from(document.querySelectorAll('link[rel="stylesheet"]')).filter(a=>a.href),n="";for(let a of o)try{let s=await(await fetch(a.href)).text();if(t&&(M(a.href)||M(s))){console.log("\u23ED\uFE0F Ignorando icon font CSS:",a.href);continue}let c=/url\(([^)]+)\)/g,i=await Promise.all(Array.from(s.matchAll(c)).map(async l=>{let m=l[1].replace(/["']/g,"");if(m.startsWith("http")||(m=new URL(m,a.href).href),t&&M(m))return console.log("\u23ED\uFE0F Ignorando icon font URL:",m),null;if($.has(m))return{original:l[0],inlined:`url(${$.get(m)})`};try{let h=await(await fetch(m)).blob(),f=await new Promise(u=>{let w=new FileReader;w.onload=()=>u(w.result),w.readAsDataURL(h)});return $.set(m,f),{original:l[0],inlined:`url(${f})`}}catch{return console.warn("\u274C No pude fetch font",m),null}})),d=s;for(let l of i)l&&(d=d.replace(l.original,l.inlined));n+=d+`
3
+ `}catch{console.warn("\u274C No pude fetch CSS",a.href)}if(n&&e){let a=document.createElement("style");a.setAttribute("data-snapdom","embedFonts"),a.textContent=n,document.head.appendChild(a)}return n}async function W(t,e,o,n,a){if(!(t instanceof Element)||!(e instanceof Element))return;for(let c of["::before","::after"])try{let i=A(t,c);if(!i)continue;let d=i.getPropertyValue("content"),l=i.getPropertyValue("background-image"),m=d&&d!=="none"&&d!=='""'&&d!=="''",g=l&&l.startsWith("url(");if(m||g){let h=i.getPropertyValue("font-family"),f=parseInt(i.getPropertyValue("font-size"))||32,u=parseInt(i.getPropertyValue("font-weight"))||!1,w=i.getPropertyValue("color")||"#000",y=document.createElement("span");y.dataset.snapdomPseudo=c;let L=B(i),v=U(L,"span",a);o.set(y,v);let P=h&&/font.*awesome|material|bootstrap|glyphicons|ionicons|remixicon|simple-line-icons|octicons|feather|typicons|weathericons/i.test(h),x=Y(d);if(P&&x.length===1){let b=document.createElement("img");b.src=await G(x,h,u,f,w),b.style="display:block;width:100%;height:100%;object-fit:contain;",y.appendChild(b)}else if(x.startsWith("url(")){let b=x.match(/url\(["']?([^"')]+)["']?\)/);if(b?.[1])try{let I=document.createElement("img"),q=await C(b[1]);I.src=q,I.style="display:block;width:100%;height:100%;object-fit:contain;",y.appendChild(I)}catch(I){console.error(`[snapdom] Error in pseudo ${c} for`,t,I)}}else x&&x!=="none"&&(y.textContent=x);c==="::before"?e.insertBefore(y,e.firstChild):e.appendChild(y)}}catch(i){console.warn(`[snapdom] Failed to capture ${c} for`,t,i)}let r=Array.from(t.children),s=Array.from(e.children).filter(c=>!c.dataset.snapdomPseudo);for(let c=0;c<Math.min(r.length,s.length);c++)await W(r[c],s[c],o,n,a)}async function Q(t,e=!1){let o=new Map,n=new WeakMap,a=new WeakMap,r;try{r=D(t,o,n,a,e)}catch(i){throw console.warn("deepClone failed:",i),i}try{await W(t,r,o,n,e)}catch(i){console.warn("inlinePseudoElements failed:",i)}let s=J(o),c=Array.from(s.entries()).map(([i,d])=>`.${d}{${i}}`).join("");for(let[i,d]of o.entries()){if(i.tagName==="STYLE")continue;let l=s.get(d);l&&i.classList.add(l);let m=i.style?.backgroundImage;i.removeAttribute("style"),m&&m!=="none"&&(i.style.backgroundImage=m)}return{clone:r,classCSS:c,styleCache:n}}async function Z(t){let e=Array.from(t.querySelectorAll("img")),o=async n=>{let a=n.src;try{let r=await C(a);n.src=r,n.width||(n.width=n.naturalWidth||100),n.height||(n.height=n.naturalHeight||100)}catch{let r=document.createElement("div");r.style=`width: ${n.width||100}px; height: ${n.height||100}px; background: #ccc; display: inline-block; text-align: center; line-height: ${n.height||100}px; color: #666; font-size: 12px;`,r.innerText="img",n.replaceWith(r)}};for(let n=0;n<e.length;n+=4){let a=e.slice(n,n+4).map(o);await Promise.allSettled(a)}}async function tt(t,e,o){let n=[[t,e]];for(;n.length;){let[a,r]=n.shift(),s=o.get(a)||A(a);o.has(a)||o.set(a,s);let c=s.getPropertyValue("background-image");if(c&&c.includes("url(")){let l=c.match(/url\(["']?([^"')]+)["']?\)/);if(l?.[1])try{let m=l[1],g;E.has(m)?g=E.get(m):(g=await C(m),E.set(m,g)),r.style.backgroundImage=`url(${g})`}catch{r.style.backgroundImage="none"}}let i=Array.from(a.children),d=Array.from(r.children);for(let l=0;l<Math.min(i.length,d.length);l++)n.push([i[l],d[l]])}}async function et(t,e={}){if(!t)throw new Error("Element cannot be null or undefined");let{compress:o=!0,embedFonts:n=!1,fast:a=!0,scale:r=1}=e,s,c,i,d="",l="",m,g;if({clone:s,classCSS:c,styleCache:i}=await Q(t,o),await new Promise(f=>{N(async()=>{await Z(s),f()},{fast:a})}),await new Promise(f=>{N(async()=>{await tt(t,s,i),f()},{fast:a})}),n&&await new Promise(f=>{N(async()=>{d=await j({ignoreIconFonts:!0}),f()},{fast:a})}),o){let f=H(s).sort(),u=f.join(",");T.has(u)?l=T.get(u):await new Promise(w=>{N(()=>{l=K(f),T.set(u,l),w()},{fast:a})})}await new Promise(f=>{N(()=>{let u=t.getBoundingClientRect(),w=Math.ceil(u.width),y=Math.ceil(u.height);s.setAttribute("xmlns","http://www.w3.org/1999/xhtml"),s.style.transformOrigin="top left",r!==1&&F()&&(s.style.scale=`${r}`);let L="http://www.w3.org/2000/svg",v=document.createElementNS(L,"foreignObject");v.setAttribute("width","100%"),v.setAttribute("height","100%");let P=document.createElement("style");P.textContent=l+d+"svg{overflow:visible;}"+c,v.appendChild(P),v.appendChild(s);let b=new XMLSerializer().serializeToString(v);g=`<svg xmlns="${L}" width="${w}" height="${y}" viewBox="0 0 ${w} ${y}">`+b+"</svg>",m=`data:image/svg+xml;charset=utf-8,${encodeURIComponent(g)}`,f()},{fast:a})});let h=document.getElementById("snapdom-sandbox");return h&&h.style.position==="absolute"&&h.remove(),m}async function st(t,{dpr:e=1,scale:o=1}){let n=new Image;return n.src=t,await n.decode(),F?(n.width=n.width*o,n.height=n.height*o):(n.width=n.width/o,n.height=n.height/o),n}async function nt(t,{dpr:e=1,scale:o=1}){let n=new Image;n.src=t,await n.decode();let a=document.createElement("canvas"),r=n.width*o*e,s=n.height*o*e;a.width=Math.ceil(r),a.height=Math.ceil(s);let c=a.getContext("2d");return c.scale(o*e,o*e),c.drawImage(n,0,0),a}async function ot(t){let e=decodeURIComponent(t.split(",")[1]);return new Blob([e],{type:"image/svg+xml"})}async function at(t,{dpr:e=1,scale:o=1},n){let a=await nt(t,{dpr:e,scale:o});if(!n)return a;let r=document.createElement("canvas");r.width=a.width,r.height=a.height;let s=r.getContext("2d");return s.fillStyle=n,s.fillRect(0,0,r.width,r.height),s.drawImage(a,0,0),r}async function V(t,{dpr:e=1,scale:o=1,backgroundColor:n="#fff",quality:a},r="png"){let s=await at(t,{dpr:e,scale:o},n),c=new Image;return c.src=s.toDataURL(`image/${r}`,a),await c.decode(),c.style.width=`${s.width/e}px`,c.style.height=`${s.height/e}px`,c}async function it(t,{dpr:e=1,scale:o=1,backgroundColor:n="#fff",format:a="png",filename:r="capture"}={}){if(a==="svg"){let g=await ot(t),h=URL.createObjectURL(g),f=document.createElement("a");f.href=h,f.download=`${r}.svg`,f.click(),URL.revokeObjectURL(h);return}let s=["jpg","jpeg","webp"].includes(a)?"#fff":void 0,i=await at(t,{dpr:e,scale:o},n??s),d={jpg:"image/jpeg",jpeg:"image/jpeg",png:"image/png",webp:"image/webp"}[a]||"image/png",l=i.toDataURL(d),m=document.createElement("a");m.href=l,m.download=`${r}.${a}`,m.click()}async function p(t,e={}){if(e={scale:1,...e},!t)throw new Error("Element cannot be null or undefined");return await p.capture(t,e)}p.capture=async(t,e={})=>{let o=await et(t,e),n=window.devicePixelRatio||1,a=e.scale||1;return{url:o,options:e,toRaw:()=>o,toImg:()=>st(o,{dpr:n,scale:a}),toCanvas:()=>nt(o,{dpr:n,scale:a}),toBlob:()=>ot(o),toPng:()=>V(o,{dpr:n,scale:a},"png"),toJpg:r=>V(o,{dpr:n,scale:a,...r},"jpeg"),toWebp:r=>V(o,{dpr:n,scale:a,...r},"webp"),download:({format:r="png",filename:s="capture",backgroundColor:c}={})=>it(o,{dpr:n,scale:a,backgroundColor:c,format:r,filename:s})}};p.toRaw=async(t,e)=>(await p.capture(t,e)).toRaw();p.toImg=async(t,e)=>(await p.capture(t,e)).toImg();p.toCanvas=async(t,e)=>(await p.capture(t,e)).toCanvas();p.toBlob=async(t,e)=>(await p.capture(t,e)).toBlob();p.toPng=async(t,e)=>(await p.capture(t,e)).toPng(e);p.toJpg=async(t,e)=>(await p.capture(t,e)).toJpg(e);p.toWebp=async(t,e)=>(await p.capture(t,e)).toWebp(e);p.download=async(t,e={})=>{let{format:o="png",filename:n="capture",backgroundColor:a,...r}=e;return await(await p.capture(t,r)).download({format:o,filename:n,backgroundColor:a})};async function ct(t=document,e={}){let{embedFonts:o=!0,reset:n=!1,preWarm:a=!0}=e;if(n){S.clear(),E.clear(),$.clear(),T.clear(),R.clear();return}await document.fonts.ready,_();let r=[],s=[];t?.querySelectorAll&&(r=Array.from(t.querySelectorAll("img[src]")),s=Array.from(t.querySelectorAll("*")));let c=[];for(let i of r){let d=i.src;S.has(d)||c.push(C(d).then(l=>S.set(d,l)).catch(()=>{}))}for(let i of s){let d=getComputedStyle(i).backgroundImage,l=X(d);l&&!E.has(l)&&c.push(C(l).then(m=>E.set(l,m)).catch(()=>{}))}o&&await j({ignoreIconFonts:!0,preCached:!0}),await Promise.all(c)}})();
@@ -1,3 +1,3 @@
1
- var S=new Map,E=new Map,N=new Map,$=new Map,v=new Map,P=new WeakMap;var ee=["div","span","p","a","img","ul","li","button","input","select","textarea","label","section","article","header","footer","nav","main","aside","h1","h2","h3","h4","h5","h6","svg","path","circle","rect","line","g","table","thead","tbody","tr","td","th"];function z(){for(let e of ee)K(e)}function K(e){if($.has(e))return $.get(e);if(new Set(["script","style","meta","link","noscript","template"]).has(e)){let r={};return $.set(e,r),r}let o=document.getElementById("snapdom-sandbox");o||(o=document.createElement("div"),o.id="snapdom-sandbox",o.style.position="absolute",o.style.left="-9999px",o.style.top="-9999px",o.style.width="0",o.style.height="0",o.style.overflow="hidden",document.body.appendChild(o));let t=document.createElement(e);t.style.all="initial",o.appendChild(t);let s=getComputedStyle(t),a={};for(let r of s)a[r]=s.getPropertyValue(r);return o.removeChild(t),$.set(e,a),a}function D(e,n,o=!1){let t=[],s=K(n);for(let[a,r]of Object.entries(e))if(!o)r&&t.push(`${a}:${r}`);else{let c=s[a];r&&r!==c&&t.push(`${a}:${r}`)}return t.sort().join(";")}function M(e){let n=new Set;return e.nodeType!==Node.ELEMENT_NODE&&e.nodeType!==Node.DOCUMENT_FRAGMENT_NODE?[]:(e.tagName&&n.add(e.tagName.toLowerCase()),typeof e.querySelectorAll=="function"&&e.querySelectorAll("*").forEach(o=>n.add(o.tagName.toLowerCase())),Array.from(n))}function F(e){let n=new Map;for(let t of e){let s=$.get(t);if(!s)continue;let a=Object.entries(s).map(([r,c])=>`${r}:${c};`).sort().join("");n.has(a)||n.set(a,[]),n.get(a).push(t)}let o="";for(let[t,s]of n.entries())o+=`${s.join(",")} { ${t} }
2
- `;return o}function H(e){let n=new Set(e.values()),o=new Map,t=1;for(let s of n)o.set(s,`c${t++}`);return o}function k(e,{fast:n=!1}={}){if(n)return e();"requestIdleCallback"in window?requestIdleCallback(e,{timeout:50}):setTimeout(e,1)}function A(e,n=null){if(!(e instanceof Element))return window.getComputedStyle(e,n);let o=P.get(e);if(o||(o=new Map,P.set(e,o)),!o.has(n)){let t=window.getComputedStyle(e,n);o.set(n,t)}return o.get(n)}function Y(e){let n=e.replace(/^['"]|['"]$/g,"");if(n.startsWith("\\"))try{return String.fromCharCode(parseInt(n.replace("\\",""),16))}catch{return n}return n}function X(e){let n=e.match(/url\(["']?([^"')]+)["']?\)/);return n?n[1]:null}function B(e){return[/font\s*awesome/i,/material\s*icons/i,/ionicons/i,/glyphicons/i,/feather/i,/bootstrap\s*icons/i,/remix\s*icons/i,/heroicons/i].some(o=>o.test(e))}function C(e,n=3e3){return S.has(e)?Promise.resolve(S.get(e)):new Promise((o,t)=>{let s=setTimeout(()=>{t(new Error("Image load timed out"))},n),a=new Image;a.crossOrigin="anonymous",a.onload=async()=>{clearTimeout(s);try{await a.decode();let r=document.createElement("canvas");r.width=a.width,r.height=a.height,r.getContext("2d").drawImage(a,0,0);try{let i=r.toDataURL("image/png");S.set(e,i),o(i)}catch{t(new Error("CORS restrictions prevented image capture"))}}catch(r){t(r)}},a.onerror=r=>{clearTimeout(s),t(new Error("Failed to load image: "+(r.message||"Unknown error")))},a.src=e})}function O(e){let n={};for(let o of e)n[o]=e.getPropertyValue(o);return n}function j(e,n,o,t,s){if(e.tagName==="STYLE")return;t.has(e)||t.set(e,A(e));let a=t.get(e),r=O(a),c=e.tagName?.toLowerCase()||"div",i=D(r,c,s);o.set(n,i)}function W(e,n,o,t,s){if(e.nodeType===Node.ELEMENT_NODE&&e.getAttribute("data-capture")==="exclude"){let c=document.createElement("div"),i=e.getBoundingClientRect();return c.style.cssText=`display: inline-block; width: ${i.width}px; height: ${i.height}px; visibility: hidden;`,c}if(e.tagName==="IFRAME"){let c=document.createElement("div");return c.textContent="",c.style.cssText=`width: ${e.offsetWidth}px; height: ${e.offsetHeight}px; background: 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;`,c}if(e.nodeType===Node.ELEMENT_NODE&&e.getAttribute("data-capture")==="placeholder"){let c=e.cloneNode(!1);t.set(c,e),j(e,c,n,o,s);let i=document.createElement("div");return i.textContent=e.getAttribute("data-placeholder-text")||"",i.style.cssText="color: #666;font-size: 12px;text-align: center;line-height: 1.4;padding: 0.5em;box-sizing: border-box;",c.appendChild(i),c}if(e.tagName==="CANVAS"){let c=e.toDataURL(),i=document.createElement("img");return i.src=c,i.width=e.width,i.height=e.height,i}if(e.nodeType===Node.TEXT_NODE){if(!e.textContent.trim())return null;if(e.parentElement?.shadowRoot){let i=e.parentElement.tagName.toLowerCase();if(customElements.get(i))return null}return e.cloneNode(!0)}if(e.nodeType!==Node.ELEMENT_NODE)return e.cloneNode(!0);let a=e.cloneNode(!1);t.set(a,e),j(e,a,n,o,s);let r=document.createDocumentFragment();if(e.childNodes.forEach(c=>{let i=W(c,n,o,t,s);i&&r.appendChild(i)}),a.appendChild(r),e.shadowRoot){let c=Array.from(e.shadowRoot.children).filter(m=>m.tagName!=="STYLE").map(m=>W(m,n,o,t)).filter(Boolean),i=document.createDocumentFragment();c.forEach(m=>i.appendChild(m)),a.appendChild(i)}return a}async function G(e,n,o,t=32,s="#000"){n=n.replace(/^['"]+|['"]+$/g,""),await document.fonts.ready,await document.fonts.load(`${t}px "${n}"`);let a=document.createElement("canvas");a.width=t,a.height=t;let r=a.getContext("2d");return r.font=o?`${o} ${t}px "${n}"`:` ${t}px "${n}"`,r.textAlign="center",r.textBaseline="middle",r.fillStyle=s,r.fillText(e,a.width/2,a.height/2),a.toDataURL()}async function q({ignoreIconFonts:e=!0,preCached:n=!1}){let o=Array.from(document.querySelectorAll('link[rel="stylesheet"]')).filter(s=>s.href),t="";for(let s of o)try{let r=await(await fetch(s.href)).text();if(e&&(B(s.href)||B(r))){console.log("\u23ED\uFE0F Ignorando icon font CSS:",s.href);continue}let c=/url\(([^)]+)\)/g,i=await Promise.all(Array.from(r.matchAll(c)).map(async l=>{let d=l[1].replace(/["']/g,"");if(d.startsWith("http")||(d=new URL(d,s.href).href),e&&B(d))return console.log("\u23ED\uFE0F Ignorando icon font URL:",d),null;if(N.has(d))return{original:l[0],inlined:`url(${N.get(d)})`};try{let y=await(await fetch(d)).blob(),f=await new Promise(h=>{let u=new FileReader;u.onload=()=>h(u.result),u.readAsDataURL(y)});return N.set(d,f),{original:l[0],inlined:`url(${f})`}}catch{return console.warn("\u274C No pude fetch font",d),null}})),m=r;for(let l of i)l&&(m=m.replace(l.original,l.inlined));t+=m+`
3
- `}catch{console.warn("\u274C No pude fetch CSS",s.href)}if(t&&n){let s=document.createElement("style");s.setAttribute("data-snapdom","embedFonts"),s.textContent=t,document.head.appendChild(s)}return t}async function R(e,n,o,t,s){if(!(e instanceof Element)||!(n instanceof Element))return;for(let c of["::before","::after"])try{let i=A(e,c);if(!i)continue;let m=i.getPropertyValue("content"),l=i.getPropertyValue("background-image"),d=m&&m!=="none"&&m!=='""'&&m!=="''",g=l&&l.startsWith("url(");if(d||g){let y=i.getPropertyValue("font-family"),f=parseInt(i.getPropertyValue("font-size"))||32,h=parseInt(i.getPropertyValue("font-weight"))||!1,u=i.getPropertyValue("color")||"#000",w=document.createElement("span");w.dataset.snapdomPseudo=c;let L=O(i),I=D(L,"span",s);o.set(w,I);let U=y&&/font.*awesome|material|bootstrap|glyphicons|ionicons|remixicon|simple-line-icons|octicons|feather|typicons|weathericons/i.test(y),x=Y(m);if(U&&x.length===1){let b=document.createElement("img");b.src=await G(x,y,h,f,u),b.style="display:block;width:100%;height:100%;object-fit:contain;",w.appendChild(b)}else if(x.startsWith("url(")){let b=x.match(/url\(["']?([^"')]+)["']?\)/);if(b?.[1])try{let T=document.createElement("img"),_=await C(b[1]);T.src=_,T.style="display:block;width:100%;height:100%;object-fit:contain;",w.appendChild(T)}catch(T){console.error(`[snapdom] Error in pseudo ${c} for`,e,T)}}else x&&x!=="none"&&(w.textContent=x);c==="::before"?n.insertBefore(w,n.firstChild):n.appendChild(w)}}catch(i){console.warn(`[snapdom] Failed to capture ${c} for`,e,i)}let a=Array.from(e.children),r=Array.from(n.children).filter(c=>!c.dataset.snapdomPseudo);for(let c=0;c<Math.min(a.length,r.length);c++)await R(a[c],r[c],o,t,s)}async function V(e,n=!1){let o=new Map,t=new WeakMap,s=new WeakMap,a;try{a=W(e,o,t,s,n)}catch(i){throw console.warn("deepClone failed:",i),i}try{await R(e,a,o,t,n)}catch(i){console.warn("inlinePseudoElements failed:",i)}let r=H(o),c=Array.from(r.entries()).map(([i,m])=>`.${m}{${i}}`).join("");for(let[i,m]of o.entries()){if(i.tagName==="STYLE")continue;let l=r.get(m);l&&i.classList.add(l);let d=i.style?.backgroundImage;i.removeAttribute("style"),d&&d!=="none"&&(i.style.backgroundImage=d)}return{clone:a,classCSS:c,styleCache:t}}async function J(e){let n=Array.from(e.querySelectorAll("img")),o=async t=>{let s=t.src;try{let a=await C(s);t.src=a,t.width||(t.width=t.naturalWidth||100),t.height||(t.height=t.naturalHeight||100)}catch{let a=document.createElement("div");a.style=`width: ${t.width||100}px; height: ${t.height||100}px; background: #ccc; display: inline-block; text-align: center; line-height: ${t.height||100}px; color: #666; font-size: 12px;`,a.innerText="img",t.replaceWith(a)}};for(let t=0;t<n.length;t+=4){let s=n.slice(t,t+4).map(o);await Promise.allSettled(s)}}async function Q(e,n,o){let t=[[e,n]];for(;t.length;){let[s,a]=t.shift(),r=o.get(s)||A(s);o.has(s)||o.set(s,r);let c=r.getPropertyValue("background-image");if(c&&c.includes("url(")){let l=c.match(/url\(["']?([^"')]+)["']?\)/);if(l?.[1])try{let d=l[1],g;E.has(d)?g=E.get(d):(g=await C(d),E.set(d,g)),a.style.backgroundImage=`url(${g})`}catch{a.style.backgroundImage="none"}}let i=Array.from(s.children),m=Array.from(a.children);for(let l=0;l<Math.min(i.length,m.length);l++)t.push([i[l],m[l]])}}async function Z(e,n={}){if(!e)throw new Error("Element cannot be null or undefined");let{compress:o=!0,embedFonts:t=!1,fast:s=!0,scale:a=1}=n,r,c,i,m="",l="",d,g;if({clone:r,classCSS:c,styleCache:i}=await V(e,o),await new Promise(f=>{k(async()=>{await J(r),f()},{fast:s})}),await new Promise(f=>{k(async()=>{await Q(e,r,i),f()},{fast:s})}),t&&await new Promise(f=>{k(async()=>{m=await q({ignoreIconFonts:!0}),f()},{fast:s})}),o){let f=M(r).sort(),h=f.join(",");v.has(h)?l=v.get(h):await new Promise(u=>{k(()=>{l=F(f),v.set(h,l),u()},{fast:s})})}await new Promise(f=>{k(()=>{let h=e.getBoundingClientRect(),u=h.width*a,w=h.height*a;a!==1&&(r.setAttribute("xmlns","http://www.w3.org/1999/xhtml"),r.style.transform=`scale(${a})`,r.style.transformOrigin="top left",r.style.width=`${h.width}px`,r.style.height=`${h.height}px`);let L="http://www.w3.org/2000/svg",I=document.createElementNS(L,"foreignObject");I.setAttribute("width","100%"),I.setAttribute("height","100%");let U=document.createElement("style");U.textContent=l+m+"svg{overflow:visible;}"+c,I.appendChild(U),I.appendChild(r);let b=new XMLSerializer().serializeToString(I);g=`<svg xmlns="${L}" width="${u}" height="${w}" viewBox="0 0 ${u} ${w}">`+b+"</svg>",d=`data:image/svg+xml;charset=utf-8,${encodeURIComponent(g)}`,f()},{fast:s})});let y=document.getElementById("snapdom-sandbox");return y&&y.style.position==="absolute"&&y.remove(),d}async function p(e,n={}){if(!e)throw new Error("Element cannot be null or undefined");return await Z(e,n)}p.toImg=async(e,n={})=>{let o=await p(e,n),t=new Image;return t.src=o,await t.decode(),t};p.toCanvas=async(e,n={})=>{let o=await p.toImg(e,n),t=document.createElement("canvas");return t.width=o.width,t.height=o.height,t.getContext("2d").drawImage(o,0,0),t};p.toPng=async(e,n={})=>{let o=await p.toCanvas(e,n),t=new Image;return t.src=o.toDataURL("image/png"),await t.decode(),t};p.toJpg=async(e,n={})=>{let{backgroundColor:o="#fff",quality:t=1}=n,s=await p.toCanvas(e,n),a=document.createElement("canvas");a.width=s.width,a.height=s.height;let r=a.getContext("2d");r.fillStyle=o,r.fillRect(0,0,a.width,a.height),r.drawImage(s,0,0);let c=new Image;return c.src=a.toDataURL("image/jpeg",t),await c.decode(),c};p.toWebp=async(e,n={})=>{let{backgroundColor:o="#fff",quality:t=1}=n,s=await p.toCanvas(e,n),a=document.createElement("canvas");a.width=s.width,a.height=s.height;let r=a.getContext("2d");r.fillStyle=o,r.fillRect(0,0,a.width,a.height),r.drawImage(s,0,0);let c=new Image;return c.src=a.toDataURL("image/webp",t),await c.decode(),c};p.toBlob=async(e,n={})=>{let o=await p(e,n),t=decodeURIComponent(o.split(",")[1]);return new Blob([t],{type:"image/svg+xml"})};async function te(e=document,n={}){let{embedFonts:o=!0,reset:t=!1,preWarm:s=!0}=n;if(t){S.clear(),E.clear(),N.clear(),v.clear(),P.clear();return}await document.fonts.ready,z();let a=[],r=[];e?.querySelectorAll&&(a=Array.from(e.querySelectorAll("img[src]")),r=Array.from(e.querySelectorAll("*")));let c=[];for(let i of a){let m=i.src;S.has(m)||c.push(C(m).then(l=>S.set(m,l)).catch(()=>{}))}for(let i of r){let m=getComputedStyle(i).backgroundImage,l=X(m);l&&!E.has(l)&&c.push(C(l).then(d=>E.set(l,d)).catch(()=>{}))}if(o&&await q({ignoreIconFonts:!0,preCached:!0}),await Promise.all(c),s){let i=M(e).sort(),m=i.join(",");if(!v.has(m)){let d=F(i);v.set(m,d)}let l=e.cloneNode(!1);await R(e,l,new Map,new WeakMap,!1)}if(s)try{await V(e,n.preWarm)}catch{}}export{te as preCache,p as snapdom};
1
+ var S=new Map,E=new Map,$=new Map,k=new Map,T=new Map,R=new WeakMap;var rt=["div","span","p","a","img","ul","li","button","input","select","textarea","label","section","article","header","footer","nav","main","aside","h1","h2","h3","h4","h5","h6","svg","path","circle","rect","line","g","table","thead","tbody","tr","td","th"];function _(){for(let t of rt)z(t)}function z(t){if(k.has(t))return k.get(t);if(new Set(["script","style","meta","link","noscript","template"]).has(t)){let s={};return k.set(t,s),s}let o=document.getElementById("snapdom-sandbox");o||(o=document.createElement("div"),o.id="snapdom-sandbox",o.style.position="absolute",o.style.left="-9999px",o.style.top="-9999px",o.style.width="0",o.style.height="0",o.style.overflow="hidden",document.body.appendChild(o));let n=document.createElement(t);n.style.all="initial",o.appendChild(n);let a=getComputedStyle(n),r={};for(let s of a)r[s]=a.getPropertyValue(s);return o.removeChild(n),k.set(t,r),r}function U(t,e,o=!1){let n=[],a=z(e);for(let[r,s]of Object.entries(t))if(!o)s&&n.push(`${r}:${s}`);else{let c=a[r];s&&s!==c&&n.push(`${r}:${s}`)}return n.sort().join(";")}function H(t){let e=new Set;return t.nodeType!==Node.ELEMENT_NODE&&t.nodeType!==Node.DOCUMENT_FRAGMENT_NODE?[]:(t.tagName&&e.add(t.tagName.toLowerCase()),typeof t.querySelectorAll=="function"&&t.querySelectorAll("*").forEach(o=>e.add(o.tagName.toLowerCase())),Array.from(e))}function K(t){let e=new Map;for(let n of t){let a=k.get(n);if(!a)continue;let r=Object.entries(a).map(([s,c])=>`${s}:${c};`).sort().join("");e.has(r)||e.set(r,[]),e.get(r).push(n)}let o="";for(let[n,a]of e.entries())o+=`${a.join(",")} { ${n} }
2
+ `;return o}function J(t){let e=new Set(t.values()),o=new Map,n=1;for(let a of e)o.set(a,`c${n++}`);return o}function N(t,{fast:e=!1}={}){if(e)return t();"requestIdleCallback"in window?requestIdleCallback(t,{timeout:50}):setTimeout(t,1)}function A(t,e=null){if(!(t instanceof Element))return window.getComputedStyle(t,e);let o=R.get(t);if(o||(o=new Map,R.set(t,o)),!o.has(e)){let n=window.getComputedStyle(t,e);o.set(e,n)}return o.get(e)}function Y(t){let e=t.replace(/^['"]|['"]$/g,"");if(e.startsWith("\\"))try{return String.fromCharCode(parseInt(e.replace("\\",""),16))}catch{return e}return e}function X(t){let e=t.match(/url\(["']?([^"')]+)["']?\)/);return e?e[1]:null}function M(t){return[/font\s*awesome/i,/material\s*icons/i,/ionicons/i,/glyphicons/i,/feather/i,/bootstrap\s*icons/i,/remix\s*icons/i,/heroicons/i].some(o=>o.test(t))}function C(t,e=3e3){return S.has(t)?Promise.resolve(S.get(t)):new Promise((o,n)=>{let a=setTimeout(()=>{n(new Error("Image load timed out"))},e),r=new Image;r.crossOrigin="anonymous",r.onload=async()=>{clearTimeout(a);try{await r.decode();let s=document.createElement("canvas");s.width=r.width,s.height=r.height,s.getContext("2d").drawImage(r,0,0);try{let i=s.toDataURL("image/png");S.set(t,i),o(i)}catch{n(new Error("CORS restrictions prevented image capture"))}}catch(s){n(s)}},r.onerror=s=>{clearTimeout(a),n(new Error("Failed to load image: "+(s.message||"Unknown error")))},r.src=t})}function B(t){let e={};for(let o of t)e[o]=t.getPropertyValue(o);return e}function F(){return/^((?!chrome|android).)*safari/i.test(navigator.userAgent)}function O(t,e,o,n,a){if(t.tagName==="STYLE")return;n.has(t)||n.set(t,A(t));let r=n.get(t),s=B(r),c=t.tagName?.toLowerCase()||"div",i=U(s,c,a);o.set(e,i)}function D(t,e,o,n,a){if(t.nodeType===Node.ELEMENT_NODE&&t.getAttribute("data-capture")==="exclude"){let c=document.createElement("div"),i=t.getBoundingClientRect();return c.style.cssText=`display: inline-block; width: ${i.width}px; height: ${i.height}px; visibility: hidden;`,c}if(t.tagName==="IFRAME"){let c=document.createElement("div");return c.textContent="",c.style.cssText=`width: ${t.offsetWidth}px; height: ${t.offsetHeight}px; background: 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;`,c}if(t.nodeType===Node.ELEMENT_NODE&&t.getAttribute("data-capture")==="placeholder"){let c=t.cloneNode(!1);n.set(c,t),O(t,c,e,o,a);let i=document.createElement("div");return i.textContent=t.getAttribute("data-placeholder-text")||"",i.style.cssText="color: #666;font-size: 12px;text-align: center;line-height: 1.4;padding: 0.5em;box-sizing: border-box;",c.appendChild(i),c}if(t.tagName==="CANVAS"){let c=t.toDataURL(),i=document.createElement("img");return i.src=c,i.width=t.width,i.height=t.height,i.style.display="inline-block",i.style.width=`${t.width}px`,i.style.height=`${t.height}px`,i}if(t.nodeType===Node.TEXT_NODE){if(!t.textContent.trim())return null;if(t.parentElement?.shadowRoot){let i=t.parentElement.tagName.toLowerCase();if(customElements.get(i))return null}return t.cloneNode(!0)}if(t.nodeType!==Node.ELEMENT_NODE)return t.cloneNode(!0);let r=t.cloneNode(!1);n.set(r,t),O(t,r,e,o,a);let s=document.createDocumentFragment();if(t.childNodes.forEach(c=>{let i=D(c,e,o,n,a);i&&s.appendChild(i)}),r.appendChild(s),t.shadowRoot){let c=Array.from(t.shadowRoot.children).filter(d=>d.tagName!=="STYLE").map(d=>D(d,e,o,n)).filter(Boolean),i=document.createDocumentFragment();c.forEach(d=>i.appendChild(d)),r.appendChild(i)}return r}async function G(t,e,o,n=32,a="#000"){e=e.replace(/^['"]+|['"]+$/g,""),await document.fonts.ready,await document.fonts.load(`${n}px "${e}"`);let r=document.createElement("canvas");r.width=n,r.height=n;let s=r.getContext("2d");return s.font=o?`${o} ${n}px "${e}"`:` ${n}px "${e}"`,s.textAlign="center",s.textBaseline="middle",s.fillStyle=a,s.fillText(t,r.width/2,r.height/2),r.toDataURL()}async function j({ignoreIconFonts:t=!0,preCached:e=!1}){let o=Array.from(document.querySelectorAll('link[rel="stylesheet"]')).filter(a=>a.href),n="";for(let a of o)try{let s=await(await fetch(a.href)).text();if(t&&(M(a.href)||M(s))){console.log("\u23ED\uFE0F Ignorando icon font CSS:",a.href);continue}let c=/url\(([^)]+)\)/g,i=await Promise.all(Array.from(s.matchAll(c)).map(async l=>{let m=l[1].replace(/["']/g,"");if(m.startsWith("http")||(m=new URL(m,a.href).href),t&&M(m))return console.log("\u23ED\uFE0F Ignorando icon font URL:",m),null;if($.has(m))return{original:l[0],inlined:`url(${$.get(m)})`};try{let h=await(await fetch(m)).blob(),f=await new Promise(u=>{let w=new FileReader;w.onload=()=>u(w.result),w.readAsDataURL(h)});return $.set(m,f),{original:l[0],inlined:`url(${f})`}}catch{return console.warn("\u274C No pude fetch font",m),null}})),d=s;for(let l of i)l&&(d=d.replace(l.original,l.inlined));n+=d+`
3
+ `}catch{console.warn("\u274C No pude fetch CSS",a.href)}if(n&&e){let a=document.createElement("style");a.setAttribute("data-snapdom","embedFonts"),a.textContent=n,document.head.appendChild(a)}return n}async function W(t,e,o,n,a){if(!(t instanceof Element)||!(e instanceof Element))return;for(let c of["::before","::after"])try{let i=A(t,c);if(!i)continue;let d=i.getPropertyValue("content"),l=i.getPropertyValue("background-image"),m=d&&d!=="none"&&d!=='""'&&d!=="''",g=l&&l.startsWith("url(");if(m||g){let h=i.getPropertyValue("font-family"),f=parseInt(i.getPropertyValue("font-size"))||32,u=parseInt(i.getPropertyValue("font-weight"))||!1,w=i.getPropertyValue("color")||"#000",y=document.createElement("span");y.dataset.snapdomPseudo=c;let L=B(i),v=U(L,"span",a);o.set(y,v);let P=h&&/font.*awesome|material|bootstrap|glyphicons|ionicons|remixicon|simple-line-icons|octicons|feather|typicons|weathericons/i.test(h),x=Y(d);if(P&&x.length===1){let b=document.createElement("img");b.src=await G(x,h,u,f,w),b.style="display:block;width:100%;height:100%;object-fit:contain;",y.appendChild(b)}else if(x.startsWith("url(")){let b=x.match(/url\(["']?([^"')]+)["']?\)/);if(b?.[1])try{let I=document.createElement("img"),q=await C(b[1]);I.src=q,I.style="display:block;width:100%;height:100%;object-fit:contain;",y.appendChild(I)}catch(I){console.error(`[snapdom] Error in pseudo ${c} for`,t,I)}}else x&&x!=="none"&&(y.textContent=x);c==="::before"?e.insertBefore(y,e.firstChild):e.appendChild(y)}}catch(i){console.warn(`[snapdom] Failed to capture ${c} for`,t,i)}let r=Array.from(t.children),s=Array.from(e.children).filter(c=>!c.dataset.snapdomPseudo);for(let c=0;c<Math.min(r.length,s.length);c++)await W(r[c],s[c],o,n,a)}async function Q(t,e=!1){let o=new Map,n=new WeakMap,a=new WeakMap,r;try{r=D(t,o,n,a,e)}catch(i){throw console.warn("deepClone failed:",i),i}try{await W(t,r,o,n,e)}catch(i){console.warn("inlinePseudoElements failed:",i)}let s=J(o),c=Array.from(s.entries()).map(([i,d])=>`.${d}{${i}}`).join("");for(let[i,d]of o.entries()){if(i.tagName==="STYLE")continue;let l=s.get(d);l&&i.classList.add(l);let m=i.style?.backgroundImage;i.removeAttribute("style"),m&&m!=="none"&&(i.style.backgroundImage=m)}return{clone:r,classCSS:c,styleCache:n}}async function Z(t){let e=Array.from(t.querySelectorAll("img")),o=async n=>{let a=n.src;try{let r=await C(a);n.src=r,n.width||(n.width=n.naturalWidth||100),n.height||(n.height=n.naturalHeight||100)}catch{let r=document.createElement("div");r.style=`width: ${n.width||100}px; height: ${n.height||100}px; background: #ccc; display: inline-block; text-align: center; line-height: ${n.height||100}px; color: #666; font-size: 12px;`,r.innerText="img",n.replaceWith(r)}};for(let n=0;n<e.length;n+=4){let a=e.slice(n,n+4).map(o);await Promise.allSettled(a)}}async function tt(t,e,o){let n=[[t,e]];for(;n.length;){let[a,r]=n.shift(),s=o.get(a)||A(a);o.has(a)||o.set(a,s);let c=s.getPropertyValue("background-image");if(c&&c.includes("url(")){let l=c.match(/url\(["']?([^"')]+)["']?\)/);if(l?.[1])try{let m=l[1],g;E.has(m)?g=E.get(m):(g=await C(m),E.set(m,g)),r.style.backgroundImage=`url(${g})`}catch{r.style.backgroundImage="none"}}let i=Array.from(a.children),d=Array.from(r.children);for(let l=0;l<Math.min(i.length,d.length);l++)n.push([i[l],d[l]])}}async function et(t,e={}){if(!t)throw new Error("Element cannot be null or undefined");let{compress:o=!0,embedFonts:n=!1,fast:a=!0,scale:r=1}=e,s,c,i,d="",l="",m,g;if({clone:s,classCSS:c,styleCache:i}=await Q(t,o),await new Promise(f=>{N(async()=>{await Z(s),f()},{fast:a})}),await new Promise(f=>{N(async()=>{await tt(t,s,i),f()},{fast:a})}),n&&await new Promise(f=>{N(async()=>{d=await j({ignoreIconFonts:!0}),f()},{fast:a})}),o){let f=H(s).sort(),u=f.join(",");T.has(u)?l=T.get(u):await new Promise(w=>{N(()=>{l=K(f),T.set(u,l),w()},{fast:a})})}await new Promise(f=>{N(()=>{let u=t.getBoundingClientRect(),w=Math.ceil(u.width),y=Math.ceil(u.height);s.setAttribute("xmlns","http://www.w3.org/1999/xhtml"),s.style.transformOrigin="top left",r!==1&&F()&&(s.style.scale=`${r}`);let L="http://www.w3.org/2000/svg",v=document.createElementNS(L,"foreignObject");v.setAttribute("width","100%"),v.setAttribute("height","100%");let P=document.createElement("style");P.textContent=l+d+"svg{overflow:visible;}"+c,v.appendChild(P),v.appendChild(s);let b=new XMLSerializer().serializeToString(v);g=`<svg xmlns="${L}" width="${w}" height="${y}" viewBox="0 0 ${w} ${y}">`+b+"</svg>",m=`data:image/svg+xml;charset=utf-8,${encodeURIComponent(g)}`,f()},{fast:a})});let h=document.getElementById("snapdom-sandbox");return h&&h.style.position==="absolute"&&h.remove(),m}async function st(t,{dpr:e=1,scale:o=1}){let n=new Image;return n.src=t,await n.decode(),F?(n.width=n.width*o,n.height=n.height*o):(n.width=n.width/o,n.height=n.height/o),n}async function nt(t,{dpr:e=1,scale:o=1}){let n=new Image;n.src=t,await n.decode();let a=document.createElement("canvas"),r=n.width*o*e,s=n.height*o*e;a.width=Math.ceil(r),a.height=Math.ceil(s);let c=a.getContext("2d");return c.scale(o*e,o*e),c.drawImage(n,0,0),a}async function ot(t){let e=decodeURIComponent(t.split(",")[1]);return new Blob([e],{type:"image/svg+xml"})}async function at(t,{dpr:e=1,scale:o=1},n){let a=await nt(t,{dpr:e,scale:o});if(!n)return a;let r=document.createElement("canvas");r.width=a.width,r.height=a.height;let s=r.getContext("2d");return s.fillStyle=n,s.fillRect(0,0,r.width,r.height),s.drawImage(a,0,0),r}async function V(t,{dpr:e=1,scale:o=1,backgroundColor:n="#fff",quality:a},r="png"){let s=await at(t,{dpr:e,scale:o},n),c=new Image;return c.src=s.toDataURL(`image/${r}`,a),await c.decode(),c.style.width=`${s.width/e}px`,c.style.height=`${s.height/e}px`,c}async function it(t,{dpr:e=1,scale:o=1,backgroundColor:n="#fff",format:a="png",filename:r="capture"}={}){if(a==="svg"){let g=await ot(t),h=URL.createObjectURL(g),f=document.createElement("a");f.href=h,f.download=`${r}.svg`,f.click(),URL.revokeObjectURL(h);return}let s=["jpg","jpeg","webp"].includes(a)?"#fff":void 0,i=await at(t,{dpr:e,scale:o},n??s),d={jpg:"image/jpeg",jpeg:"image/jpeg",png:"image/png",webp:"image/webp"}[a]||"image/png",l=i.toDataURL(d),m=document.createElement("a");m.href=l,m.download=`${r}.${a}`,m.click()}async function p(t,e={}){if(e={scale:1,...e},!t)throw new Error("Element cannot be null or undefined");return await p.capture(t,e)}p.capture=async(t,e={})=>{let o=await et(t,e),n=window.devicePixelRatio||1,a=e.scale||1;return{url:o,options:e,toRaw:()=>o,toImg:()=>st(o,{dpr:n,scale:a}),toCanvas:()=>nt(o,{dpr:n,scale:a}),toBlob:()=>ot(o),toPng:()=>V(o,{dpr:n,scale:a},"png"),toJpg:r=>V(o,{dpr:n,scale:a,...r},"jpeg"),toWebp:r=>V(o,{dpr:n,scale:a,...r},"webp"),download:({format:r="png",filename:s="capture",backgroundColor:c}={})=>it(o,{dpr:n,scale:a,backgroundColor:c,format:r,filename:s})}};p.toRaw=async(t,e)=>(await p.capture(t,e)).toRaw();p.toImg=async(t,e)=>(await p.capture(t,e)).toImg();p.toCanvas=async(t,e)=>(await p.capture(t,e)).toCanvas();p.toBlob=async(t,e)=>(await p.capture(t,e)).toBlob();p.toPng=async(t,e)=>(await p.capture(t,e)).toPng(e);p.toJpg=async(t,e)=>(await p.capture(t,e)).toJpg(e);p.toWebp=async(t,e)=>(await p.capture(t,e)).toWebp(e);p.download=async(t,e={})=>{let{format:o="png",filename:n="capture",backgroundColor:a,...r}=e;return await(await p.capture(t,r)).download({format:o,filename:n,backgroundColor:a})};async function ct(t=document,e={}){let{embedFonts:o=!0,reset:n=!1,preWarm:a=!0}=e;if(n){S.clear(),E.clear(),$.clear(),T.clear(),R.clear();return}await document.fonts.ready,_();let r=[],s=[];t?.querySelectorAll&&(r=Array.from(t.querySelectorAll("img[src]")),s=Array.from(t.querySelectorAll("*")));let c=[];for(let i of r){let d=i.src;S.has(d)||c.push(C(d).then(l=>S.set(d,l)).catch(()=>{}))}for(let i of s){let d=getComputedStyle(i).backgroundImage,l=X(d);l&&!E.has(l)&&c.push(C(l).then(m=>E.set(l,m)).catch(()=>{}))}o&&await j({ignoreIconFonts:!0,preCached:!0}),await Promise.all(c)}export{ct as preCache,p as snapdom};
package/dist/snapdom.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
 
2
2
  /*
3
3
  * snapdom
4
- * v.0.9.9
4
+ * v.1.0.0
5
5
  * Author Juan Martin Muda
6
6
  * License MIT
7
7
  **/
@@ -247,6 +247,9 @@ function snapshotComputedStyle(style) {
247
247
  }
248
248
  return snap;
249
249
  }
250
+ function isSafari() {
251
+ return /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
252
+ }
250
253
 
251
254
  // src/modules/styles.js
252
255
  function inlineAllStyles(source, clone, styleMap, cache, compress) {
@@ -291,6 +294,9 @@ function deepClone(node, styleMap, styleCache, nodeMap, compress) {
291
294
  img.src = dataURL;
292
295
  img.width = node.width;
293
296
  img.height = node.height;
297
+ img.style.display = "inline-block";
298
+ img.style.width = `${node.width}px`;
299
+ img.style.height = `${node.height}px`;
294
300
  return img;
295
301
  }
296
302
  if (node.nodeType === Node.TEXT_NODE) {
@@ -596,14 +602,12 @@ async function captureDOM(element, options = {}) {
596
602
  await new Promise((resolve) => {
597
603
  idle(() => {
598
604
  const rect = element.getBoundingClientRect();
599
- const w = rect.width * scale;
600
- const h = rect.height * scale;
601
- if (scale !== 1) {
602
- clone.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
603
- clone.style.transform = `scale(${scale})`;
604
- clone.style.transformOrigin = "top left";
605
- clone.style.width = `${rect.width}px`;
606
- clone.style.height = `${rect.height}px`;
605
+ const w = Math.ceil(rect.width);
606
+ const h = Math.ceil(rect.height);
607
+ clone.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
608
+ clone.style.transformOrigin = "top left";
609
+ if (scale !== 1 && isSafari()) {
610
+ clone.style.scale = `${scale}`;
607
611
  }
608
612
  const svgNS = "http://www.w3.org/2000/svg";
609
613
  const fo = document.createElementNS(svgNS, "foreignObject");
@@ -628,68 +632,122 @@ async function captureDOM(element, options = {}) {
628
632
  }
629
633
 
630
634
  // src/api/snapdom.js
631
- async function snapdom(element, options = {}) {
632
- if (!element) {
633
- throw new Error("Element cannot be null or undefined");
635
+ async function toImg(url, { dpr = 1, scale = 1 }) {
636
+ const img = new Image();
637
+ img.src = url;
638
+ await img.decode();
639
+ if (isSafari) {
640
+ img.width = img.width * scale;
641
+ img.height = img.height * scale;
642
+ } else {
643
+ img.width = img.width / scale;
644
+ img.height = img.height / scale;
634
645
  }
635
- return await captureDOM(element, options);
646
+ return img;
636
647
  }
637
- snapdom.toImg = async (el, options = {}) => {
638
- const url = await snapdom(el, options);
648
+ async function toCanvas(url, { dpr = 1, scale = 1 }) {
639
649
  const img = new Image();
640
650
  img.src = url;
641
651
  await img.decode();
642
- return img;
643
- };
644
- snapdom.toCanvas = async (el, options = {}) => {
645
- const img = await snapdom.toImg(el, options);
646
652
  const canvas = document.createElement("canvas");
647
- canvas.width = img.width;
648
- canvas.height = img.height;
649
- canvas.getContext("2d").drawImage(img, 0, 0);
653
+ const width = img.width * scale * dpr;
654
+ const height = img.height * scale * dpr;
655
+ canvas.width = Math.ceil(width);
656
+ canvas.height = Math.ceil(height);
657
+ const ctx = canvas.getContext("2d");
658
+ ctx.scale(scale * dpr, scale * dpr);
659
+ ctx.drawImage(img, 0, 0);
650
660
  return canvas;
651
- };
652
- snapdom.toPng = async (el, options = {}) => {
653
- const canvas = await snapdom.toCanvas(el, options);
654
- const img = new Image();
655
- img.src = canvas.toDataURL("image/png");
656
- await img.decode();
657
- return img;
658
- };
659
- snapdom.toJpg = async (el, options = {}) => {
660
- const { backgroundColor = "#fff", quality = 1 } = options;
661
- const canvas = await snapdom.toCanvas(el, options);
662
- const temp = document.createElement("canvas");
663
- temp.width = canvas.width;
664
- temp.height = canvas.height;
665
- const ctx = temp.getContext("2d");
666
- ctx.fillStyle = backgroundColor;
667
- ctx.fillRect(0, 0, temp.width, temp.height);
668
- ctx.drawImage(canvas, 0, 0);
669
- const img = new Image();
670
- img.src = temp.toDataURL("image/jpeg", quality);
671
- await img.decode();
672
- return img;
673
- };
674
- snapdom.toWebp = async (el, options = {}) => {
675
- const { backgroundColor = "#fff", quality = 1 } = options;
676
- const canvas = await snapdom.toCanvas(el, options);
661
+ }
662
+ async function toBlob(url) {
663
+ const svgText = decodeURIComponent(url.split(",")[1]);
664
+ return new Blob([svgText], { type: "image/svg+xml" });
665
+ }
666
+ async function createBackground(url, { dpr = 1, scale = 1 }, backgroundColor) {
667
+ const baseCanvas = await toCanvas(url, { dpr, scale });
668
+ if (!backgroundColor) return baseCanvas;
677
669
  const temp = document.createElement("canvas");
678
- temp.width = canvas.width;
679
- temp.height = canvas.height;
670
+ temp.width = baseCanvas.width;
671
+ temp.height = baseCanvas.height;
680
672
  const ctx = temp.getContext("2d");
681
673
  ctx.fillStyle = backgroundColor;
682
674
  ctx.fillRect(0, 0, temp.width, temp.height);
683
- ctx.drawImage(canvas, 0, 0);
675
+ ctx.drawImage(baseCanvas, 0, 0);
676
+ return temp;
677
+ }
678
+ async function toRasterImg(url, { dpr = 1, scale = 1, backgroundColor = "#fff", quality }, format = "png") {
679
+ const canvas = await createBackground(url, { dpr, scale }, backgroundColor);
684
680
  const img = new Image();
685
- img.src = temp.toDataURL("image/webp", quality);
681
+ img.src = canvas.toDataURL(`image/${format}`, quality);
686
682
  await img.decode();
683
+ img.style.width = `${canvas.width / dpr}px`;
684
+ img.style.height = `${canvas.height / dpr}px`;
687
685
  return img;
686
+ }
687
+ async function download(url, { dpr = 1, scale = 1, backgroundColor = "#fff", format = "png", filename = "capture" } = {}) {
688
+ if (format === "svg") {
689
+ const blob = await toBlob(url);
690
+ const objectURL = URL.createObjectURL(blob);
691
+ const a2 = document.createElement("a");
692
+ a2.href = objectURL;
693
+ a2.download = `${filename}.svg`;
694
+ a2.click();
695
+ URL.revokeObjectURL(objectURL);
696
+ return;
697
+ }
698
+ const defaultBg = ["jpg", "jpeg", "webp"].includes(format) ? "#fff" : void 0;
699
+ const finalBg = backgroundColor ?? defaultBg;
700
+ const canvas = await createBackground(url, { dpr, scale }, finalBg);
701
+ const mime = {
702
+ jpg: "image/jpeg",
703
+ jpeg: "image/jpeg",
704
+ png: "image/png",
705
+ webp: "image/webp"
706
+ }[format] || "image/png";
707
+ const dataURL = canvas.toDataURL(mime);
708
+ const a = document.createElement("a");
709
+ a.href = dataURL;
710
+ a.download = `${filename}.${format}`;
711
+ a.click();
712
+ }
713
+ async function snapdom(element, options = {}) {
714
+ options = { scale: 1, ...options };
715
+ if (!element) throw new Error("Element cannot be null or undefined");
716
+ return await snapdom.capture(element, options);
717
+ }
718
+ snapdom.capture = async (el, options = {}) => {
719
+ const url = await captureDOM(el, options);
720
+ const dpr = window.devicePixelRatio || 1;
721
+ const scale = options.scale || 1;
722
+ return {
723
+ url,
724
+ options,
725
+ toRaw: () => url,
726
+ toImg: () => toImg(url, { dpr, scale }),
727
+ toCanvas: () => toCanvas(url, { dpr, scale }),
728
+ toBlob: () => toBlob(url),
729
+ toPng: () => toRasterImg(url, { dpr, scale }, "png"),
730
+ toJpg: (options2) => toRasterImg(url, { dpr, scale, ...options2 }, "jpeg"),
731
+ toWebp: (options2) => toRasterImg(url, { dpr, scale, ...options2 }, "webp"),
732
+ download: ({ format = "png", filename = "capture", backgroundColor } = {}) => download(url, { dpr, scale, backgroundColor, format, filename })
733
+ };
688
734
  };
689
- snapdom.toBlob = async (el, options = {}) => {
690
- const dataUrl = await snapdom(el, options);
691
- const svgText = decodeURIComponent(dataUrl.split(",")[1]);
692
- return new Blob([svgText], { type: "image/svg+xml" });
735
+ snapdom.toRaw = async (el, options) => (await snapdom.capture(el, options)).toRaw();
736
+ snapdom.toImg = async (el, options) => (await snapdom.capture(el, options)).toImg();
737
+ snapdom.toCanvas = async (el, options) => (await snapdom.capture(el, options)).toCanvas();
738
+ snapdom.toBlob = async (el, options) => (await snapdom.capture(el, options)).toBlob();
739
+ snapdom.toPng = async (el, options) => (await snapdom.capture(el, options)).toPng(options);
740
+ snapdom.toJpg = async (el, options) => (await snapdom.capture(el, options)).toJpg(options);
741
+ snapdom.toWebp = async (el, options) => (await snapdom.capture(el, options)).toWebp(options);
742
+ snapdom.download = async (el, options = {}) => {
743
+ const {
744
+ format = "png",
745
+ filename = "capture",
746
+ backgroundColor,
747
+ ...rest
748
+ } = options;
749
+ const capture = await snapdom.capture(el, rest);
750
+ return await capture.download({ format, filename, backgroundColor });
693
751
  };
694
752
 
695
753
  // src/api/preCache.js
@@ -734,33 +792,6 @@ async function preCache(root = document, options = {}) {
734
792
  await embedCustomFonts({ ignoreIconFonts: true, preCached: true });
735
793
  }
736
794
  await Promise.all(promises);
737
- if (preWarm) {
738
- const usedTags = collectUsedTagNames(root).sort();
739
- const tagKey = usedTags.join(",");
740
- if (!baseCSSCache.has(tagKey)) {
741
- const css = generateDedupedBaseCSS(usedTags);
742
- baseCSSCache.set(tagKey, css);
743
- }
744
- const clone = root.cloneNode(false);
745
- await inlinePseudoElements(
746
- root,
747
- clone,
748
- /* @__PURE__ */ new Map(),
749
- /* @__PURE__ */ new WeakMap(),
750
- /* compress */
751
- false
752
- );
753
- }
754
- if (preWarm) {
755
- try {
756
- await prepareClone(
757
- root,
758
- /* compress: */
759
- options.preWarm
760
- );
761
- } catch (e) {
762
- }
763
- }
764
795
  }
765
796
  export {
766
797
  preCache,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zumer/snapdom",
3
- "version": "0.9.9",
3
+ "version": "1.0.0",
4
4
  "description": "snapDOM captures DOM elements as images with exceptional speed and minimal overhead. Designed to avoid bottlenecks and long tasks.",
5
5
  "main": "dist/snapdom.js",
6
6
  "module": "dist/snapdom.mjs",