@zumer/snapdom 0.9.9 → 1.0.0-pre.1747581859131

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,157 +62,100 @@ 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);
82
-
83
- document.body.appendChild(img);
84
- ```
85
-
86
- ## API
87
-
88
- ### Note: API may evolve until v1.0.0!
89
-
90
- The main API is exposed as `snapdom` and offers multiple capture methods:
91
-
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 |
102
-
103
- ---
104
-
105
- ### Options
106
-
107
- You can pass an `options` object to control the behavior of all capture methods:
108
-
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`. |
117
-
118
- ---
119
-
120
- ### `preCache()` – Optional helper
121
-
122
- The `preCache()` function can be used to load external resources (like images and fonts) in advance.
67
+ ### Reusable result (recommended)
123
68
 
124
69
  ```js
125
- import { preCache } from '@zumer/snapdom';
70
+ const el = document.querySelector('#target');
71
+ const result = await snapdom(el, { scale: 2 });
126
72
 
127
- await preCache(document.body, {
128
- embedFonts: true,
129
- preWarm: true
130
- });
131
- ```
73
+ const img = await result.toPng();
74
+ document.body.appendChild(img);
132
75
 
133
- ```js
134
- import { snapdom, preCache } from './snapdom.mjs';
135
- window.addEventListener('load', async () => {
136
- await preCache();
137
- console.log('📦 Resources preloaded');
138
- });
76
+ await result.download({ format: 'jpg', filename: 'my-capture' });
139
77
  ```
140
78
 
141
- **Options for `preCache()`:**
142
-
143
- * `embedFonts` *(boolean, default: true)* — Inlines non-icon fonts during preload.
144
- * `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
79
+ ### One-step shortcuts
148
80
 
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.
81
+ ```js
82
+ const el = document.querySelector('#target');
83
+ const png = await snapdom.toPng(el);
84
+ document.body.appendChild(png);
158
85
 
159
- **Now with improved fidelity and even faster performance.**
86
+ const blob = await snapdom.toBlob(el);
87
+ ```
160
88
 
161
- ## Full example
89
+ ## API
162
90
 
163
- ```html
164
- <div id="captureMe">
165
- <h1 style="color: tomato;">Hello World!</h1>
166
- <p>This content will be captured.</p>
167
- </div>
91
+ ### `snapdom(el, options?)`
168
92
 
169
- <button id="captureBtn">Capture as img</button>
93
+ Returns an object with reusable export methods:
170
94
 
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>
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
+ }
181
107
  ```
182
108
 
183
- ## Limitations
184
-
185
- * External images must be CORS-accessible.
186
- * Iframes are not captured.
187
-
188
- ### Please if you find a bug **open an issue**.
109
+ ### Shortcut methods
189
110
 
190
- ## Benchmarks
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 |
191
120
 
192
- `snapDOM` is not only highly accurate — it’s **extremely fast**.
121
+ ## Options
193
122
 
123
+ All capture methods accept an `options` object:
194
124
 
195
- Latest benchmarks show significant performance improvements against other libraries:
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) |
196
133
 
197
- | Scenario | vs. `modern-screenshot` | vs. `html2canvas` |
198
- | -------------------------------- | :---------------------: | :---------------: |
199
- | Small element (200×100) | 6.46× faster | 32.27× faster |
200
- | Modal size (400×300) | 7.28× faster | 32.66× faster |
201
- | Page view (1200×800) | 13.17× faster | 35.29× faster |
202
- | Large scroll area (2000×1500) | 38.23× faster | 68.85× faster |
203
- | Very large element (4000×2000) | 93.31× faster | 133.12× faster |
204
- | Complex small element (200×100) | 3.97× faster | 15.23× faster |
205
- | Complex modal (400×300) | 2.32× faster | 5.33× faster |
206
- | Complex page (1200×800) | 1.62× faster | 1.65× faster |
207
- | Complex large scroll (2000×1500) | 1.66× faster | 1.24× faster |
208
- | Complex very large (4000×2000) | 1.52× faster | 1.28× faster |
134
+ ### Download options
209
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
+ ```
210
143
 
211
- ### Run the benchmarks
144
+ ## Features
212
145
 
213
- To run these benchmarks yourself:
146
+ * Captures **shadow DOM** and Web Components
147
+ * Supports `::before` and `::after` pseudo-elements
148
+ * Inlines background images and fonts
149
+ * Handles **Font Awesome**, **Material Icons**, and more
150
+ * `data-capture="exclude"` to ignore an element
151
+ * `data-capture="placeholder"` with `data-placeholder-text` for masked replacements
214
152
 
215
- ```sh
216
- git clone https://github.com/zumerlab/snapdom.git
217
- cd snapdom
218
- npm install
219
- npm run test:benchmark
220
- ```
153
+ ## Limitations
221
154
 
222
- They execute in **headless Chromium** using real DOM nodes.
155
+ * External images must be CORS-accessible
156
+ * Iframes are not supported
157
+ * When WebP format is used on Safari, it will fallback to PNG rendering.
223
158
 
224
159
  ## License
225
160
 
226
161
  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-pre.1747581859131
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,T=new Map,k=new Map,R=new WeakMap;var re=["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 e of re)z(e)}function z(e){if(T.has(e))return T.get(e);if(new Set(["script","style","meta","link","noscript","template"]).has(e)){let s={};return T.set(e,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(e);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),T.set(e,r),r}function U(e,t,o=!1){let n=[],a=z(t);for(let[r,s]of Object.entries(e))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(e){let t=new Set;return e.nodeType!==Node.ELEMENT_NODE&&e.nodeType!==Node.DOCUMENT_FRAGMENT_NODE?[]:(e.tagName&&t.add(e.tagName.toLowerCase()),typeof e.querySelectorAll=="function"&&e.querySelectorAll("*").forEach(o=>t.add(o.tagName.toLowerCase())),Array.from(t))}function K(e){let t=new Map;for(let n of e){let a=T.get(n);if(!a)continue;let r=Object.entries(a).map(([s,c])=>`${s}:${c};`).sort().join("");t.has(r)||t.set(r,[]),t.get(r).push(n)}let o="";for(let[n,a]of t.entries())o+=`${a.join(",")} { ${n} }
2
+ `;return o}function J(e){let t=new Set(e.values()),o=new Map,n=1;for(let a of t)o.set(a,`c${n++}`);return o}function N(e,{fast:t=!1}={}){if(t)return e();"requestIdleCallback"in window?requestIdleCallback(e,{timeout:50}):setTimeout(e,1)}function A(e,t=null){if(!(e instanceof Element))return window.getComputedStyle(e,t);let o=R.get(e);if(o||(o=new Map,R.set(e,o)),!o.has(t)){let n=window.getComputedStyle(e,t);o.set(t,n)}return o.get(t)}function Y(e){let t=e.replace(/^['"]|['"]$/g,"");if(t.startsWith("\\"))try{return String.fromCharCode(parseInt(t.replace("\\",""),16))}catch{return t}return t}function X(e){let t=e.match(/url\(["']?([^"')]+)["']?\)/);return t?t[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,t=3e3){return S.has(e)?Promise.resolve(S.get(e)):new Promise((o,n)=>{let a=setTimeout(()=>{n(new Error("Image load timed out"))},t),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(e,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=e})}function M(e){let t={};for(let o of e)t[o]=e.getPropertyValue(o);return t}function D(){return/^((?!chrome|android).)*safari/i.test(navigator.userAgent)}function O(e,t,o,n,a){if(e.tagName==="STYLE")return;n.has(e)||n.set(e,A(e));let r=n.get(e),s=M(r),c=e.tagName?.toLowerCase()||"div",i=U(s,c,a);o.set(t,i)}function F(e,t,o,n,a){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);n.set(c,e),O(e,c,t,o,a);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.style.display="inline-block",i.style.width=`${e.width}px`,i.style.height=`${e.height}px`,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 r=e.cloneNode(!1);n.set(r,e),O(e,r,t,o,a);let s=document.createDocumentFragment();if(e.childNodes.forEach(c=>{let i=F(c,t,o,n,a);i&&s.appendChild(i)}),r.appendChild(s),e.shadowRoot){let c=Array.from(e.shadowRoot.children).filter(m=>m.tagName!=="STYLE").map(m=>F(m,t,o,n)).filter(Boolean),i=document.createDocumentFragment();c.forEach(m=>i.appendChild(m)),r.appendChild(i)}return r}async function G(e,t,o,n=32,a="#000"){t=t.replace(/^['"]+|['"]+$/g,""),await document.fonts.ready,await document.fonts.load(`${n}px "${t}"`);let r=document.createElement("canvas");r.width=n,r.height=n;let s=r.getContext("2d");return s.font=o?`${o} ${n}px "${t}"`:` ${n}px "${t}"`,s.textAlign="center",s.textBaseline="middle",s.fillStyle=a,s.fillText(e,r.width/2,r.height/2),r.toDataURL()}async function j({ignoreIconFonts:e=!0,preCached:t=!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(e&&(B(a.href)||B(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 d=l[1].replace(/["']/g,"");if(d.startsWith("http")||(d=new URL(d,a.href).href),e&&B(d))return console.log("\u23ED\uFE0F Ignorando icon font URL:",d),null;if($.has(d))return{original:l[0],inlined:`url(${$.get(d)})`};try{let h=await(await fetch(d)).blob(),p=await new Promise(u=>{let w=new FileReader;w.onload=()=>u(w.result),w.readAsDataURL(h)});return $.set(d,p),{original:l[0],inlined:`url(${p})`}}catch{return console.warn("\u274C No pude fetch font",d),null}})),m=s;for(let l of i)l&&(m=m.replace(l.original,l.inlined));n+=m+`
3
+ `}catch{console.warn("\u274C No pude fetch CSS",a.href)}if(n&&t){let a=document.createElement("style");a.setAttribute("data-snapdom","embedFonts"),a.textContent=n,document.head.appendChild(a)}return n}async function W(e,t,o,n,a){if(!(e instanceof Element)||!(t 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 h=i.getPropertyValue("font-family"),p=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 P=M(i),v=U(P,"span",a);o.set(y,v);let L=h&&/font.*awesome|material|bootstrap|glyphicons|ionicons|remixicon|simple-line-icons|octicons|feather|typicons|weathericons/i.test(h),x=Y(m);if(L&&x.length===1){let b=document.createElement("img");b.src=await G(x,h,u,p,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`,e,I)}}else x&&x!=="none"&&(y.textContent=x);c==="::before"?t.insertBefore(y,t.firstChild):t.appendChild(y)}}catch(i){console.warn(`[snapdom] Failed to capture ${c} for`,e,i)}let r=Array.from(e.children),s=Array.from(t.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(e,t=!1){let o=new Map,n=new WeakMap,a=new WeakMap,r;try{r=F(e,o,n,a,t)}catch(i){throw console.warn("deepClone failed:",i),i}try{await W(e,r,o,n,t)}catch(i){console.warn("inlinePseudoElements failed:",i)}let s=J(o),c=Array.from(s.entries()).map(([i,m])=>`.${m}{${i}}`).join("");for(let[i,m]of o.entries()){if(i.tagName==="STYLE")continue;let l=s.get(m);l&&i.classList.add(l);let d=i.style?.backgroundImage;i.removeAttribute("style"),d&&d!=="none"&&(i.style.backgroundImage=d)}return{clone:r,classCSS:c,styleCache:n}}async function Z(e){let t=Array.from(e.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<t.length;n+=4){let a=t.slice(n,n+4).map(o);await Promise.allSettled(a)}}async function ee(e,t,o){let n=[[e,t]];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 d=l[1],g;E.has(d)?g=E.get(d):(g=await C(d),E.set(d,g)),r.style.backgroundImage=`url(${g})`}catch{r.style.backgroundImage="none"}}let i=Array.from(a.children),m=Array.from(r.children);for(let l=0;l<Math.min(i.length,m.length);l++)n.push([i[l],m[l]])}}async function te(e,t={}){if(!e)throw new Error("Element cannot be null or undefined");let{compress:o=!0,embedFonts:n=!1,fast:a=!0,scale:r=1}=t,s,c,i,m="",l="",d,g;if({clone:s,classCSS:c,styleCache:i}=await Q(e,o),await new Promise(p=>{N(async()=>{await Z(s),p()},{fast:a})}),await new Promise(p=>{N(async()=>{await ee(e,s,i),p()},{fast:a})}),n&&await new Promise(p=>{N(async()=>{m=await j({ignoreIconFonts:!0}),p()},{fast:a})}),o){let p=H(s).sort(),u=p.join(",");k.has(u)?l=k.get(u):await new Promise(w=>{N(()=>{l=K(p),k.set(u,l),w()},{fast:a})})}await new Promise(p=>{N(()=>{let u=e.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&&D()&&(s.style.scale=`${r}`);let P="http://www.w3.org/2000/svg",v=document.createElementNS(P,"foreignObject");v.setAttribute("width","100%"),v.setAttribute("height","100%");let L=document.createElement("style");L.textContent=l+m+"svg{overflow:visible;}"+c,v.appendChild(L),v.appendChild(s);let b=new XMLSerializer().serializeToString(v);g=`<svg xmlns="${P}" width="${w}" height="${y}" viewBox="0 0 ${w} ${y}">`+b+"</svg>",d=`data:image/svg+xml;charset=utf-8,${encodeURIComponent(g)}`,p()},{fast:a})});let h=document.getElementById("snapdom-sandbox");return h&&h.style.position==="absolute"&&h.remove(),d}async function se(e,{dpr:t=1,scale:o=1}){let n=new Image;return n.src=e,await n.decode(),D?(n.width=n.width*o,n.height=n.height*o):(n.width=n.width/o,n.height=n.height/o),n}async function ne(e,{dpr:t=1,scale:o=1}){let n=new Image;n.src=e,await n.decode();let a=document.createElement("canvas"),r=n.width*o*t,s=n.height*o*t;a.width=Math.ceil(r),a.height=Math.ceil(s);let c=a.getContext("2d");return c.scale(o*t,o*t),c.drawImage(n,0,0),a}async function oe(e){let t=decodeURIComponent(e.split(",")[1]);return new Blob([t],{type:"image/svg+xml"})}async function ae(e,{dpr:t=1,scale:o=1},n){let a=await ne(e,{dpr:t,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(e,{dpr:t=1,scale:o=1,backgroundColor:n="#fff",quality:a},r="png"){let s=await ae(e,{dpr:t,scale:o},n),c=new Image;return c.src=s.toDataURL(`image/${r}`,a),await c.decode(),c.style.width=`${s.width/t}px`,c.style.height=`${s.height/t}px`,c}async function ie(e,{dpr:t=1,scale:o=1,backgroundColor:n="#fff",format:a="png",filename:r="capture"}={}){if(a==="svg"){let g=await oe(e),h=URL.createObjectURL(g),p=document.createElement("a");p.href=h,p.download=`${r}.svg`,p.click(),URL.revokeObjectURL(h);return}let s=["jpg","jpeg","webp"].includes(a)?"#fff":void 0,i=await ae(e,{dpr:t,scale:o},n??s),m={jpg:"image/jpeg",jpeg:"image/jpeg",png:"image/png",webp:"image/webp"}[a]||"image/png",l=i.toDataURL(m),d=document.createElement("a");d.href=l,d.download=`${r}.${a}`,d.click()}async function f(e,t={}){if(t={scale:1,...t},!e)throw new Error("Element cannot be null or undefined");return await f.capture(e,t)}f.capture=async(e,t={})=>{let o=await te(e,t),n=window.devicePixelRatio||1,a=t.scale||1;return{url:o,options:t,toRaw:()=>o,toImg:()=>se(o,{dpr:n,scale:a}),toCanvas:()=>ne(o,{dpr:n,scale:a}),toBlob:()=>oe(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}={})=>ie(o,{dpr:n,scale:a,backgroundColor:c,format:r,filename:s})}};f.toRaw=async(e,t)=>(await f.capture(e,t)).toRaw();f.toImg=async(e,t)=>(await f.capture(e,t)).toImg();f.toCanvas=async(e,t)=>(await f.capture(e,t)).toCanvas();f.toBlob=async(e,t)=>(await f.capture(e,t)).toBlob();f.toPng=async(e,t)=>(await f.capture(e,t)).toPng(t);f.toJpg=async(e,t)=>(await f.capture(e,t)).toJpg(t);f.toWebp=async(e,t)=>(await f.capture(e,t)).toWebp(t);f.download=async(e,t={})=>{let{format:o="png",filename:n="capture",backgroundColor:a,...r}=t;return await(await f.capture(e,r)).download({format:o,filename:n,backgroundColor:a})};async function ce(e=document,t={}){let{embedFonts:o=!0,reset:n=!1,preWarm:a=!0}=t;if(n){S.clear(),E.clear(),$.clear(),k.clear(),R.clear();return}await document.fonts.ready,_();let r=[],s=[];e?.querySelectorAll&&(r=Array.from(e.querySelectorAll("img[src]")),s=Array.from(e.querySelectorAll("*")));let c=[];for(let i of r){let m=i.src;S.has(m)||c.push(C(m).then(l=>S.set(m,l)).catch(()=>{}))}for(let i of s){let m=getComputedStyle(i).backgroundImage,l=X(m);l&&!E.has(l)&&c.push(C(l).then(d=>E.set(l,d)).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,T=new Map,k=new Map,R=new WeakMap;var re=["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 e of re)z(e)}function z(e){if(T.has(e))return T.get(e);if(new Set(["script","style","meta","link","noscript","template"]).has(e)){let s={};return T.set(e,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(e);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),T.set(e,r),r}function U(e,t,o=!1){let n=[],a=z(t);for(let[r,s]of Object.entries(e))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(e){let t=new Set;return e.nodeType!==Node.ELEMENT_NODE&&e.nodeType!==Node.DOCUMENT_FRAGMENT_NODE?[]:(e.tagName&&t.add(e.tagName.toLowerCase()),typeof e.querySelectorAll=="function"&&e.querySelectorAll("*").forEach(o=>t.add(o.tagName.toLowerCase())),Array.from(t))}function K(e){let t=new Map;for(let n of e){let a=T.get(n);if(!a)continue;let r=Object.entries(a).map(([s,c])=>`${s}:${c};`).sort().join("");t.has(r)||t.set(r,[]),t.get(r).push(n)}let o="";for(let[n,a]of t.entries())o+=`${a.join(",")} { ${n} }
2
+ `;return o}function J(e){let t=new Set(e.values()),o=new Map,n=1;for(let a of t)o.set(a,`c${n++}`);return o}function N(e,{fast:t=!1}={}){if(t)return e();"requestIdleCallback"in window?requestIdleCallback(e,{timeout:50}):setTimeout(e,1)}function A(e,t=null){if(!(e instanceof Element))return window.getComputedStyle(e,t);let o=R.get(e);if(o||(o=new Map,R.set(e,o)),!o.has(t)){let n=window.getComputedStyle(e,t);o.set(t,n)}return o.get(t)}function Y(e){let t=e.replace(/^['"]|['"]$/g,"");if(t.startsWith("\\"))try{return String.fromCharCode(parseInt(t.replace("\\",""),16))}catch{return t}return t}function X(e){let t=e.match(/url\(["']?([^"')]+)["']?\)/);return t?t[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,t=3e3){return S.has(e)?Promise.resolve(S.get(e)):new Promise((o,n)=>{let a=setTimeout(()=>{n(new Error("Image load timed out"))},t),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(e,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=e})}function M(e){let t={};for(let o of e)t[o]=e.getPropertyValue(o);return t}function D(){return/^((?!chrome|android).)*safari/i.test(navigator.userAgent)}function O(e,t,o,n,a){if(e.tagName==="STYLE")return;n.has(e)||n.set(e,A(e));let r=n.get(e),s=M(r),c=e.tagName?.toLowerCase()||"div",i=U(s,c,a);o.set(t,i)}function F(e,t,o,n,a){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);n.set(c,e),O(e,c,t,o,a);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.style.display="inline-block",i.style.width=`${e.width}px`,i.style.height=`${e.height}px`,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 r=e.cloneNode(!1);n.set(r,e),O(e,r,t,o,a);let s=document.createDocumentFragment();if(e.childNodes.forEach(c=>{let i=F(c,t,o,n,a);i&&s.appendChild(i)}),r.appendChild(s),e.shadowRoot){let c=Array.from(e.shadowRoot.children).filter(m=>m.tagName!=="STYLE").map(m=>F(m,t,o,n)).filter(Boolean),i=document.createDocumentFragment();c.forEach(m=>i.appendChild(m)),r.appendChild(i)}return r}async function G(e,t,o,n=32,a="#000"){t=t.replace(/^['"]+|['"]+$/g,""),await document.fonts.ready,await document.fonts.load(`${n}px "${t}"`);let r=document.createElement("canvas");r.width=n,r.height=n;let s=r.getContext("2d");return s.font=o?`${o} ${n}px "${t}"`:` ${n}px "${t}"`,s.textAlign="center",s.textBaseline="middle",s.fillStyle=a,s.fillText(e,r.width/2,r.height/2),r.toDataURL()}async function j({ignoreIconFonts:e=!0,preCached:t=!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(e&&(B(a.href)||B(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 d=l[1].replace(/["']/g,"");if(d.startsWith("http")||(d=new URL(d,a.href).href),e&&B(d))return console.log("\u23ED\uFE0F Ignorando icon font URL:",d),null;if($.has(d))return{original:l[0],inlined:`url(${$.get(d)})`};try{let h=await(await fetch(d)).blob(),p=await new Promise(u=>{let w=new FileReader;w.onload=()=>u(w.result),w.readAsDataURL(h)});return $.set(d,p),{original:l[0],inlined:`url(${p})`}}catch{return console.warn("\u274C No pude fetch font",d),null}})),m=s;for(let l of i)l&&(m=m.replace(l.original,l.inlined));n+=m+`
3
+ `}catch{console.warn("\u274C No pude fetch CSS",a.href)}if(n&&t){let a=document.createElement("style");a.setAttribute("data-snapdom","embedFonts"),a.textContent=n,document.head.appendChild(a)}return n}async function W(e,t,o,n,a){if(!(e instanceof Element)||!(t 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 h=i.getPropertyValue("font-family"),p=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 P=M(i),v=U(P,"span",a);o.set(y,v);let L=h&&/font.*awesome|material|bootstrap|glyphicons|ionicons|remixicon|simple-line-icons|octicons|feather|typicons|weathericons/i.test(h),x=Y(m);if(L&&x.length===1){let b=document.createElement("img");b.src=await G(x,h,u,p,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`,e,I)}}else x&&x!=="none"&&(y.textContent=x);c==="::before"?t.insertBefore(y,t.firstChild):t.appendChild(y)}}catch(i){console.warn(`[snapdom] Failed to capture ${c} for`,e,i)}let r=Array.from(e.children),s=Array.from(t.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(e,t=!1){let o=new Map,n=new WeakMap,a=new WeakMap,r;try{r=F(e,o,n,a,t)}catch(i){throw console.warn("deepClone failed:",i),i}try{await W(e,r,o,n,t)}catch(i){console.warn("inlinePseudoElements failed:",i)}let s=J(o),c=Array.from(s.entries()).map(([i,m])=>`.${m}{${i}}`).join("");for(let[i,m]of o.entries()){if(i.tagName==="STYLE")continue;let l=s.get(m);l&&i.classList.add(l);let d=i.style?.backgroundImage;i.removeAttribute("style"),d&&d!=="none"&&(i.style.backgroundImage=d)}return{clone:r,classCSS:c,styleCache:n}}async function Z(e){let t=Array.from(e.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<t.length;n+=4){let a=t.slice(n,n+4).map(o);await Promise.allSettled(a)}}async function ee(e,t,o){let n=[[e,t]];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 d=l[1],g;E.has(d)?g=E.get(d):(g=await C(d),E.set(d,g)),r.style.backgroundImage=`url(${g})`}catch{r.style.backgroundImage="none"}}let i=Array.from(a.children),m=Array.from(r.children);for(let l=0;l<Math.min(i.length,m.length);l++)n.push([i[l],m[l]])}}async function te(e,t={}){if(!e)throw new Error("Element cannot be null or undefined");let{compress:o=!0,embedFonts:n=!1,fast:a=!0,scale:r=1}=t,s,c,i,m="",l="",d,g;if({clone:s,classCSS:c,styleCache:i}=await Q(e,o),await new Promise(p=>{N(async()=>{await Z(s),p()},{fast:a})}),await new Promise(p=>{N(async()=>{await ee(e,s,i),p()},{fast:a})}),n&&await new Promise(p=>{N(async()=>{m=await j({ignoreIconFonts:!0}),p()},{fast:a})}),o){let p=H(s).sort(),u=p.join(",");k.has(u)?l=k.get(u):await new Promise(w=>{N(()=>{l=K(p),k.set(u,l),w()},{fast:a})})}await new Promise(p=>{N(()=>{let u=e.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&&D()&&(s.style.scale=`${r}`);let P="http://www.w3.org/2000/svg",v=document.createElementNS(P,"foreignObject");v.setAttribute("width","100%"),v.setAttribute("height","100%");let L=document.createElement("style");L.textContent=l+m+"svg{overflow:visible;}"+c,v.appendChild(L),v.appendChild(s);let b=new XMLSerializer().serializeToString(v);g=`<svg xmlns="${P}" width="${w}" height="${y}" viewBox="0 0 ${w} ${y}">`+b+"</svg>",d=`data:image/svg+xml;charset=utf-8,${encodeURIComponent(g)}`,p()},{fast:a})});let h=document.getElementById("snapdom-sandbox");return h&&h.style.position==="absolute"&&h.remove(),d}async function se(e,{dpr:t=1,scale:o=1}){let n=new Image;return n.src=e,await n.decode(),D?(n.width=n.width*o,n.height=n.height*o):(n.width=n.width/o,n.height=n.height/o),n}async function ne(e,{dpr:t=1,scale:o=1}){let n=new Image;n.src=e,await n.decode();let a=document.createElement("canvas"),r=n.width*o*t,s=n.height*o*t;a.width=Math.ceil(r),a.height=Math.ceil(s);let c=a.getContext("2d");return c.scale(o*t,o*t),c.drawImage(n,0,0),a}async function oe(e){let t=decodeURIComponent(e.split(",")[1]);return new Blob([t],{type:"image/svg+xml"})}async function ae(e,{dpr:t=1,scale:o=1},n){let a=await ne(e,{dpr:t,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(e,{dpr:t=1,scale:o=1,backgroundColor:n="#fff",quality:a},r="png"){let s=await ae(e,{dpr:t,scale:o},n),c=new Image;return c.src=s.toDataURL(`image/${r}`,a),await c.decode(),c.style.width=`${s.width/t}px`,c.style.height=`${s.height/t}px`,c}async function ie(e,{dpr:t=1,scale:o=1,backgroundColor:n="#fff",format:a="png",filename:r="capture"}={}){if(a==="svg"){let g=await oe(e),h=URL.createObjectURL(g),p=document.createElement("a");p.href=h,p.download=`${r}.svg`,p.click(),URL.revokeObjectURL(h);return}let s=["jpg","jpeg","webp"].includes(a)?"#fff":void 0,i=await ae(e,{dpr:t,scale:o},n??s),m={jpg:"image/jpeg",jpeg:"image/jpeg",png:"image/png",webp:"image/webp"}[a]||"image/png",l=i.toDataURL(m),d=document.createElement("a");d.href=l,d.download=`${r}.${a}`,d.click()}async function f(e,t={}){if(t={scale:1,...t},!e)throw new Error("Element cannot be null or undefined");return await f.capture(e,t)}f.capture=async(e,t={})=>{let o=await te(e,t),n=window.devicePixelRatio||1,a=t.scale||1;return{url:o,options:t,toRaw:()=>o,toImg:()=>se(o,{dpr:n,scale:a}),toCanvas:()=>ne(o,{dpr:n,scale:a}),toBlob:()=>oe(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}={})=>ie(o,{dpr:n,scale:a,backgroundColor:c,format:r,filename:s})}};f.toRaw=async(e,t)=>(await f.capture(e,t)).toRaw();f.toImg=async(e,t)=>(await f.capture(e,t)).toImg();f.toCanvas=async(e,t)=>(await f.capture(e,t)).toCanvas();f.toBlob=async(e,t)=>(await f.capture(e,t)).toBlob();f.toPng=async(e,t)=>(await f.capture(e,t)).toPng(t);f.toJpg=async(e,t)=>(await f.capture(e,t)).toJpg(t);f.toWebp=async(e,t)=>(await f.capture(e,t)).toWebp(t);f.download=async(e,t={})=>{let{format:o="png",filename:n="capture",backgroundColor:a,...r}=t;return await(await f.capture(e,r)).download({format:o,filename:n,backgroundColor:a})};async function ce(e=document,t={}){let{embedFonts:o=!0,reset:n=!1,preWarm:a=!0}=t;if(n){S.clear(),E.clear(),$.clear(),k.clear(),R.clear();return}await document.fonts.ready,_();let r=[],s=[];e?.querySelectorAll&&(r=Array.from(e.querySelectorAll("img[src]")),s=Array.from(e.querySelectorAll("*")));let c=[];for(let i of r){let m=i.src;S.has(m)||c.push(C(m).then(l=>S.set(m,l)).catch(()=>{}))}for(let i of s){let m=getComputedStyle(i).backgroundImage,l=X(m);l&&!E.has(l)&&c.push(C(l).then(d=>E.set(l,d)).catch(()=>{}))}o&&await j({ignoreIconFonts:!0,preCached:!0}),await Promise.all(c)}export{ce as preCache,f 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-pre.1747581859131
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-pre.1747581859131",
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",