@zumer/snapdom 0.9.0 → 0.9.2

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,33 +1,36 @@
1
1
  # snapDOM
2
2
 
3
- **snapDOM** is a high-fidelity DOM capture tool, developed as part of the animation engine for Zumly — a framework for creating smooth zoom-based view transitions.
3
+ **snapDOM** is a high-fidelity DOM capture tool, developed as part of the animation engine I'm building for Zumly — a framework for creating smooth zoom-based view transitions.
4
4
 
5
5
  It converts any HTML element into a scalable SVG image, preserving styles, fonts, backgrounds, shadow DOM content, pseudo-elements, and more.
6
6
 
7
7
  - 📸 Full DOM capture
8
8
  - 🎨 Embedded styles, pseudo-elements, and fonts
9
9
  - 🖼️ Export to SVG, PNG, JPG, WebP, or `canvas`
10
- - ⚡ Lightweight, no dependencies
10
+ - ⚡ Ultra fast, no dependencies
11
11
  - 📦 100% based on standard Web APIs
12
12
 
13
- ---
14
- ¡Obvio, Martín! 🔥
15
- Acá te integro la parte de **Installation** agregando también la opción **CDN**, de manera prolija y siguiendo tu estilo:
16
-
17
- ---
18
13
 
19
14
  ## Installation
20
15
 
21
- You can use **snapDOM** by including it via **CDN**, **script tag**, or by **importing it as a module**.
16
+ You can use **snapDOM** via **NPM**, **CDN**, **script tag**, or by **importing as a module**.
17
+
18
+ ### NPM / Yarn
19
+
20
+ ```sh
21
+ npm i @zumer/snapdom
22
+ ```
23
+
24
+ ```sh
25
+ yarn add @zumer/snapdom
26
+ ```
22
27
 
23
28
  ### CDN
24
29
 
25
30
  ```html
26
- <script src="https://unpkg.com/@zumer/snapdom@latest/snapdom.min.js"></script>
31
+ <script src="https://unpkg.com/@zumer/snapdom@latest/dist/snapdom.min.js"></script>
27
32
  ```
28
33
 
29
- ---
30
-
31
34
  ### Script tag (local)
32
35
 
33
36
  ```html
@@ -36,8 +39,6 @@ You can use **snapDOM** by including it via **CDN**, **script tag**, or by **imp
36
39
 
37
40
  The global object `snapdom` will be available.
38
41
 
39
- ---
40
-
41
42
  ### ES Module
42
43
 
43
44
  ```javascript
@@ -46,15 +47,14 @@ import { snapdom } from './snapdom.mjs';
46
47
 
47
48
  ### Script Tag (Type Module)
48
49
 
49
- ```
50
+ ```javascript
50
51
  <script type="module">
51
- import { snapdom } from 'https://unpkg.com/@zumer/snapdom@latest/snapdom.mjs';
52
+ import { snapdom } from 'https://unpkg.com/@zumer/snapdom@latest/dist/snapdom.mjs';
52
53
  </script>
53
54
  ```
54
55
 
55
56
  Now you can call `snapdom(el)`, `snapdom.toPng(el)`, etc., directly in your JavaScript.
56
57
 
57
- ---
58
58
 
59
59
  ## Basic usage
60
60
 
@@ -68,26 +68,27 @@ img.src = svgDataUrl;
68
68
  document.body.appendChild(img);
69
69
  ```
70
70
 
71
- ---
72
71
 
73
72
  ## API
74
73
 
74
+ ### Note: API may evolve until v1.0.0!
75
+
75
76
  The main API is exposed as `snapdom` and offers multiple capture methods:
76
77
 
77
- | Method | Description | Returns |
78
- |:-------|:------------|:--------|
79
- | `snapdom(el, scale?)` | Captures as SVG Data URL | `Promise<string>` |
80
- | `snapdom.toImg(el, scale?)` | Captures as `HTMLImageElement` (SVG) | `Promise<HTMLImageElement>` |
81
- | `snapdom.toCanvas(el, scale?)` | Captures as `HTMLCanvasElement` | `Promise<HTMLCanvasElement>` |
82
- | `snapdom.toPng(el, scale?)` | Captures as PNG image (`Image`) | `Promise<HTMLImageElement>` |
83
- | `snapdom.toJpg(el, scale?, quality?)` | Captures as JPG image (`Image`) | `Promise<HTMLImageElement>` |
84
- | `snapdom.toWebp(el, scale?, quality?)` | Captures as WebP image (`Image`) | `Promise<HTMLImageElement>` |
85
- | `snapdom.toBlob(el, scale?)` | Captures as SVG `Blob` | `Promise<Blob>` |
86
-
87
- **Parameters:**
88
- - `el`: DOM element to capture.
89
- - `scale`: Scale factor (default is `1`).
90
- - `quality`: Compression quality for JPG/WebP (range `0`–`1`).
78
+ | Method | Description | Returns |
79
+ |:----------------------|:---------------------------------------|:-------------------------|
80
+ | `snapdom(el, options?)` | Captures as SVG Data URL | `Promise<string>` |
81
+ | `snapdom.toImg(el, options?)` | Captures as `HTMLImageElement` (SVG) | `Promise<HTMLImageElement>` |
82
+ | `snapdom.toCanvas(el, options?)` | Captures as `HTMLCanvasElement` | `Promise<HTMLCanvasElement>` |
83
+ | `snapdom.toPng(el, options?)` | Captures as PNG image (`Image`) | `Promise<HTMLImageElement>` |
84
+ | `snapdom.toJpg(el, options?)` | Captures as JPG image (`Image`) | `Promise<HTMLImageElement>` |
85
+ | `snapdom.toWebp(el, options?)` | Captures as WebP image (`Image`) | `Promise<HTMLImageElement>` |
86
+ | `snapdom.toBlob(el, options?)` | Captures as SVG `Blob` | `Promise<Blob>` |
87
+
88
+ **Options:**
89
+ - `scale` *(number)*: Scale factor (default is `1`)
90
+ - `quality` *(number)*: Compression quality for JPG/WebP (0–1)
91
+ - `backgroundColor` *(string)*: Background fill for JPG/WebP exports
91
92
 
92
93
  ---
93
94
 
@@ -96,12 +97,13 @@ The main API is exposed as `snapdom` and offers multiple capture methods:
96
97
  - **Shadow DOM**: Captures content inside Web Components and `shadowRoot`.
97
98
  - **Pseudo-elements**: Captures `::before` and `::after`, including background images.
98
99
  - **Backgrounds and images**: Inlines external images as Data URLs.
99
- - **Fonts**: Replicates applied font families without needing external font files.
100
+ - **Fonts**: Replicates applied font families without requiring external font files.
101
+ - **Icon fonts**: Captures icon fonts like **Font Awesome** and **Material Icons**.
100
102
  - **Placeholder and Exclusion**:
101
103
  - `data-capture="exclude"`: Skips an element while preserving layout space.
102
- - `data-capture="placeholder"` + `data-placeholder-text="Text"`: Replaces an element with decorative placeholder text.
104
+ - `data-capture="placeholder"` + `data-placeholder-text="Text"`: Replaces an element with placeholder text.
103
105
 
104
- ---
106
+ **Now with improved fidelity and even faster performance.**
105
107
 
106
108
  ## Full example
107
109
 
@@ -111,20 +113,20 @@ The main API is exposed as `snapdom` and offers multiple capture methods:
111
113
  <p>This content will be captured.</p>
112
114
  </div>
113
115
 
116
+ <button id="captureBtn">Capture as img</button>
117
+
114
118
  <script type="module">
115
- import { snapdom } from './snapdom.esm.js';
119
+ import { snapdom } from './snapdom.mjs';
116
120
 
117
- const button = document.createElement('button');
118
- button.textContent = "Capture";
119
- button.onclick = async () => {
120
- const img = await snapdom.toPng(document.getElementById('captureMe'), 2);
121
+ const button = document.getElementById('captureBtn');
122
+ button.addEventListener('click', async () => {
123
+ const target = document.getElementById('captureMe');
124
+ const img = await snapdom.toImg(target);
121
125
  document.body.appendChild(img);
122
- };
123
- document.body.appendChild(button);
126
+ });
124
127
  </script>
125
128
  ```
126
129
 
127
- ---
128
130
 
129
131
  ## Limitations
130
132
 
@@ -132,32 +134,38 @@ The main API is exposed as `snapdom` and offers multiple capture methods:
132
134
  - Fonts must be fully loaded before capturing (`document.fonts.ready` is automatically awaited).
133
135
  - Iframes are not captured.
134
136
 
135
- ---
136
137
 
137
- ## Benchmark
138
+ ## Benchmarks
138
139
 
139
- `snapDOM` is not only highly accurate — it's also **extremely fast** at capturing large or complex DOM structures.
140
+ `snapDOM` is not only highly accurate — it’s **extremely fast**, especially for large elements.
140
141
 
141
- In benchmark tests against popular libraries:
142
+ Latest benchmarks show **major speed improvements** over existing libraries:
142
143
 
143
- | Element Size | Winner | Compared to `modern-screenshot` | Compared to `html2canvas` |
144
- |:------------:|:------:|:-------------------------------:|:-------------------------:|
145
- | 200×100 (Small) | `modern-screenshot` | 1.18× faster | 4.46× faster |
146
- | 400×300 (Modal) | `snapDOM` | 1.04× faster | 4.07× faster |
147
- | 1200×800 (Page view) | `snapDOM` | 2.43× faster | 5.74× faster |
148
- | 2000×1500 (Large scroll area) | `snapDOM` | 5.02× faster | 9.35× faster |
149
- | 4000×2000 (Very large) | `snapDOM` | 11.35× faster | 15.98× faster |
144
+ | Element Size | vs. `modern-screenshot` | vs. `html2canvas` |
145
+ |:------------------------:|:-----------------------:|:-----------------:|
146
+ | **Small (200×100)** | 9.84× faster | 33.19× faster |
147
+ | **Modal (400×300)** | 11.72× faster | 36.03× faster |
148
+ | **Page view (1200×800)** | 24.40× faster | 55.65× faster |
149
+ | **Large (2000×1500)** | 49.85× faster | 90.74× faster |
150
+ | **Very large (4000×2000)** | 138.93× faster | 148.41× faster |
150
151
 
151
- ✅ **Key insight**:
152
- While `modern-screenshot` is yet slightly faster for very small elements, **snapDOM dramatically outperforms all others as the DOM size grows**.
152
+ ✅ **snapDOM** also improved fidelity, even on complex layouts:
153
+ - Up to **7.82× faster** for complex modal captures.
154
+ - Still faster even at very large sizes.
153
155
 
154
- ✅ **Perfect for:**
155
- - Capturing full-page views
156
- - Capturing modal windows
157
- - Complex layouts with custom fonts, backgrounds, or shadow DOM
156
+ ### Run the benchmarks
157
+ To run these benchmarks yourself:
158
+
159
+ ```sh
160
+ git clone https://github.com/zumerlab/snapdom.git
161
+ cd snapdom
162
+ npm install
163
+ npm run test:benchmark
164
+ ```
165
+
166
+ They execute in **headless Chromium** using real DOM nodes.
158
167
 
159
- ---
160
168
 
161
169
  ## License
162
170
 
163
- MIT © Juan Martín Muda - Zumerlab
171
+ MIT © Zumerlab
package/dist/snapdom.js CHANGED
@@ -1,28 +1,17 @@
1
1
 
2
2
  /*
3
3
  * snapdom
4
- * v.0.9.0
4
+ * v.0.9.2
5
5
  * Author Juan Martin Muda
6
6
  * License MIT
7
7
  **/
8
8
  (() => {
9
9
  // src/utils/cssTools.js
10
- function getDefaultStyles() {
11
- const defaultEl = document.createElement("div");
12
- document.body.appendChild(defaultEl);
13
- const styles = window.getComputedStyle(defaultEl);
14
- const defaults = {};
15
- for (let prop of styles) {
16
- defaults[prop] = styles.getPropertyValue(prop);
17
- }
18
- document.body.removeChild(defaultEl);
19
- return defaults;
20
- }
21
- function getStyleKey(style, defaults) {
10
+ function getStyleKey(style, defaults, mode) {
22
11
  const entries = [];
23
- for (let prop of Array.from(style)) {
12
+ for (let prop of style) {
24
13
  const value = style.getPropertyValue(prop);
25
- if (value && value !== defaults[prop]) {
14
+ if (value) {
26
15
  entries.push(`${prop}:${value}`);
27
16
  }
28
17
  }
@@ -106,8 +95,6 @@
106
95
  shadowContent.forEach((child) => shadowFrag.appendChild(child));
107
96
  clone.appendChild(shadowFrag);
108
97
  }
109
- clone.querySelectorAll("style").forEach((tag) => tag.removeAttribute("style"));
110
- if (clone.tagName === "STYLE") clone.removeAttribute("style");
111
98
  return clone;
112
99
  }
113
100
 
@@ -146,11 +133,6 @@
146
133
  });
147
134
  }
148
135
 
149
- // src/utils/delay.js
150
- function delay(ms = 0) {
151
- return new Promise((resolve) => setTimeout(resolve, ms));
152
- }
153
-
154
136
  // src/modules/images.js
155
137
  async function inlineImages(clone) {
156
138
  const imgs = Array.from(clone.querySelectorAll("img"));
@@ -171,7 +153,6 @@
171
153
  for (let i = 0; i < imgs.length; i += 4) {
172
154
  const group = imgs.slice(i, i + 4).map(processImg);
173
155
  await Promise.allSettled(group);
174
- await delay(1);
175
156
  }
176
157
  }
177
158
 
@@ -199,7 +180,6 @@
199
180
  for (let i = 0; i < Math.min(sChildren.length, cChildren.length); i++) {
200
181
  queue.push([sChildren[i], cChildren[i]]);
201
182
  }
202
- await delay(1);
203
183
  }
204
184
  }
205
185
 
@@ -221,7 +201,29 @@
221
201
  "margin",
222
202
  "padding",
223
203
  "border",
204
+ "border-width",
205
+ "border-style",
206
+ "border-color",
224
207
  "border-radius",
208
+ "box-sizing",
209
+ "display",
210
+ "overflow",
211
+ "overflow-x",
212
+ "overflow-y",
213
+ "flex",
214
+ "flex-grow",
215
+ "flex-shrink",
216
+ "flex-basis",
217
+ "flex-direction",
218
+ "justify-content",
219
+ "align-items",
220
+ "align-content",
221
+ "gap",
222
+ "row-gap",
223
+ "column-gap",
224
+ "grid",
225
+ "grid-template-columns",
226
+ "grid-template-rows",
225
227
  "background",
226
228
  "background-color",
227
229
  "background-image",
@@ -230,29 +232,44 @@
230
232
  "font-family",
231
233
  "font-size",
232
234
  "font-weight",
235
+ "font-style",
236
+ "font-stretch",
233
237
  "text-align",
238
+ "vertical-align",
234
239
  "text-decoration",
235
240
  "line-height",
236
241
  "letter-spacing",
237
- "display",
242
+ "white-space",
243
+ "word-break",
244
+ "text-overflow",
238
245
  "visibility",
239
246
  "opacity",
240
- "overflow",
241
247
  "box-shadow",
242
248
  "transform",
243
249
  "transition",
244
- "flex",
245
- "grid"
250
+ "clip-path",
251
+ "-webkit-text-fill-color"
246
252
  ];
247
253
  for (const pseudo of ["::before", "::after"]) {
248
254
  try {
249
255
  const style = window.getComputedStyle(source, pseudo);
250
256
  const content = style.getPropertyValue("content");
251
- if (content && content !== "none" && content !== '""' && content !== "''") {
257
+ const bg = style.getPropertyValue("background-image");
258
+ if (content && content !== "none" && content !== '""' && content !== "''" || bg && bg.startsWith("url(")) {
259
+ const fontFamily = style.getPropertyValue("font-family");
252
260
  const pseudoEl = document.createElement("span");
253
261
  pseudoEl.className = `snapdom-pseudo-${pseudo.replace("::", "")}`;
254
- let cleanContent = content.replace(/^\"|\"$/g, "");
255
- if (cleanContent.startsWith("url(")) {
262
+ let cleanContent = parseContent(content);
263
+ const isIconFont = fontFamily && /font.*awesome|material|bootstrap|glyphicons|ionicons|remixicon|simple-line-icons|octicons|feather|typicons|weathericons/i.test(fontFamily);
264
+ const fontSize = parseInt(style.getPropertyValue("font-size")) || 32;
265
+ const fontWeight = parseInt(style.getPropertyValue("font-weight")) || false;
266
+ const color = style.getPropertyValue("color") || "#000";
267
+ if (isIconFont && cleanContent.length === 1) {
268
+ const imgEl = document.createElement("img");
269
+ imgEl.src = await iconToImage(cleanContent, fontFamily, fontWeight, fontSize, color);
270
+ imgEl.style = "display:block;width:100%;height:100%;object-fit:contain;";
271
+ pseudoEl.appendChild(imgEl);
272
+ } else if (cleanContent.startsWith("url(")) {
256
273
  const match = cleanContent.match(/url\(["']?([^"')]+)["']?\)/);
257
274
  if (match?.[1]) {
258
275
  try {
@@ -262,6 +279,7 @@
262
279
  imgEl.style = "display:block;width:100%;height:100%;object-fit:contain;";
263
280
  pseudoEl.appendChild(imgEl);
264
281
  } catch (e) {
282
+ console.error(`[snapdom] Error in pseudo ${pseudo} for`, source, e);
265
283
  }
266
284
  }
267
285
  } else {
@@ -289,9 +307,34 @@
289
307
  );
290
308
  for (let i = 0; i < Math.min(sChildren.length, cChildren.length); i++) {
291
309
  await inlinePseudoElements(sChildren[i], cChildren[i]);
292
- if (i % 5 === 0) await delay(1);
293
310
  }
294
311
  }
312
+ function parseContent(content) {
313
+ let clean = content.replace(/^['"]|['"]$/g, "");
314
+ if (clean.startsWith("\\")) {
315
+ try {
316
+ return String.fromCharCode(parseInt(clean.replace("\\", ""), 16));
317
+ } catch {
318
+ return clean;
319
+ }
320
+ }
321
+ return clean;
322
+ }
323
+ async function iconToImage(unicodeChar, fontFamily, fontWeight, fontSize = 32, color = "#000") {
324
+ fontFamily = fontFamily.replace(/^['"]+|['"]+$/g, "");
325
+ await document.fonts.ready;
326
+ await document.fonts.load(`${fontSize}px "${fontFamily}"`);
327
+ const canvas = document.createElement("canvas");
328
+ canvas.width = fontSize;
329
+ canvas.height = fontSize;
330
+ const ctx = canvas.getContext("2d");
331
+ ctx.font = fontWeight ? `${fontWeight} ${fontSize}px "${fontFamily}"` : ` ${fontSize}px "${fontFamily}"`;
332
+ ctx.textAlign = "center";
333
+ ctx.textBaseline = "middle";
334
+ ctx.fillStyle = color;
335
+ ctx.fillText(unicodeChar, canvas.width / 2, canvas.height / 2);
336
+ return canvas.toDataURL();
337
+ }
295
338
 
296
339
  // src/utils/normalize.js
297
340
  function normalizeFontName(str) {
@@ -381,7 +424,7 @@
381
424
  const nodeMap = /* @__PURE__ */ new WeakMap();
382
425
  let defaults;
383
426
  try {
384
- defaults = getDefaultStyles();
427
+ defaults = {};
385
428
  } catch (e) {
386
429
  console.warn("getDefaultStyles failed:", e);
387
430
  defaults = {};
@@ -434,7 +477,8 @@
434
477
  }
435
478
 
436
479
  // src/core/capture.js
437
- async function capture(element, scale = 1) {
480
+ async function capture(element, options = {}) {
481
+ const { scale = 1 } = options;
438
482
  let clone, classCSS, styleCache;
439
483
  try {
440
484
  ({ clone, classCSS, styleCache } = await prepareClone(element));
@@ -442,8 +486,9 @@
442
486
  console.error("prepareClone failed:", e);
443
487
  throw e;
444
488
  }
445
- const w = element.offsetWidth * scale;
446
- const h = element.offsetHeight * scale;
489
+ const rect = element.getBoundingClientRect();
490
+ const w = rect.width * scale;
491
+ const h = rect.height * scale;
447
492
  let fonts = "";
448
493
  try {
449
494
  fonts = await inlineFonts(styleCache);
@@ -476,49 +521,64 @@
476
521
  }
477
522
 
478
523
  // src/api/snapdom.js
479
- async function snapdom(el, scale = 1) {
480
- return await capture(el, scale);
524
+ async function snapdom(element, options = {}) {
525
+ return await capture(element, options);
481
526
  }
482
- snapdom.toImg = async function(el, scale = 1) {
483
- const dataURL = await snapdom(el, scale);
527
+ snapdom.toImg = async (el, opt = {}) => {
528
+ const url = await snapdom(el, opt);
484
529
  const img = new Image();
485
- img.src = dataURL;
530
+ img.src = url;
486
531
  await img.decode();
487
532
  return img;
488
533
  };
489
- snapdom.toCanvas = async function(el, scale = 1) {
490
- const img = await snapdom.toImg(el, scale);
534
+ snapdom.toCanvas = async (el, opt = {}) => {
535
+ const img = await snapdom.toImg(el, opt);
491
536
  const canvas = document.createElement("canvas");
492
537
  canvas.width = img.width;
493
538
  canvas.height = img.height;
494
- const ctx = canvas.getContext("2d");
495
- ctx.drawImage(img, 0, 0);
539
+ canvas.getContext("2d").drawImage(img, 0, 0);
496
540
  return canvas;
497
541
  };
498
- snapdom.toPng = async function(el, scale = 1) {
499
- const canvas = await snapdom.toCanvas(el, scale);
542
+ snapdom.toPng = async (el, opt = {}) => {
543
+ const canvas = await snapdom.toCanvas(el, opt);
500
544
  const img = new Image();
501
545
  img.src = canvas.toDataURL("image/png");
502
546
  await img.decode();
503
547
  return img;
504
548
  };
505
- snapdom.toJpg = async function(el, scale = 1, quality = 0.92) {
506
- const canvas = await snapdom.toCanvas(el, scale);
549
+ snapdom.toJpg = async (el, opt = {}) => {
550
+ const { backgroundColor = "#fff", quality = 0.92 } = opt;
551
+ const canvas = await snapdom.toCanvas(el, opt);
552
+ const temp = document.createElement("canvas");
553
+ temp.width = canvas.width;
554
+ temp.height = canvas.height;
555
+ const ctx = temp.getContext("2d");
556
+ ctx.fillStyle = backgroundColor;
557
+ ctx.fillRect(0, 0, temp.width, temp.height);
558
+ ctx.drawImage(canvas, 0, 0);
507
559
  const img = new Image();
508
- img.src = canvas.toDataURL("image/jpeg", quality);
560
+ img.src = temp.toDataURL("image/jpeg", quality);
509
561
  await img.decode();
510
562
  return img;
511
563
  };
512
- snapdom.toWebp = async function(el, scale = 1, quality = 0.92) {
513
- const canvas = await snapdom.toCanvas(el, scale);
564
+ snapdom.toWebp = async (el, opt = {}) => {
565
+ const { backgroundColor = "#fff", quality = 0.92 } = opt;
566
+ const canvas = await snapdom.toCanvas(el, opt);
567
+ const temp = document.createElement("canvas");
568
+ temp.width = canvas.width;
569
+ temp.height = canvas.height;
570
+ const ctx = temp.getContext("2d");
571
+ ctx.fillStyle = backgroundColor;
572
+ ctx.fillRect(0, 0, temp.width, temp.height);
573
+ ctx.drawImage(canvas, 0, 0);
514
574
  const img = new Image();
515
- img.src = canvas.toDataURL("image/webp", quality);
575
+ img.src = temp.toDataURL("image/webp", quality);
516
576
  await img.decode();
517
577
  return img;
518
578
  };
519
- snapdom.toBlob = async function(el, scale = 1) {
520
- const dataURL = await snapdom(el, scale);
521
- const svgXml = decodeURIComponent(dataURL.split(",")[1]);
522
- return new Blob([svgXml], { type: "image/svg+xml" });
579
+ snapdom.toBlob = async (el, opt = {}) => {
580
+ const dataUrl = await snapdom(el, opt);
581
+ const svgText = decodeURIComponent(dataUrl.split(",")[1]);
582
+ return new Blob([svgText], { type: "image/svg+xml" });
523
583
  };
524
584
  })();
@@ -1 +1 @@
1
- (()=>{function b(){let t=document.createElement("div");document.body.appendChild(t);let a=window.getComputedStyle(t),r={};for(let e of a)r[e]=a.getPropertyValue(e);return document.body.removeChild(t),r}function S(t,a){let r=[];for(let e of Array.from(t)){let i=t.getPropertyValue(e);i&&i!==a[e]&&r.push(`${e}:${i}`)}return r.sort().join(";")}function E(t){let a=new Set(t.values()),r=new Map,e=1;for(let i of a)r.set(i,`c${e++}`);return r}function w(t,a,r,e,i){if(t.tagName==="STYLE")return;i.has(t)||i.set(t,window.getComputedStyle(t));let o=i.get(t),c=S(o,e);r.set(a,c)}function y(t,a,r,e,i){if(t.nodeType===Node.ELEMENT_NODE&&t.getAttribute("data-capture")==="exclude"){let s=document.createElement("div"),n=t.getBoundingClientRect();return s.style.cssText=`display: inline-block; width: ${n.width}px; height: ${n.height}px; visibility: hidden;`,s}if(t.tagName==="IFRAME"){let s=document.createElement("div");return s.textContent="",s.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;`,s}if(t.nodeType===Node.ELEMENT_NODE&&t.getAttribute("data-capture")==="placeholder"){let s=t.cloneNode(!1);i.set(s,t),w(t,s,a,r,e);let n=document.createElement("div");return n.textContent=t.getAttribute("data-placeholder-text")||"",n.style.cssText="color: #666;font-size: 12px;text-align: center;line-height: 1.4;padding: 0.5em;box-sizing: border-box;",s.appendChild(n),s}if(t.tagName==="CANVAS"){let s=t.toDataURL(),n=document.createElement("img");return n.src=s,n.width=t.width,n.height=t.height,n}if(t.nodeType===Node.TEXT_NODE){if(!t.textContent.trim())return null;if(t.parentElement?.shadowRoot){let n=t.parentElement.tagName.toLowerCase();if(customElements.get(n))return null}return t.cloneNode(!0)}if(t.nodeType!==Node.ELEMENT_NODE)return t.cloneNode(!0);let o=t.cloneNode(!1);i.set(o,t),w(t,o,a,r,e);let c=document.createDocumentFragment();if(t.childNodes.forEach(s=>{let n=y(s,a,r,e,i);n&&c.appendChild(n)}),o.appendChild(c),t.shadowRoot){let s=Array.from(t.shadowRoot.children).filter(d=>d.tagName!=="STYLE").map(d=>y(d,a,r,e,i)).filter(Boolean),n=document.createDocumentFragment();s.forEach(d=>n.appendChild(d)),o.appendChild(n)}return o.querySelectorAll("style").forEach(s=>s.removeAttribute("style")),o.tagName==="STYLE"&&o.removeAttribute("style"),o}function h(t,a=3e3){return new Promise((r,e)=>{let i=setTimeout(()=>{e(new Error("Image load timed out"))},a),o=new Image;o.crossOrigin="anonymous",o.onload=async()=>{clearTimeout(i);try{await o.decode();let c=document.createElement("canvas");c.width=o.width,c.height=o.height,c.getContext("2d").drawImage(o,0,0);try{let n=c.toDataURL("image/png");r(n)}catch{e(new Error("CORS restrictions prevented image capture"))}}catch(c){e(c)}},o.onerror=c=>{clearTimeout(i),e(new Error("Failed to load image: "+(c.message||"Unknown error")))},o.src=t})}function u(t=0){return new Promise(a=>setTimeout(a,t))}async function v(t){let a=Array.from(t.querySelectorAll("img")),r=async e=>{let i=e.src;try{let o=await h(i);e.src=o,e.width||(e.width=e.naturalWidth||100),e.height||(e.height=e.naturalHeight||100)}catch{let o=document.createElement("div");o.style=`width: ${e.width||100}px; height: ${e.height||100}px; background: #ccc; display: inline-block; text-align: center; line-height: ${e.height||100}px; color: #666; font-size: 12px;`,o.innerText="img",e.replaceWith(o)}};for(let e=0;e<a.length;e+=4){let i=a.slice(e,e+4).map(r);await Promise.allSettled(i),await u(1)}}async function A(t,a,r){let e=[[t,a]];for(;e.length;){let[i,o]=e.shift(),c=r.get(i)||window.getComputedStyle(i);r.has(i)||r.set(i,c);let s=c.getPropertyValue("background-image");if(s&&s.includes("url(")){let l=s.match(/url\(["']?([^"')]+)["']?\)/);if(l?.[1])try{let m=await h(l[1]);o.style.backgroundImage=`url(${m})`}catch{o.style.backgroundImage="none"}}let n=Array.from(i.children),d=Array.from(o.children);for(let l=0;l<Math.min(n.length,d.length);l++)e.push([n[l],d[l]]);await u(1)}}async function x(t,a){let r=["position","top","right","bottom","left","z-index","width","height","min-width","max-width","min-height","max-height","margin","padding","border","border-radius","background","background-color","background-image","color","font","font-family","font-size","font-weight","text-align","text-decoration","line-height","letter-spacing","display","visibility","opacity","overflow","box-shadow","transform","transition","flex","grid"];for(let o of["::before","::after"])try{let c=window.getComputedStyle(t,o),s=c.getPropertyValue("content");if(s&&s!=="none"&&s!=='""'&&s!=="''"){let n=document.createElement("span");n.className=`snapdom-pseudo-${o.replace("::","")}`;let d=s.replace(/^\"|\"$/g,"");if(d.startsWith("url(")){let l=d.match(/url\(["']?([^"')]+)["']?\)/);if(l?.[1])try{let m=document.createElement("img"),f=await h(l[1]);m.src=f,m.style="display:block;width:100%;height:100%;object-fit:contain;",n.appendChild(m)}catch{}}else n.textContent=d;for(let l of r){let m=c.getPropertyValue(l);m&&(n.style[l]=m)}n.style.position||(n.style.position="absolute"),o==="::before"?a.insertBefore(n,a.firstChild):a.appendChild(n)}}catch{}let e=Array.from(t.children),i=Array.from(a.children).filter(o=>!o.classList.contains("snapdom-pseudo-before")&&!o.classList.contains("snapdom-pseudo-after"));for(let o=0;o<Math.min(e.length,i.length);o++)await x(e[o],i[o]),o%5===0&&await u(1)}function C(t){return t.toLowerCase().replace(/\s+/g,"-").replace(/[^a-z0-9-]/g,"").replace(/-+/g,"-").replace(/^-|-$/g,"")}function g(t){return{normal:"400",bold:"700",italic:"italic",oblique:"italic","ultra-condensed":"50","extra-condensed":"62",condensed:"75","semi-condensed":"87",expanded:"125","semi-expanded":"112","extra-expanded":"150","ultra-expanded":"200"}[t]||t.replace(/%/g,"")}async function $(t=new WeakMap){await document.fonts.ready;let a=[];Array.from(document.styleSheets).forEach(o=>{try{Array.from(o.cssRules).forEach(c=>{c instanceof CSSFontFaceRule&&a.push(c.cssText)})}catch(c){console.warn("Could not access stylesheet rules:",c)}});let r=new Map;document.querySelectorAll("*").forEach(o=>{let c=t.get(o)||window.getComputedStyle(o);t.has(o)||t.set(o,c);let s=c.fontFamily?.split(",")[0]?.trim().replace(/["']/g,"");if(!s)return;let n=g(c.fontWeight||"400"),d=g(c.fontStyle||"normal"),l=g(c.fontStretch||"normal");r.has(s)||r.set(s,new Set),r.get(s).add(`${n}_${d}_${l}`)});let i=[...a];for(let[o,c]of r){let s=C(o);for(let n of c){let[d,l,m]=n.split("_"),f=`fg-f-${s}-w${d}-${l}-s${m}`,R=[`font-family:"${o}",sans-serif`,`font-weight:${d}`,`font-style:${l}`,`font-stretch:${m}%`].filter(Boolean).join(";");i.push(`.${f}{${R}}`)}}return i.push("*{font-family:inherit;font-weight:inherit;font-style:inherit;}"),i.join("")}function N(t,a,r){let e=r.get(a)||window.getComputedStyle(a),i=e.fontFamily?.split(",")[0]?.trim().replace(/["']/g,"");if(!i)return;let o=g(e.fontWeight||"400"),c=g(e.fontStyle||"normal"),s=g(e.fontStretch||"normal"),n=C(i);t.classList.add(`fg-f-${n}-w${o}-${c}-s${s}`)}async function I(t){let a=new Map,r=new WeakMap,e=new WeakMap,i;try{i=b()}catch(n){console.warn("getDefaultStyles failed:",n),i={}}let o;try{o=y(t,a,i,r,e)}catch(n){throw console.warn("deepCloneWithShadow failed:",n),n}try{await v(o)}catch(n){console.warn("inlineImages failed:",n)}try{await A(t,o,r)}catch(n){console.warn("inlineBackgroundImages failed:",n)}try{await x(t,o)}catch(n){console.warn("inlinePseudoElements failed:",n)}let c=E(a),s=Array.from(c.entries()).map(([n,d])=>`.${d}{${n}}`).join("");for(let[n,d]of a.entries()){if(n.tagName==="STYLE")continue;let l=c.get(d);if(l){n.classList.add(l);try{N(n,e.get(n),r)}catch(f){console.warn("applyFontClasses failed for node:",n,f)}}let m=n.style?.backgroundImage;n.removeAttribute("style"),m&&m!=="none"&&(n.style.backgroundImage=m)}return{clone:o,classCSS:s,styleCache:r}}function k(t){return t.replace(/>\s+</g,"><").replace(/\s{2,}/g," ").replace(/\n/g,"").trim()}async function L(t,a=1){let r,e,i;try{({clone:r,classCSS:e,styleCache:i}=await I(t))}catch(f){throw console.error("prepareClone failed:",f),f}let o=t.offsetWidth*a,c=t.offsetHeight*a,s="";try{s=await $(i)}catch(f){console.warn("inlineFonts failed:",f)}let n=document.createElementNS("http://www.w3.org/2000/svg","svg");n.setAttribute("width",o),n.setAttribute("height",c),n.setAttribute("viewBox",`0 0 ${o} ${c}`);let d=document.createElementNS("http://www.w3.org/2000/svg","foreignObject");d.setAttribute("width","100%"),d.setAttribute("height","100%");let l=document.createElement("div");l.setAttribute("xmlns","http://www.w3.org/1999/xhtml"),l.setAttribute("style",`width:${o}px;height:${c}px;`);let m=document.createElement("style");m.textContent=s+"svg{overflow:visible}"+e,l.appendChild(m),l.appendChild(r),d.appendChild(l),n.appendChild(d);try{let f=new XMLSerializer().serializeToString(n);return`data:image/svg+xml;charset=utf-8,${encodeURIComponent(k(f))}`}catch(f){throw console.error("SVG serialization failed:",f),f}}async function p(t,a=1){return await L(t,a)}p.toImg=async function(t,a=1){let r=await p(t,a),e=new Image;return e.src=r,await e.decode(),e};p.toCanvas=async function(t,a=1){let r=await p.toImg(t,a),e=document.createElement("canvas");return e.width=r.width,e.height=r.height,e.getContext("2d").drawImage(r,0,0),e};p.toPng=async function(t,a=1){let r=await p.toCanvas(t,a),e=new Image;return e.src=r.toDataURL("image/png"),await e.decode(),e};p.toJpg=async function(t,a=1,r=.92){let e=await p.toCanvas(t,a),i=new Image;return i.src=e.toDataURL("image/jpeg",r),await i.decode(),i};p.toWebp=async function(t,a=1,r=.92){let e=await p.toCanvas(t,a),i=new Image;return i.src=e.toDataURL("image/webp",r),await i.decode(),i};p.toBlob=async function(t,a=1){let r=await p(t,a),e=decodeURIComponent(r.split(",")[1]);return new Blob([e],{type:"image/svg+xml"})};})();
1
+ (()=>{function E(t,n,s){let o=[];for(let i of t){let e=t.getPropertyValue(i);e&&o.push(`${i}:${e}`)}return o.sort().join(";")}function S(t){let n=new Set(t.values()),s=new Map,o=1;for(let i of n)s.set(i,`c${o++}`);return s}function v(t,n,s,o,i){if(t.tagName==="STYLE")return;i.has(t)||i.set(t,window.getComputedStyle(t));let e=i.get(t),r=E(e,o);s.set(n,r)}function b(t,n,s,o,i){if(t.nodeType===Node.ELEMENT_NODE&&t.getAttribute("data-capture")==="exclude"){let l=document.createElement("div"),a=t.getBoundingClientRect();return l.style.cssText=`display: inline-block; width: ${a.width}px; height: ${a.height}px; visibility: hidden;`,l}if(t.tagName==="IFRAME"){let l=document.createElement("div");return l.textContent="",l.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;`,l}if(t.nodeType===Node.ELEMENT_NODE&&t.getAttribute("data-capture")==="placeholder"){let l=t.cloneNode(!1);i.set(l,t),v(t,l,n,s,o);let a=document.createElement("div");return a.textContent=t.getAttribute("data-placeholder-text")||"",a.style.cssText="color: #666;font-size: 12px;text-align: center;line-height: 1.4;padding: 0.5em;box-sizing: border-box;",l.appendChild(a),l}if(t.tagName==="CANVAS"){let l=t.toDataURL(),a=document.createElement("img");return a.src=l,a.width=t.width,a.height=t.height,a}if(t.nodeType===Node.TEXT_NODE){if(!t.textContent.trim())return null;if(t.parentElement?.shadowRoot){let a=t.parentElement.tagName.toLowerCase();if(customElements.get(a))return null}return t.cloneNode(!0)}if(t.nodeType!==Node.ELEMENT_NODE)return t.cloneNode(!0);let e=t.cloneNode(!1);i.set(e,t),v(t,e,n,s,o);let r=document.createDocumentFragment();if(t.childNodes.forEach(l=>{let a=b(l,n,s,o,i);a&&r.appendChild(a)}),e.appendChild(r),t.shadowRoot){let l=Array.from(t.shadowRoot.children).filter(d=>d.tagName!=="STYLE").map(d=>b(d,n,s,o,i)).filter(Boolean),a=document.createDocumentFragment();l.forEach(d=>a.appendChild(d)),e.appendChild(a)}return e}function x(t,n=3e3){return new Promise((s,o)=>{let i=setTimeout(()=>{o(new Error("Image load timed out"))},n),e=new Image;e.crossOrigin="anonymous",e.onload=async()=>{clearTimeout(i);try{await e.decode();let r=document.createElement("canvas");r.width=e.width,r.height=e.height,r.getContext("2d").drawImage(e,0,0);try{let a=r.toDataURL("image/png");s(a)}catch{o(new Error("CORS restrictions prevented image capture"))}}catch(r){o(r)}},e.onerror=r=>{clearTimeout(i),o(new Error("Failed to load image: "+(r.message||"Unknown error")))},e.src=t})}async function $(t){let n=Array.from(t.querySelectorAll("img")),s=async o=>{let i=o.src;try{let e=await x(i);o.src=e,o.width||(o.width=o.naturalWidth||100),o.height||(o.height=o.naturalHeight||100)}catch{let e=document.createElement("div");e.style=`width: ${o.width||100}px; height: ${o.height||100}px; background: #ccc; display: inline-block; text-align: center; line-height: ${o.height||100}px; color: #666; font-size: 12px;`,e.innerText="img",o.replaceWith(e)}};for(let o=0;o<n.length;o+=4){let i=n.slice(o,o+4).map(s);await Promise.allSettled(i)}}async function I(t,n,s){let o=[[t,n]];for(;o.length;){let[i,e]=o.shift(),r=s.get(i)||window.getComputedStyle(i);s.has(i)||s.set(i,r);let l=r.getPropertyValue("background-image");if(l&&l.includes("url(")){let c=l.match(/url\(["']?([^"')]+)["']?\)/);if(c?.[1])try{let m=await x(c[1]);e.style.backgroundImage=`url(${m})`}catch{e.style.backgroundImage="none"}}let a=Array.from(i.children),d=Array.from(e.children);for(let c=0;c<Math.min(a.length,d.length);c++)o.push([a[c],d[c]])}}async function C(t,n){let s=["position","top","right","bottom","left","z-index","width","height","min-width","max-width","min-height","max-height","margin","padding","border","border-width","border-style","border-color","border-radius","box-sizing","display","overflow","overflow-x","overflow-y","flex","flex-grow","flex-shrink","flex-basis","flex-direction","justify-content","align-items","align-content","gap","row-gap","column-gap","grid","grid-template-columns","grid-template-rows","background","background-color","background-image","color","font","font-family","font-size","font-weight","font-style","font-stretch","text-align","vertical-align","text-decoration","line-height","letter-spacing","white-space","word-break","text-overflow","visibility","opacity","box-shadow","transform","transition","clip-path","-webkit-text-fill-color"];for(let e of["::before","::after"])try{let r=window.getComputedStyle(t,e),l=r.getPropertyValue("content"),a=r.getPropertyValue("background-image");if(l&&l!=="none"&&l!=='""'&&l!=="''"||a&&a.startsWith("url(")){let d=r.getPropertyValue("font-family"),c=document.createElement("span");c.className=`snapdom-pseudo-${e.replace("::","")}`;let m=P(l),g=d&&/font.*awesome|material|bootstrap|glyphicons|ionicons|remixicon|simple-line-icons|octicons|feather|typicons|weathericons/i.test(d),y=parseInt(r.getPropertyValue("font-size"))||32,u=parseInt(r.getPropertyValue("font-weight"))||!1,L=r.getPropertyValue("color")||"#000";if(g&&m.length===1){let f=document.createElement("img");f.src=await U(m,d,u,y,L),f.style="display:block;width:100%;height:100%;object-fit:contain;",c.appendChild(f)}else if(m.startsWith("url(")){let f=m.match(/url\(["']?([^"')]+)["']?\)/);if(f?.[1])try{let h=document.createElement("img"),D=await x(f[1]);h.src=D,h.style="display:block;width:100%;height:100%;object-fit:contain;",c.appendChild(h)}catch(h){console.error(`[snapdom] Error in pseudo ${e} for`,t,h)}}else c.textContent=m;for(let f of s){let h=r.getPropertyValue(f);h&&(c.style[f]=h)}c.style.position||(c.style.position="absolute"),e==="::before"?n.insertBefore(c,n.firstChild):n.appendChild(c)}}catch{}let o=Array.from(t.children),i=Array.from(n.children).filter(e=>!e.classList.contains("snapdom-pseudo-before")&&!e.classList.contains("snapdom-pseudo-after"));for(let e=0;e<Math.min(o.length,i.length);e++)await C(o[e],i[e])}function P(t){let n=t.replace(/^['"]|['"]$/g,"");if(n.startsWith("\\"))try{return String.fromCharCode(parseInt(n.replace("\\",""),16))}catch{return n}return n}async function U(t,n,s,o=32,i="#000"){n=n.replace(/^['"]+|['"]+$/g,""),await document.fonts.ready,await document.fonts.load(`${o}px "${n}"`);let e=document.createElement("canvas");e.width=o,e.height=o;let r=e.getContext("2d");return r.font=s?`${s} ${o}px "${n}"`:` ${o}px "${n}"`,r.textAlign="center",r.textBaseline="middle",r.fillStyle=i,r.fillText(t,e.width/2,e.height/2),e.toDataURL()}function k(t){return t.toLowerCase().replace(/\s+/g,"-").replace(/[^a-z0-9-]/g,"").replace(/-+/g,"-").replace(/^-|-$/g,"")}function w(t){return{normal:"400",bold:"700",italic:"italic",oblique:"italic","ultra-condensed":"50","extra-condensed":"62",condensed:"75","semi-condensed":"87",expanded:"125","semi-expanded":"112","extra-expanded":"150","ultra-expanded":"200"}[t]||t.replace(/%/g,"")}async function A(t=new WeakMap){await document.fonts.ready;let n=[];Array.from(document.styleSheets).forEach(e=>{try{Array.from(e.cssRules).forEach(r=>{r instanceof CSSFontFaceRule&&n.push(r.cssText)})}catch(r){console.warn("Could not access stylesheet rules:",r)}});let s=new Map;document.querySelectorAll("*").forEach(e=>{let r=t.get(e)||window.getComputedStyle(e);t.has(e)||t.set(e,r);let l=r.fontFamily?.split(",")[0]?.trim().replace(/["']/g,"");if(!l)return;let a=w(r.fontWeight||"400"),d=w(r.fontStyle||"normal"),c=w(r.fontStretch||"normal");s.has(l)||s.set(l,new Set),s.get(l).add(`${a}_${d}_${c}`)});let i=[...n];for(let[e,r]of s){let l=k(e);for(let a of r){let[d,c,m]=a.split("_"),g=`fg-f-${l}-w${d}-${c}-s${m}`,y=[`font-family:"${e}",sans-serif`,`font-weight:${d}`,`font-style:${c}`,`font-stretch:${m}%`].filter(Boolean).join(";");i.push(`.${g}{${y}}`)}}return i.push("*{font-family:inherit;font-weight:inherit;font-style:inherit;}"),i.join("")}function N(t,n,s){let o=s.get(n)||window.getComputedStyle(n),i=o.fontFamily?.split(",")[0]?.trim().replace(/["']/g,"");if(!i)return;let e=w(o.fontWeight||"400"),r=w(o.fontStyle||"normal"),l=w(o.fontStretch||"normal"),a=k(i);t.classList.add(`fg-f-${a}-w${e}-${r}-s${l}`)}async function z(t){let n=new Map,s=new WeakMap,o=new WeakMap,i;try{i={}}catch(a){console.warn("getDefaultStyles failed:",a),i={}}let e;try{e=b(t,n,i,s,o)}catch(a){throw console.warn("deepCloneWithShadow failed:",a),a}try{await $(e)}catch(a){console.warn("inlineImages failed:",a)}try{await I(t,e,s)}catch(a){console.warn("inlineBackgroundImages failed:",a)}try{await C(t,e)}catch(a){console.warn("inlinePseudoElements failed:",a)}let r=S(n),l=Array.from(r.entries()).map(([a,d])=>`.${d}{${a}}`).join("");for(let[a,d]of n.entries()){if(a.tagName==="STYLE")continue;let c=r.get(d);if(c){a.classList.add(c);try{N(a,o.get(a),s)}catch(g){console.warn("applyFontClasses failed for node:",a,g)}}let m=a.style?.backgroundImage;a.removeAttribute("style"),m&&m!=="none"&&(a.style.backgroundImage=m)}return{clone:e,classCSS:l,styleCache:s}}function R(t){return t.replace(/>\s+</g,"><").replace(/\s{2,}/g," ").replace(/\n/g,"").trim()}async function T(t,n={}){let{scale:s=1}=n,o,i,e;try{({clone:o,classCSS:i,styleCache:e}=await z(t))}catch(u){throw console.error("prepareClone failed:",u),u}let r=t.getBoundingClientRect(),l=r.width*s,a=r.height*s,d="";try{d=await A(e)}catch(u){console.warn("inlineFonts failed:",u)}let c=document.createElementNS("http://www.w3.org/2000/svg","svg");c.setAttribute("width",l),c.setAttribute("height",a),c.setAttribute("viewBox",`0 0 ${l} ${a}`);let m=document.createElementNS("http://www.w3.org/2000/svg","foreignObject");m.setAttribute("width","100%"),m.setAttribute("height","100%");let g=document.createElement("div");g.setAttribute("xmlns","http://www.w3.org/1999/xhtml"),g.setAttribute("style",`width:${l}px;height:${a}px;`);let y=document.createElement("style");y.textContent=d+"svg{overflow:visible}"+i,g.appendChild(y),g.appendChild(o),m.appendChild(g),c.appendChild(m);try{let u=new XMLSerializer().serializeToString(c);return`data:image/svg+xml;charset=utf-8,${encodeURIComponent(R(u))}`}catch(u){throw console.error("SVG serialization failed:",u),u}}async function p(t,n={}){return await T(t,n)}p.toImg=async(t,n={})=>{let s=await p(t,n),o=new Image;return o.src=s,await o.decode(),o};p.toCanvas=async(t,n={})=>{let s=await p.toImg(t,n),o=document.createElement("canvas");return o.width=s.width,o.height=s.height,o.getContext("2d").drawImage(s,0,0),o};p.toPng=async(t,n={})=>{let s=await p.toCanvas(t,n),o=new Image;return o.src=s.toDataURL("image/png"),await o.decode(),o};p.toJpg=async(t,n={})=>{let{backgroundColor:s="#fff",quality:o=.92}=n,i=await p.toCanvas(t,n),e=document.createElement("canvas");e.width=i.width,e.height=i.height;let r=e.getContext("2d");r.fillStyle=s,r.fillRect(0,0,e.width,e.height),r.drawImage(i,0,0);let l=new Image;return l.src=e.toDataURL("image/jpeg",o),await l.decode(),l};p.toWebp=async(t,n={})=>{let{backgroundColor:s="#fff",quality:o=.92}=n,i=await p.toCanvas(t,n),e=document.createElement("canvas");e.width=i.width,e.height=i.height;let r=e.getContext("2d");r.fillStyle=s,r.fillRect(0,0,e.width,e.height),r.drawImage(i,0,0);let l=new Image;return l.src=e.toDataURL("image/webp",o),await l.decode(),l};p.toBlob=async(t,n={})=>{let s=await p(t,n),o=decodeURIComponent(s.split(",")[1]);return new Blob([o],{type:"image/svg+xml"})};})();
@@ -1 +1 @@
1
- function b(){let t=document.createElement("div");document.body.appendChild(t);let a=window.getComputedStyle(t),r={};for(let e of a)r[e]=a.getPropertyValue(e);return document.body.removeChild(t),r}function S(t,a){let r=[];for(let e of Array.from(t)){let i=t.getPropertyValue(e);i&&i!==a[e]&&r.push(`${e}:${i}`)}return r.sort().join(";")}function E(t){let a=new Set(t.values()),r=new Map,e=1;for(let i of a)r.set(i,`c${e++}`);return r}function w(t,a,r,e,i){if(t.tagName==="STYLE")return;i.has(t)||i.set(t,window.getComputedStyle(t));let o=i.get(t),c=S(o,e);r.set(a,c)}function y(t,a,r,e,i){if(t.nodeType===Node.ELEMENT_NODE&&t.getAttribute("data-capture")==="exclude"){let s=document.createElement("div"),n=t.getBoundingClientRect();return s.style.cssText=`display: inline-block; width: ${n.width}px; height: ${n.height}px; visibility: hidden;`,s}if(t.tagName==="IFRAME"){let s=document.createElement("div");return s.textContent="",s.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;`,s}if(t.nodeType===Node.ELEMENT_NODE&&t.getAttribute("data-capture")==="placeholder"){let s=t.cloneNode(!1);i.set(s,t),w(t,s,a,r,e);let n=document.createElement("div");return n.textContent=t.getAttribute("data-placeholder-text")||"",n.style.cssText="color: #666;font-size: 12px;text-align: center;line-height: 1.4;padding: 0.5em;box-sizing: border-box;",s.appendChild(n),s}if(t.tagName==="CANVAS"){let s=t.toDataURL(),n=document.createElement("img");return n.src=s,n.width=t.width,n.height=t.height,n}if(t.nodeType===Node.TEXT_NODE){if(!t.textContent.trim())return null;if(t.parentElement?.shadowRoot){let n=t.parentElement.tagName.toLowerCase();if(customElements.get(n))return null}return t.cloneNode(!0)}if(t.nodeType!==Node.ELEMENT_NODE)return t.cloneNode(!0);let o=t.cloneNode(!1);i.set(o,t),w(t,o,a,r,e);let c=document.createDocumentFragment();if(t.childNodes.forEach(s=>{let n=y(s,a,r,e,i);n&&c.appendChild(n)}),o.appendChild(c),t.shadowRoot){let s=Array.from(t.shadowRoot.children).filter(d=>d.tagName!=="STYLE").map(d=>y(d,a,r,e,i)).filter(Boolean),n=document.createDocumentFragment();s.forEach(d=>n.appendChild(d)),o.appendChild(n)}return o.querySelectorAll("style").forEach(s=>s.removeAttribute("style")),o.tagName==="STYLE"&&o.removeAttribute("style"),o}function h(t,a=3e3){return new Promise((r,e)=>{let i=setTimeout(()=>{e(new Error("Image load timed out"))},a),o=new Image;o.crossOrigin="anonymous",o.onload=async()=>{clearTimeout(i);try{await o.decode();let c=document.createElement("canvas");c.width=o.width,c.height=o.height,c.getContext("2d").drawImage(o,0,0);try{let n=c.toDataURL("image/png");r(n)}catch{e(new Error("CORS restrictions prevented image capture"))}}catch(c){e(c)}},o.onerror=c=>{clearTimeout(i),e(new Error("Failed to load image: "+(c.message||"Unknown error")))},o.src=t})}function u(t=0){return new Promise(a=>setTimeout(a,t))}async function v(t){let a=Array.from(t.querySelectorAll("img")),r=async e=>{let i=e.src;try{let o=await h(i);e.src=o,e.width||(e.width=e.naturalWidth||100),e.height||(e.height=e.naturalHeight||100)}catch{let o=document.createElement("div");o.style=`width: ${e.width||100}px; height: ${e.height||100}px; background: #ccc; display: inline-block; text-align: center; line-height: ${e.height||100}px; color: #666; font-size: 12px;`,o.innerText="img",e.replaceWith(o)}};for(let e=0;e<a.length;e+=4){let i=a.slice(e,e+4).map(r);await Promise.allSettled(i),await u(1)}}async function A(t,a,r){let e=[[t,a]];for(;e.length;){let[i,o]=e.shift(),c=r.get(i)||window.getComputedStyle(i);r.has(i)||r.set(i,c);let s=c.getPropertyValue("background-image");if(s&&s.includes("url(")){let l=s.match(/url\(["']?([^"')]+)["']?\)/);if(l?.[1])try{let m=await h(l[1]);o.style.backgroundImage=`url(${m})`}catch{o.style.backgroundImage="none"}}let n=Array.from(i.children),d=Array.from(o.children);for(let l=0;l<Math.min(n.length,d.length);l++)e.push([n[l],d[l]]);await u(1)}}async function x(t,a){let r=["position","top","right","bottom","left","z-index","width","height","min-width","max-width","min-height","max-height","margin","padding","border","border-radius","background","background-color","background-image","color","font","font-family","font-size","font-weight","text-align","text-decoration","line-height","letter-spacing","display","visibility","opacity","overflow","box-shadow","transform","transition","flex","grid"];for(let o of["::before","::after"])try{let c=window.getComputedStyle(t,o),s=c.getPropertyValue("content");if(s&&s!=="none"&&s!=='""'&&s!=="''"){let n=document.createElement("span");n.className=`snapdom-pseudo-${o.replace("::","")}`;let d=s.replace(/^\"|\"$/g,"");if(d.startsWith("url(")){let l=d.match(/url\(["']?([^"')]+)["']?\)/);if(l?.[1])try{let m=document.createElement("img"),f=await h(l[1]);m.src=f,m.style="display:block;width:100%;height:100%;object-fit:contain;",n.appendChild(m)}catch{}}else n.textContent=d;for(let l of r){let m=c.getPropertyValue(l);m&&(n.style[l]=m)}n.style.position||(n.style.position="absolute"),o==="::before"?a.insertBefore(n,a.firstChild):a.appendChild(n)}}catch{}let e=Array.from(t.children),i=Array.from(a.children).filter(o=>!o.classList.contains("snapdom-pseudo-before")&&!o.classList.contains("snapdom-pseudo-after"));for(let o=0;o<Math.min(e.length,i.length);o++)await x(e[o],i[o]),o%5===0&&await u(1)}function C(t){return t.toLowerCase().replace(/\s+/g,"-").replace(/[^a-z0-9-]/g,"").replace(/-+/g,"-").replace(/^-|-$/g,"")}function g(t){return{normal:"400",bold:"700",italic:"italic",oblique:"italic","ultra-condensed":"50","extra-condensed":"62",condensed:"75","semi-condensed":"87",expanded:"125","semi-expanded":"112","extra-expanded":"150","ultra-expanded":"200"}[t]||t.replace(/%/g,"")}async function $(t=new WeakMap){await document.fonts.ready;let a=[];Array.from(document.styleSheets).forEach(o=>{try{Array.from(o.cssRules).forEach(c=>{c instanceof CSSFontFaceRule&&a.push(c.cssText)})}catch(c){console.warn("Could not access stylesheet rules:",c)}});let r=new Map;document.querySelectorAll("*").forEach(o=>{let c=t.get(o)||window.getComputedStyle(o);t.has(o)||t.set(o,c);let s=c.fontFamily?.split(",")[0]?.trim().replace(/["']/g,"");if(!s)return;let n=g(c.fontWeight||"400"),d=g(c.fontStyle||"normal"),l=g(c.fontStretch||"normal");r.has(s)||r.set(s,new Set),r.get(s).add(`${n}_${d}_${l}`)});let i=[...a];for(let[o,c]of r){let s=C(o);for(let n of c){let[d,l,m]=n.split("_"),f=`fg-f-${s}-w${d}-${l}-s${m}`,R=[`font-family:"${o}",sans-serif`,`font-weight:${d}`,`font-style:${l}`,`font-stretch:${m}%`].filter(Boolean).join(";");i.push(`.${f}{${R}}`)}}return i.push("*{font-family:inherit;font-weight:inherit;font-style:inherit;}"),i.join("")}function N(t,a,r){let e=r.get(a)||window.getComputedStyle(a),i=e.fontFamily?.split(",")[0]?.trim().replace(/["']/g,"");if(!i)return;let o=g(e.fontWeight||"400"),c=g(e.fontStyle||"normal"),s=g(e.fontStretch||"normal"),n=C(i);t.classList.add(`fg-f-${n}-w${o}-${c}-s${s}`)}async function I(t){let a=new Map,r=new WeakMap,e=new WeakMap,i;try{i=b()}catch(n){console.warn("getDefaultStyles failed:",n),i={}}let o;try{o=y(t,a,i,r,e)}catch(n){throw console.warn("deepCloneWithShadow failed:",n),n}try{await v(o)}catch(n){console.warn("inlineImages failed:",n)}try{await A(t,o,r)}catch(n){console.warn("inlineBackgroundImages failed:",n)}try{await x(t,o)}catch(n){console.warn("inlinePseudoElements failed:",n)}let c=E(a),s=Array.from(c.entries()).map(([n,d])=>`.${d}{${n}}`).join("");for(let[n,d]of a.entries()){if(n.tagName==="STYLE")continue;let l=c.get(d);if(l){n.classList.add(l);try{N(n,e.get(n),r)}catch(f){console.warn("applyFontClasses failed for node:",n,f)}}let m=n.style?.backgroundImage;n.removeAttribute("style"),m&&m!=="none"&&(n.style.backgroundImage=m)}return{clone:o,classCSS:s,styleCache:r}}function k(t){return t.replace(/>\s+</g,"><").replace(/\s{2,}/g," ").replace(/\n/g,"").trim()}async function L(t,a=1){let r,e,i;try{({clone:r,classCSS:e,styleCache:i}=await I(t))}catch(f){throw console.error("prepareClone failed:",f),f}let o=t.offsetWidth*a,c=t.offsetHeight*a,s="";try{s=await $(i)}catch(f){console.warn("inlineFonts failed:",f)}let n=document.createElementNS("http://www.w3.org/2000/svg","svg");n.setAttribute("width",o),n.setAttribute("height",c),n.setAttribute("viewBox",`0 0 ${o} ${c}`);let d=document.createElementNS("http://www.w3.org/2000/svg","foreignObject");d.setAttribute("width","100%"),d.setAttribute("height","100%");let l=document.createElement("div");l.setAttribute("xmlns","http://www.w3.org/1999/xhtml"),l.setAttribute("style",`width:${o}px;height:${c}px;`);let m=document.createElement("style");m.textContent=s+"svg{overflow:visible}"+e,l.appendChild(m),l.appendChild(r),d.appendChild(l),n.appendChild(d);try{let f=new XMLSerializer().serializeToString(n);return`data:image/svg+xml;charset=utf-8,${encodeURIComponent(k(f))}`}catch(f){throw console.error("SVG serialization failed:",f),f}}async function p(t,a=1){return await L(t,a)}p.toImg=async function(t,a=1){let r=await p(t,a),e=new Image;return e.src=r,await e.decode(),e};p.toCanvas=async function(t,a=1){let r=await p.toImg(t,a),e=document.createElement("canvas");return e.width=r.width,e.height=r.height,e.getContext("2d").drawImage(r,0,0),e};p.toPng=async function(t,a=1){let r=await p.toCanvas(t,a),e=new Image;return e.src=r.toDataURL("image/png"),await e.decode(),e};p.toJpg=async function(t,a=1,r=.92){let e=await p.toCanvas(t,a),i=new Image;return i.src=e.toDataURL("image/jpeg",r),await i.decode(),i};p.toWebp=async function(t,a=1,r=.92){let e=await p.toCanvas(t,a),i=new Image;return i.src=e.toDataURL("image/webp",r),await i.decode(),i};p.toBlob=async function(t,a=1){let r=await p(t,a),e=decodeURIComponent(r.split(",")[1]);return new Blob([e],{type:"image/svg+xml"})};export{p as snapdom};
1
+ function E(t,n,s){let o=[];for(let i of t){let e=t.getPropertyValue(i);e&&o.push(`${i}:${e}`)}return o.sort().join(";")}function S(t){let n=new Set(t.values()),s=new Map,o=1;for(let i of n)s.set(i,`c${o++}`);return s}function v(t,n,s,o,i){if(t.tagName==="STYLE")return;i.has(t)||i.set(t,window.getComputedStyle(t));let e=i.get(t),r=E(e,o);s.set(n,r)}function b(t,n,s,o,i){if(t.nodeType===Node.ELEMENT_NODE&&t.getAttribute("data-capture")==="exclude"){let l=document.createElement("div"),a=t.getBoundingClientRect();return l.style.cssText=`display: inline-block; width: ${a.width}px; height: ${a.height}px; visibility: hidden;`,l}if(t.tagName==="IFRAME"){let l=document.createElement("div");return l.textContent="",l.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;`,l}if(t.nodeType===Node.ELEMENT_NODE&&t.getAttribute("data-capture")==="placeholder"){let l=t.cloneNode(!1);i.set(l,t),v(t,l,n,s,o);let a=document.createElement("div");return a.textContent=t.getAttribute("data-placeholder-text")||"",a.style.cssText="color: #666;font-size: 12px;text-align: center;line-height: 1.4;padding: 0.5em;box-sizing: border-box;",l.appendChild(a),l}if(t.tagName==="CANVAS"){let l=t.toDataURL(),a=document.createElement("img");return a.src=l,a.width=t.width,a.height=t.height,a}if(t.nodeType===Node.TEXT_NODE){if(!t.textContent.trim())return null;if(t.parentElement?.shadowRoot){let a=t.parentElement.tagName.toLowerCase();if(customElements.get(a))return null}return t.cloneNode(!0)}if(t.nodeType!==Node.ELEMENT_NODE)return t.cloneNode(!0);let e=t.cloneNode(!1);i.set(e,t),v(t,e,n,s,o);let r=document.createDocumentFragment();if(t.childNodes.forEach(l=>{let a=b(l,n,s,o,i);a&&r.appendChild(a)}),e.appendChild(r),t.shadowRoot){let l=Array.from(t.shadowRoot.children).filter(d=>d.tagName!=="STYLE").map(d=>b(d,n,s,o,i)).filter(Boolean),a=document.createDocumentFragment();l.forEach(d=>a.appendChild(d)),e.appendChild(a)}return e}function x(t,n=3e3){return new Promise((s,o)=>{let i=setTimeout(()=>{o(new Error("Image load timed out"))},n),e=new Image;e.crossOrigin="anonymous",e.onload=async()=>{clearTimeout(i);try{await e.decode();let r=document.createElement("canvas");r.width=e.width,r.height=e.height,r.getContext("2d").drawImage(e,0,0);try{let a=r.toDataURL("image/png");s(a)}catch{o(new Error("CORS restrictions prevented image capture"))}}catch(r){o(r)}},e.onerror=r=>{clearTimeout(i),o(new Error("Failed to load image: "+(r.message||"Unknown error")))},e.src=t})}async function $(t){let n=Array.from(t.querySelectorAll("img")),s=async o=>{let i=o.src;try{let e=await x(i);o.src=e,o.width||(o.width=o.naturalWidth||100),o.height||(o.height=o.naturalHeight||100)}catch{let e=document.createElement("div");e.style=`width: ${o.width||100}px; height: ${o.height||100}px; background: #ccc; display: inline-block; text-align: center; line-height: ${o.height||100}px; color: #666; font-size: 12px;`,e.innerText="img",o.replaceWith(e)}};for(let o=0;o<n.length;o+=4){let i=n.slice(o,o+4).map(s);await Promise.allSettled(i)}}async function I(t,n,s){let o=[[t,n]];for(;o.length;){let[i,e]=o.shift(),r=s.get(i)||window.getComputedStyle(i);s.has(i)||s.set(i,r);let l=r.getPropertyValue("background-image");if(l&&l.includes("url(")){let c=l.match(/url\(["']?([^"')]+)["']?\)/);if(c?.[1])try{let m=await x(c[1]);e.style.backgroundImage=`url(${m})`}catch{e.style.backgroundImage="none"}}let a=Array.from(i.children),d=Array.from(e.children);for(let c=0;c<Math.min(a.length,d.length);c++)o.push([a[c],d[c]])}}async function C(t,n){let s=["position","top","right","bottom","left","z-index","width","height","min-width","max-width","min-height","max-height","margin","padding","border","border-width","border-style","border-color","border-radius","box-sizing","display","overflow","overflow-x","overflow-y","flex","flex-grow","flex-shrink","flex-basis","flex-direction","justify-content","align-items","align-content","gap","row-gap","column-gap","grid","grid-template-columns","grid-template-rows","background","background-color","background-image","color","font","font-family","font-size","font-weight","font-style","font-stretch","text-align","vertical-align","text-decoration","line-height","letter-spacing","white-space","word-break","text-overflow","visibility","opacity","box-shadow","transform","transition","clip-path","-webkit-text-fill-color"];for(let e of["::before","::after"])try{let r=window.getComputedStyle(t,e),l=r.getPropertyValue("content"),a=r.getPropertyValue("background-image");if(l&&l!=="none"&&l!=='""'&&l!=="''"||a&&a.startsWith("url(")){let d=r.getPropertyValue("font-family"),c=document.createElement("span");c.className=`snapdom-pseudo-${e.replace("::","")}`;let m=P(l),g=d&&/font.*awesome|material|bootstrap|glyphicons|ionicons|remixicon|simple-line-icons|octicons|feather|typicons|weathericons/i.test(d),y=parseInt(r.getPropertyValue("font-size"))||32,u=parseInt(r.getPropertyValue("font-weight"))||!1,L=r.getPropertyValue("color")||"#000";if(g&&m.length===1){let f=document.createElement("img");f.src=await U(m,d,u,y,L),f.style="display:block;width:100%;height:100%;object-fit:contain;",c.appendChild(f)}else if(m.startsWith("url(")){let f=m.match(/url\(["']?([^"')]+)["']?\)/);if(f?.[1])try{let h=document.createElement("img"),D=await x(f[1]);h.src=D,h.style="display:block;width:100%;height:100%;object-fit:contain;",c.appendChild(h)}catch(h){console.error(`[snapdom] Error in pseudo ${e} for`,t,h)}}else c.textContent=m;for(let f of s){let h=r.getPropertyValue(f);h&&(c.style[f]=h)}c.style.position||(c.style.position="absolute"),e==="::before"?n.insertBefore(c,n.firstChild):n.appendChild(c)}}catch{}let o=Array.from(t.children),i=Array.from(n.children).filter(e=>!e.classList.contains("snapdom-pseudo-before")&&!e.classList.contains("snapdom-pseudo-after"));for(let e=0;e<Math.min(o.length,i.length);e++)await C(o[e],i[e])}function P(t){let n=t.replace(/^['"]|['"]$/g,"");if(n.startsWith("\\"))try{return String.fromCharCode(parseInt(n.replace("\\",""),16))}catch{return n}return n}async function U(t,n,s,o=32,i="#000"){n=n.replace(/^['"]+|['"]+$/g,""),await document.fonts.ready,await document.fonts.load(`${o}px "${n}"`);let e=document.createElement("canvas");e.width=o,e.height=o;let r=e.getContext("2d");return r.font=s?`${s} ${o}px "${n}"`:` ${o}px "${n}"`,r.textAlign="center",r.textBaseline="middle",r.fillStyle=i,r.fillText(t,e.width/2,e.height/2),e.toDataURL()}function k(t){return t.toLowerCase().replace(/\s+/g,"-").replace(/[^a-z0-9-]/g,"").replace(/-+/g,"-").replace(/^-|-$/g,"")}function w(t){return{normal:"400",bold:"700",italic:"italic",oblique:"italic","ultra-condensed":"50","extra-condensed":"62",condensed:"75","semi-condensed":"87",expanded:"125","semi-expanded":"112","extra-expanded":"150","ultra-expanded":"200"}[t]||t.replace(/%/g,"")}async function A(t=new WeakMap){await document.fonts.ready;let n=[];Array.from(document.styleSheets).forEach(e=>{try{Array.from(e.cssRules).forEach(r=>{r instanceof CSSFontFaceRule&&n.push(r.cssText)})}catch(r){console.warn("Could not access stylesheet rules:",r)}});let s=new Map;document.querySelectorAll("*").forEach(e=>{let r=t.get(e)||window.getComputedStyle(e);t.has(e)||t.set(e,r);let l=r.fontFamily?.split(",")[0]?.trim().replace(/["']/g,"");if(!l)return;let a=w(r.fontWeight||"400"),d=w(r.fontStyle||"normal"),c=w(r.fontStretch||"normal");s.has(l)||s.set(l,new Set),s.get(l).add(`${a}_${d}_${c}`)});let i=[...n];for(let[e,r]of s){let l=k(e);for(let a of r){let[d,c,m]=a.split("_"),g=`fg-f-${l}-w${d}-${c}-s${m}`,y=[`font-family:"${e}",sans-serif`,`font-weight:${d}`,`font-style:${c}`,`font-stretch:${m}%`].filter(Boolean).join(";");i.push(`.${g}{${y}}`)}}return i.push("*{font-family:inherit;font-weight:inherit;font-style:inherit;}"),i.join("")}function N(t,n,s){let o=s.get(n)||window.getComputedStyle(n),i=o.fontFamily?.split(",")[0]?.trim().replace(/["']/g,"");if(!i)return;let e=w(o.fontWeight||"400"),r=w(o.fontStyle||"normal"),l=w(o.fontStretch||"normal"),a=k(i);t.classList.add(`fg-f-${a}-w${e}-${r}-s${l}`)}async function z(t){let n=new Map,s=new WeakMap,o=new WeakMap,i;try{i={}}catch(a){console.warn("getDefaultStyles failed:",a),i={}}let e;try{e=b(t,n,i,s,o)}catch(a){throw console.warn("deepCloneWithShadow failed:",a),a}try{await $(e)}catch(a){console.warn("inlineImages failed:",a)}try{await I(t,e,s)}catch(a){console.warn("inlineBackgroundImages failed:",a)}try{await C(t,e)}catch(a){console.warn("inlinePseudoElements failed:",a)}let r=S(n),l=Array.from(r.entries()).map(([a,d])=>`.${d}{${a}}`).join("");for(let[a,d]of n.entries()){if(a.tagName==="STYLE")continue;let c=r.get(d);if(c){a.classList.add(c);try{N(a,o.get(a),s)}catch(g){console.warn("applyFontClasses failed for node:",a,g)}}let m=a.style?.backgroundImage;a.removeAttribute("style"),m&&m!=="none"&&(a.style.backgroundImage=m)}return{clone:e,classCSS:l,styleCache:s}}function R(t){return t.replace(/>\s+</g,"><").replace(/\s{2,}/g," ").replace(/\n/g,"").trim()}async function T(t,n={}){let{scale:s=1}=n,o,i,e;try{({clone:o,classCSS:i,styleCache:e}=await z(t))}catch(u){throw console.error("prepareClone failed:",u),u}let r=t.getBoundingClientRect(),l=r.width*s,a=r.height*s,d="";try{d=await A(e)}catch(u){console.warn("inlineFonts failed:",u)}let c=document.createElementNS("http://www.w3.org/2000/svg","svg");c.setAttribute("width",l),c.setAttribute("height",a),c.setAttribute("viewBox",`0 0 ${l} ${a}`);let m=document.createElementNS("http://www.w3.org/2000/svg","foreignObject");m.setAttribute("width","100%"),m.setAttribute("height","100%");let g=document.createElement("div");g.setAttribute("xmlns","http://www.w3.org/1999/xhtml"),g.setAttribute("style",`width:${l}px;height:${a}px;`);let y=document.createElement("style");y.textContent=d+"svg{overflow:visible}"+i,g.appendChild(y),g.appendChild(o),m.appendChild(g),c.appendChild(m);try{let u=new XMLSerializer().serializeToString(c);return`data:image/svg+xml;charset=utf-8,${encodeURIComponent(R(u))}`}catch(u){throw console.error("SVG serialization failed:",u),u}}async function p(t,n={}){return await T(t,n)}p.toImg=async(t,n={})=>{let s=await p(t,n),o=new Image;return o.src=s,await o.decode(),o};p.toCanvas=async(t,n={})=>{let s=await p.toImg(t,n),o=document.createElement("canvas");return o.width=s.width,o.height=s.height,o.getContext("2d").drawImage(s,0,0),o};p.toPng=async(t,n={})=>{let s=await p.toCanvas(t,n),o=new Image;return o.src=s.toDataURL("image/png"),await o.decode(),o};p.toJpg=async(t,n={})=>{let{backgroundColor:s="#fff",quality:o=.92}=n,i=await p.toCanvas(t,n),e=document.createElement("canvas");e.width=i.width,e.height=i.height;let r=e.getContext("2d");r.fillStyle=s,r.fillRect(0,0,e.width,e.height),r.drawImage(i,0,0);let l=new Image;return l.src=e.toDataURL("image/jpeg",o),await l.decode(),l};p.toWebp=async(t,n={})=>{let{backgroundColor:s="#fff",quality:o=.92}=n,i=await p.toCanvas(t,n),e=document.createElement("canvas");e.width=i.width,e.height=i.height;let r=e.getContext("2d");r.fillStyle=s,r.fillRect(0,0,e.width,e.height),r.drawImage(i,0,0);let l=new Image;return l.src=e.toDataURL("image/webp",o),await l.decode(),l};p.toBlob=async(t,n={})=>{let s=await p(t,n),o=decodeURIComponent(s.split(",")[1]);return new Blob([o],{type:"image/svg+xml"})};export{p as snapdom};
package/dist/snapdom.mjs CHANGED
@@ -1,28 +1,17 @@
1
1
 
2
2
  /*
3
3
  * snapdom
4
- * v.0.9.0
4
+ * v.0.9.2
5
5
  * Author Juan Martin Muda
6
6
  * License MIT
7
7
  **/
8
8
 
9
9
  // src/utils/cssTools.js
10
- function getDefaultStyles() {
11
- const defaultEl = document.createElement("div");
12
- document.body.appendChild(defaultEl);
13
- const styles = window.getComputedStyle(defaultEl);
14
- const defaults = {};
15
- for (let prop of styles) {
16
- defaults[prop] = styles.getPropertyValue(prop);
17
- }
18
- document.body.removeChild(defaultEl);
19
- return defaults;
20
- }
21
- function getStyleKey(style, defaults) {
10
+ function getStyleKey(style, defaults, mode) {
22
11
  const entries = [];
23
- for (let prop of Array.from(style)) {
12
+ for (let prop of style) {
24
13
  const value = style.getPropertyValue(prop);
25
- if (value && value !== defaults[prop]) {
14
+ if (value) {
26
15
  entries.push(`${prop}:${value}`);
27
16
  }
28
17
  }
@@ -106,8 +95,6 @@ function deepCloneWithShadow(node, styleMap, defaults, styleCache, nodeMap) {
106
95
  shadowContent.forEach((child) => shadowFrag.appendChild(child));
107
96
  clone.appendChild(shadowFrag);
108
97
  }
109
- clone.querySelectorAll("style").forEach((tag) => tag.removeAttribute("style"));
110
- if (clone.tagName === "STYLE") clone.removeAttribute("style");
111
98
  return clone;
112
99
  }
113
100
 
@@ -146,11 +133,6 @@ function fetchImageAsDataURL(src, timeout = 3e3) {
146
133
  });
147
134
  }
148
135
 
149
- // src/utils/delay.js
150
- function delay(ms = 0) {
151
- return new Promise((resolve) => setTimeout(resolve, ms));
152
- }
153
-
154
136
  // src/modules/images.js
155
137
  async function inlineImages(clone) {
156
138
  const imgs = Array.from(clone.querySelectorAll("img"));
@@ -171,7 +153,6 @@ async function inlineImages(clone) {
171
153
  for (let i = 0; i < imgs.length; i += 4) {
172
154
  const group = imgs.slice(i, i + 4).map(processImg);
173
155
  await Promise.allSettled(group);
174
- await delay(1);
175
156
  }
176
157
  }
177
158
 
@@ -199,7 +180,6 @@ async function inlineBackgroundImages(source, clone, styleCache) {
199
180
  for (let i = 0; i < Math.min(sChildren.length, cChildren.length); i++) {
200
181
  queue.push([sChildren[i], cChildren[i]]);
201
182
  }
202
- await delay(1);
203
183
  }
204
184
  }
205
185
 
@@ -221,7 +201,29 @@ async function inlinePseudoElements(source, clone) {
221
201
  "margin",
222
202
  "padding",
223
203
  "border",
204
+ "border-width",
205
+ "border-style",
206
+ "border-color",
224
207
  "border-radius",
208
+ "box-sizing",
209
+ "display",
210
+ "overflow",
211
+ "overflow-x",
212
+ "overflow-y",
213
+ "flex",
214
+ "flex-grow",
215
+ "flex-shrink",
216
+ "flex-basis",
217
+ "flex-direction",
218
+ "justify-content",
219
+ "align-items",
220
+ "align-content",
221
+ "gap",
222
+ "row-gap",
223
+ "column-gap",
224
+ "grid",
225
+ "grid-template-columns",
226
+ "grid-template-rows",
225
227
  "background",
226
228
  "background-color",
227
229
  "background-image",
@@ -230,29 +232,44 @@ async function inlinePseudoElements(source, clone) {
230
232
  "font-family",
231
233
  "font-size",
232
234
  "font-weight",
235
+ "font-style",
236
+ "font-stretch",
233
237
  "text-align",
238
+ "vertical-align",
234
239
  "text-decoration",
235
240
  "line-height",
236
241
  "letter-spacing",
237
- "display",
242
+ "white-space",
243
+ "word-break",
244
+ "text-overflow",
238
245
  "visibility",
239
246
  "opacity",
240
- "overflow",
241
247
  "box-shadow",
242
248
  "transform",
243
249
  "transition",
244
- "flex",
245
- "grid"
250
+ "clip-path",
251
+ "-webkit-text-fill-color"
246
252
  ];
247
253
  for (const pseudo of ["::before", "::after"]) {
248
254
  try {
249
255
  const style = window.getComputedStyle(source, pseudo);
250
256
  const content = style.getPropertyValue("content");
251
- if (content && content !== "none" && content !== '""' && content !== "''") {
257
+ const bg = style.getPropertyValue("background-image");
258
+ if (content && content !== "none" && content !== '""' && content !== "''" || bg && bg.startsWith("url(")) {
259
+ const fontFamily = style.getPropertyValue("font-family");
252
260
  const pseudoEl = document.createElement("span");
253
261
  pseudoEl.className = `snapdom-pseudo-${pseudo.replace("::", "")}`;
254
- let cleanContent = content.replace(/^\"|\"$/g, "");
255
- if (cleanContent.startsWith("url(")) {
262
+ let cleanContent = parseContent(content);
263
+ const isIconFont = fontFamily && /font.*awesome|material|bootstrap|glyphicons|ionicons|remixicon|simple-line-icons|octicons|feather|typicons|weathericons/i.test(fontFamily);
264
+ const fontSize = parseInt(style.getPropertyValue("font-size")) || 32;
265
+ const fontWeight = parseInt(style.getPropertyValue("font-weight")) || false;
266
+ const color = style.getPropertyValue("color") || "#000";
267
+ if (isIconFont && cleanContent.length === 1) {
268
+ const imgEl = document.createElement("img");
269
+ imgEl.src = await iconToImage(cleanContent, fontFamily, fontWeight, fontSize, color);
270
+ imgEl.style = "display:block;width:100%;height:100%;object-fit:contain;";
271
+ pseudoEl.appendChild(imgEl);
272
+ } else if (cleanContent.startsWith("url(")) {
256
273
  const match = cleanContent.match(/url\(["']?([^"')]+)["']?\)/);
257
274
  if (match?.[1]) {
258
275
  try {
@@ -262,6 +279,7 @@ async function inlinePseudoElements(source, clone) {
262
279
  imgEl.style = "display:block;width:100%;height:100%;object-fit:contain;";
263
280
  pseudoEl.appendChild(imgEl);
264
281
  } catch (e) {
282
+ console.error(`[snapdom] Error in pseudo ${pseudo} for`, source, e);
265
283
  }
266
284
  }
267
285
  } else {
@@ -289,9 +307,34 @@ async function inlinePseudoElements(source, clone) {
289
307
  );
290
308
  for (let i = 0; i < Math.min(sChildren.length, cChildren.length); i++) {
291
309
  await inlinePseudoElements(sChildren[i], cChildren[i]);
292
- if (i % 5 === 0) await delay(1);
293
310
  }
294
311
  }
312
+ function parseContent(content) {
313
+ let clean = content.replace(/^['"]|['"]$/g, "");
314
+ if (clean.startsWith("\\")) {
315
+ try {
316
+ return String.fromCharCode(parseInt(clean.replace("\\", ""), 16));
317
+ } catch {
318
+ return clean;
319
+ }
320
+ }
321
+ return clean;
322
+ }
323
+ async function iconToImage(unicodeChar, fontFamily, fontWeight, fontSize = 32, color = "#000") {
324
+ fontFamily = fontFamily.replace(/^['"]+|['"]+$/g, "");
325
+ await document.fonts.ready;
326
+ await document.fonts.load(`${fontSize}px "${fontFamily}"`);
327
+ const canvas = document.createElement("canvas");
328
+ canvas.width = fontSize;
329
+ canvas.height = fontSize;
330
+ const ctx = canvas.getContext("2d");
331
+ ctx.font = fontWeight ? `${fontWeight} ${fontSize}px "${fontFamily}"` : ` ${fontSize}px "${fontFamily}"`;
332
+ ctx.textAlign = "center";
333
+ ctx.textBaseline = "middle";
334
+ ctx.fillStyle = color;
335
+ ctx.fillText(unicodeChar, canvas.width / 2, canvas.height / 2);
336
+ return canvas.toDataURL();
337
+ }
295
338
 
296
339
  // src/utils/normalize.js
297
340
  function normalizeFontName(str) {
@@ -381,7 +424,7 @@ async function prepareClone(element) {
381
424
  const nodeMap = /* @__PURE__ */ new WeakMap();
382
425
  let defaults;
383
426
  try {
384
- defaults = getDefaultStyles();
427
+ defaults = {};
385
428
  } catch (e) {
386
429
  console.warn("getDefaultStyles failed:", e);
387
430
  defaults = {};
@@ -434,7 +477,8 @@ function minifySVG(svgString) {
434
477
  }
435
478
 
436
479
  // src/core/capture.js
437
- async function capture(element, scale = 1) {
480
+ async function capture(element, options = {}) {
481
+ const { scale = 1 } = options;
438
482
  let clone, classCSS, styleCache;
439
483
  try {
440
484
  ({ clone, classCSS, styleCache } = await prepareClone(element));
@@ -442,8 +486,9 @@ async function capture(element, scale = 1) {
442
486
  console.error("prepareClone failed:", e);
443
487
  throw e;
444
488
  }
445
- const w = element.offsetWidth * scale;
446
- const h = element.offsetHeight * scale;
489
+ const rect = element.getBoundingClientRect();
490
+ const w = rect.width * scale;
491
+ const h = rect.height * scale;
447
492
  let fonts = "";
448
493
  try {
449
494
  fonts = await inlineFonts(styleCache);
@@ -476,50 +521,65 @@ async function capture(element, scale = 1) {
476
521
  }
477
522
 
478
523
  // src/api/snapdom.js
479
- async function snapdom(el, scale = 1) {
480
- return await capture(el, scale);
524
+ async function snapdom(element, options = {}) {
525
+ return await capture(element, options);
481
526
  }
482
- snapdom.toImg = async function(el, scale = 1) {
483
- const dataURL = await snapdom(el, scale);
527
+ snapdom.toImg = async (el, opt = {}) => {
528
+ const url = await snapdom(el, opt);
484
529
  const img = new Image();
485
- img.src = dataURL;
530
+ img.src = url;
486
531
  await img.decode();
487
532
  return img;
488
533
  };
489
- snapdom.toCanvas = async function(el, scale = 1) {
490
- const img = await snapdom.toImg(el, scale);
534
+ snapdom.toCanvas = async (el, opt = {}) => {
535
+ const img = await snapdom.toImg(el, opt);
491
536
  const canvas = document.createElement("canvas");
492
537
  canvas.width = img.width;
493
538
  canvas.height = img.height;
494
- const ctx = canvas.getContext("2d");
495
- ctx.drawImage(img, 0, 0);
539
+ canvas.getContext("2d").drawImage(img, 0, 0);
496
540
  return canvas;
497
541
  };
498
- snapdom.toPng = async function(el, scale = 1) {
499
- const canvas = await snapdom.toCanvas(el, scale);
542
+ snapdom.toPng = async (el, opt = {}) => {
543
+ const canvas = await snapdom.toCanvas(el, opt);
500
544
  const img = new Image();
501
545
  img.src = canvas.toDataURL("image/png");
502
546
  await img.decode();
503
547
  return img;
504
548
  };
505
- snapdom.toJpg = async function(el, scale = 1, quality = 0.92) {
506
- const canvas = await snapdom.toCanvas(el, scale);
549
+ snapdom.toJpg = async (el, opt = {}) => {
550
+ const { backgroundColor = "#fff", quality = 0.92 } = opt;
551
+ const canvas = await snapdom.toCanvas(el, opt);
552
+ const temp = document.createElement("canvas");
553
+ temp.width = canvas.width;
554
+ temp.height = canvas.height;
555
+ const ctx = temp.getContext("2d");
556
+ ctx.fillStyle = backgroundColor;
557
+ ctx.fillRect(0, 0, temp.width, temp.height);
558
+ ctx.drawImage(canvas, 0, 0);
507
559
  const img = new Image();
508
- img.src = canvas.toDataURL("image/jpeg", quality);
560
+ img.src = temp.toDataURL("image/jpeg", quality);
509
561
  await img.decode();
510
562
  return img;
511
563
  };
512
- snapdom.toWebp = async function(el, scale = 1, quality = 0.92) {
513
- const canvas = await snapdom.toCanvas(el, scale);
564
+ snapdom.toWebp = async (el, opt = {}) => {
565
+ const { backgroundColor = "#fff", quality = 0.92 } = opt;
566
+ const canvas = await snapdom.toCanvas(el, opt);
567
+ const temp = document.createElement("canvas");
568
+ temp.width = canvas.width;
569
+ temp.height = canvas.height;
570
+ const ctx = temp.getContext("2d");
571
+ ctx.fillStyle = backgroundColor;
572
+ ctx.fillRect(0, 0, temp.width, temp.height);
573
+ ctx.drawImage(canvas, 0, 0);
514
574
  const img = new Image();
515
- img.src = canvas.toDataURL("image/webp", quality);
575
+ img.src = temp.toDataURL("image/webp", quality);
516
576
  await img.decode();
517
577
  return img;
518
578
  };
519
- snapdom.toBlob = async function(el, scale = 1) {
520
- const dataURL = await snapdom(el, scale);
521
- const svgXml = decodeURIComponent(dataURL.split(",")[1]);
522
- return new Blob([svgXml], { type: "image/svg+xml" });
579
+ snapdom.toBlob = async (el, opt = {}) => {
580
+ const dataUrl = await snapdom(el, opt);
581
+ const svgText = decodeURIComponent(dataUrl.split(",")[1]);
582
+ return new Blob([svgText], { type: "image/svg+xml" });
523
583
  };
524
584
  export {
525
585
  snapdom
package/package.json CHANGED
@@ -1,8 +1,15 @@
1
1
  {
2
2
  "name": "@zumer/snapdom",
3
- "version": "0.9.0",
3
+ "version": "0.9.2",
4
4
  "description": "snapDOM captures DOM elements as images with exceptional speed and minimal overhead. Designed to avoid bottlenecks and long tasks.",
5
- "main": "src/index.js",
5
+ "main": "dist/snapdom.js",
6
+ "module": "dist/snapdom.mjs",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./dist/snapdom.mjs",
10
+ "require": "./dist/snapdom.js"
11
+ }
12
+ },
6
13
  "scripts": {
7
14
  "test": "npx vitest run --browser.headless --reporter=verbose",
8
15
  "test:benchmark": "npx vitest bench --browser.headless --watch=false",
package/src/index.js DELETED
@@ -1,6 +0,0 @@
1
- /**
2
- * @file snapdom.js
3
- */
4
-
5
- export { snapdom } from './api/snapdom';
6
-