@zumer/snapdom 1.8.0 → 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,7 +1,7 @@
1
1
 
2
2
  /*
3
3
  * snapdom
4
- * v.1.8.0
4
+ * v.1.9.3
5
5
  * Author Juan Martin Muda
6
6
  * License MIT
7
7
  **/
@@ -214,10 +214,9 @@
214
214
  if (bgCache.has(encodedUrl)) {
215
215
  return options.skipInline ? void 0 : `url(${bgCache.get(encodedUrl)})`;
216
216
  } else {
217
- const crossOrigin = options.crossOrigin ? options.crossOrigin(encodedUrl) : "anonymous";
218
- const dataUrl = yield fetchImage(encodedUrl, 3e3, crossOrigin);
217
+ const dataUrl = yield fetchImage(encodedUrl, { useProxy: options.useProxy });
219
218
  bgCache.set(encodedUrl, dataUrl);
220
- return options.skipInline ? void 0 : `url(${dataUrl})`;
219
+ return options.skipInline ? void 0 : `url("${dataUrl}")`;
221
220
  }
222
221
  }
223
222
  if (isGradient || entry === "none") {
@@ -270,30 +269,86 @@
270
269
  }
271
270
  return url;
272
271
  }
273
- function isIconFont(familyOrUrl) {
274
- const iconFontPatterns = [
275
- /font\s*awesome/i,
276
- /material\s*icons/i,
277
- /ionicons/i,
278
- /glyphicons/i,
279
- /feather/i,
280
- /bootstrap\s*icons/i,
281
- /remix\s*icons/i,
282
- /heroicons/i,
283
- /lucide/i
284
- ];
285
- return iconFontPatterns.some((rx) => rx.test(familyOrUrl));
286
- }
287
- 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}`);
288
319
  if (imageCache.has(src)) {
320
+ console.log(`[SnapDOM - fetchImage] Cache hit for: ${src}`);
289
321
  return Promise.resolve(imageCache.get(src));
290
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
+ }
291
345
  return new Promise((resolve, reject) => {
292
346
  const timeoutId = setTimeout(() => {
347
+ console.log(`[SnapDOM - fetchImage] Timeout after ${timeout}ms for image: ${src}`);
293
348
  reject(new Error("Image load timed out"));
294
349
  }, timeout);
295
350
  const image = new Image();
296
- image.crossOrigin = crossOrigin;
351
+ image.crossOrigin = crossOriginValue;
297
352
  image.onload = () => __async(null, null, function* () {
298
353
  clearTimeout(timeoutId);
299
354
  try {
@@ -303,21 +358,30 @@
303
358
  canvas.height = image.height;
304
359
  const ctx = canvas.getContext("2d");
305
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) {
306
365
  try {
307
- const dataURL = canvas.toDataURL("image/png");
308
- imageCache.set(src, dataURL);
309
- resolve(dataURL);
310
- } catch (e) {
311
- 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);
312
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);
313
381
  } catch (e) {
314
382
  reject(e);
315
383
  }
316
384
  });
317
- image.onerror = (e) => {
318
- clearTimeout(timeoutId);
319
- reject(new Error("Failed to load image: " + (e.message || "Unknown error")));
320
- };
321
385
  image.src = src;
322
386
  });
323
387
  }
@@ -351,9 +415,9 @@
351
415
  return cleaned.trim().replace(/\s{2,}/g, " ");
352
416
  }
353
417
  function safeEncodeURI(uri) {
418
+ if (/%[0-9A-Fa-f]{2}/.test(uri)) return uri;
354
419
  try {
355
- const newURI = decodeURI(uri);
356
- return encodeURI(newURI);
420
+ return encodeURI(uri);
357
421
  } catch (e) {
358
422
  return uri;
359
423
  }
@@ -376,6 +440,20 @@
376
440
  }
377
441
 
378
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
+ }
379
457
  function inlineAllStyles(source, clone, styleMap, cache, compress) {
380
458
  var _a;
381
459
  if (source.tagName === "STYLE") return;
@@ -383,28 +461,72 @@
383
461
  cache.set(source, getStyle(source));
384
462
  }
385
463
  const style = cache.get(source);
386
- const snapshot = snapshotComputedStyle(style);
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
+ }
387
474
  const tagName = ((_a = source.tagName) == null ? void 0 : _a.toLowerCase()) || "div";
388
475
  const key = getStyleKey(snapshot, tagName, compress);
476
+ snapshotKeyCache.set(hash, key);
389
477
  styleMap.set(clone, key);
390
478
  }
391
479
 
392
480
  // src/core/clone.js
393
- function deepClone(node, styleMap, styleCache, nodeMap, compress) {
481
+ function deepClone(node, styleMap, styleCache, nodeMap, compress, options = {}, originalRoot) {
394
482
  var _a;
395
- if (node.nodeType === Node.ELEMENT_NODE && node.getAttribute("data-capture") === "exclude") {
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") {
396
492
  const spacer = document.createElement("div");
397
493
  const rect = node.getBoundingClientRect();
398
494
  spacer.style.cssText = `display: inline-block; width: ${rect.width}px; height: ${rect.height}px; visibility: hidden;`;
399
495
  return spacer;
400
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
+ }
401
523
  if (node.tagName === "IFRAME") {
402
524
  const fallback = document.createElement("div");
403
525
  fallback.textContent = "";
404
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;`;
405
527
  return fallback;
406
528
  }
407
- if (node.nodeType === Node.ELEMENT_NODE && node.getAttribute("data-capture") === "placeholder") {
529
+ if (node.getAttribute("data-capture") === "placeholder") {
408
530
  const clone2 = node.cloneNode(false);
409
531
  nodeMap.set(clone2, node);
410
532
  inlineAllStyles(node, clone2, styleMap, styleCache, compress);
@@ -425,14 +547,6 @@
425
547
  img.style.height = node.style.height || `${node.height}px`;
426
548
  return img;
427
549
  }
428
- if (node.nodeType === Node.TEXT_NODE) {
429
- if ((_a = node.parentElement) == null ? void 0 : _a.shadowRoot) {
430
- const tag = node.parentElement.tagName.toLowerCase();
431
- if (customElements.get(tag)) return null;
432
- }
433
- return node.cloneNode(true);
434
- }
435
- if (node.nodeType !== Node.ELEMENT_NODE) return node.cloneNode(true);
436
550
  const clone = node.cloneNode(false);
437
551
  nodeMap.set(clone, node);
438
552
  if (node instanceof HTMLInputElement) {
@@ -457,13 +571,14 @@
457
571
  }
458
572
  inlineAllStyles(node, clone, styleMap, styleCache, compress);
459
573
  const frag = document.createDocumentFragment();
574
+ const rootElement = originalRoot || node;
460
575
  node.childNodes.forEach((child) => {
461
- const clonedChild = deepClone(child, styleMap, styleCache, nodeMap, compress);
576
+ const clonedChild = deepClone(child, styleMap, styleCache, nodeMap, compress, options, rootElement);
462
577
  if (clonedChild) frag.appendChild(clonedChild);
463
578
  });
464
579
  clone.appendChild(frag);
465
580
  if (node.shadowRoot) {
466
- 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);
467
582
  const shadowFrag = document.createDocumentFragment();
468
583
  shadowContent.forEach((child) => shadowFrag.appendChild(child));
469
584
  clone.appendChild(shadowFrag);
@@ -471,6 +586,43 @@
471
586
  return clone;
472
587
  }
473
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
+
474
626
  // src/modules/fonts.js
475
627
  function iconToImage(unicodeChar, fontFamily, fontWeight, fontSize = 32, color = "#000") {
476
628
  return __async(this, null, function* () {
@@ -513,7 +665,7 @@
513
665
  });
514
666
  }
515
667
  function embedCustomFonts() {
516
- return __async(this, arguments, function* ({ ignoreIconFonts = true, preCached = false } = {}) {
668
+ return __async(this, arguments, function* ({ preCached = false } = {}) {
517
669
  if (resourceCache.has("fonts-embed-css")) {
518
670
  if (preCached) {
519
671
  const style = document.createElement("style");
@@ -530,6 +682,7 @@
530
682
  const matches = Array.from(cssText.matchAll(importRegex));
531
683
  for (const match of matches) {
532
684
  const importUrl = match[1];
685
+ if (isIconFont(importUrl)) continue;
533
686
  if (!isStylesheetLoaded(importUrl)) {
534
687
  styleImports.push(importUrl);
535
688
  }
@@ -542,7 +695,7 @@
542
695
  try {
543
696
  const res = yield fetch(link.href);
544
697
  const cssText = yield res.text();
545
- if (ignoreIconFonts && (isIconFont(link.href) || isIconFont(cssText))) continue;
698
+ if (isIconFont(link.href) || isIconFont(cssText)) continue;
546
699
  const urlRegex = /url\((["']?)([^"')]+)\1\)/g;
547
700
  const inlinedCSS = yield Promise.all(
548
701
  Array.from(cssText.matchAll(urlRegex)).map((match) => __async(null, null, function* () {
@@ -552,7 +705,7 @@
552
705
  if (!url.startsWith("http") && !url.startsWith("data:")) {
553
706
  url = new URL(url, link.href).href;
554
707
  }
555
- if (ignoreIconFonts && isIconFont(url)) return null;
708
+ if (isIconFont(url)) return null;
556
709
  if (resourceCache.has(url)) {
557
710
  processedFontURLs.add(url);
558
711
  return { original: match[0], inlined: `url(${resourceCache.get(url)})` };
@@ -590,7 +743,8 @@
590
743
  for (const rule of sheet.cssRules) {
591
744
  if (rule.type === CSSRule.FONT_FACE_RULE) {
592
745
  const src = rule.style.getPropertyValue("src");
593
- if (!src) continue;
746
+ const family = rule.style.getPropertyValue("font-family");
747
+ if (!src || isIconFont(family)) continue;
594
748
  const urlRegex = /url\((["']?)([^"')]+)\1\)/g;
595
749
  let inlinedSrc = src;
596
750
  const matches = Array.from(src.matchAll(urlRegex));
@@ -601,7 +755,7 @@
601
755
  if (!url.startsWith("http") && !url.startsWith("data:")) {
602
756
  url = new URL(url, sheet.href || location.href).href;
603
757
  }
604
- if (ignoreIconFonts && isIconFont(url)) continue;
758
+ if (isIconFont(url)) continue;
605
759
  if (resourceCache.has(url)) {
606
760
  processedFontURLs.add(url);
607
761
  inlinedSrc = inlinedSrc.replace(match[0], `url(${resourceCache.get(url)})`);
@@ -624,7 +778,7 @@
624
778
  }
625
779
  }
626
780
  finalCSS += `@font-face {
627
- font-family: ${rule.style.getPropertyValue("font-family")};
781
+ font-family: ${family};
628
782
  src: ${inlinedSrc};
629
783
  font-style: ${rule.style.getPropertyValue("font-style") || "normal"};
630
784
  font-weight: ${rule.style.getPropertyValue("font-weight") || "normal"};
@@ -639,6 +793,7 @@
639
793
  }
640
794
  for (const font of document.fonts) {
641
795
  if (font.family && font.status === "loaded" && font._snapdomSrc) {
796
+ if (isIconFont(font.family)) continue;
642
797
  let b64 = font._snapdomSrc;
643
798
  if (!b64.startsWith("data:")) {
644
799
  if (resourceCache.has(font._snapdomSrc)) {
@@ -684,7 +839,7 @@
684
839
  }
685
840
 
686
841
  // src/modules/pseudo.js
687
- function inlinePseudoElements(source, clone, styleMap, styleCache, compress, embedFonts = false) {
842
+ function inlinePseudoElements(source, clone, styleMap, styleCache, compress, embedFonts = false, useProxy) {
688
843
  return __async(this, null, function* () {
689
844
  if (!(source instanceof Element) || !(clone instanceof Element)) return;
690
845
  for (const pseudo of ["::before", "::after", "::first-letter"]) {
@@ -731,23 +886,21 @@
731
886
  const snapshot = snapshotComputedStyle(style);
732
887
  const key = getStyleKey(snapshot, "span", compress);
733
888
  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
- );
889
+ const isIconFont2 = isIconFont(fontFamily);
737
890
  const cleanContent = parseContent(content);
738
- if (!embedFonts && isIconFont2 && cleanContent.length === 1) {
891
+ if (isIconFont2 && cleanContent.length === 1) {
739
892
  const imgEl = document.createElement("img");
740
893
  imgEl.src = yield iconToImage(cleanContent, fontFamily, fontWeight, fontSize, color);
741
- imgEl.style = "display:block;width:100%;height:100%;object-fit:contain;";
894
+ imgEl.style = `width:${fontSize}px;height:auto;object-fit:contain;`;
742
895
  pseudoEl.appendChild(imgEl);
743
896
  } else if (cleanContent.startsWith("url(")) {
744
897
  const rawUrl = extractURL(cleanContent);
745
898
  if (rawUrl && rawUrl.trim() !== "") {
746
899
  try {
747
900
  const imgEl = document.createElement("img");
748
- const dataUrl = yield fetchImage(safeEncodeURI(rawUrl));
901
+ const dataUrl = yield fetchImage(safeEncodeURI(rawUrl, { useProxy }));
749
902
  imgEl.src = dataUrl;
750
- imgEl.style = "display:block;width:100%;height:100%;object-fit:contain;";
903
+ imgEl.style = `width:${fontSize}px;height:auto;object-fit:contain;`;
751
904
  pseudoEl.appendChild(imgEl);
752
905
  } catch (e) {
753
906
  console.error(`[snapdom] Error in pseudo ${pseudo} for`, source, e);
@@ -786,31 +939,67 @@
786
939
  styleMap,
787
940
  styleCache,
788
941
  compress,
789
- embedFonts
942
+ embedFonts,
943
+ useProxy
790
944
  );
791
945
  }
792
946
  });
793
947
  }
794
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;
970
+ }
971
+ }
972
+ }
973
+ if (defs.childNodes.length) {
974
+ svg.insertBefore(defs, svg.firstChild);
975
+ }
976
+ });
977
+ }
978
+
795
979
  // src/core/prepare.js
796
- function prepareClone(element, compress = false, embedFonts = false) {
797
- return __async(this, null, function* () {
980
+ function prepareClone(_0) {
981
+ return __async(this, arguments, function* (element, compress = false, embedFonts = false, options = {}) {
798
982
  var _a;
799
983
  const styleMap = /* @__PURE__ */ new Map();
800
984
  const styleCache = /* @__PURE__ */ new WeakMap();
801
985
  const nodeMap = /* @__PURE__ */ new Map();
802
986
  let clone;
803
987
  try {
804
- clone = deepClone(element, styleMap, styleCache, nodeMap, compress);
988
+ clone = deepClone(element, styleMap, styleCache, nodeMap, compress, options, element);
805
989
  } catch (e) {
806
990
  console.warn("deepClone failed:", e);
807
991
  throw e;
808
992
  }
809
993
  try {
810
- yield inlinePseudoElements(element, clone, styleMap, styleCache, compress, embedFonts);
994
+ yield inlinePseudoElements(element, clone, styleMap, styleCache, compress, embedFonts, options.useProxy);
811
995
  } catch (e) {
812
996
  console.warn("inlinePseudoElements failed:", e);
813
997
  }
998
+ try {
999
+ inlineExternalDef(clone);
1000
+ } catch (e) {
1001
+ console.warn("inlineExternalDef failed:", e);
1002
+ }
814
1003
  let classCSS = "";
815
1004
  if (compress) {
816
1005
  const keyToClass = generateCSSClasses(styleMap);
@@ -863,6 +1052,12 @@
863
1052
  clone.style.clear = "none";
864
1053
  clone.style.transform = transform || "";
865
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
+ }
866
1061
  return { clone, classCSS, styleCache };
867
1062
  });
868
1063
  }
@@ -874,8 +1069,7 @@
874
1069
  const processImg = (img) => __async(null, null, function* () {
875
1070
  const src = img.src;
876
1071
  try {
877
- const crossOrigin = options.crossOrigin ? options.crossOrigin(src) : "anonymous";
878
- const dataUrl = yield fetchImage(src, 3e3, crossOrigin);
1072
+ const dataUrl = yield fetchImage(src, { useProxy: options.useProxy });
879
1073
  img.src = dataUrl;
880
1074
  if (!img.width) img.width = img.naturalWidth || 100;
881
1075
  if (!img.height) img.height = img.naturalHeight || 100;
@@ -915,10 +1109,7 @@
915
1109
  const newBgParts = yield Promise.all(
916
1110
  bgSplits.map((entry) => inlineSingleBackgroundEntry(entry, options))
917
1111
  );
918
- const hasRealBg = newBgParts.some(
919
- (p) => p && p !== "none" && !/^url\(undefined\)/.test(p)
920
- );
921
- if (hasRealBg) {
1112
+ if (newBgParts.some((p) => p && p !== "none" && !/^url\(undefined\)/.test(p))) {
922
1113
  cloneNode.style.backgroundImage = newBgParts.join(", ");
923
1114
  }
924
1115
  if (bgColor && bgColor !== "transparent" && bgColor !== "rgba(0, 0, 0, 0)") {
@@ -937,14 +1128,13 @@
937
1128
  function captureDOM(_0) {
938
1129
  return __async(this, arguments, function* (element, options = {}) {
939
1130
  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;
1131
+ const { compress = true, embedFonts = false, fast = true, scale = 1, useProxy = "" } = options;
942
1132
  let clone, classCSS, styleCache;
943
1133
  let fontsCSS = "";
944
1134
  let baseCSS = "";
945
1135
  let dataURL;
946
1136
  let svgString;
947
- ({ clone, classCSS, styleCache } = yield prepareClone(element, compress, embedFonts));
1137
+ ({ clone, classCSS, styleCache } = yield prepareClone(element, compress, embedFonts, options));
948
1138
  yield new Promise((resolve) => {
949
1139
  idle(() => __async(null, null, function* () {
950
1140
  yield inlineImages(clone, options);
@@ -960,7 +1150,7 @@
960
1150
  if (embedFonts) {
961
1151
  yield new Promise((resolve) => {
962
1152
  idle(() => __async(null, null, function* () {
963
- fontsCSS = yield embedCustomFonts({ ignoreIconFonts: !embedFonts });
1153
+ fontsCSS = yield embedCustomFonts();
964
1154
  resolve();
965
1155
  }), { fast });
966
1156
  });
@@ -1073,10 +1263,27 @@
1073
1263
  return canvas;
1074
1264
  });
1075
1265
  }
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" });
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
+ });
1080
1287
  });
1081
1288
  }
1082
1289
  function createBackground(_0, _1, _2) {
@@ -1136,6 +1343,9 @@
1136
1343
  return __async(this, arguments, function* (element, options = {}) {
1137
1344
  options = __spreadValues({ scale: 1 }, options);
1138
1345
  if (!element) throw new Error("Element cannot be null or undefined");
1346
+ if (options.iconFonts) {
1347
+ extendIconFonts(options.iconFonts);
1348
+ }
1139
1349
  return yield snapdom.capture(element, options);
1140
1350
  });
1141
1351
  }
@@ -1149,7 +1359,7 @@
1149
1359
  toRaw: () => url,
1150
1360
  toImg: () => toImg(url, { dpr, scale }),
1151
1361
  toCanvas: () => toCanvas(url, { dpr, scale }),
1152
- toBlob: () => toBlob(url),
1362
+ toBlob: (options2) => toBlob(url, __spreadValues({ dpr, scale }, options2)),
1153
1363
  toPng: (options2) => toRasterImg(url, __spreadValues({ dpr, scale }, options2), "png"),
1154
1364
  toJpg: (options2) => toRasterImg(url, __spreadValues({ dpr, scale }, options2), "jpeg"),
1155
1365
  toWebp: (options2) => toRasterImg(url, __spreadValues({ dpr, scale }, options2), "webp"),
@@ -1166,7 +1376,7 @@
1166
1376
  return (yield snapdom.capture(el, options)).toCanvas();
1167
1377
  });
1168
1378
  snapdom.toBlob = (el, options) => __async(null, null, function* () {
1169
- return (yield snapdom.capture(el, options)).toBlob();
1379
+ return (yield snapdom.capture(el, options)).toBlob(options);
1170
1380
  });
1171
1381
  snapdom.toPng = (el, options) => __async(null, null, function* () {
1172
1382
  return (yield snapdom.capture(el, options)).toPng(options);
@@ -1194,7 +1404,7 @@
1194
1404
  // src/api/preCache.js
1195
1405
  function preCache() {
1196
1406
  return __async(this, arguments, function* (root = document, options = {}) {
1197
- const { embedFonts = true, reset = false, crossOrigin: crossOriginFn } = options;
1407
+ const { embedFonts = true, reset = false } = options;
1198
1408
  if (reset) {
1199
1409
  imageCache.clear();
1200
1410
  bgCache.clear();
@@ -1213,9 +1423,8 @@
1213
1423
  for (const img of imgEls) {
1214
1424
  const src = img.src;
1215
1425
  if (!imageCache.has(src)) {
1216
- const crossOrigin = crossOriginFn ? crossOriginFn(src) : "anonymous";
1217
1426
  promises.push(
1218
- fetchImage(src, 3e3, crossOrigin).then((dataURL) => imageCache.set(src, dataURL)).catch(() => {
1427
+ fetchImage(src, { useProxy: options.useProxy }).then((dataURL) => imageCache.set(src, dataURL)).catch(() => {
1219
1428
  })
1220
1429
  );
1221
1430
  }
@@ -1228,7 +1437,7 @@
1228
1437
  const isUrl = entry.startsWith("url(");
1229
1438
  if (isUrl) {
1230
1439
  promises.push(
1231
- inlineSingleBackgroundEntry(entry, { crossOrigin: crossOriginFn, skipInline: true }).catch(() => {
1440
+ inlineSingleBackgroundEntry(entry, options).catch(() => {
1232
1441
  })
1233
1442
  );
1234
1443
  }
@@ -1236,7 +1445,7 @@
1236
1445
  }
1237
1446
  }
1238
1447
  if (embedFonts) {
1239
- yield embedCustomFonts({ ignoreIconFonts: !embedFonts, preCached: true });
1448
+ yield embedCustomFonts({ preCached: true });
1240
1449
  }
1241
1450
  yield Promise.all(promises);
1242
1451
  });