@zumer/snapdom 1.7.1 → 1.9.3

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.7.1
4
+ * v.1.9.3
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,28 @@
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 dataUrl = yield fetchImage(encodedUrl, { useProxy: options.useProxy });
218
+ bgCache.set(encodedUrl, dataUrl);
219
+ return options.skipInline ? void 0 : `url("${dataUrl}")`;
220
+ }
221
+ }
222
+ if (isGradient || entry === "none") {
223
+ return entry;
224
+ }
225
+ return entry;
226
+ });
227
+ }
155
228
  function idle(fn, { fast = false } = {}) {
156
229
  if (fast) return fn();
157
230
  if ("requestIdleCallback" in window) {
@@ -180,7 +253,7 @@
180
253
  if (clean.startsWith("\\")) {
181
254
  try {
182
255
  return String.fromCharCode(parseInt(clean.replace("\\", ""), 16));
183
- } catch {
256
+ } catch (e) {
184
257
  return clean;
185
258
  }
186
259
  }
@@ -196,53 +269,119 @@
196
269
  }
197
270
  return url;
198
271
  }
199
- function isIconFont(familyOrUrl) {
200
- const iconFontPatterns = [
201
- /font\s*awesome/i,
202
- /material\s*icons/i,
203
- /ionicons/i,
204
- /glyphicons/i,
205
- /feather/i,
206
- /bootstrap\s*icons/i,
207
- /remix\s*icons/i,
208
- /heroicons/i
209
- ];
210
- return iconFontPatterns.some((rx) => rx.test(familyOrUrl));
211
- }
212
- function fetchImage(src, timeout = 3e3, crossOrigin = "anonymous") {
272
+ function fetchImage(src, { timeout = 3e3, useProxy = "" } = {}) {
273
+ function getCrossOriginMode(url) {
274
+ try {
275
+ const parsed = new URL(url, window.location.href);
276
+ return parsed.origin === window.location.origin ? "use-credentials" : "anonymous";
277
+ } catch (e) {
278
+ return "anonymous";
279
+ }
280
+ }
281
+ function fetchWithFallback(url) {
282
+ return __async(this, null, function* () {
283
+ const fetchBlobAsDataURL = (fetchUrl) => fetch(fetchUrl, {
284
+ mode: "cors",
285
+ credentials: getCrossOriginMode(fetchUrl) === "use-credentials" ? "include" : "omit"
286
+ }).then((r) => r.blob()).then((blob) => new Promise((resolve, reject) => {
287
+ const reader = new FileReader();
288
+ reader.onloadend = () => {
289
+ const base64 = reader.result;
290
+ if (typeof base64 !== "string" || !base64.startsWith("data:image/")) {
291
+ reject(new Error("Invalid image data URL"));
292
+ return;
293
+ }
294
+ resolve(base64);
295
+ };
296
+ reader.onerror = () => reject(new Error("FileReader error"));
297
+ reader.readAsDataURL(blob);
298
+ }));
299
+ try {
300
+ return yield fetchBlobAsDataURL(url);
301
+ } catch (e) {
302
+ if (useProxy && typeof useProxy === "string") {
303
+ const proxied = useProxy.replace(/\/$/, "") + safeEncodeURI(url);
304
+ try {
305
+ return yield fetchBlobAsDataURL(proxied);
306
+ } catch (e2) {
307
+ console.error(`[SnapDOM - fetchImage] Proxy fallback failed for: ${url}`);
308
+ throw new Error("CORS restrictions prevented image capture (even via proxy)");
309
+ }
310
+ } else {
311
+ console.error(`[SnapDOM - fetchImage] No valid proxy URL provided for fallback: ${url}`);
312
+ throw new Error("Fetch fallback failed and no proxy provided");
313
+ }
314
+ }
315
+ });
316
+ }
317
+ const crossOriginValue = getCrossOriginMode(src);
318
+ console.log(`[SnapDOM - fetchImage] Start loading image: ${src} with crossOrigin=${crossOriginValue}`);
213
319
  if (imageCache.has(src)) {
320
+ console.log(`[SnapDOM - fetchImage] Cache hit for: ${src}`);
214
321
  return Promise.resolve(imageCache.get(src));
215
322
  }
323
+ const isDataURI = src.startsWith("data:image/");
324
+ if (isDataURI) {
325
+ imageCache.set(src, src);
326
+ return Promise.resolve(src);
327
+ }
328
+ const isSVG = /\.svg(\?.*)?$/i.test(src);
329
+ if (isSVG) {
330
+ return (() => __async(null, null, function* () {
331
+ try {
332
+ const response = yield fetch(src, {
333
+ mode: "cors",
334
+ credentials: crossOriginValue === "use-credentials" ? "include" : "omit"
335
+ });
336
+ const svgText = yield response.text();
337
+ const encoded = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgText)}`;
338
+ imageCache.set(src, encoded);
339
+ return encoded;
340
+ } catch (e) {
341
+ return fetchWithFallback(src);
342
+ }
343
+ }))();
344
+ }
216
345
  return new Promise((resolve, reject) => {
217
346
  const timeoutId = setTimeout(() => {
347
+ console.log(`[SnapDOM - fetchImage] Timeout after ${timeout}ms for image: ${src}`);
218
348
  reject(new Error("Image load timed out"));
219
349
  }, timeout);
220
350
  const image = new Image();
221
- image.crossOrigin = crossOrigin;
222
- image.onload = async () => {
351
+ image.crossOrigin = crossOriginValue;
352
+ image.onload = () => __async(null, null, function* () {
223
353
  clearTimeout(timeoutId);
224
354
  try {
225
- await image.decode();
355
+ yield image.decode();
226
356
  const canvas = document.createElement("canvas");
227
357
  canvas.width = image.width;
228
358
  canvas.height = image.height;
229
359
  const ctx = canvas.getContext("2d");
230
360
  ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
361
+ const dataURL = canvas.toDataURL("image/png");
362
+ imageCache.set(src, dataURL);
363
+ resolve(dataURL);
364
+ } catch (e) {
231
365
  try {
232
- const dataURL = canvas.toDataURL("image/png");
233
- imageCache.set(src, dataURL);
234
- resolve(dataURL);
235
- } catch (e) {
236
- reject(new Error("CORS restrictions prevented image capture"));
366
+ const fallbackDataURL = yield fetchWithFallback(src);
367
+ imageCache.set(src, fallbackDataURL);
368
+ resolve(fallbackDataURL);
369
+ } catch (e2) {
370
+ reject(e2);
237
371
  }
372
+ }
373
+ });
374
+ image.onerror = () => __async(null, null, function* () {
375
+ clearTimeout(timeoutId);
376
+ console.error(`[SnapDOM - fetchImage] Image failed to load: ${src}`);
377
+ try {
378
+ const fallbackDataURL = yield fetchWithFallback(src);
379
+ imageCache.set(src, fallbackDataURL);
380
+ resolve(fallbackDataURL);
238
381
  } catch (e) {
239
382
  reject(e);
240
383
  }
241
- };
242
- image.onerror = (e) => {
243
- clearTimeout(timeoutId);
244
- reject(new Error("Failed to load image: " + (e.message || "Unknown error")));
245
- };
384
+ });
246
385
  image.src = src;
247
386
  });
248
387
  }
@@ -256,35 +395,138 @@
256
395
  function isSafari() {
257
396
  return /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
258
397
  }
398
+ function stripTranslate(transform) {
399
+ if (!transform || transform === "none") return "";
400
+ let cleaned = transform.replace(/translate[XY]?\([^)]*\)/g, "");
401
+ cleaned = cleaned.replace(/matrix\(([^)]+)\)/g, (_, values) => {
402
+ const parts = values.split(",").map((s) => s.trim());
403
+ if (parts.length !== 6) return `matrix(${values})`;
404
+ parts[4] = "0";
405
+ parts[5] = "0";
406
+ return `matrix(${parts.join(", ")})`;
407
+ });
408
+ cleaned = cleaned.replace(/matrix3d\(([^)]+)\)/g, (_, values) => {
409
+ const parts = values.split(",").map((s) => s.trim());
410
+ if (parts.length !== 16) return `matrix3d(${values})`;
411
+ parts[12] = "0";
412
+ parts[13] = "0";
413
+ return `matrix3d(${parts.join(", ")})`;
414
+ });
415
+ return cleaned.trim().replace(/\s{2,}/g, " ");
416
+ }
417
+ function safeEncodeURI(uri) {
418
+ if (/%[0-9A-Fa-f]{2}/.test(uri)) return uri;
419
+ try {
420
+ return encodeURI(uri);
421
+ } catch (e) {
422
+ return uri;
423
+ }
424
+ }
425
+ function splitBackgroundImage(bg) {
426
+ const parts = [];
427
+ let depth = 0;
428
+ let lastIndex = 0;
429
+ for (let i = 0; i < bg.length; i++) {
430
+ const char = bg[i];
431
+ if (char === "(") depth++;
432
+ if (char === ")") depth--;
433
+ if (char === "," && depth === 0) {
434
+ parts.push(bg.slice(lastIndex, i).trim());
435
+ lastIndex = i + 1;
436
+ }
437
+ }
438
+ parts.push(bg.slice(lastIndex).trim());
439
+ return parts;
440
+ }
259
441
 
260
442
  // src/modules/styles.js
443
+ var snapshotCache = /* @__PURE__ */ new WeakMap();
444
+ var snapshotKeyCache = /* @__PURE__ */ new Map();
445
+ function snapshotComputedStyleFull(style) {
446
+ const result = {};
447
+ for (let i = 0; i < style.length; i++) {
448
+ const prop = style[i];
449
+ let val = style.getPropertyValue(prop);
450
+ if ((prop === "background-image" || prop === "content") && val.includes("url(") && !val.includes("data:")) {
451
+ val = "none";
452
+ }
453
+ result[prop] = val;
454
+ }
455
+ return result;
456
+ }
261
457
  function inlineAllStyles(source, clone, styleMap, cache, compress) {
458
+ var _a;
262
459
  if (source.tagName === "STYLE") return;
263
460
  if (!cache.has(source)) {
264
461
  cache.set(source, getStyle(source));
265
462
  }
266
463
  const style = cache.get(source);
267
- const snapshot = snapshotComputedStyle(style);
268
- const tagName = source.tagName?.toLowerCase() || "div";
464
+ if (!snapshotCache.has(source)) {
465
+ const snapshot2 = snapshotComputedStyleFull(style);
466
+ snapshotCache.set(source, snapshot2);
467
+ }
468
+ const snapshot = snapshotCache.get(source);
469
+ const hash = Object.entries(snapshot).sort(([a], [b]) => a.localeCompare(b)).map(([prop, val]) => `${prop}:${val}`).join(";");
470
+ if (snapshotKeyCache.has(hash)) {
471
+ styleMap.set(clone, snapshotKeyCache.get(hash));
472
+ return;
473
+ }
474
+ const tagName = ((_a = source.tagName) == null ? void 0 : _a.toLowerCase()) || "div";
269
475
  const key = getStyleKey(snapshot, tagName, compress);
476
+ snapshotKeyCache.set(hash, key);
270
477
  styleMap.set(clone, key);
271
478
  }
272
479
 
273
480
  // src/core/clone.js
274
- function deepClone(node, styleMap, styleCache, nodeMap, compress) {
275
- if (node.nodeType === Node.ELEMENT_NODE && node.getAttribute("data-capture") === "exclude") {
481
+ function deepClone(node, styleMap, styleCache, nodeMap, compress, options = {}, originalRoot) {
482
+ var _a;
483
+ if (node.nodeType === Node.TEXT_NODE) {
484
+ if ((_a = node.parentElement) == null ? void 0 : _a.shadowRoot) {
485
+ const tag = node.parentElement.tagName.toLowerCase();
486
+ if (customElements.get(tag)) return null;
487
+ }
488
+ return node.cloneNode(true);
489
+ }
490
+ if (node.nodeType !== Node.ELEMENT_NODE) return node.cloneNode(true);
491
+ if (node.getAttribute("data-capture") === "exclude") {
276
492
  const spacer = document.createElement("div");
277
493
  const rect = node.getBoundingClientRect();
278
494
  spacer.style.cssText = `display: inline-block; width: ${rect.width}px; height: ${rect.height}px; visibility: hidden;`;
279
495
  return spacer;
280
496
  }
497
+ if (options.exclude && Array.isArray(options.exclude) && options.exclude.length > 0) {
498
+ for (const selector of options.exclude) {
499
+ try {
500
+ if (node.matches && node.matches(selector)) {
501
+ const spacer = document.createElement("div");
502
+ const rect = node.getBoundingClientRect();
503
+ spacer.style.cssText = `display: inline-block; width: ${rect.width}px; height: ${rect.height}px; visibility: hidden;`;
504
+ return spacer;
505
+ }
506
+ } catch (err) {
507
+ console.warn(`Invalid selector in exclude option: ${selector}`, err);
508
+ }
509
+ }
510
+ }
511
+ if (typeof options.filter === "function") {
512
+ try {
513
+ if (!options.filter(node, originalRoot || node)) {
514
+ const spacer = document.createElement("div");
515
+ const rect = node.getBoundingClientRect();
516
+ spacer.style.cssText = `display: inline-block; width: ${rect.width}px; height: ${rect.height}px; visibility: hidden;`;
517
+ return spacer;
518
+ }
519
+ } catch (err) {
520
+ console.warn("Error in filter function:", err);
521
+ }
522
+ }
281
523
  if (node.tagName === "IFRAME") {
282
524
  const fallback = document.createElement("div");
283
525
  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;`;
526
+ 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
527
  return fallback;
286
528
  }
287
- if (node.nodeType === Node.ELEMENT_NODE && node.getAttribute("data-capture") === "placeholder") {
529
+ if (node.getAttribute("data-capture") === "placeholder") {
288
530
  const clone2 = node.cloneNode(false);
289
531
  nodeMap.set(clone2, node);
290
532
  inlineAllStyles(node, clone2, styleMap, styleCache, compress);
@@ -301,18 +543,10 @@
301
543
  img.width = node.width;
302
544
  img.height = node.height;
303
545
  img.style.display = "inline-block";
304
- img.style.width = `${node.width}px`;
305
- img.style.height = `${node.height}px`;
546
+ img.style.width = node.style.width || `${node.width}px`;
547
+ img.style.height = node.style.height || `${node.height}px`;
306
548
  return img;
307
549
  }
308
- if (node.nodeType === Node.TEXT_NODE) {
309
- if (node.parentElement?.shadowRoot) {
310
- const tag = node.parentElement.tagName.toLowerCase();
311
- if (customElements.get(tag)) return null;
312
- }
313
- return node.cloneNode(true);
314
- }
315
- if (node.nodeType !== Node.ELEMENT_NODE) return node.cloneNode(true);
316
550
  const clone = node.cloneNode(false);
317
551
  nodeMap.set(clone, node);
318
552
  if (node instanceof HTMLInputElement) {
@@ -337,13 +571,14 @@
337
571
  }
338
572
  inlineAllStyles(node, clone, styleMap, styleCache, compress);
339
573
  const frag = document.createDocumentFragment();
574
+ const rootElement = originalRoot || node;
340
575
  node.childNodes.forEach((child) => {
341
- const clonedChild = deepClone(child, styleMap, styleCache, nodeMap, compress);
576
+ const clonedChild = deepClone(child, styleMap, styleCache, nodeMap, compress, options, rootElement);
342
577
  if (clonedChild) frag.appendChild(clonedChild);
343
578
  });
344
579
  clone.appendChild(frag);
345
580
  if (node.shadowRoot) {
346
- const shadowContent = Array.from(node.shadowRoot.children).filter((el) => el.tagName !== "STYLE").map((el) => deepClone(el, styleMap, styleCache, nodeMap)).filter(Boolean);
581
+ const shadowContent = Array.from(node.shadowRoot.children).filter((el) => el.tagName !== "STYLE").map((el) => deepClone(el, styleMap, styleCache, nodeMap, compress, options, rootElement)).filter(Boolean);
347
582
  const shadowFrag = document.createDocumentFragment();
348
583
  shadowContent.forEach((child) => shadowFrag.appendChild(child));
349
584
  clone.appendChild(shadowFrag);
@@ -351,553 +586,771 @@
351
586
  return clone;
352
587
  }
353
588
 
589
+ // src/modules/iconFonts.js
590
+ var defaultIconFonts = [
591
+ // /uicons/i,
592
+ /font\s*awesome/i,
593
+ /material\s*icons/i,
594
+ /ionicons/i,
595
+ /glyphicons/i,
596
+ /feather/i,
597
+ /bootstrap\s*icons/i,
598
+ /remix\s*icons/i,
599
+ /heroicons/i,
600
+ /layui/i,
601
+ /lucide/i
602
+ ];
603
+ var userIconFonts = [];
604
+ function extendIconFonts(fonts) {
605
+ const list = Array.isArray(fonts) ? fonts : [fonts];
606
+ for (const f of list) {
607
+ if (f instanceof RegExp) {
608
+ userIconFonts.push(f);
609
+ } else if (typeof f === "string") {
610
+ userIconFonts.push(new RegExp(f, "i"));
611
+ } else {
612
+ console.warn("[snapdom] Ignored invalid iconFont value:", f);
613
+ }
614
+ }
615
+ }
616
+ function isIconFont(input) {
617
+ const text = typeof input === "string" ? input : "";
618
+ const candidates = [...defaultIconFonts, ...userIconFonts];
619
+ for (const rx of candidates) {
620
+ if (rx instanceof RegExp && rx.test(text)) return true;
621
+ }
622
+ if (/icon/i.test(text) || /glyph/i.test(text) || /symbols/i.test(text) || /feather/i.test(text) || /fontawesome/i.test(text)) return true;
623
+ return false;
624
+ }
625
+
354
626
  // 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;
627
+ function iconToImage(unicodeChar, fontFamily, fontWeight, fontSize = 32, color = "#000") {
628
+ return __async(this, null, function* () {
629
+ fontFamily = fontFamily.replace(/^['"]+|['"]+$/g, "");
630
+ const dpr = window.devicePixelRatio || 1;
631
+ const tempCanvas = document.createElement("canvas");
632
+ const tempCtx = tempCanvas.getContext("2d");
633
+ tempCtx.font = fontWeight ? `${fontWeight} ${fontSize}px "${fontFamily}"` : `${fontSize}px "${fontFamily}"`;
634
+ const metrics = tempCtx.measureText(unicodeChar);
635
+ const ascent = metrics.actualBoundingBoxAscent || fontSize * 0.8;
636
+ const descent = metrics.actualBoundingBoxDescent || fontSize * 0.2;
637
+ const height = ascent + descent;
638
+ const width = metrics.width;
639
+ const canvas = document.createElement("canvas");
640
+ canvas.width = Math.ceil(width * dpr);
641
+ canvas.height = Math.ceil(height * dpr);
642
+ const ctx = canvas.getContext("2d");
643
+ ctx.scale(dpr, dpr);
644
+ ctx.font = tempCtx.font;
645
+ ctx.textAlign = "left";
646
+ ctx.textBaseline = "alphabetic";
647
+ ctx.fillStyle = color;
648
+ ctx.fillText(unicodeChar, 0, ascent);
649
+ return canvas.toDataURL();
650
+ });
651
+ }
652
+ function isStylesheetLoaded(href) {
653
+ return Array.from(document.styleSheets).some((sheet) => sheet.href === href);
654
+ }
655
+ function injectLinkIfMissing(href) {
656
+ return new Promise((resolve) => {
657
+ if (isStylesheetLoaded(href)) return resolve(null);
658
+ const link = document.createElement("link");
659
+ link.rel = "stylesheet";
660
+ link.href = href;
661
+ link.setAttribute("data-snapdom", "injected-import");
662
+ link.onload = () => resolve(link);
663
+ link.onerror = () => resolve(null);
664
+ document.head.appendChild(link);
665
+ });
666
+ }
667
+ function embedCustomFonts() {
668
+ return __async(this, arguments, function* ({ preCached = false } = {}) {
669
+ if (resourceCache.has("fonts-embed-css")) {
670
+ if (preCached) {
671
+ const style = document.createElement("style");
672
+ style.setAttribute("data-snapdom", "embedFonts");
673
+ style.textContent = resourceCache.get("fonts-embed-css");
674
+ document.head.appendChild(style);
387
675
  }
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)})` };
407
- }
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("[snapdom] Failed to fetch font:", url);
420
- return null;
421
- }
422
- })
423
- );
424
- let cssFinal = cssText;
425
- for (const r of inlinedCSS) {
426
- if (r) {
427
- cssFinal = cssFinal.replace(r.original, r.inlined);
676
+ return resourceCache.get("fonts-embed-css");
677
+ }
678
+ const importRegex = /@import\s+url\(["']?([^"')]+)["']?\)/g;
679
+ const styleImports = [];
680
+ for (const styleTag of document.querySelectorAll("style")) {
681
+ const cssText = styleTag.textContent || "";
682
+ const matches = Array.from(cssText.matchAll(importRegex));
683
+ for (const match of matches) {
684
+ const importUrl = match[1];
685
+ if (isIconFont(importUrl)) continue;
686
+ if (!isStylesheetLoaded(importUrl)) {
687
+ styleImports.push(importUrl);
428
688
  }
429
689
  }
430
- finalCSS += cssFinal + "\n";
431
- } catch (e) {
432
- console.warn("[snapdom] Failed to fetch CSS:", link.href);
433
690
  }
434
- }
435
- for (const sheet of document.styleSheets) {
436
- try {
437
- if (!sheet.href || links.every((link) => link.href !== sheet.href)) {
438
- for (const rule of sheet.cssRules) {
439
- if (rule.type === CSSRule.FONT_FACE_RULE) {
440
- const src = rule.style.getPropertyValue("src");
441
- if (!src) continue;
442
- const urlRegex = /url\(([^)]+)\)/g;
443
- let inlinedSrc = src;
444
- const matches = Array.from(src.matchAll(urlRegex));
445
- for (const match of matches) {
446
- let rawUrl = match[1].trim().replace(/^["']|["']$/g, "");
447
- if (!rawUrl) continue;
448
- let url = rawUrl;
449
- if (!url.startsWith("http") && !url.startsWith("data:")) {
450
- url = new URL(url, sheet.href || location.href).href;
451
- }
452
- if (ignoreIconFonts && isIconFont(url)) {
453
- continue;
454
- }
455
- if (resourceCache.has(url)) {
456
- inlinedSrc = inlinedSrc.replace(match[0], `url(${resourceCache.get(url)})`);
457
- continue;
458
- }
459
- try {
460
- const res = await fetch(url);
461
- const blob = await res.blob();
462
- const b64 = await new Promise((resolve) => {
463
- const reader = new FileReader();
464
- reader.onload = () => resolve(reader.result);
465
- reader.readAsDataURL(blob);
466
- });
467
- resourceCache.set(url, b64);
468
- inlinedSrc = inlinedSrc.replace(match[0], `url(${b64})`);
469
- } catch (e) {
470
- console.warn("[snapdom] Failed to fetch font URL:", url);
471
- }
691
+ yield Promise.all(styleImports.map(injectLinkIfMissing));
692
+ const links = Array.from(document.querySelectorAll('link[rel="stylesheet"]')).filter((link) => link.href);
693
+ let finalCSS = "";
694
+ for (const link of links) {
695
+ try {
696
+ const res = yield fetch(link.href);
697
+ const cssText = yield res.text();
698
+ if (isIconFont(link.href) || isIconFont(cssText)) continue;
699
+ const urlRegex = /url\((["']?)([^"')]+)\1\)/g;
700
+ const inlinedCSS = yield Promise.all(
701
+ Array.from(cssText.matchAll(urlRegex)).map((match) => __async(null, null, function* () {
702
+ let rawUrl = extractURL(match[0]);
703
+ if (!rawUrl) return null;
704
+ let url = rawUrl;
705
+ if (!url.startsWith("http") && !url.startsWith("data:")) {
706
+ url = new URL(url, link.href).href;
707
+ }
708
+ if (isIconFont(url)) return null;
709
+ if (resourceCache.has(url)) {
710
+ processedFontURLs.add(url);
711
+ return { original: match[0], inlined: `url(${resourceCache.get(url)})` };
472
712
  }
473
- finalCSS += `@font-face {
474
- font-family: ${rule.style.getPropertyValue("font-family")};
713
+ if (processedFontURLs.has(url)) return null;
714
+ try {
715
+ const fontRes = yield fetch(url);
716
+ const blob = yield fontRes.blob();
717
+ const b64 = yield new Promise((resolve) => {
718
+ const reader = new FileReader();
719
+ reader.onload = () => resolve(reader.result);
720
+ reader.readAsDataURL(blob);
721
+ });
722
+ resourceCache.set(url, b64);
723
+ processedFontURLs.add(url);
724
+ return { original: match[0], inlined: `url(${b64})` };
725
+ } catch (e) {
726
+ console.warn("[snapdom] Failed to fetch font resource:", url);
727
+ return null;
728
+ }
729
+ }))
730
+ );
731
+ let cssFinal = cssText;
732
+ for (const r of inlinedCSS) {
733
+ if (r) cssFinal = cssFinal.replace(r.original, r.inlined);
734
+ }
735
+ finalCSS += cssFinal + "\n";
736
+ } catch (e) {
737
+ console.warn("[snapdom] Failed to fetch CSS:", link.href);
738
+ }
739
+ }
740
+ for (const sheet of document.styleSheets) {
741
+ try {
742
+ if (!sheet.href || links.every((link) => link.href !== sheet.href)) {
743
+ for (const rule of sheet.cssRules) {
744
+ if (rule.type === CSSRule.FONT_FACE_RULE) {
745
+ const src = rule.style.getPropertyValue("src");
746
+ const family = rule.style.getPropertyValue("font-family");
747
+ if (!src || isIconFont(family)) continue;
748
+ const urlRegex = /url\((["']?)([^"')]+)\1\)/g;
749
+ let inlinedSrc = src;
750
+ const matches = Array.from(src.matchAll(urlRegex));
751
+ for (const match of matches) {
752
+ let rawUrl = match[2].trim();
753
+ if (!rawUrl) continue;
754
+ let url = rawUrl;
755
+ if (!url.startsWith("http") && !url.startsWith("data:")) {
756
+ url = new URL(url, sheet.href || location.href).href;
757
+ }
758
+ if (isIconFont(url)) continue;
759
+ if (resourceCache.has(url)) {
760
+ processedFontURLs.add(url);
761
+ inlinedSrc = inlinedSrc.replace(match[0], `url(${resourceCache.get(url)})`);
762
+ continue;
763
+ }
764
+ if (processedFontURLs.has(url)) continue;
765
+ try {
766
+ const res = yield fetch(url);
767
+ const blob = yield res.blob();
768
+ const b64 = yield new Promise((resolve) => {
769
+ const reader = new FileReader();
770
+ reader.onload = () => resolve(reader.result);
771
+ reader.readAsDataURL(blob);
772
+ });
773
+ resourceCache.set(url, b64);
774
+ processedFontURLs.add(url);
775
+ inlinedSrc = inlinedSrc.replace(match[0], `url(${b64})`);
776
+ } catch (e) {
777
+ console.warn("[snapdom] Failed to fetch font URL:", url);
778
+ }
779
+ }
780
+ finalCSS += `@font-face {
781
+ font-family: ${family};
475
782
  src: ${inlinedSrc};
476
783
  font-style: ${rule.style.getPropertyValue("font-style") || "normal"};
477
784
  font-weight: ${rule.style.getPropertyValue("font-weight") || "normal"};
478
785
  }
479
786
  `;
787
+ }
480
788
  }
481
789
  }
790
+ } catch (e) {
791
+ console.warn("[snapdom] Cannot access stylesheet", sheet.href, e);
482
792
  }
483
- } catch (e) {
484
- console.warn("[snapdom] Cannot access stylesheet", sheet.href, e);
485
793
  }
486
- }
487
- for (const font of document.fonts) {
488
- if (font.family && font.status === "loaded" && font._snapdomSrc) {
489
- let b64 = font._snapdomSrc;
490
- if (!b64.startsWith("data:")) {
491
- if (resourceCache.has(font._snapdomSrc)) {
492
- b64 = resourceCache.get(font._snapdomSrc);
493
- } else {
494
- try {
495
- const res = await fetch(font._snapdomSrc);
496
- const blob = await res.blob();
497
- b64 = await new Promise((resolve) => {
498
- const reader = new FileReader();
499
- reader.onload = () => resolve(reader.result);
500
- reader.readAsDataURL(blob);
501
- });
502
- resourceCache.set(font._snapdomSrc, b64);
503
- } catch (e) {
504
- console.warn("[snapdom] Failed to fetch dynamic font src:", font._snapdomSrc);
505
- continue;
794
+ for (const font of document.fonts) {
795
+ if (font.family && font.status === "loaded" && font._snapdomSrc) {
796
+ if (isIconFont(font.family)) continue;
797
+ let b64 = font._snapdomSrc;
798
+ if (!b64.startsWith("data:")) {
799
+ if (resourceCache.has(font._snapdomSrc)) {
800
+ b64 = resourceCache.get(font._snapdomSrc);
801
+ processedFontURLs.add(font._snapdomSrc);
802
+ } else if (!processedFontURLs.has(font._snapdomSrc)) {
803
+ try {
804
+ const res = yield fetch(font._snapdomSrc);
805
+ const blob = yield res.blob();
806
+ b64 = yield new Promise((resolve) => {
807
+ const reader = new FileReader();
808
+ reader.onload = () => resolve(reader.result);
809
+ reader.readAsDataURL(blob);
810
+ });
811
+ resourceCache.set(font._snapdomSrc, b64);
812
+ processedFontURLs.add(font._snapdomSrc);
813
+ } catch (e) {
814
+ console.warn("[snapdom] Failed to fetch dynamic font src:", font._snapdomSrc);
815
+ continue;
816
+ }
506
817
  }
507
818
  }
508
- }
509
- finalCSS += `@font-face {
819
+ finalCSS += `@font-face {
510
820
  font-family: '${font.family}';
511
821
  src: url(${b64});
512
822
  font-style: ${font.style || "normal"};
513
823
  font-weight: ${font.weight || "normal"};
514
824
  }
515
825
  `;
826
+ }
516
827
  }
517
- }
518
- if (finalCSS && preCached) {
519
- const style = document.createElement("style");
520
- style.setAttribute("data-snapdom", "embedFonts");
521
- style.textContent = finalCSS;
522
- document.head.appendChild(style);
523
- }
524
- return finalCSS;
828
+ if (finalCSS) {
829
+ resourceCache.set("fonts-embed-css", finalCSS);
830
+ if (preCached) {
831
+ const style = document.createElement("style");
832
+ style.setAttribute("data-snapdom", "embedFonts");
833
+ style.textContent = finalCSS;
834
+ document.head.appendChild(style);
835
+ }
836
+ }
837
+ return finalCSS;
838
+ });
525
839
  }
526
840
 
527
841
  // src/modules/pseudo.js
528
- async function inlinePseudoElements(source, clone, styleMap, styleCache, compress, embedFonts = false) {
529
- if (!(source instanceof Element) || !(clone instanceof Element)) return;
530
- for (const pseudo of ["::before", "::after"]) {
531
- try {
532
- const style = getStyle(source, pseudo);
533
- if (!style) continue;
534
- const content = style.getPropertyValue("content");
535
- const bg = style.getPropertyValue("background-image");
536
- const hasContent = content && content !== "none" && content !== '""' && content !== "''";
537
- const hasBg = bg && bg.startsWith("url(");
538
- if (hasContent || hasBg) {
539
- const fontFamily = style.getPropertyValue("font-family");
540
- const fontSize = parseInt(style.getPropertyValue("font-size")) || 32;
541
- const fontWeight = parseInt(style.getPropertyValue("font-weight")) || false;
542
- const color = style.getPropertyValue("color") || "#000";
543
- const pseudoEl = document.createElement("span");
544
- pseudoEl.dataset.snapdomPseudo = pseudo;
545
- const snapshot = snapshotComputedStyle(style);
546
- const key = getStyleKey(snapshot, "span", compress);
547
- styleMap.set(pseudoEl, key);
548
- const isIconFont2 = fontFamily && /font.*awesome|material|bootstrap|glyphicons|ionicons|remixicon|simple-line-icons|octicons|feather|typicons|weathericons/i.test(
549
- fontFamily
550
- );
551
- let cleanContent = parseContent(content);
552
- if (!embedFonts && isIconFont2 && cleanContent.length === 1) {
553
- const imgEl = document.createElement("img");
554
- imgEl.src = await iconToImage(
555
- cleanContent,
556
- fontFamily,
557
- fontWeight,
558
- fontSize,
559
- color
842
+ function inlinePseudoElements(source, clone, styleMap, styleCache, compress, embedFonts = false, useProxy) {
843
+ return __async(this, null, function* () {
844
+ if (!(source instanceof Element) || !(clone instanceof Element)) return;
845
+ for (const pseudo of ["::before", "::after", "::first-letter"]) {
846
+ try {
847
+ const style = getStyle(source, pseudo);
848
+ if (!style || typeof style[Symbol.iterator] !== "function") continue;
849
+ if (pseudo === "::first-letter") {
850
+ const normal = getComputedStyle(source);
851
+ const isMeaningful = style.color !== normal.color || style.fontSize !== normal.fontSize || style.fontWeight !== normal.fontWeight;
852
+ if (!isMeaningful) continue;
853
+ const textNode = Array.from(clone.childNodes).find(
854
+ (n) => n.nodeType === Node.TEXT_NODE && n.textContent && n.textContent.trim().length > 0
560
855
  );
561
- imgEl.style = "display:block;width:100%;height:100%;object-fit:contain;";
562
- pseudoEl.appendChild(imgEl);
563
- } else if (cleanContent.startsWith("url(")) {
564
- const rawUrl = extractURL(cleanContent);
565
- if (rawUrl) {
566
- try {
567
- const imgEl = document.createElement("img");
568
- const dataUrl = await fetchImage(encodeURI(rawUrl));
569
- imgEl.src = dataUrl;
570
- imgEl.style = "display:block;width:100%;height:100%;object-fit:contain;";
571
- pseudoEl.appendChild(imgEl);
572
- } catch (e) {
573
- console.error(`[snapdom] Error in pseudo ${pseudo} for`, source, e);
856
+ if (!textNode) continue;
857
+ const text = textNode.textContent;
858
+ const match = text.match(/^([^\p{L}\p{N}\s]*[\p{L}\p{N}](?:['’])?)/u);
859
+ const first = match == null ? void 0 : match[0];
860
+ const rest = text.slice((first == null ? void 0 : first.length) || 0);
861
+ if (!first || /[\uD800-\uDFFF]/.test(first)) continue;
862
+ const span = document.createElement("span");
863
+ span.textContent = first;
864
+ span.dataset.snapdomPseudo = "::first-letter";
865
+ const snapshot = snapshotComputedStyle(style);
866
+ const key = getStyleKey(snapshot, "span", compress);
867
+ styleMap.set(span, key);
868
+ const restNode = document.createTextNode(rest);
869
+ clone.replaceChild(restNode, textNode);
870
+ clone.insertBefore(span, restNode);
871
+ continue;
872
+ }
873
+ const content = style.getPropertyValue("content");
874
+ const bg = style.getPropertyValue("background-image");
875
+ const bgColor = style.getPropertyValue("background-color");
876
+ const hasContent = content !== "none";
877
+ const hasBg = bg && bg !== "none";
878
+ const hasBgColor = bgColor && bgColor !== "transparent" && bgColor !== "rgba(0, 0, 0, 0)";
879
+ if (hasContent || hasBg || hasBgColor) {
880
+ const fontFamily = style.getPropertyValue("font-family");
881
+ const fontSize = parseInt(style.getPropertyValue("font-size")) || 32;
882
+ const fontWeight = parseInt(style.getPropertyValue("font-weight")) || false;
883
+ const color = style.getPropertyValue("color") || "#000";
884
+ const pseudoEl = document.createElement("span");
885
+ pseudoEl.dataset.snapdomPseudo = pseudo;
886
+ const snapshot = snapshotComputedStyle(style);
887
+ const key = getStyleKey(snapshot, "span", compress);
888
+ styleMap.set(pseudoEl, key);
889
+ const isIconFont2 = isIconFont(fontFamily);
890
+ const cleanContent = parseContent(content);
891
+ if (isIconFont2 && cleanContent.length === 1) {
892
+ const imgEl = document.createElement("img");
893
+ imgEl.src = yield iconToImage(cleanContent, fontFamily, fontWeight, fontSize, color);
894
+ imgEl.style = `width:${fontSize}px;height:auto;object-fit:contain;`;
895
+ pseudoEl.appendChild(imgEl);
896
+ } else if (cleanContent.startsWith("url(")) {
897
+ const rawUrl = extractURL(cleanContent);
898
+ if (rawUrl && rawUrl.trim() !== "") {
899
+ try {
900
+ const imgEl = document.createElement("img");
901
+ const dataUrl = yield fetchImage(safeEncodeURI(rawUrl, { useProxy }));
902
+ imgEl.src = dataUrl;
903
+ imgEl.style = `width:${fontSize}px;height:auto;object-fit:contain;`;
904
+ pseudoEl.appendChild(imgEl);
905
+ } catch (e) {
906
+ console.error(`[snapdom] Error in pseudo ${pseudo} for`, source, e);
907
+ }
574
908
  }
909
+ } else if (!isIconFont2 && cleanContent && cleanContent !== "none") {
910
+ pseudoEl.textContent = cleanContent;
575
911
  }
576
- } else if (!isIconFont2 && cleanContent && cleanContent !== "none") {
577
- pseudoEl.textContent = cleanContent;
578
- }
579
- if (hasBg && bg) {
580
- const rawUrl = extractURL(bg);
581
- if (rawUrl) {
912
+ if (hasBg) {
582
913
  try {
583
- const dataUrl = await fetchImage(encodeURI(rawUrl));
584
- pseudoEl.style.backgroundImage = `url(${dataUrl})`;
914
+ const bgSplits = splitBackgroundImage(bg);
915
+ const newBgParts = yield Promise.all(
916
+ bgSplits.map((entry) => inlineSingleBackgroundEntry(entry))
917
+ );
918
+ pseudoEl.style.backgroundImage = newBgParts.join(", ");
585
919
  } catch (e) {
586
920
  console.warn(`[snapdom] Failed to inline background-image for ${pseudo}`, e);
587
921
  }
588
922
  }
923
+ if (hasBgColor) pseudoEl.style.backgroundColor = bgColor;
924
+ const hasContent2 = pseudoEl.childNodes.length > 0 || pseudoEl.textContent && pseudoEl.textContent.trim() !== "";
925
+ const hasVisibleBox = hasContent2 || hasBg || hasBgColor;
926
+ if (!hasVisibleBox) continue;
927
+ pseudo === "::before" ? clone.insertBefore(pseudoEl, clone.firstChild) : clone.appendChild(pseudoEl);
589
928
  }
590
- const hasContent2 = pseudoEl.childNodes.length > 0 || pseudoEl.textContent && pseudoEl.textContent.trim() !== "";
591
- if (!hasContent2 && !hasBg) continue;
592
- if (pseudo === "::before") {
593
- clone.insertBefore(pseudoEl, clone.firstChild);
594
- } else {
595
- clone.appendChild(pseudoEl);
929
+ } catch (e) {
930
+ console.warn(`[snapdom] Failed to capture ${pseudo} for`, source, e);
931
+ }
932
+ }
933
+ const sChildren = Array.from(source.children);
934
+ const cChildren = Array.from(clone.children).filter((child) => !child.dataset.snapdomPseudo);
935
+ for (let i = 0; i < Math.min(sChildren.length, cChildren.length); i++) {
936
+ yield inlinePseudoElements(
937
+ sChildren[i],
938
+ cChildren[i],
939
+ styleMap,
940
+ styleCache,
941
+ compress,
942
+ embedFonts,
943
+ useProxy
944
+ );
945
+ }
946
+ });
947
+ }
948
+
949
+ // src/modules/svgDefs.js
950
+ function inlineExternalDef(root) {
951
+ if (!root) return;
952
+ const defsSources = document.querySelectorAll("svg > defs");
953
+ if (!defsSources.length) return;
954
+ root.querySelectorAll("svg").forEach((svg) => {
955
+ const uses = svg.querySelectorAll("use");
956
+ if (!uses.length) return;
957
+ const usedIds = /* @__PURE__ */ new Set();
958
+ uses.forEach((use) => {
959
+ const href = use.getAttribute("xlink:href") || use.getAttribute("href");
960
+ if (href && href.startsWith("#")) usedIds.add(href.slice(1));
961
+ });
962
+ if (!usedIds.size) return;
963
+ const defs = document.createElementNS("http://www.w3.org/2000/svg", "defs");
964
+ for (const id of usedIds) {
965
+ for (const source of defsSources) {
966
+ const def = source.querySelector(`#${CSS.escape(id)}`);
967
+ if (def) {
968
+ defs.appendChild(def.cloneNode(true));
969
+ break;
596
970
  }
597
971
  }
598
- } catch (e) {
599
- console.warn(`[snapdom] Failed to capture ${pseudo} for`, source, e);
600
972
  }
601
- }
602
- const sChildren = Array.from(source.children);
603
- const cChildren = Array.from(clone.children).filter((child) => !child.dataset.snapdomPseudo);
604
- for (let i = 0; i < Math.min(sChildren.length, cChildren.length); i++) {
605
- await inlinePseudoElements(
606
- sChildren[i],
607
- cChildren[i],
608
- styleMap,
609
- styleCache,
610
- compress,
611
- embedFonts
612
- );
613
- }
973
+ if (defs.childNodes.length) {
974
+ svg.insertBefore(defs, svg.firstChild);
975
+ }
976
+ });
614
977
  }
615
978
 
616
979
  // src/core/prepare.js
617
- async function prepareClone(element, compress = false, embedFonts = false) {
618
- const styleMap = /* @__PURE__ */ new Map();
619
- const styleCache = /* @__PURE__ */ new WeakMap();
620
- const nodeMap = /* @__PURE__ */ new Map();
621
- let clone;
622
- try {
623
- clone = deepClone(element, styleMap, styleCache, nodeMap, compress);
624
- } catch (e) {
625
- console.warn("deepClone failed:", e);
626
- throw e;
627
- }
628
- try {
629
- await inlinePseudoElements(element, clone, styleMap, styleCache, compress, embedFonts);
630
- } catch (e) {
631
- console.warn("inlinePseudoElements failed:", e);
632
- }
633
- let classCSS = "";
634
- if (compress) {
635
- const keyToClass = generateCSSClasses(styleMap);
636
- classCSS = Array.from(keyToClass.entries()).map(([key, className]) => `.${className}{${key}}`).join("");
637
- for (const [node, key] of styleMap.entries()) {
638
- if (node.tagName === "STYLE") continue;
639
- const className = keyToClass.get(key);
640
- if (className) node.classList.add(className);
641
- const bgImage = node.style?.backgroundImage;
642
- node.removeAttribute("style");
643
- if (bgImage && bgImage !== "none") node.style.backgroundImage = bgImage;
980
+ function prepareClone(_0) {
981
+ return __async(this, arguments, function* (element, compress = false, embedFonts = false, options = {}) {
982
+ var _a;
983
+ const styleMap = /* @__PURE__ */ new Map();
984
+ const styleCache = /* @__PURE__ */ new WeakMap();
985
+ const nodeMap = /* @__PURE__ */ new Map();
986
+ let clone;
987
+ try {
988
+ clone = deepClone(element, styleMap, styleCache, nodeMap, compress, options, element);
989
+ } catch (e) {
990
+ console.warn("deepClone failed:", e);
991
+ throw e;
644
992
  }
645
- } else {
646
- for (const [node, key] of styleMap.entries()) {
647
- if (node.tagName === "STYLE") continue;
648
- node.setAttribute("style", key.replace(/;/g, "; "));
993
+ try {
994
+ yield inlinePseudoElements(element, clone, styleMap, styleCache, compress, embedFonts, options.useProxy);
995
+ } catch (e) {
996
+ console.warn("inlinePseudoElements failed:", e);
649
997
  }
650
- }
651
- for (const [cloneNode, originalNode] of nodeMap.entries()) {
652
- const scrollX = originalNode.scrollLeft;
653
- const scrollY = originalNode.scrollTop;
654
- const hasScroll = scrollX || scrollY;
655
- if (hasScroll && cloneNode instanceof HTMLElement) {
656
- cloneNode.style.overflow = "hidden";
657
- cloneNode.style.scrollbarWidth = "none";
658
- cloneNode.style.msOverflowStyle = "none";
659
- const inner = document.createElement("div");
660
- inner.style.transform = `translate(${-scrollX}px, ${-scrollY}px)`;
661
- inner.style.willChange = "transform";
662
- inner.style.display = "inline-block";
663
- inner.style.width = "100%";
664
- while (cloneNode.firstChild) {
665
- inner.appendChild(cloneNode.firstChild);
998
+ try {
999
+ inlineExternalDef(clone);
1000
+ } catch (e) {
1001
+ console.warn("inlineExternalDef failed:", e);
1002
+ }
1003
+ let classCSS = "";
1004
+ if (compress) {
1005
+ const keyToClass = generateCSSClasses(styleMap);
1006
+ classCSS = Array.from(keyToClass.entries()).map(([key, className]) => `.${className}{${key}}`).join("");
1007
+ for (const [node, key] of styleMap.entries()) {
1008
+ if (node.tagName === "STYLE") continue;
1009
+ const className = keyToClass.get(key);
1010
+ if (className) node.classList.add(className);
1011
+ const bgImage = (_a = node.style) == null ? void 0 : _a.backgroundImage;
1012
+ node.removeAttribute("style");
1013
+ if (bgImage && bgImage !== "none") node.style.backgroundImage = bgImage;
1014
+ }
1015
+ } else {
1016
+ for (const [node, key] of styleMap.entries()) {
1017
+ if (node.tagName === "STYLE") continue;
1018
+ node.setAttribute("style", key.replace(/;/g, "; "));
666
1019
  }
667
- cloneNode.appendChild(inner);
668
1020
  }
669
- }
670
- return { clone, classCSS, styleCache };
1021
+ for (const [cloneNode, originalNode] of nodeMap.entries()) {
1022
+ const scrollX = originalNode.scrollLeft;
1023
+ const scrollY = originalNode.scrollTop;
1024
+ const hasScroll = scrollX || scrollY;
1025
+ if (hasScroll && cloneNode instanceof HTMLElement) {
1026
+ cloneNode.style.overflow = "hidden";
1027
+ cloneNode.style.scrollbarWidth = "none";
1028
+ cloneNode.style.msOverflowStyle = "none";
1029
+ const inner = document.createElement("div");
1030
+ inner.style.transform = `translate(${-scrollX}px, ${-scrollY}px)`;
1031
+ inner.style.willChange = "transform";
1032
+ inner.style.display = "inline-block";
1033
+ inner.style.width = "100%";
1034
+ while (cloneNode.firstChild) {
1035
+ inner.appendChild(cloneNode.firstChild);
1036
+ }
1037
+ cloneNode.appendChild(inner);
1038
+ }
1039
+ }
1040
+ if (element === nodeMap.get(clone)) {
1041
+ const computed = styleCache.get(element) || window.getComputedStyle(element);
1042
+ styleCache.set(element, computed);
1043
+ const transform = stripTranslate(computed.transform);
1044
+ clone.style.margin = "0";
1045
+ clone.style.position = "static";
1046
+ clone.style.top = "auto";
1047
+ clone.style.left = "auto";
1048
+ clone.style.right = "auto";
1049
+ clone.style.bottom = "auto";
1050
+ clone.style.zIndex = "auto";
1051
+ clone.style.float = "none";
1052
+ clone.style.clear = "none";
1053
+ clone.style.transform = transform || "";
1054
+ }
1055
+ for (const [cloneNode, originalNode] of nodeMap.entries()) {
1056
+ if (originalNode.tagName === "PRE") {
1057
+ cloneNode.style.marginTop = "0";
1058
+ cloneNode.style.marginBlockStart = "0";
1059
+ }
1060
+ }
1061
+ return { clone, classCSS, styleCache };
1062
+ });
671
1063
  }
672
1064
 
673
1065
  // src/modules/images.js
674
- async function inlineImages(clone, options = {}) {
675
- const imgs = Array.from(clone.querySelectorAll("img"));
676
- const processImg = async (img) => {
677
- const src = img.src;
678
- try {
679
- const crossOrigin = options.crossOrigin ? options.crossOrigin(src) : "anonymous";
680
- const dataUrl = await fetchImage(src, 3e3, crossOrigin);
681
- img.src = dataUrl;
682
- if (!img.width) img.width = img.naturalWidth || 100;
683
- if (!img.height) img.height = img.naturalHeight || 100;
684
- } catch {
685
- const fallback = document.createElement("div");
686
- 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;`;
687
- fallback.innerText = "img";
688
- img.replaceWith(fallback);
1066
+ function inlineImages(_0) {
1067
+ return __async(this, arguments, function* (clone, options = {}) {
1068
+ const imgs = Array.from(clone.querySelectorAll("img"));
1069
+ const processImg = (img) => __async(null, null, function* () {
1070
+ const src = img.src;
1071
+ try {
1072
+ const dataUrl = yield fetchImage(src, { useProxy: options.useProxy });
1073
+ img.src = dataUrl;
1074
+ if (!img.width) img.width = img.naturalWidth || 100;
1075
+ if (!img.height) img.height = img.naturalHeight || 100;
1076
+ } catch (e) {
1077
+ const fallback = document.createElement("div");
1078
+ 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;`;
1079
+ fallback.innerText = "img";
1080
+ img.replaceWith(fallback);
1081
+ }
1082
+ });
1083
+ for (let i = 0; i < imgs.length; i += 4) {
1084
+ const group = imgs.slice(i, i + 4).map(processImg);
1085
+ yield Promise.allSettled(group);
689
1086
  }
690
- };
691
- for (let i = 0; i < imgs.length; i += 4) {
692
- const group = imgs.slice(i, i + 4).map(processImg);
693
- await Promise.allSettled(group);
694
- }
1087
+ });
695
1088
  }
696
1089
 
697
1090
  // src/modules/background.js
698
- async function inlineBackgroundImages(source, clone, styleCache, options = {}) {
699
- const queue = [[source, clone]];
700
- while (queue.length) {
701
- const [srcNode, cloneNode] = queue.shift();
702
- const style = styleCache.get(srcNode) || getStyle(srcNode);
703
- if (!styleCache.has(srcNode)) styleCache.set(srcNode, style);
704
- const bg = style.getPropertyValue("background-image");
705
- const bgSplits = bg.split(",");
706
- for (let i = 0; i < bgSplits.length; i++) {
707
- const rawUrl = extractURL(bgSplits[i]);
708
- if (rawUrl) {
709
- try {
710
- let bgUrl = encodeURI(rawUrl);
711
- let dataUrl;
712
- if (bgCache.has(bgUrl)) {
713
- dataUrl = bgCache.get(bgUrl);
714
- } else {
715
- const crossOrigin = options.crossOrigin ? options.crossOrigin(bgUrl) : "anonymous";
716
- dataUrl = await fetchImage(bgUrl, 3e3, crossOrigin);
717
- bgCache.set(bgUrl, dataUrl);
718
- }
719
- bgSplits[i] = `url(${dataUrl})`;
720
- } catch {
721
- bgSplits[i] = "none";
1091
+ function inlineBackgroundImages(_0, _1, _2) {
1092
+ return __async(this, arguments, function* (source, clone, styleCache, options = {}) {
1093
+ const queue = [[source, clone]];
1094
+ while (queue.length) {
1095
+ const [srcNode, cloneNode] = queue.shift();
1096
+ const style = styleCache.get(srcNode) || getStyle(srcNode);
1097
+ if (!styleCache.has(srcNode)) styleCache.set(srcNode, style);
1098
+ const bg = style.getPropertyValue("background-image");
1099
+ const bgColor = style.getPropertyValue("background-color");
1100
+ if (!bg || bg === "none") {
1101
+ const sChildren2 = Array.from(srcNode.children);
1102
+ const cChildren2 = Array.from(cloneNode.children);
1103
+ for (let i = 0; i < Math.min(sChildren2.length, cChildren2.length); i++) {
1104
+ queue.push([sChildren2[i], cChildren2[i]]);
722
1105
  }
1106
+ continue;
1107
+ }
1108
+ const bgSplits = splitBackgroundImage(bg);
1109
+ const newBgParts = yield Promise.all(
1110
+ bgSplits.map((entry) => inlineSingleBackgroundEntry(entry, options))
1111
+ );
1112
+ if (newBgParts.some((p) => p && p !== "none" && !/^url\(undefined\)/.test(p))) {
1113
+ cloneNode.style.backgroundImage = newBgParts.join(", ");
1114
+ }
1115
+ if (bgColor && bgColor !== "transparent" && bgColor !== "rgba(0, 0, 0, 0)") {
1116
+ cloneNode.style.backgroundColor = bgColor;
1117
+ }
1118
+ const sChildren = Array.from(srcNode.children);
1119
+ const cChildren = Array.from(cloneNode.children);
1120
+ for (let i = 0; i < Math.min(sChildren.length, cChildren.length); i++) {
1121
+ queue.push([sChildren[i], cChildren[i]]);
723
1122
  }
724
1123
  }
725
- if (bgSplits.some((s) => s.includes("url("))) {
726
- cloneNode.style.backgroundImage = bgSplits.join(",");
727
- }
728
- const sChildren = Array.from(srcNode.children);
729
- const cChildren = Array.from(cloneNode.children);
730
- for (let i = 0; i < Math.min(sChildren.length, cChildren.length); i++) {
731
- queue.push([sChildren[i], cChildren[i]]);
732
- }
733
- }
1124
+ });
734
1125
  }
735
1126
 
736
1127
  // src/core/capture.js
737
- async function captureDOM(element, options = {}) {
738
- if (!element) throw new Error("Element cannot be null or undefined");
739
- const { compress = true, embedFonts = false, fast = true, scale = 1 } = options;
740
- let clone, classCSS, styleCache;
741
- let fontsCSS = "";
742
- let baseCSS = "";
743
- let dataURL;
744
- let svgString;
745
- ({ clone, classCSS, styleCache } = await prepareClone(element, compress, embedFonts));
746
- await new Promise((resolve) => {
747
- idle(async () => {
748
- await inlineImages(clone, options);
749
- resolve();
750
- }, { fast });
751
- });
752
- await new Promise((resolve) => {
753
- idle(async () => {
754
- await inlineBackgroundImages(element, clone, styleCache, options);
755
- resolve();
756
- }, { fast });
757
- });
758
- if (embedFonts) {
759
- await new Promise((resolve) => {
760
- idle(async () => {
761
- fontsCSS = await embedCustomFonts({ ignoreIconFonts: !embedFonts });
1128
+ function captureDOM(_0) {
1129
+ return __async(this, arguments, function* (element, options = {}) {
1130
+ if (!element) throw new Error("Element cannot be null or undefined");
1131
+ const { compress = true, embedFonts = false, fast = true, scale = 1, useProxy = "" } = options;
1132
+ let clone, classCSS, styleCache;
1133
+ let fontsCSS = "";
1134
+ let baseCSS = "";
1135
+ let dataURL;
1136
+ let svgString;
1137
+ ({ clone, classCSS, styleCache } = yield prepareClone(element, compress, embedFonts, options));
1138
+ yield new Promise((resolve) => {
1139
+ idle(() => __async(null, null, function* () {
1140
+ yield inlineImages(clone, options);
762
1141
  resolve();
763
- }, { fast });
1142
+ }), { fast });
764
1143
  });
765
- }
766
- if (compress) {
767
- const usedTags = collectUsedTagNames(clone).sort();
768
- const tagKey = usedTags.join(",");
769
- if (baseCSSCache.has(tagKey)) {
770
- baseCSS = baseCSSCache.get(tagKey);
771
- } else {
772
- await new Promise((resolve) => {
773
- idle(() => {
774
- baseCSS = generateDedupedBaseCSS(usedTags);
775
- baseCSSCache.set(tagKey, baseCSS);
1144
+ yield new Promise((resolve) => {
1145
+ idle(() => __async(null, null, function* () {
1146
+ yield inlineBackgroundImages(element, clone, styleCache, options);
1147
+ resolve();
1148
+ }), { fast });
1149
+ });
1150
+ if (embedFonts) {
1151
+ yield new Promise((resolve) => {
1152
+ idle(() => __async(null, null, function* () {
1153
+ fontsCSS = yield embedCustomFonts();
776
1154
  resolve();
777
- }, { fast });
1155
+ }), { fast });
778
1156
  });
779
1157
  }
780
- }
781
- await new Promise((resolve) => {
782
- idle(() => {
783
- const rect = element.getBoundingClientRect();
784
- const w = Math.ceil(rect.width);
785
- const h = Math.ceil(rect.height);
786
- clone.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
787
- clone.style.transformOrigin = "top left";
788
- if (scale !== 1 && isSafari()) {
789
- clone.style.scale = `${scale}`;
790
- }
791
- const svgNS = "http://www.w3.org/2000/svg";
792
- const fo = document.createElementNS(svgNS, "foreignObject");
793
- fo.setAttribute("width", "100%");
794
- fo.setAttribute("height", "100%");
795
- const styleTag = document.createElement("style");
796
- styleTag.textContent = baseCSS + fontsCSS + "svg{overflow:visible;}" + classCSS;
797
- fo.appendChild(styleTag);
798
- fo.appendChild(clone);
799
- const serializer = new XMLSerializer();
800
- const foString = serializer.serializeToString(fo);
801
- const svgHeader = `<svg xmlns="${svgNS}" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">`;
802
- const svgFooter = "</svg>";
803
- svgString = svgHeader + foString + svgFooter;
804
- dataURL = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgString)}`;
805
- resolve();
806
- }, { fast });
1158
+ if (compress) {
1159
+ const usedTags = collectUsedTagNames(clone).sort();
1160
+ const tagKey = usedTags.join(",");
1161
+ if (baseCSSCache.has(tagKey)) {
1162
+ baseCSS = baseCSSCache.get(tagKey);
1163
+ } else {
1164
+ yield new Promise((resolve) => {
1165
+ idle(() => {
1166
+ baseCSS = generateDedupedBaseCSS(usedTags);
1167
+ baseCSSCache.set(tagKey, baseCSS);
1168
+ resolve();
1169
+ }, { fast });
1170
+ });
1171
+ }
1172
+ }
1173
+ yield new Promise((resolve) => {
1174
+ idle(() => {
1175
+ const rect = element.getBoundingClientRect();
1176
+ let w = rect.width;
1177
+ let h = rect.height;
1178
+ const hasW = Number.isFinite(options.width);
1179
+ const hasH = Number.isFinite(options.height);
1180
+ const hasScale = typeof scale === "number" && scale !== 1;
1181
+ if (!hasScale) {
1182
+ const aspect = rect.width / rect.height;
1183
+ if (hasW && hasH) {
1184
+ w = options.width;
1185
+ h = options.height;
1186
+ } else if (hasW) {
1187
+ w = options.width;
1188
+ h = w / aspect;
1189
+ } else if (hasH) {
1190
+ h = options.height;
1191
+ w = h * aspect;
1192
+ }
1193
+ }
1194
+ w = Math.ceil(w);
1195
+ h = Math.ceil(h);
1196
+ clone.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
1197
+ clone.style.transformOrigin = "top left";
1198
+ if (!hasScale && (hasW || hasH)) {
1199
+ const originalW = rect.width;
1200
+ const originalH = rect.height;
1201
+ const scaleX = w / originalW;
1202
+ const scaleY = h / originalH;
1203
+ const existingTransform = clone.style.transform || "";
1204
+ const scaleTransform = `scale(${scaleX}, ${scaleY})`;
1205
+ clone.style.transform = `${scaleTransform} ${existingTransform}`.trim();
1206
+ } else if (hasScale && isSafari()) {
1207
+ clone.style.scale = `${scale}`;
1208
+ }
1209
+ const svgNS = "http://www.w3.org/2000/svg";
1210
+ const fo = document.createElementNS(svgNS, "foreignObject");
1211
+ fo.setAttribute("width", "100%");
1212
+ fo.setAttribute("height", "100%");
1213
+ const styleTag = document.createElement("style");
1214
+ styleTag.textContent = baseCSS + fontsCSS + "svg{overflow:visible;}" + classCSS;
1215
+ fo.appendChild(styleTag);
1216
+ fo.appendChild(clone);
1217
+ const serializer = new XMLSerializer();
1218
+ const foString = serializer.serializeToString(fo);
1219
+ const svgHeader = `<svg xmlns="${svgNS}" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">`;
1220
+ const svgFooter = "</svg>";
1221
+ svgString = svgHeader + foString + svgFooter;
1222
+ dataURL = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgString)}`;
1223
+ resolve();
1224
+ }, { fast });
1225
+ });
1226
+ const sandbox = document.getElementById("snapdom-sandbox");
1227
+ if (sandbox && sandbox.style.position === "absolute") sandbox.remove();
1228
+ return dataURL;
807
1229
  });
808
- const sandbox = document.getElementById("snapdom-sandbox");
809
- if (sandbox && sandbox.style.position === "absolute") sandbox.remove();
810
- return dataURL;
811
1230
  }
812
1231
 
813
1232
  // src/api/snapdom.js
814
- async function toImg(url, { dpr = 1, scale = 1 }) {
815
- const img = new Image();
816
- img.src = url;
817
- await img.decode();
818
- if (isSafari) {
819
- img.width = img.width * scale;
820
- img.height = img.height * scale;
821
- } else {
822
- img.width = img.width / scale;
823
- img.height = img.height / scale;
824
- }
825
- return img;
826
- }
827
- async function toCanvas(url, { dpr = 1, scale = 1 } = {}) {
828
- const img = new Image();
829
- img.src = url;
830
- await img.decode();
831
- const canvas = document.createElement("canvas");
832
- const width = img.width * scale;
833
- const height = img.height * scale;
834
- canvas.width = Math.ceil(width * dpr);
835
- canvas.height = Math.ceil(height * dpr);
836
- const ctx = canvas.getContext("2d");
837
- ctx.scale(dpr, dpr);
838
- ctx.drawImage(img, 0, 0, width, height);
839
- canvas.style.width = `${width}px`;
840
- canvas.style.height = `${height}px`;
841
- return canvas;
842
- }
843
- async function toBlob(url) {
844
- const svgText = decodeURIComponent(url.split(",")[1]);
845
- return new Blob([svgText], { type: "image/svg+xml" });
846
- }
847
- async function createBackground(url, { dpr = 1, scale = 1 }, backgroundColor) {
848
- const baseCanvas = await toCanvas(url, { dpr, scale });
849
- if (!backgroundColor) return baseCanvas;
850
- const temp = document.createElement("canvas");
851
- temp.width = baseCanvas.width;
852
- temp.height = baseCanvas.height;
853
- const ctx = temp.getContext("2d");
854
- ctx.fillStyle = backgroundColor;
855
- ctx.fillRect(0, 0, temp.width, temp.height);
856
- ctx.drawImage(baseCanvas, 0, 0);
857
- return temp;
858
- }
859
- async function toRasterImg(url, { dpr = 1, scale = 1, backgroundColor = "#fff", quality }, format = "png") {
860
- const canvas = await createBackground(url, { dpr, scale }, backgroundColor);
861
- const img = new Image();
862
- img.src = canvas.toDataURL(`image/${format}`, quality);
863
- await img.decode();
864
- img.style.width = `${canvas.width / dpr}px`;
865
- img.style.height = `${canvas.height / dpr}px`;
866
- return img;
867
- }
868
- async function download(url, { dpr = 1, scale = 1, backgroundColor = "#fff", format = "png", filename = "capture" } = {}) {
869
- if (format === "svg") {
870
- const blob = await toBlob(url);
871
- const objectURL = URL.createObjectURL(blob);
872
- const a2 = document.createElement("a");
873
- a2.href = objectURL;
874
- a2.download = `${filename}.svg`;
875
- a2.click();
876
- URL.revokeObjectURL(objectURL);
877
- return;
878
- }
879
- const defaultBg = ["jpg", "jpeg", "webp"].includes(format) ? "#fff" : void 0;
880
- const finalBg = backgroundColor ?? defaultBg;
881
- const canvas = await createBackground(url, { dpr, scale }, finalBg);
882
- const mime = {
883
- jpg: "image/jpeg",
884
- jpeg: "image/jpeg",
885
- png: "image/png",
886
- webp: "image/webp"
887
- }[format] || "image/png";
888
- const dataURL = canvas.toDataURL(mime);
889
- const a = document.createElement("a");
890
- a.href = dataURL;
891
- a.download = `${filename}.${format}`;
892
- a.click();
893
- }
894
- async function snapdom(element, options = {}) {
895
- options = { scale: 1, ...options };
896
- if (!element) throw new Error("Element cannot be null or undefined");
897
- return await snapdom.capture(element, options);
898
- }
899
- snapdom.capture = async (el, options = {}) => {
900
- const url = await captureDOM(el, options);
1233
+ function toImg(_0, _1) {
1234
+ return __async(this, arguments, function* (url, { dpr = 1, scale = 1 }) {
1235
+ const img = new Image();
1236
+ img.src = url;
1237
+ yield img.decode();
1238
+ if (isSafari) {
1239
+ img.width = img.width * scale;
1240
+ img.height = img.height * scale;
1241
+ } else {
1242
+ img.width = img.width / scale;
1243
+ img.height = img.height / scale;
1244
+ }
1245
+ return img;
1246
+ });
1247
+ }
1248
+ function toCanvas(_0) {
1249
+ return __async(this, arguments, function* (url, { dpr = 1, scale = 1 } = {}) {
1250
+ const img = new Image();
1251
+ img.src = url;
1252
+ yield img.decode();
1253
+ const canvas = document.createElement("canvas");
1254
+ const width = img.width * scale;
1255
+ const height = img.height * scale;
1256
+ canvas.width = Math.ceil(width * dpr);
1257
+ canvas.height = Math.ceil(height * dpr);
1258
+ const ctx = canvas.getContext("2d");
1259
+ ctx.scale(dpr, dpr);
1260
+ ctx.drawImage(img, 0, 0, width, height);
1261
+ canvas.style.width = `${width}px`;
1262
+ canvas.style.height = `${height}px`;
1263
+ return canvas;
1264
+ });
1265
+ }
1266
+ function toBlob(_0) {
1267
+ return __async(this, arguments, function* (url, {
1268
+ type = "svg",
1269
+ scale = 1,
1270
+ backgroundColor = "#fff",
1271
+ quality
1272
+ } = {}) {
1273
+ const mime = {
1274
+ jpg: "image/jpeg",
1275
+ jpeg: "image/jpeg",
1276
+ png: "image/png",
1277
+ webp: "image/webp"
1278
+ }[type] || "image/png";
1279
+ if (type === "svg") {
1280
+ const svgText = decodeURIComponent(url.split(",")[1]);
1281
+ return new Blob([svgText], { type: "image/svg+xml" });
1282
+ }
1283
+ const canvas = yield createBackground(url, { dpr: 1, scale }, backgroundColor);
1284
+ return new Promise((resolve) => {
1285
+ canvas.toBlob((blob) => resolve(blob), `${mime}`, quality);
1286
+ });
1287
+ });
1288
+ }
1289
+ function createBackground(_0, _1, _2) {
1290
+ return __async(this, arguments, function* (url, { dpr = 1, scale = 1 }, backgroundColor) {
1291
+ const baseCanvas = yield toCanvas(url, { dpr, scale });
1292
+ if (!backgroundColor) return baseCanvas;
1293
+ const temp = document.createElement("canvas");
1294
+ temp.width = baseCanvas.width;
1295
+ temp.height = baseCanvas.height;
1296
+ const ctx = temp.getContext("2d");
1297
+ ctx.fillStyle = backgroundColor;
1298
+ ctx.fillRect(0, 0, temp.width, temp.height);
1299
+ ctx.drawImage(baseCanvas, 0, 0);
1300
+ return temp;
1301
+ });
1302
+ }
1303
+ function toRasterImg(_0, _1) {
1304
+ return __async(this, arguments, function* (url, { dpr = 1, scale = 1, backgroundColor = "#fff", quality }, format = "png") {
1305
+ const canvas = yield createBackground(url, { dpr, scale }, backgroundColor);
1306
+ const img = new Image();
1307
+ img.src = canvas.toDataURL(`image/${format}`, quality);
1308
+ yield img.decode();
1309
+ img.style.width = `${canvas.width / dpr}px`;
1310
+ img.style.height = `${canvas.height / dpr}px`;
1311
+ return img;
1312
+ });
1313
+ }
1314
+ function download(_0) {
1315
+ return __async(this, arguments, function* (url, { dpr = 1, scale = 1, backgroundColor = "#fff", format = "png", filename = "capture" } = {}) {
1316
+ if (format === "svg") {
1317
+ const blob = yield toBlob(url);
1318
+ const objectURL = URL.createObjectURL(blob);
1319
+ const a2 = document.createElement("a");
1320
+ a2.href = objectURL;
1321
+ a2.download = `${filename}.svg`;
1322
+ a2.click();
1323
+ URL.revokeObjectURL(objectURL);
1324
+ return;
1325
+ }
1326
+ const defaultBg = ["jpg", "jpeg", "webp"].includes(format) ? "#fff" : void 0;
1327
+ const finalBg = backgroundColor != null ? backgroundColor : defaultBg;
1328
+ const canvas = yield createBackground(url, { dpr, scale }, finalBg);
1329
+ const mime = {
1330
+ jpg: "image/jpeg",
1331
+ jpeg: "image/jpeg",
1332
+ png: "image/png",
1333
+ webp: "image/webp"
1334
+ }[format] || "image/png";
1335
+ const dataURL = canvas.toDataURL(mime);
1336
+ const a = document.createElement("a");
1337
+ a.href = dataURL;
1338
+ a.download = `${filename}.${format}`;
1339
+ a.click();
1340
+ });
1341
+ }
1342
+ function snapdom(_0) {
1343
+ return __async(this, arguments, function* (element, options = {}) {
1344
+ options = __spreadValues({ scale: 1 }, options);
1345
+ if (!element) throw new Error("Element cannot be null or undefined");
1346
+ if (options.iconFonts) {
1347
+ extendIconFonts(options.iconFonts);
1348
+ }
1349
+ return yield snapdom.capture(element, options);
1350
+ });
1351
+ }
1352
+ snapdom.capture = (_0, ..._1) => __async(null, [_0, ..._1], function* (el, options = {}) {
1353
+ const url = yield captureDOM(el, options);
901
1354
  const dpr = window.devicePixelRatio || 1;
902
1355
  const scale = options.scale || 1;
903
1356
  return {
@@ -906,75 +1359,96 @@
906
1359
  toRaw: () => url,
907
1360
  toImg: () => toImg(url, { dpr, scale }),
908
1361
  toCanvas: () => toCanvas(url, { dpr, scale }),
909
- toBlob: () => toBlob(url),
910
- toPng: (options2) => toRasterImg(url, { dpr, scale, ...options2 }, "png"),
911
- toJpg: (options2) => toRasterImg(url, { dpr, scale, ...options2 }, "jpeg"),
912
- toWebp: (options2) => toRasterImg(url, { dpr, scale, ...options2 }, "webp"),
1362
+ toBlob: (options2) => toBlob(url, __spreadValues({ dpr, scale }, options2)),
1363
+ toPng: (options2) => toRasterImg(url, __spreadValues({ dpr, scale }, options2), "png"),
1364
+ toJpg: (options2) => toRasterImg(url, __spreadValues({ dpr, scale }, options2), "jpeg"),
1365
+ toWebp: (options2) => toRasterImg(url, __spreadValues({ dpr, scale }, options2), "webp"),
913
1366
  download: ({ format = "png", filename = "capture", backgroundColor } = {}) => download(url, { dpr, scale, backgroundColor, format, filename })
914
1367
  };
915
- };
916
- snapdom.toRaw = async (el, options) => (await snapdom.capture(el, options)).toRaw();
917
- snapdom.toImg = async (el, options) => (await snapdom.capture(el, options)).toImg();
918
- snapdom.toCanvas = async (el, options) => (await snapdom.capture(el, options)).toCanvas();
919
- snapdom.toBlob = async (el, options) => (await snapdom.capture(el, options)).toBlob();
920
- snapdom.toPng = async (el, options) => (await snapdom.capture(el, options)).toPng(options);
921
- snapdom.toJpg = async (el, options) => (await snapdom.capture(el, options)).toJpg(options);
922
- snapdom.toWebp = async (el, options) => (await snapdom.capture(el, options)).toWebp(options);
923
- snapdom.download = async (el, options = {}) => {
924
- const {
1368
+ });
1369
+ snapdom.toRaw = (el, options) => __async(null, null, function* () {
1370
+ return (yield snapdom.capture(el, options)).toRaw();
1371
+ });
1372
+ snapdom.toImg = (el, options) => __async(null, null, function* () {
1373
+ return (yield snapdom.capture(el, options)).toImg();
1374
+ });
1375
+ snapdom.toCanvas = (el, options) => __async(null, null, function* () {
1376
+ return (yield snapdom.capture(el, options)).toCanvas();
1377
+ });
1378
+ snapdom.toBlob = (el, options) => __async(null, null, function* () {
1379
+ return (yield snapdom.capture(el, options)).toBlob(options);
1380
+ });
1381
+ snapdom.toPng = (el, options) => __async(null, null, function* () {
1382
+ return (yield snapdom.capture(el, options)).toPng(options);
1383
+ });
1384
+ snapdom.toJpg = (el, options) => __async(null, null, function* () {
1385
+ return (yield snapdom.capture(el, options)).toJpg(options);
1386
+ });
1387
+ snapdom.toWebp = (el, options) => __async(null, null, function* () {
1388
+ return (yield snapdom.capture(el, options)).toWebp(options);
1389
+ });
1390
+ snapdom.download = (_0, ..._1) => __async(null, [_0, ..._1], function* (el, options = {}) {
1391
+ const _a = options, {
925
1392
  format = "png",
926
1393
  filename = "capture",
927
- backgroundColor,
928
- ...rest
929
- } = options;
930
- const capture = await snapdom.capture(el, rest);
931
- return await capture.download({ format, filename, backgroundColor });
932
- };
1394
+ backgroundColor
1395
+ } = _a, rest = __objRest(_a, [
1396
+ "format",
1397
+ "filename",
1398
+ "backgroundColor"
1399
+ ]);
1400
+ const capture = yield snapdom.capture(el, rest);
1401
+ return yield capture.download({ format, filename, backgroundColor });
1402
+ });
933
1403
 
934
1404
  // src/api/preCache.js
935
- async function preCache(root = document, options = {}) {
936
- const { embedFonts = true, reset = false, crossOrigin: crossOriginFn } = options;
937
- if (reset) {
938
- imageCache.clear();
939
- bgCache.clear();
940
- resourceCache.clear();
941
- baseCSSCache.clear();
942
- computedStyleCache.clear();
943
- return;
944
- }
945
- await document.fonts.ready;
946
- precacheCommonTags();
947
- let imgEls = [], allEls = [];
948
- if (root?.querySelectorAll) {
949
- imgEls = Array.from(root.querySelectorAll("img[src]"));
950
- allEls = Array.from(root.querySelectorAll("*"));
951
- }
952
- const promises = [];
953
- for (const img of imgEls) {
954
- const src = img.src;
955
- if (!imageCache.has(src)) {
956
- const crossOrigin = crossOriginFn ? crossOriginFn(src) : "anonymous";
957
- promises.push(
958
- fetchImage(src, 3e3, crossOrigin).then((dataURL) => imageCache.set(src, dataURL)).catch(() => {
959
- })
960
- );
1405
+ function preCache() {
1406
+ return __async(this, arguments, function* (root = document, options = {}) {
1407
+ const { embedFonts = true, reset = false } = options;
1408
+ if (reset) {
1409
+ imageCache.clear();
1410
+ bgCache.clear();
1411
+ resourceCache.clear();
1412
+ baseCSSCache.clear();
1413
+ return;
961
1414
  }
962
- }
963
- for (const el of allEls) {
964
- const bg = getComputedStyle(el).backgroundImage;
965
- const url = extractURL(bg);
966
- if (url && !bgCache.has(url)) {
967
- const crossOrigin = crossOriginFn ? crossOriginFn(url) : "anonymous";
968
- promises.push(
969
- fetchImage(url, 3e3, crossOrigin).then((dataURL) => bgCache.set(url, dataURL)).catch(() => {
970
- })
971
- );
1415
+ yield document.fonts.ready;
1416
+ precacheCommonTags();
1417
+ let imgEls = [], allEls = [];
1418
+ if (root == null ? void 0 : root.querySelectorAll) {
1419
+ imgEls = Array.from(root.querySelectorAll("img[src]"));
1420
+ allEls = Array.from(root.querySelectorAll("*"));
972
1421
  }
973
- }
974
- if (embedFonts) {
975
- await embedCustomFonts({ ignoreIconFonts: !embedFonts, preCached: true });
976
- }
977
- await Promise.all(promises);
1422
+ const promises = [];
1423
+ for (const img of imgEls) {
1424
+ const src = img.src;
1425
+ if (!imageCache.has(src)) {
1426
+ promises.push(
1427
+ fetchImage(src, { useProxy: options.useProxy }).then((dataURL) => imageCache.set(src, dataURL)).catch(() => {
1428
+ })
1429
+ );
1430
+ }
1431
+ }
1432
+ for (const el of allEls) {
1433
+ const bg = getStyle(el).backgroundImage;
1434
+ if (bg && bg !== "none") {
1435
+ const bgSplits = splitBackgroundImage(bg);
1436
+ for (const entry of bgSplits) {
1437
+ const isUrl = entry.startsWith("url(");
1438
+ if (isUrl) {
1439
+ promises.push(
1440
+ inlineSingleBackgroundEntry(entry, options).catch(() => {
1441
+ })
1442
+ );
1443
+ }
1444
+ }
1445
+ }
1446
+ }
1447
+ if (embedFonts) {
1448
+ yield embedCustomFonts({ preCached: true });
1449
+ }
1450
+ yield Promise.all(promises);
1451
+ });
978
1452
  }
979
1453
 
980
1454
  // src/index.browser.js