@zumer/snapdom 1.3.0 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/snapdom.js CHANGED
@@ -1,11 +1,61 @@
1
1
 
2
2
  /*
3
3
  * snapdom
4
- * v.1.3.0
4
+ * v.1.8.0
5
5
  * Author Juan Martin Muda
6
6
  * License MIT
7
7
  **/
8
+
8
9
  (() => {
10
+ var __defProp = Object.defineProperty;
11
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
12
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
13
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
14
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
15
+ var __spreadValues = (a, b) => {
16
+ for (var prop in b || (b = {}))
17
+ if (__hasOwnProp.call(b, prop))
18
+ __defNormalProp(a, prop, b[prop]);
19
+ if (__getOwnPropSymbols)
20
+ for (var prop of __getOwnPropSymbols(b)) {
21
+ if (__propIsEnum.call(b, prop))
22
+ __defNormalProp(a, prop, b[prop]);
23
+ }
24
+ return a;
25
+ };
26
+ var __objRest = (source, exclude) => {
27
+ var target = {};
28
+ for (var prop in source)
29
+ if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
30
+ target[prop] = source[prop];
31
+ if (source != null && __getOwnPropSymbols)
32
+ for (var prop of __getOwnPropSymbols(source)) {
33
+ if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
34
+ target[prop] = source[prop];
35
+ }
36
+ return target;
37
+ };
38
+ var __async = (__this, __arguments, generator) => {
39
+ return new Promise((resolve, reject) => {
40
+ var fulfilled = (value) => {
41
+ try {
42
+ step(generator.next(value));
43
+ } catch (e) {
44
+ reject(e);
45
+ }
46
+ };
47
+ var rejected = (value) => {
48
+ try {
49
+ step(generator.throw(value));
50
+ } catch (e) {
51
+ reject(e);
52
+ }
53
+ };
54
+ var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
55
+ step((generator = generator.apply(__this, __arguments)).next());
56
+ });
57
+ };
58
+
9
59
  // src/core/cache.js
10
60
  var imageCache = /* @__PURE__ */ new Map();
11
61
  var bgCache = /* @__PURE__ */ new Map();
@@ -13,6 +63,7 @@
13
63
  var defaultStylesCache = /* @__PURE__ */ new Map();
14
64
  var baseCSSCache = /* @__PURE__ */ new Map();
15
65
  var computedStyleCache = /* @__PURE__ */ new WeakMap();
66
+ var processedFontURLs = /* @__PURE__ */ new Set();
16
67
 
17
68
  // src/utils/cssTools.js
18
69
  var commonTags = [
@@ -152,6 +203,29 @@
152
203
  }
153
204
 
154
205
  // src/utils/helpers.js
206
+ function inlineSingleBackgroundEntry(_0) {
207
+ return __async(this, arguments, function* (entry, options = {}) {
208
+ const isUrl = entry.startsWith("url(");
209
+ const isGradient = /^((repeating-)?(linear|radial|conic)-gradient)\(/i.test(entry);
210
+ if (isUrl) {
211
+ const rawUrl = extractURL(entry);
212
+ if (!rawUrl) return entry;
213
+ const encodedUrl = safeEncodeURI(rawUrl);
214
+ if (bgCache.has(encodedUrl)) {
215
+ return options.skipInline ? void 0 : `url(${bgCache.get(encodedUrl)})`;
216
+ } else {
217
+ const crossOrigin = options.crossOrigin ? options.crossOrigin(encodedUrl) : "anonymous";
218
+ const dataUrl = yield fetchImage(encodedUrl, 3e3, crossOrigin);
219
+ bgCache.set(encodedUrl, dataUrl);
220
+ return options.skipInline ? void 0 : `url(${dataUrl})`;
221
+ }
222
+ }
223
+ if (isGradient || entry === "none") {
224
+ return entry;
225
+ }
226
+ return entry;
227
+ });
228
+ }
155
229
  function idle(fn, { fast = false } = {}) {
156
230
  if (fast) return fn();
157
231
  if ("requestIdleCallback" in window) {
@@ -180,7 +254,7 @@
180
254
  if (clean.startsWith("\\")) {
181
255
  try {
182
256
  return String.fromCharCode(parseInt(clean.replace("\\", ""), 16));
183
- } catch {
257
+ } catch (e) {
184
258
  return clean;
185
259
  }
186
260
  }
@@ -205,11 +279,12 @@
205
279
  /feather/i,
206
280
  /bootstrap\s*icons/i,
207
281
  /remix\s*icons/i,
208
- /heroicons/i
282
+ /heroicons/i,
283
+ /lucide/i
209
284
  ];
210
285
  return iconFontPatterns.some((rx) => rx.test(familyOrUrl));
211
286
  }
212
- function fetchImage(src, timeout = 3e3) {
287
+ function fetchImage(src, timeout = 3e3, crossOrigin = "anonymous") {
213
288
  if (imageCache.has(src)) {
214
289
  return Promise.resolve(imageCache.get(src));
215
290
  }
@@ -218,11 +293,11 @@
218
293
  reject(new Error("Image load timed out"));
219
294
  }, timeout);
220
295
  const image = new Image();
221
- image.crossOrigin = "anonymous";
222
- image.onload = async () => {
296
+ image.crossOrigin = crossOrigin;
297
+ image.onload = () => __async(null, null, function* () {
223
298
  clearTimeout(timeoutId);
224
299
  try {
225
- await image.decode();
300
+ yield image.decode();
226
301
  const canvas = document.createElement("canvas");
227
302
  canvas.width = image.width;
228
303
  canvas.height = image.height;
@@ -238,7 +313,7 @@
238
313
  } catch (e) {
239
314
  reject(e);
240
315
  }
241
- };
316
+ });
242
317
  image.onerror = (e) => {
243
318
  clearTimeout(timeoutId);
244
319
  reject(new Error("Failed to load image: " + (e.message || "Unknown error")));
@@ -256,22 +331,67 @@
256
331
  function isSafari() {
257
332
  return /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
258
333
  }
334
+ function stripTranslate(transform) {
335
+ if (!transform || transform === "none") return "";
336
+ let cleaned = transform.replace(/translate[XY]?\([^)]*\)/g, "");
337
+ cleaned = cleaned.replace(/matrix\(([^)]+)\)/g, (_, values) => {
338
+ const parts = values.split(",").map((s) => s.trim());
339
+ if (parts.length !== 6) return `matrix(${values})`;
340
+ parts[4] = "0";
341
+ parts[5] = "0";
342
+ return `matrix(${parts.join(", ")})`;
343
+ });
344
+ cleaned = cleaned.replace(/matrix3d\(([^)]+)\)/g, (_, values) => {
345
+ const parts = values.split(",").map((s) => s.trim());
346
+ if (parts.length !== 16) return `matrix3d(${values})`;
347
+ parts[12] = "0";
348
+ parts[13] = "0";
349
+ return `matrix3d(${parts.join(", ")})`;
350
+ });
351
+ return cleaned.trim().replace(/\s{2,}/g, " ");
352
+ }
353
+ function safeEncodeURI(uri) {
354
+ try {
355
+ const newURI = decodeURI(uri);
356
+ return encodeURI(newURI);
357
+ } catch (e) {
358
+ return uri;
359
+ }
360
+ }
361
+ function splitBackgroundImage(bg) {
362
+ const parts = [];
363
+ let depth = 0;
364
+ let lastIndex = 0;
365
+ for (let i = 0; i < bg.length; i++) {
366
+ const char = bg[i];
367
+ if (char === "(") depth++;
368
+ if (char === ")") depth--;
369
+ if (char === "," && depth === 0) {
370
+ parts.push(bg.slice(lastIndex, i).trim());
371
+ lastIndex = i + 1;
372
+ }
373
+ }
374
+ parts.push(bg.slice(lastIndex).trim());
375
+ return parts;
376
+ }
259
377
 
260
378
  // src/modules/styles.js
261
379
  function inlineAllStyles(source, clone, styleMap, cache, compress) {
380
+ var _a;
262
381
  if (source.tagName === "STYLE") return;
263
382
  if (!cache.has(source)) {
264
383
  cache.set(source, getStyle(source));
265
384
  }
266
385
  const style = cache.get(source);
267
386
  const snapshot = snapshotComputedStyle(style);
268
- const tagName = source.tagName?.toLowerCase() || "div";
387
+ const tagName = ((_a = source.tagName) == null ? void 0 : _a.toLowerCase()) || "div";
269
388
  const key = getStyleKey(snapshot, tagName, compress);
270
389
  styleMap.set(clone, key);
271
390
  }
272
391
 
273
392
  // src/core/clone.js
274
393
  function deepClone(node, styleMap, styleCache, nodeMap, compress) {
394
+ var _a;
275
395
  if (node.nodeType === Node.ELEMENT_NODE && node.getAttribute("data-capture") === "exclude") {
276
396
  const spacer = document.createElement("div");
277
397
  const rect = node.getBoundingClientRect();
@@ -281,7 +401,7 @@
281
401
  if (node.tagName === "IFRAME") {
282
402
  const fallback = document.createElement("div");
283
403
  fallback.textContent = "";
284
- fallback.style.cssText = `width: ${node.offsetWidth}px; height: ${node.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;`;
404
+ fallback.style.cssText = `width: ${node.offsetWidth}px; height: ${node.offsetHeight}px; background-image: repeating-linear-gradient(45deg, #ddd, #ddd 5px, #f9f9f9 5px, #f9f9f9 10px);display: flex;align-items: center;justify-content: center;font-size: 12px;color: #555; border: 1px solid #aaa;`;
285
405
  return fallback;
286
406
  }
287
407
  if (node.nodeType === Node.ELEMENT_NODE && node.getAttribute("data-capture") === "placeholder") {
@@ -301,12 +421,12 @@
301
421
  img.width = node.width;
302
422
  img.height = node.height;
303
423
  img.style.display = "inline-block";
304
- img.style.width = `${node.width}px`;
305
- img.style.height = `${node.height}px`;
424
+ img.style.width = node.style.width || `${node.width}px`;
425
+ img.style.height = node.style.height || `${node.height}px`;
306
426
  return img;
307
427
  }
308
428
  if (node.nodeType === Node.TEXT_NODE) {
309
- if (node.parentElement?.shadowRoot) {
429
+ if ((_a = node.parentElement) == null ? void 0 : _a.shadowRoot) {
310
430
  const tag = node.parentElement.tagName.toLowerCase();
311
431
  if (customElements.get(tag)) return null;
312
432
  }
@@ -352,461 +472,675 @@
352
472
  }
353
473
 
354
474
  // src/modules/fonts.js
355
- async function iconToImage(unicodeChar, fontFamily, fontWeight, fontSize = 32, color = "#000") {
356
- fontFamily = fontFamily.replace(/^['"]+|['"]+$/g, "");
357
- const dpr = window.devicePixelRatio || 1;
358
- const tempCanvas = document.createElement("canvas");
359
- const tempCtx = tempCanvas.getContext("2d");
360
- tempCtx.font = fontWeight ? `${fontWeight} ${fontSize}px "${fontFamily}"` : `${fontSize}px "${fontFamily}"`;
361
- const metrics = tempCtx.measureText(unicodeChar);
362
- const ascent = metrics.actualBoundingBoxAscent || fontSize * 0.8;
363
- const descent = metrics.actualBoundingBoxDescent || fontSize * 0.2;
364
- const height = ascent + descent;
365
- const width = metrics.width;
366
- const canvas = document.createElement("canvas");
367
- canvas.width = Math.ceil(width * dpr);
368
- canvas.height = Math.ceil(height * dpr);
369
- const ctx = canvas.getContext("2d");
370
- ctx.scale(dpr, dpr);
371
- ctx.font = tempCtx.font;
372
- ctx.textAlign = "left";
373
- ctx.textBaseline = "alphabetic";
374
- ctx.fillStyle = color;
375
- ctx.fillText(unicodeChar, 0, ascent);
376
- return canvas.toDataURL();
377
- }
378
- async function embedCustomFonts({ ignoreIconFonts = true, preCached = false }) {
379
- const links = Array.from(document.querySelectorAll('link[rel="stylesheet"]')).filter((link) => link.href);
380
- let finalCSS = "";
381
- for (const link of links) {
382
- try {
383
- const res = await fetch(link.href);
384
- const cssText = await res.text();
385
- if (ignoreIconFonts && (isIconFont(link.href) || isIconFont(cssText))) {
386
- continue;
475
+ function iconToImage(unicodeChar, fontFamily, fontWeight, fontSize = 32, color = "#000") {
476
+ return __async(this, null, function* () {
477
+ fontFamily = fontFamily.replace(/^['"]+|['"]+$/g, "");
478
+ const dpr = window.devicePixelRatio || 1;
479
+ const tempCanvas = document.createElement("canvas");
480
+ const tempCtx = tempCanvas.getContext("2d");
481
+ tempCtx.font = fontWeight ? `${fontWeight} ${fontSize}px "${fontFamily}"` : `${fontSize}px "${fontFamily}"`;
482
+ const metrics = tempCtx.measureText(unicodeChar);
483
+ const ascent = metrics.actualBoundingBoxAscent || fontSize * 0.8;
484
+ const descent = metrics.actualBoundingBoxDescent || fontSize * 0.2;
485
+ const height = ascent + descent;
486
+ const width = metrics.width;
487
+ const canvas = document.createElement("canvas");
488
+ canvas.width = Math.ceil(width * dpr);
489
+ canvas.height = Math.ceil(height * dpr);
490
+ const ctx = canvas.getContext("2d");
491
+ ctx.scale(dpr, dpr);
492
+ ctx.font = tempCtx.font;
493
+ ctx.textAlign = "left";
494
+ ctx.textBaseline = "alphabetic";
495
+ ctx.fillStyle = color;
496
+ ctx.fillText(unicodeChar, 0, ascent);
497
+ return canvas.toDataURL();
498
+ });
499
+ }
500
+ function isStylesheetLoaded(href) {
501
+ return Array.from(document.styleSheets).some((sheet) => sheet.href === href);
502
+ }
503
+ function injectLinkIfMissing(href) {
504
+ return new Promise((resolve) => {
505
+ if (isStylesheetLoaded(href)) return resolve(null);
506
+ const link = document.createElement("link");
507
+ link.rel = "stylesheet";
508
+ link.href = href;
509
+ link.setAttribute("data-snapdom", "injected-import");
510
+ link.onload = () => resolve(link);
511
+ link.onerror = () => resolve(null);
512
+ document.head.appendChild(link);
513
+ });
514
+ }
515
+ function embedCustomFonts() {
516
+ return __async(this, arguments, function* ({ ignoreIconFonts = true, preCached = false } = {}) {
517
+ if (resourceCache.has("fonts-embed-css")) {
518
+ if (preCached) {
519
+ const style = document.createElement("style");
520
+ style.setAttribute("data-snapdom", "embedFonts");
521
+ style.textContent = resourceCache.get("fonts-embed-css");
522
+ document.head.appendChild(style);
387
523
  }
388
- const urlRegex = /url\(([^)]+)\)/g;
389
- const inlinedCSS = await Promise.all(
390
- Array.from(cssText.matchAll(urlRegex)).map(async (match) => {
391
- let rawUrl = extractURL(match[0]);
392
- if (!rawUrl) return null;
393
- let url = rawUrl;
394
- if (!url.startsWith("http")) {
395
- url = new URL(url, link.href).href;
396
- }
397
- try {
398
- url = encodeURI(url);
399
- } catch (e) {
400
- console.warn("[snapdom] Failed to encode font URL:", url);
401
- }
402
- if (ignoreIconFonts && isIconFont(url)) {
403
- return null;
404
- }
405
- if (resourceCache.has(url)) {
406
- return { original: match[0], inlined: `url(${resourceCache.get(url)})` };
524
+ return resourceCache.get("fonts-embed-css");
525
+ }
526
+ const importRegex = /@import\s+url\(["']?([^"')]+)["']?\)/g;
527
+ const styleImports = [];
528
+ for (const styleTag of document.querySelectorAll("style")) {
529
+ const cssText = styleTag.textContent || "";
530
+ const matches = Array.from(cssText.matchAll(importRegex));
531
+ for (const match of matches) {
532
+ const importUrl = match[1];
533
+ if (!isStylesheetLoaded(importUrl)) {
534
+ styleImports.push(importUrl);
535
+ }
536
+ }
537
+ }
538
+ yield Promise.all(styleImports.map(injectLinkIfMissing));
539
+ const links = Array.from(document.querySelectorAll('link[rel="stylesheet"]')).filter((link) => link.href);
540
+ let finalCSS = "";
541
+ for (const link of links) {
542
+ try {
543
+ const res = yield fetch(link.href);
544
+ const cssText = yield res.text();
545
+ if (ignoreIconFonts && (isIconFont(link.href) || isIconFont(cssText))) continue;
546
+ const urlRegex = /url\((["']?)([^"')]+)\1\)/g;
547
+ const inlinedCSS = yield Promise.all(
548
+ Array.from(cssText.matchAll(urlRegex)).map((match) => __async(null, null, function* () {
549
+ let rawUrl = extractURL(match[0]);
550
+ if (!rawUrl) return null;
551
+ let url = rawUrl;
552
+ if (!url.startsWith("http") && !url.startsWith("data:")) {
553
+ url = new URL(url, link.href).href;
554
+ }
555
+ if (ignoreIconFonts && isIconFont(url)) return null;
556
+ if (resourceCache.has(url)) {
557
+ processedFontURLs.add(url);
558
+ return { original: match[0], inlined: `url(${resourceCache.get(url)})` };
559
+ }
560
+ if (processedFontURLs.has(url)) return null;
561
+ try {
562
+ const fontRes = yield fetch(url);
563
+ const blob = yield fontRes.blob();
564
+ const b64 = yield new Promise((resolve) => {
565
+ const reader = new FileReader();
566
+ reader.onload = () => resolve(reader.result);
567
+ reader.readAsDataURL(blob);
568
+ });
569
+ resourceCache.set(url, b64);
570
+ processedFontURLs.add(url);
571
+ return { original: match[0], inlined: `url(${b64})` };
572
+ } catch (e) {
573
+ console.warn("[snapdom] Failed to fetch font resource:", url);
574
+ return null;
575
+ }
576
+ }))
577
+ );
578
+ let cssFinal = cssText;
579
+ for (const r of inlinedCSS) {
580
+ if (r) cssFinal = cssFinal.replace(r.original, r.inlined);
581
+ }
582
+ finalCSS += cssFinal + "\n";
583
+ } catch (e) {
584
+ console.warn("[snapdom] Failed to fetch CSS:", link.href);
585
+ }
586
+ }
587
+ for (const sheet of document.styleSheets) {
588
+ try {
589
+ if (!sheet.href || links.every((link) => link.href !== sheet.href)) {
590
+ for (const rule of sheet.cssRules) {
591
+ if (rule.type === CSSRule.FONT_FACE_RULE) {
592
+ const src = rule.style.getPropertyValue("src");
593
+ if (!src) continue;
594
+ const urlRegex = /url\((["']?)([^"')]+)\1\)/g;
595
+ let inlinedSrc = src;
596
+ const matches = Array.from(src.matchAll(urlRegex));
597
+ for (const match of matches) {
598
+ let rawUrl = match[2].trim();
599
+ if (!rawUrl) continue;
600
+ let url = rawUrl;
601
+ if (!url.startsWith("http") && !url.startsWith("data:")) {
602
+ url = new URL(url, sheet.href || location.href).href;
603
+ }
604
+ if (ignoreIconFonts && isIconFont(url)) continue;
605
+ if (resourceCache.has(url)) {
606
+ processedFontURLs.add(url);
607
+ inlinedSrc = inlinedSrc.replace(match[0], `url(${resourceCache.get(url)})`);
608
+ continue;
609
+ }
610
+ if (processedFontURLs.has(url)) continue;
611
+ try {
612
+ const res = yield fetch(url);
613
+ const blob = yield res.blob();
614
+ const b64 = yield new Promise((resolve) => {
615
+ const reader = new FileReader();
616
+ reader.onload = () => resolve(reader.result);
617
+ reader.readAsDataURL(blob);
618
+ });
619
+ resourceCache.set(url, b64);
620
+ processedFontURLs.add(url);
621
+ inlinedSrc = inlinedSrc.replace(match[0], `url(${b64})`);
622
+ } catch (e) {
623
+ console.warn("[snapdom] Failed to fetch font URL:", url);
624
+ }
625
+ }
626
+ finalCSS += `@font-face {
627
+ font-family: ${rule.style.getPropertyValue("font-family")};
628
+ src: ${inlinedSrc};
629
+ font-style: ${rule.style.getPropertyValue("font-style") || "normal"};
630
+ font-weight: ${rule.style.getPropertyValue("font-weight") || "normal"};
631
+ }
632
+ `;
633
+ }
407
634
  }
408
- try {
409
- const fontRes = await fetch(url);
410
- const blob = await fontRes.blob();
411
- const b64 = await new Promise((resolve) => {
412
- const reader = new FileReader();
413
- reader.onload = () => resolve(reader.result);
414
- reader.readAsDataURL(blob);
415
- });
416
- resourceCache.set(url, b64);
417
- return { original: match[0], inlined: `url(${b64})` };
418
- } catch (err) {
419
- console.warn("\u274C Failed to fetch font:", url);
420
- return null;
635
+ }
636
+ } catch (e) {
637
+ console.warn("[snapdom] Cannot access stylesheet", sheet.href, e);
638
+ }
639
+ }
640
+ for (const font of document.fonts) {
641
+ if (font.family && font.status === "loaded" && font._snapdomSrc) {
642
+ let b64 = font._snapdomSrc;
643
+ if (!b64.startsWith("data:")) {
644
+ if (resourceCache.has(font._snapdomSrc)) {
645
+ b64 = resourceCache.get(font._snapdomSrc);
646
+ processedFontURLs.add(font._snapdomSrc);
647
+ } else if (!processedFontURLs.has(font._snapdomSrc)) {
648
+ try {
649
+ const res = yield fetch(font._snapdomSrc);
650
+ const blob = yield res.blob();
651
+ b64 = yield new Promise((resolve) => {
652
+ const reader = new FileReader();
653
+ reader.onload = () => resolve(reader.result);
654
+ reader.readAsDataURL(blob);
655
+ });
656
+ resourceCache.set(font._snapdomSrc, b64);
657
+ processedFontURLs.add(font._snapdomSrc);
658
+ } catch (e) {
659
+ console.warn("[snapdom] Failed to fetch dynamic font src:", font._snapdomSrc);
660
+ continue;
661
+ }
421
662
  }
422
- })
423
- );
424
- let cssFinal = cssText;
425
- for (const r of inlinedCSS) {
426
- if (r) {
427
- cssFinal = cssFinal.replace(r.original, r.inlined);
428
663
  }
664
+ finalCSS += `@font-face {
665
+ font-family: '${font.family}';
666
+ src: url(${b64});
667
+ font-style: ${font.style || "normal"};
668
+ font-weight: ${font.weight || "normal"};
669
+ }
670
+ `;
429
671
  }
430
- finalCSS += cssFinal + "\n";
431
- } catch (e) {
432
- console.warn("\u274C Failed to fetch CSS:", link.href);
433
672
  }
434
- }
435
- if (finalCSS && preCached) {
436
- const style = document.createElement("style");
437
- style.setAttribute("data-snapdom", "embedFonts");
438
- style.textContent = finalCSS;
439
- document.head.appendChild(style);
440
- }
441
- return finalCSS;
673
+ if (finalCSS) {
674
+ resourceCache.set("fonts-embed-css", finalCSS);
675
+ if (preCached) {
676
+ const style = document.createElement("style");
677
+ style.setAttribute("data-snapdom", "embedFonts");
678
+ style.textContent = finalCSS;
679
+ document.head.appendChild(style);
680
+ }
681
+ }
682
+ return finalCSS;
683
+ });
442
684
  }
443
685
 
444
686
  // src/modules/pseudo.js
445
- async function inlinePseudoElements(source, clone, styleMap, styleCache, compress, embedFonts = false) {
446
- if (!(source instanceof Element) || !(clone instanceof Element)) return;
447
- for (const pseudo of ["::before", "::after"]) {
448
- try {
449
- const style = getStyle(source, pseudo);
450
- if (!style) continue;
451
- const content = style.getPropertyValue("content");
452
- const bg = style.getPropertyValue("background-image");
453
- const hasContent = content && content !== "none" && content !== '""' && content !== "''";
454
- const hasBg = bg && bg.startsWith("url(");
455
- if (hasContent || hasBg) {
456
- const fontFamily = style.getPropertyValue("font-family");
457
- const fontSize = parseInt(style.getPropertyValue("font-size")) || 32;
458
- const fontWeight = parseInt(style.getPropertyValue("font-weight")) || false;
459
- const color = style.getPropertyValue("color") || "#000";
460
- const pseudoEl = document.createElement("span");
461
- pseudoEl.dataset.snapdomPseudo = pseudo;
462
- const snapshot = snapshotComputedStyle(style);
463
- const key = getStyleKey(snapshot, "span", compress);
464
- styleMap.set(pseudoEl, key);
465
- const isIconFont2 = fontFamily && /font.*awesome|material|bootstrap|glyphicons|ionicons|remixicon|simple-line-icons|octicons|feather|typicons|weathericons/i.test(
466
- fontFamily
467
- );
468
- let cleanContent = parseContent(content);
469
- if (!embedFonts && isIconFont2 && cleanContent.length === 1) {
470
- const imgEl = document.createElement("img");
471
- imgEl.src = await iconToImage(
472
- cleanContent,
473
- fontFamily,
474
- fontWeight,
475
- fontSize,
476
- color
687
+ function inlinePseudoElements(source, clone, styleMap, styleCache, compress, embedFonts = false) {
688
+ return __async(this, null, function* () {
689
+ if (!(source instanceof Element) || !(clone instanceof Element)) return;
690
+ for (const pseudo of ["::before", "::after", "::first-letter"]) {
691
+ try {
692
+ const style = getStyle(source, pseudo);
693
+ if (!style || typeof style[Symbol.iterator] !== "function") continue;
694
+ if (pseudo === "::first-letter") {
695
+ const normal = getComputedStyle(source);
696
+ const isMeaningful = style.color !== normal.color || style.fontSize !== normal.fontSize || style.fontWeight !== normal.fontWeight;
697
+ if (!isMeaningful) continue;
698
+ const textNode = Array.from(clone.childNodes).find(
699
+ (n) => n.nodeType === Node.TEXT_NODE && n.textContent && n.textContent.trim().length > 0
477
700
  );
478
- imgEl.style = "display:block;width:100%;height:100%;object-fit:contain;";
479
- pseudoEl.appendChild(imgEl);
480
- } else if (cleanContent.startsWith("url(")) {
481
- const rawUrl = extractURL(cleanContent);
482
- if (rawUrl) {
483
- try {
484
- const imgEl = document.createElement("img");
485
- const dataUrl = await fetchImage(encodeURI(rawUrl));
486
- imgEl.src = dataUrl;
487
- imgEl.style = "display:block;width:100%;height:100%;object-fit:contain;";
488
- pseudoEl.appendChild(imgEl);
489
- } catch (e) {
490
- console.error(`[snapdom] Error in pseudo ${pseudo} for`, source, e);
701
+ if (!textNode) continue;
702
+ const text = textNode.textContent;
703
+ const match = text.match(/^([^\p{L}\p{N}\s]*[\p{L}\p{N}](?:['’])?)/u);
704
+ const first = match == null ? void 0 : match[0];
705
+ const rest = text.slice((first == null ? void 0 : first.length) || 0);
706
+ if (!first || /[\uD800-\uDFFF]/.test(first)) continue;
707
+ const span = document.createElement("span");
708
+ span.textContent = first;
709
+ span.dataset.snapdomPseudo = "::first-letter";
710
+ const snapshot = snapshotComputedStyle(style);
711
+ const key = getStyleKey(snapshot, "span", compress);
712
+ styleMap.set(span, key);
713
+ const restNode = document.createTextNode(rest);
714
+ clone.replaceChild(restNode, textNode);
715
+ clone.insertBefore(span, restNode);
716
+ continue;
717
+ }
718
+ const content = style.getPropertyValue("content");
719
+ const bg = style.getPropertyValue("background-image");
720
+ const bgColor = style.getPropertyValue("background-color");
721
+ const hasContent = content !== "none";
722
+ const hasBg = bg && bg !== "none";
723
+ const hasBgColor = bgColor && bgColor !== "transparent" && bgColor !== "rgba(0, 0, 0, 0)";
724
+ if (hasContent || hasBg || hasBgColor) {
725
+ const fontFamily = style.getPropertyValue("font-family");
726
+ const fontSize = parseInt(style.getPropertyValue("font-size")) || 32;
727
+ const fontWeight = parseInt(style.getPropertyValue("font-weight")) || false;
728
+ const color = style.getPropertyValue("color") || "#000";
729
+ const pseudoEl = document.createElement("span");
730
+ pseudoEl.dataset.snapdomPseudo = pseudo;
731
+ const snapshot = snapshotComputedStyle(style);
732
+ const key = getStyleKey(snapshot, "span", compress);
733
+ styleMap.set(pseudoEl, key);
734
+ const isIconFont2 = fontFamily && /font.*awesome|material|bootstrap|glyphicons|ionicons|remixicon|simple-line-icons|octicons|feather|typicons|weathericons|lucide/i.test(
735
+ fontFamily
736
+ );
737
+ const cleanContent = parseContent(content);
738
+ if (!embedFonts && isIconFont2 && cleanContent.length === 1) {
739
+ const imgEl = document.createElement("img");
740
+ imgEl.src = yield iconToImage(cleanContent, fontFamily, fontWeight, fontSize, color);
741
+ imgEl.style = "display:block;width:100%;height:100%;object-fit:contain;";
742
+ pseudoEl.appendChild(imgEl);
743
+ } else if (cleanContent.startsWith("url(")) {
744
+ const rawUrl = extractURL(cleanContent);
745
+ if (rawUrl && rawUrl.trim() !== "") {
746
+ try {
747
+ const imgEl = document.createElement("img");
748
+ const dataUrl = yield fetchImage(safeEncodeURI(rawUrl));
749
+ imgEl.src = dataUrl;
750
+ imgEl.style = "display:block;width:100%;height:100%;object-fit:contain;";
751
+ pseudoEl.appendChild(imgEl);
752
+ } catch (e) {
753
+ console.error(`[snapdom] Error in pseudo ${pseudo} for`, source, e);
754
+ }
491
755
  }
756
+ } else if (!isIconFont2 && cleanContent && cleanContent !== "none") {
757
+ pseudoEl.textContent = cleanContent;
492
758
  }
493
- } else if (!isIconFont2 && cleanContent && cleanContent !== "none") {
494
- pseudoEl.textContent = cleanContent;
495
- }
496
- if (hasBg && bg) {
497
- const rawUrl = extractURL(bg);
498
- if (rawUrl) {
759
+ if (hasBg) {
499
760
  try {
500
- const dataUrl = await fetchImage(encodeURI(rawUrl));
501
- pseudoEl.style.backgroundImage = `url(${dataUrl})`;
761
+ const bgSplits = splitBackgroundImage(bg);
762
+ const newBgParts = yield Promise.all(
763
+ bgSplits.map((entry) => inlineSingleBackgroundEntry(entry))
764
+ );
765
+ pseudoEl.style.backgroundImage = newBgParts.join(", ");
502
766
  } catch (e) {
503
767
  console.warn(`[snapdom] Failed to inline background-image for ${pseudo}`, e);
504
768
  }
505
769
  }
770
+ if (hasBgColor) pseudoEl.style.backgroundColor = bgColor;
771
+ const hasContent2 = pseudoEl.childNodes.length > 0 || pseudoEl.textContent && pseudoEl.textContent.trim() !== "";
772
+ const hasVisibleBox = hasContent2 || hasBg || hasBgColor;
773
+ if (!hasVisibleBox) continue;
774
+ pseudo === "::before" ? clone.insertBefore(pseudoEl, clone.firstChild) : clone.appendChild(pseudoEl);
506
775
  }
507
- const hasContent2 = pseudoEl.childNodes.length > 0 || pseudoEl.textContent && pseudoEl.textContent.trim() !== "";
508
- if (!hasContent2 && !hasBg) continue;
509
- if (pseudo === "::before") {
510
- clone.insertBefore(pseudoEl, clone.firstChild);
511
- } else {
512
- clone.appendChild(pseudoEl);
513
- }
776
+ } catch (e) {
777
+ console.warn(`[snapdom] Failed to capture ${pseudo} for`, source, e);
514
778
  }
515
- } catch (e) {
516
- console.warn(`[snapdom] Failed to capture ${pseudo} for`, source, e);
517
779
  }
518
- }
519
- const sChildren = Array.from(source.children);
520
- const cChildren = Array.from(clone.children).filter((child) => !child.dataset.snapdomPseudo);
521
- for (let i = 0; i < Math.min(sChildren.length, cChildren.length); i++) {
522
- await inlinePseudoElements(
523
- sChildren[i],
524
- cChildren[i],
525
- styleMap,
526
- styleCache,
527
- compress,
528
- embedFonts
529
- );
530
- }
780
+ const sChildren = Array.from(source.children);
781
+ const cChildren = Array.from(clone.children).filter((child) => !child.dataset.snapdomPseudo);
782
+ for (let i = 0; i < Math.min(sChildren.length, cChildren.length); i++) {
783
+ yield inlinePseudoElements(
784
+ sChildren[i],
785
+ cChildren[i],
786
+ styleMap,
787
+ styleCache,
788
+ compress,
789
+ embedFonts
790
+ );
791
+ }
792
+ });
531
793
  }
532
794
 
533
795
  // src/core/prepare.js
534
- async function prepareClone(element, compress = false, embedFonts = false) {
535
- const styleMap = /* @__PURE__ */ new Map();
536
- const styleCache = /* @__PURE__ */ new WeakMap();
537
- const nodeMap = /* @__PURE__ */ new Map();
538
- let clone;
539
- try {
540
- clone = deepClone(element, styleMap, styleCache, nodeMap, compress);
541
- } catch (e) {
542
- console.warn("deepClone failed:", e);
543
- throw e;
544
- }
545
- try {
546
- await inlinePseudoElements(element, clone, styleMap, styleCache, compress, embedFonts);
547
- } catch (e) {
548
- console.warn("inlinePseudoElements failed:", e);
549
- }
550
- let classCSS = "";
551
- if (compress) {
552
- const keyToClass = generateCSSClasses(styleMap);
553
- classCSS = Array.from(keyToClass.entries()).map(([key, className]) => `.${className}{${key}}`).join("");
554
- for (const [node, key] of styleMap.entries()) {
555
- if (node.tagName === "STYLE") continue;
556
- const className = keyToClass.get(key);
557
- if (className) node.classList.add(className);
558
- const bgImage = node.style?.backgroundImage;
559
- node.removeAttribute("style");
560
- if (bgImage && bgImage !== "none") node.style.backgroundImage = bgImage;
796
+ function prepareClone(element, compress = false, embedFonts = false) {
797
+ return __async(this, null, function* () {
798
+ var _a;
799
+ const styleMap = /* @__PURE__ */ new Map();
800
+ const styleCache = /* @__PURE__ */ new WeakMap();
801
+ const nodeMap = /* @__PURE__ */ new Map();
802
+ let clone;
803
+ try {
804
+ clone = deepClone(element, styleMap, styleCache, nodeMap, compress);
805
+ } catch (e) {
806
+ console.warn("deepClone failed:", e);
807
+ throw e;
561
808
  }
562
- } else {
563
- for (const [node, key] of styleMap.entries()) {
564
- if (node.tagName === "STYLE") continue;
565
- node.setAttribute("style", key.replace(/;/g, "; "));
809
+ try {
810
+ yield inlinePseudoElements(element, clone, styleMap, styleCache, compress, embedFonts);
811
+ } catch (e) {
812
+ console.warn("inlinePseudoElements failed:", e);
566
813
  }
567
- }
568
- for (const [cloneNode, originalNode] of nodeMap.entries()) {
569
- const scrollX = originalNode.scrollLeft;
570
- const scrollY = originalNode.scrollTop;
571
- const hasScroll = scrollX || scrollY;
572
- if (hasScroll && cloneNode instanceof HTMLElement) {
573
- cloneNode.style.overflow = "hidden";
574
- cloneNode.style.scrollbarWidth = "none";
575
- cloneNode.style.msOverflowStyle = "none";
576
- const inner = document.createElement("div");
577
- inner.style.transform = `translate(${-scrollX}px, ${-scrollY}px)`;
578
- inner.style.willChange = "transform";
579
- inner.style.display = "inline-block";
580
- inner.style.width = "100%";
581
- while (cloneNode.firstChild) {
582
- inner.appendChild(cloneNode.firstChild);
814
+ let classCSS = "";
815
+ if (compress) {
816
+ const keyToClass = generateCSSClasses(styleMap);
817
+ classCSS = Array.from(keyToClass.entries()).map(([key, className]) => `.${className}{${key}}`).join("");
818
+ for (const [node, key] of styleMap.entries()) {
819
+ if (node.tagName === "STYLE") continue;
820
+ const className = keyToClass.get(key);
821
+ if (className) node.classList.add(className);
822
+ const bgImage = (_a = node.style) == null ? void 0 : _a.backgroundImage;
823
+ node.removeAttribute("style");
824
+ if (bgImage && bgImage !== "none") node.style.backgroundImage = bgImage;
825
+ }
826
+ } else {
827
+ for (const [node, key] of styleMap.entries()) {
828
+ if (node.tagName === "STYLE") continue;
829
+ node.setAttribute("style", key.replace(/;/g, "; "));
583
830
  }
584
- cloneNode.appendChild(inner);
585
831
  }
586
- }
587
- return { clone, classCSS, styleCache };
832
+ for (const [cloneNode, originalNode] of nodeMap.entries()) {
833
+ const scrollX = originalNode.scrollLeft;
834
+ const scrollY = originalNode.scrollTop;
835
+ const hasScroll = scrollX || scrollY;
836
+ if (hasScroll && cloneNode instanceof HTMLElement) {
837
+ cloneNode.style.overflow = "hidden";
838
+ cloneNode.style.scrollbarWidth = "none";
839
+ cloneNode.style.msOverflowStyle = "none";
840
+ const inner = document.createElement("div");
841
+ inner.style.transform = `translate(${-scrollX}px, ${-scrollY}px)`;
842
+ inner.style.willChange = "transform";
843
+ inner.style.display = "inline-block";
844
+ inner.style.width = "100%";
845
+ while (cloneNode.firstChild) {
846
+ inner.appendChild(cloneNode.firstChild);
847
+ }
848
+ cloneNode.appendChild(inner);
849
+ }
850
+ }
851
+ if (element === nodeMap.get(clone)) {
852
+ const computed = styleCache.get(element) || window.getComputedStyle(element);
853
+ styleCache.set(element, computed);
854
+ const transform = stripTranslate(computed.transform);
855
+ clone.style.margin = "0";
856
+ clone.style.position = "static";
857
+ clone.style.top = "auto";
858
+ clone.style.left = "auto";
859
+ clone.style.right = "auto";
860
+ clone.style.bottom = "auto";
861
+ clone.style.zIndex = "auto";
862
+ clone.style.float = "none";
863
+ clone.style.clear = "none";
864
+ clone.style.transform = transform || "";
865
+ }
866
+ return { clone, classCSS, styleCache };
867
+ });
588
868
  }
589
869
 
590
870
  // src/modules/images.js
591
- async function inlineImages(clone) {
592
- const imgs = Array.from(clone.querySelectorAll("img"));
593
- const processImg = async (img) => {
594
- const src = img.src;
595
- try {
596
- const dataUrl = await fetchImage(src);
597
- img.src = dataUrl;
598
- if (!img.width) img.width = img.naturalWidth || 100;
599
- if (!img.height) img.height = img.naturalHeight || 100;
600
- } catch {
601
- const fallback = document.createElement("div");
602
- fallback.style = `width: ${img.width || 100}px; height: ${img.height || 100}px; background: #ccc; display: inline-block; text-align: center; line-height: ${img.height || 100}px; color: #666; font-size: 12px;`;
603
- fallback.innerText = "img";
604
- img.replaceWith(fallback);
871
+ function inlineImages(_0) {
872
+ return __async(this, arguments, function* (clone, options = {}) {
873
+ const imgs = Array.from(clone.querySelectorAll("img"));
874
+ const processImg = (img) => __async(null, null, function* () {
875
+ const src = img.src;
876
+ try {
877
+ const crossOrigin = options.crossOrigin ? options.crossOrigin(src) : "anonymous";
878
+ const dataUrl = yield fetchImage(src, 3e3, crossOrigin);
879
+ img.src = dataUrl;
880
+ if (!img.width) img.width = img.naturalWidth || 100;
881
+ if (!img.height) img.height = img.naturalHeight || 100;
882
+ } catch (e) {
883
+ const fallback = document.createElement("div");
884
+ fallback.style = `width: ${img.width || 100}px; height: ${img.height || 100}px; background: #ccc; display: inline-block; text-align: center; line-height: ${img.height || 100}px; color: #666; font-size: 12px;`;
885
+ fallback.innerText = "img";
886
+ img.replaceWith(fallback);
887
+ }
888
+ });
889
+ for (let i = 0; i < imgs.length; i += 4) {
890
+ const group = imgs.slice(i, i + 4).map(processImg);
891
+ yield Promise.allSettled(group);
605
892
  }
606
- };
607
- for (let i = 0; i < imgs.length; i += 4) {
608
- const group = imgs.slice(i, i + 4).map(processImg);
609
- await Promise.allSettled(group);
610
- }
893
+ });
611
894
  }
612
895
 
613
896
  // src/modules/background.js
614
- async function inlineBackgroundImages(source, clone, styleCache) {
615
- const queue = [[source, clone]];
616
- while (queue.length) {
617
- const [srcNode, cloneNode] = queue.shift();
618
- const style = styleCache.get(srcNode) || getStyle(srcNode);
619
- if (!styleCache.has(srcNode)) styleCache.set(srcNode, style);
620
- const bg = style.getPropertyValue("background-image");
621
- const rawUrl = extractURL(bg);
622
- if (rawUrl) {
623
- try {
624
- let bgUrl = encodeURI(rawUrl);
625
- let dataUrl;
626
- if (bgCache.has(bgUrl)) {
627
- dataUrl = bgCache.get(bgUrl);
628
- } else {
629
- dataUrl = await fetchImage(bgUrl);
630
- bgCache.set(bgUrl, dataUrl);
897
+ function inlineBackgroundImages(_0, _1, _2) {
898
+ return __async(this, arguments, function* (source, clone, styleCache, options = {}) {
899
+ const queue = [[source, clone]];
900
+ while (queue.length) {
901
+ const [srcNode, cloneNode] = queue.shift();
902
+ const style = styleCache.get(srcNode) || getStyle(srcNode);
903
+ if (!styleCache.has(srcNode)) styleCache.set(srcNode, style);
904
+ const bg = style.getPropertyValue("background-image");
905
+ const bgColor = style.getPropertyValue("background-color");
906
+ if (!bg || bg === "none") {
907
+ const sChildren2 = Array.from(srcNode.children);
908
+ const cChildren2 = Array.from(cloneNode.children);
909
+ for (let i = 0; i < Math.min(sChildren2.length, cChildren2.length); i++) {
910
+ queue.push([sChildren2[i], cChildren2[i]]);
631
911
  }
632
- cloneNode.style.backgroundImage = `url(${dataUrl})`;
633
- } catch {
634
- cloneNode.style.backgroundImage = "none";
912
+ continue;
913
+ }
914
+ const bgSplits = splitBackgroundImage(bg);
915
+ const newBgParts = yield Promise.all(
916
+ bgSplits.map((entry) => inlineSingleBackgroundEntry(entry, options))
917
+ );
918
+ const hasRealBg = newBgParts.some(
919
+ (p) => p && p !== "none" && !/^url\(undefined\)/.test(p)
920
+ );
921
+ if (hasRealBg) {
922
+ cloneNode.style.backgroundImage = newBgParts.join(", ");
923
+ }
924
+ if (bgColor && bgColor !== "transparent" && bgColor !== "rgba(0, 0, 0, 0)") {
925
+ cloneNode.style.backgroundColor = bgColor;
926
+ }
927
+ const sChildren = Array.from(srcNode.children);
928
+ const cChildren = Array.from(cloneNode.children);
929
+ for (let i = 0; i < Math.min(sChildren.length, cChildren.length); i++) {
930
+ queue.push([sChildren[i], cChildren[i]]);
635
931
  }
636
932
  }
637
- const sChildren = Array.from(srcNode.children);
638
- const cChildren = Array.from(cloneNode.children);
639
- for (let i = 0; i < Math.min(sChildren.length, cChildren.length); i++) {
640
- queue.push([sChildren[i], cChildren[i]]);
641
- }
642
- }
933
+ });
643
934
  }
644
935
 
645
936
  // src/core/capture.js
646
- async function captureDOM(element, options = {}) {
647
- if (!element) throw new Error("Element cannot be null or undefined");
648
- const { compress = true, embedFonts = false, fast = true, scale = 1 } = options;
649
- let clone, classCSS, styleCache;
650
- let fontsCSS = "";
651
- let baseCSS = "";
652
- let dataURL;
653
- let svgString;
654
- ({ clone, classCSS, styleCache } = await prepareClone(element, compress, embedFonts));
655
- await new Promise((resolve) => {
656
- idle(async () => {
657
- await inlineImages(clone);
658
- resolve();
659
- }, { fast });
660
- });
661
- await new Promise((resolve) => {
662
- idle(async () => {
663
- await inlineBackgroundImages(element, clone, styleCache);
664
- resolve();
665
- }, { fast });
666
- });
667
- if (embedFonts) {
668
- await new Promise((resolve) => {
669
- idle(async () => {
670
- fontsCSS = await embedCustomFonts({ ignoreIconFonts: !embedFonts });
937
+ function captureDOM(_0) {
938
+ return __async(this, arguments, function* (element, options = {}) {
939
+ if (!element) throw new Error("Element cannot be null or undefined");
940
+ if (!(element instanceof Element)) throw new Error("captureDOM: Only Element nodes are supported");
941
+ const { compress = true, embedFonts = false, fast = true, scale = 1 } = options;
942
+ let clone, classCSS, styleCache;
943
+ let fontsCSS = "";
944
+ let baseCSS = "";
945
+ let dataURL;
946
+ let svgString;
947
+ ({ clone, classCSS, styleCache } = yield prepareClone(element, compress, embedFonts));
948
+ yield new Promise((resolve) => {
949
+ idle(() => __async(null, null, function* () {
950
+ yield inlineImages(clone, options);
671
951
  resolve();
672
- }, { fast });
952
+ }), { fast });
673
953
  });
674
- }
675
- if (compress) {
676
- const usedTags = collectUsedTagNames(clone).sort();
677
- const tagKey = usedTags.join(",");
678
- if (baseCSSCache.has(tagKey)) {
679
- baseCSS = baseCSSCache.get(tagKey);
680
- } else {
681
- await new Promise((resolve) => {
682
- idle(() => {
683
- baseCSS = generateDedupedBaseCSS(usedTags);
684
- baseCSSCache.set(tagKey, baseCSS);
954
+ yield new Promise((resolve) => {
955
+ idle(() => __async(null, null, function* () {
956
+ yield inlineBackgroundImages(element, clone, styleCache, options);
957
+ resolve();
958
+ }), { fast });
959
+ });
960
+ if (embedFonts) {
961
+ yield new Promise((resolve) => {
962
+ idle(() => __async(null, null, function* () {
963
+ fontsCSS = yield embedCustomFonts({ ignoreIconFonts: !embedFonts });
685
964
  resolve();
686
- }, { fast });
965
+ }), { fast });
687
966
  });
688
967
  }
689
- }
690
- await new Promise((resolve) => {
691
- idle(() => {
692
- const rect = element.getBoundingClientRect();
693
- const w = Math.ceil(rect.width);
694
- const h = Math.ceil(rect.height);
695
- clone.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
696
- clone.style.transformOrigin = "top left";
697
- if (scale !== 1 && isSafari()) {
698
- clone.style.scale = `${scale}`;
968
+ if (compress) {
969
+ const usedTags = collectUsedTagNames(clone).sort();
970
+ const tagKey = usedTags.join(",");
971
+ if (baseCSSCache.has(tagKey)) {
972
+ baseCSS = baseCSSCache.get(tagKey);
973
+ } else {
974
+ yield new Promise((resolve) => {
975
+ idle(() => {
976
+ baseCSS = generateDedupedBaseCSS(usedTags);
977
+ baseCSSCache.set(tagKey, baseCSS);
978
+ resolve();
979
+ }, { fast });
980
+ });
699
981
  }
700
- const svgNS = "http://www.w3.org/2000/svg";
701
- const fo = document.createElementNS(svgNS, "foreignObject");
702
- fo.setAttribute("width", "100%");
703
- fo.setAttribute("height", "100%");
704
- const styleTag = document.createElement("style");
705
- styleTag.textContent = baseCSS + fontsCSS + "svg{overflow:visible;}" + classCSS;
706
- fo.appendChild(styleTag);
707
- fo.appendChild(clone);
708
- const serializer = new XMLSerializer();
709
- const foString = serializer.serializeToString(fo);
710
- const svgHeader = `<svg xmlns="${svgNS}" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">`;
711
- const svgFooter = "</svg>";
712
- svgString = svgHeader + foString + svgFooter;
713
- dataURL = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgString)}`;
714
- resolve();
715
- }, { fast });
982
+ }
983
+ yield new Promise((resolve) => {
984
+ idle(() => {
985
+ const rect = element.getBoundingClientRect();
986
+ let w = rect.width;
987
+ let h = rect.height;
988
+ const hasW = Number.isFinite(options.width);
989
+ const hasH = Number.isFinite(options.height);
990
+ const hasScale = typeof scale === "number" && scale !== 1;
991
+ if (!hasScale) {
992
+ const aspect = rect.width / rect.height;
993
+ if (hasW && hasH) {
994
+ w = options.width;
995
+ h = options.height;
996
+ } else if (hasW) {
997
+ w = options.width;
998
+ h = w / aspect;
999
+ } else if (hasH) {
1000
+ h = options.height;
1001
+ w = h * aspect;
1002
+ }
1003
+ }
1004
+ w = Math.ceil(w);
1005
+ h = Math.ceil(h);
1006
+ clone.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
1007
+ clone.style.transformOrigin = "top left";
1008
+ if (!hasScale && (hasW || hasH)) {
1009
+ const originalW = rect.width;
1010
+ const originalH = rect.height;
1011
+ const scaleX = w / originalW;
1012
+ const scaleY = h / originalH;
1013
+ const existingTransform = clone.style.transform || "";
1014
+ const scaleTransform = `scale(${scaleX}, ${scaleY})`;
1015
+ clone.style.transform = `${scaleTransform} ${existingTransform}`.trim();
1016
+ } else if (hasScale && isSafari()) {
1017
+ clone.style.scale = `${scale}`;
1018
+ }
1019
+ const svgNS = "http://www.w3.org/2000/svg";
1020
+ const fo = document.createElementNS(svgNS, "foreignObject");
1021
+ fo.setAttribute("width", "100%");
1022
+ fo.setAttribute("height", "100%");
1023
+ const styleTag = document.createElement("style");
1024
+ styleTag.textContent = baseCSS + fontsCSS + "svg{overflow:visible;}" + classCSS;
1025
+ fo.appendChild(styleTag);
1026
+ fo.appendChild(clone);
1027
+ const serializer = new XMLSerializer();
1028
+ const foString = serializer.serializeToString(fo);
1029
+ const svgHeader = `<svg xmlns="${svgNS}" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">`;
1030
+ const svgFooter = "</svg>";
1031
+ svgString = svgHeader + foString + svgFooter;
1032
+ dataURL = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgString)}`;
1033
+ resolve();
1034
+ }, { fast });
1035
+ });
1036
+ const sandbox = document.getElementById("snapdom-sandbox");
1037
+ if (sandbox && sandbox.style.position === "absolute") sandbox.remove();
1038
+ return dataURL;
716
1039
  });
717
- const sandbox = document.getElementById("snapdom-sandbox");
718
- if (sandbox && sandbox.style.position === "absolute") sandbox.remove();
719
- return dataURL;
720
1040
  }
721
1041
 
722
1042
  // src/api/snapdom.js
723
- async function toImg(url, { dpr = 1, scale = 1 }) {
724
- const img = new Image();
725
- img.src = url;
726
- await img.decode();
727
- if (isSafari) {
728
- img.width = img.width * scale;
729
- img.height = img.height * scale;
730
- } else {
731
- img.width = img.width / scale;
732
- img.height = img.height / scale;
733
- }
734
- return img;
735
- }
736
- async function toCanvas(url, { dpr = 1, scale = 1 } = {}) {
737
- const img = new Image();
738
- img.src = url;
739
- await img.decode();
740
- const canvas = document.createElement("canvas");
741
- const width = img.width * scale;
742
- const height = img.height * scale;
743
- canvas.width = Math.ceil(width * dpr);
744
- canvas.height = Math.ceil(height * dpr);
745
- const ctx = canvas.getContext("2d");
746
- ctx.scale(dpr, dpr);
747
- ctx.drawImage(img, 0, 0, width, height);
748
- canvas.style.width = `${width}px`;
749
- canvas.style.height = `${height}px`;
750
- return canvas;
751
- }
752
- async function toBlob(url) {
753
- const svgText = decodeURIComponent(url.split(",")[1]);
754
- return new Blob([svgText], { type: "image/svg+xml" });
755
- }
756
- async function createBackground(url, { dpr = 1, scale = 1 }, backgroundColor) {
757
- const baseCanvas = await toCanvas(url, { dpr, scale });
758
- if (!backgroundColor) return baseCanvas;
759
- const temp = document.createElement("canvas");
760
- temp.width = baseCanvas.width;
761
- temp.height = baseCanvas.height;
762
- const ctx = temp.getContext("2d");
763
- ctx.fillStyle = backgroundColor;
764
- ctx.fillRect(0, 0, temp.width, temp.height);
765
- ctx.drawImage(baseCanvas, 0, 0);
766
- return temp;
767
- }
768
- async function toRasterImg(url, { dpr = 1, scale = 1, backgroundColor = "#fff", quality }, format = "png") {
769
- const canvas = await createBackground(url, { dpr, scale }, backgroundColor);
770
- const img = new Image();
771
- img.src = canvas.toDataURL(`image/${format}`, quality);
772
- await img.decode();
773
- img.style.width = `${canvas.width / dpr}px`;
774
- img.style.height = `${canvas.height / dpr}px`;
775
- return img;
776
- }
777
- async function download(url, { dpr = 1, scale = 1, backgroundColor = "#fff", format = "png", filename = "capture" } = {}) {
778
- if (format === "svg") {
779
- const blob = await toBlob(url);
780
- const objectURL = URL.createObjectURL(blob);
781
- const a2 = document.createElement("a");
782
- a2.href = objectURL;
783
- a2.download = `${filename}.svg`;
784
- a2.click();
785
- URL.revokeObjectURL(objectURL);
786
- return;
787
- }
788
- const defaultBg = ["jpg", "jpeg", "webp"].includes(format) ? "#fff" : void 0;
789
- const finalBg = backgroundColor ?? defaultBg;
790
- const canvas = await createBackground(url, { dpr, scale }, finalBg);
791
- const mime = {
792
- jpg: "image/jpeg",
793
- jpeg: "image/jpeg",
794
- png: "image/png",
795
- webp: "image/webp"
796
- }[format] || "image/png";
797
- const dataURL = canvas.toDataURL(mime);
798
- const a = document.createElement("a");
799
- a.href = dataURL;
800
- a.download = `${filename}.${format}`;
801
- a.click();
802
- }
803
- async function snapdom(element, options = {}) {
804
- options = { scale: 1, ...options };
805
- if (!element) throw new Error("Element cannot be null or undefined");
806
- return await snapdom.capture(element, options);
807
- }
808
- snapdom.capture = async (el, options = {}) => {
809
- const url = await captureDOM(el, options);
1043
+ function toImg(_0, _1) {
1044
+ return __async(this, arguments, function* (url, { dpr = 1, scale = 1 }) {
1045
+ const img = new Image();
1046
+ img.src = url;
1047
+ yield img.decode();
1048
+ if (isSafari) {
1049
+ img.width = img.width * scale;
1050
+ img.height = img.height * scale;
1051
+ } else {
1052
+ img.width = img.width / scale;
1053
+ img.height = img.height / scale;
1054
+ }
1055
+ return img;
1056
+ });
1057
+ }
1058
+ function toCanvas(_0) {
1059
+ return __async(this, arguments, function* (url, { dpr = 1, scale = 1 } = {}) {
1060
+ const img = new Image();
1061
+ img.src = url;
1062
+ yield img.decode();
1063
+ const canvas = document.createElement("canvas");
1064
+ const width = img.width * scale;
1065
+ const height = img.height * scale;
1066
+ canvas.width = Math.ceil(width * dpr);
1067
+ canvas.height = Math.ceil(height * dpr);
1068
+ const ctx = canvas.getContext("2d");
1069
+ ctx.scale(dpr, dpr);
1070
+ ctx.drawImage(img, 0, 0, width, height);
1071
+ canvas.style.width = `${width}px`;
1072
+ canvas.style.height = `${height}px`;
1073
+ return canvas;
1074
+ });
1075
+ }
1076
+ function toBlob(url) {
1077
+ return __async(this, null, function* () {
1078
+ const svgText = decodeURIComponent(url.split(",")[1]);
1079
+ return new Blob([svgText], { type: "image/svg+xml" });
1080
+ });
1081
+ }
1082
+ function createBackground(_0, _1, _2) {
1083
+ return __async(this, arguments, function* (url, { dpr = 1, scale = 1 }, backgroundColor) {
1084
+ const baseCanvas = yield toCanvas(url, { dpr, scale });
1085
+ if (!backgroundColor) return baseCanvas;
1086
+ const temp = document.createElement("canvas");
1087
+ temp.width = baseCanvas.width;
1088
+ temp.height = baseCanvas.height;
1089
+ const ctx = temp.getContext("2d");
1090
+ ctx.fillStyle = backgroundColor;
1091
+ ctx.fillRect(0, 0, temp.width, temp.height);
1092
+ ctx.drawImage(baseCanvas, 0, 0);
1093
+ return temp;
1094
+ });
1095
+ }
1096
+ function toRasterImg(_0, _1) {
1097
+ return __async(this, arguments, function* (url, { dpr = 1, scale = 1, backgroundColor = "#fff", quality }, format = "png") {
1098
+ const canvas = yield createBackground(url, { dpr, scale }, backgroundColor);
1099
+ const img = new Image();
1100
+ img.src = canvas.toDataURL(`image/${format}`, quality);
1101
+ yield img.decode();
1102
+ img.style.width = `${canvas.width / dpr}px`;
1103
+ img.style.height = `${canvas.height / dpr}px`;
1104
+ return img;
1105
+ });
1106
+ }
1107
+ function download(_0) {
1108
+ return __async(this, arguments, function* (url, { dpr = 1, scale = 1, backgroundColor = "#fff", format = "png", filename = "capture" } = {}) {
1109
+ if (format === "svg") {
1110
+ const blob = yield toBlob(url);
1111
+ const objectURL = URL.createObjectURL(blob);
1112
+ const a2 = document.createElement("a");
1113
+ a2.href = objectURL;
1114
+ a2.download = `${filename}.svg`;
1115
+ a2.click();
1116
+ URL.revokeObjectURL(objectURL);
1117
+ return;
1118
+ }
1119
+ const defaultBg = ["jpg", "jpeg", "webp"].includes(format) ? "#fff" : void 0;
1120
+ const finalBg = backgroundColor != null ? backgroundColor : defaultBg;
1121
+ const canvas = yield createBackground(url, { dpr, scale }, finalBg);
1122
+ const mime = {
1123
+ jpg: "image/jpeg",
1124
+ jpeg: "image/jpeg",
1125
+ png: "image/png",
1126
+ webp: "image/webp"
1127
+ }[format] || "image/png";
1128
+ const dataURL = canvas.toDataURL(mime);
1129
+ const a = document.createElement("a");
1130
+ a.href = dataURL;
1131
+ a.download = `${filename}.${format}`;
1132
+ a.click();
1133
+ });
1134
+ }
1135
+ function snapdom(_0) {
1136
+ return __async(this, arguments, function* (element, options = {}) {
1137
+ options = __spreadValues({ scale: 1 }, options);
1138
+ if (!element) throw new Error("Element cannot be null or undefined");
1139
+ return yield snapdom.capture(element, options);
1140
+ });
1141
+ }
1142
+ snapdom.capture = (_0, ..._1) => __async(null, [_0, ..._1], function* (el, options = {}) {
1143
+ const url = yield captureDOM(el, options);
810
1144
  const dpr = window.devicePixelRatio || 1;
811
1145
  const scale = options.scale || 1;
812
1146
  return {
@@ -816,72 +1150,96 @@
816
1150
  toImg: () => toImg(url, { dpr, scale }),
817
1151
  toCanvas: () => toCanvas(url, { dpr, scale }),
818
1152
  toBlob: () => toBlob(url),
819
- toPng: (options2) => toRasterImg(url, { dpr, scale, ...options2 }, "png"),
820
- toJpg: (options2) => toRasterImg(url, { dpr, scale, ...options2 }, "jpeg"),
821
- toWebp: (options2) => toRasterImg(url, { dpr, scale, ...options2 }, "webp"),
1153
+ toPng: (options2) => toRasterImg(url, __spreadValues({ dpr, scale }, options2), "png"),
1154
+ toJpg: (options2) => toRasterImg(url, __spreadValues({ dpr, scale }, options2), "jpeg"),
1155
+ toWebp: (options2) => toRasterImg(url, __spreadValues({ dpr, scale }, options2), "webp"),
822
1156
  download: ({ format = "png", filename = "capture", backgroundColor } = {}) => download(url, { dpr, scale, backgroundColor, format, filename })
823
1157
  };
824
- };
825
- snapdom.toRaw = async (el, options) => (await snapdom.capture(el, options)).toRaw();
826
- snapdom.toImg = async (el, options) => (await snapdom.capture(el, options)).toImg();
827
- snapdom.toCanvas = async (el, options) => (await snapdom.capture(el, options)).toCanvas();
828
- snapdom.toBlob = async (el, options) => (await snapdom.capture(el, options)).toBlob();
829
- snapdom.toPng = async (el, options) => (await snapdom.capture(el, options)).toPng(options);
830
- snapdom.toJpg = async (el, options) => (await snapdom.capture(el, options)).toJpg(options);
831
- snapdom.toWebp = async (el, options) => (await snapdom.capture(el, options)).toWebp(options);
832
- snapdom.download = async (el, options = {}) => {
833
- const {
1158
+ });
1159
+ snapdom.toRaw = (el, options) => __async(null, null, function* () {
1160
+ return (yield snapdom.capture(el, options)).toRaw();
1161
+ });
1162
+ snapdom.toImg = (el, options) => __async(null, null, function* () {
1163
+ return (yield snapdom.capture(el, options)).toImg();
1164
+ });
1165
+ snapdom.toCanvas = (el, options) => __async(null, null, function* () {
1166
+ return (yield snapdom.capture(el, options)).toCanvas();
1167
+ });
1168
+ snapdom.toBlob = (el, options) => __async(null, null, function* () {
1169
+ return (yield snapdom.capture(el, options)).toBlob();
1170
+ });
1171
+ snapdom.toPng = (el, options) => __async(null, null, function* () {
1172
+ return (yield snapdom.capture(el, options)).toPng(options);
1173
+ });
1174
+ snapdom.toJpg = (el, options) => __async(null, null, function* () {
1175
+ return (yield snapdom.capture(el, options)).toJpg(options);
1176
+ });
1177
+ snapdom.toWebp = (el, options) => __async(null, null, function* () {
1178
+ return (yield snapdom.capture(el, options)).toWebp(options);
1179
+ });
1180
+ snapdom.download = (_0, ..._1) => __async(null, [_0, ..._1], function* (el, options = {}) {
1181
+ const _a = options, {
834
1182
  format = "png",
835
1183
  filename = "capture",
836
- backgroundColor,
837
- ...rest
838
- } = options;
839
- const capture = await snapdom.capture(el, rest);
840
- return await capture.download({ format, filename, backgroundColor });
841
- };
1184
+ backgroundColor
1185
+ } = _a, rest = __objRest(_a, [
1186
+ "format",
1187
+ "filename",
1188
+ "backgroundColor"
1189
+ ]);
1190
+ const capture = yield snapdom.capture(el, rest);
1191
+ return yield capture.download({ format, filename, backgroundColor });
1192
+ });
842
1193
 
843
1194
  // src/api/preCache.js
844
- async function preCache(root = document, options = {}) {
845
- const { embedFonts = true, reset = false, preWarm = true } = options;
846
- if (reset) {
847
- imageCache.clear();
848
- bgCache.clear();
849
- resourceCache.clear();
850
- baseCSSCache.clear();
851
- computedStyleCache.clear();
852
- return;
853
- }
854
- await document.fonts.ready;
855
- precacheCommonTags();
856
- let imgEls = [], allEls = [];
857
- if (root?.querySelectorAll) {
858
- imgEls = Array.from(root.querySelectorAll("img[src]"));
859
- allEls = Array.from(root.querySelectorAll("*"));
860
- }
861
- const promises = [];
862
- for (const img of imgEls) {
863
- const src = img.src;
864
- if (!imageCache.has(src)) {
865
- promises.push(
866
- fetchImage(src).then((dataURL) => imageCache.set(src, dataURL)).catch(() => {
867
- })
868
- );
1195
+ function preCache() {
1196
+ return __async(this, arguments, function* (root = document, options = {}) {
1197
+ const { embedFonts = true, reset = false, crossOrigin: crossOriginFn } = options;
1198
+ if (reset) {
1199
+ imageCache.clear();
1200
+ bgCache.clear();
1201
+ resourceCache.clear();
1202
+ baseCSSCache.clear();
1203
+ return;
869
1204
  }
870
- }
871
- for (const el of allEls) {
872
- const bg = getComputedStyle(el).backgroundImage;
873
- const url = extractURL(bg);
874
- if (url && !bgCache.has(url)) {
875
- promises.push(
876
- fetchImage(url).then((dataURL) => bgCache.set(url, dataURL)).catch(() => {
877
- })
878
- );
1205
+ yield document.fonts.ready;
1206
+ precacheCommonTags();
1207
+ let imgEls = [], allEls = [];
1208
+ if (root == null ? void 0 : root.querySelectorAll) {
1209
+ imgEls = Array.from(root.querySelectorAll("img[src]"));
1210
+ allEls = Array.from(root.querySelectorAll("*"));
879
1211
  }
880
- }
881
- if (embedFonts) {
882
- await embedCustomFonts({ ignoreIconFonts: !embedFonts, preCached: true });
883
- }
884
- await Promise.all(promises);
1212
+ const promises = [];
1213
+ for (const img of imgEls) {
1214
+ const src = img.src;
1215
+ if (!imageCache.has(src)) {
1216
+ const crossOrigin = crossOriginFn ? crossOriginFn(src) : "anonymous";
1217
+ promises.push(
1218
+ fetchImage(src, 3e3, crossOrigin).then((dataURL) => imageCache.set(src, dataURL)).catch(() => {
1219
+ })
1220
+ );
1221
+ }
1222
+ }
1223
+ for (const el of allEls) {
1224
+ const bg = getStyle(el).backgroundImage;
1225
+ if (bg && bg !== "none") {
1226
+ const bgSplits = splitBackgroundImage(bg);
1227
+ for (const entry of bgSplits) {
1228
+ const isUrl = entry.startsWith("url(");
1229
+ if (isUrl) {
1230
+ promises.push(
1231
+ inlineSingleBackgroundEntry(entry, { crossOrigin: crossOriginFn, skipInline: true }).catch(() => {
1232
+ })
1233
+ );
1234
+ }
1235
+ }
1236
+ }
1237
+ }
1238
+ if (embedFonts) {
1239
+ yield embedCustomFonts({ ignoreIconFonts: !embedFonts, preCached: true });
1240
+ }
1241
+ yield Promise.all(promises);
1242
+ });
885
1243
  }
886
1244
 
887
1245
  // src/index.browser.js