@zumer/snapdom 1.8.0 → 1.9.3-next-dev.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/snapdom.js CHANGED
@@ -1,7 +1,7 @@
1
1
 
2
2
  /*
3
3
  * snapdom
4
- * v.1.8.0
4
+ * v.1.9.3-next-dev.0
5
5
  * Author Juan Martin Muda
6
6
  * License MIT
7
7
  **/
@@ -205,19 +205,17 @@
205
205
  // src/utils/helpers.js
206
206
  function inlineSingleBackgroundEntry(_0) {
207
207
  return __async(this, arguments, function* (entry, options = {}) {
208
- const isUrl = entry.startsWith("url(");
208
+ const match = entry.match(/url\(["']?(.*?)["']?\)/);
209
+ const rawUrl = match == null ? void 0 : match[1];
209
210
  const isGradient = /^((repeating-)?(linear|radial|conic)-gradient)\(/i.test(entry);
210
- if (isUrl) {
211
- const rawUrl = extractURL(entry);
212
- if (!rawUrl) return entry;
211
+ if (rawUrl) {
213
212
  const encodedUrl = safeEncodeURI(rawUrl);
214
213
  if (bgCache.has(encodedUrl)) {
215
214
  return options.skipInline ? void 0 : `url(${bgCache.get(encodedUrl)})`;
216
215
  } else {
217
- const crossOrigin = options.crossOrigin ? options.crossOrigin(encodedUrl) : "anonymous";
218
- const dataUrl = yield fetchImage(encodedUrl, 3e3, crossOrigin);
216
+ const dataUrl = yield fetchImage(encodedUrl, { useProxy: options.useProxy });
219
217
  bgCache.set(encodedUrl, dataUrl);
220
- return options.skipInline ? void 0 : `url(${dataUrl})`;
218
+ return options.skipInline ? void 0 : `url("${dataUrl}")`;
221
219
  }
222
220
  }
223
221
  if (isGradient || entry === "none") {
@@ -270,30 +268,86 @@
270
268
  }
271
269
  return url;
272
270
  }
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") {
271
+ function fetchImage(src, { timeout = 3e3, useProxy = "" } = {}) {
272
+ function getCrossOriginMode(url) {
273
+ try {
274
+ const parsed = new URL(url, window.location.href);
275
+ return parsed.origin === window.location.origin ? "use-credentials" : "anonymous";
276
+ } catch (e) {
277
+ return "anonymous";
278
+ }
279
+ }
280
+ function fetchWithFallback(url) {
281
+ return __async(this, null, function* () {
282
+ const fetchBlobAsDataURL = (fetchUrl) => fetch(fetchUrl, {
283
+ mode: "cors",
284
+ credentials: getCrossOriginMode(fetchUrl) === "use-credentials" ? "include" : "omit"
285
+ }).then((r) => r.blob()).then((blob) => new Promise((resolve, reject) => {
286
+ const reader = new FileReader();
287
+ reader.onloadend = () => {
288
+ const base64 = reader.result;
289
+ if (typeof base64 !== "string" || !base64.startsWith("data:image/")) {
290
+ reject(new Error("Invalid image data URL"));
291
+ return;
292
+ }
293
+ resolve(base64);
294
+ };
295
+ reader.onerror = () => reject(new Error("FileReader error"));
296
+ reader.readAsDataURL(blob);
297
+ }));
298
+ try {
299
+ return yield fetchBlobAsDataURL(url);
300
+ } catch (e) {
301
+ if (useProxy && typeof useProxy === "string") {
302
+ const proxied = useProxy.replace(/\/$/, "") + safeEncodeURI(url);
303
+ try {
304
+ return yield fetchBlobAsDataURL(proxied);
305
+ } catch (e2) {
306
+ console.error(`[SnapDOM - fetchImage] Proxy fallback failed for: ${url}`);
307
+ throw new Error("CORS restrictions prevented image capture (even via proxy)");
308
+ }
309
+ } else {
310
+ console.error(`[SnapDOM - fetchImage] No valid proxy URL provided for fallback: ${url}`);
311
+ throw new Error("Fetch fallback failed and no proxy provided");
312
+ }
313
+ }
314
+ });
315
+ }
316
+ const crossOriginValue = getCrossOriginMode(src);
317
+ console.log(`[SnapDOM - fetchImage] Start loading image: ${src} with crossOrigin=${crossOriginValue}`);
288
318
  if (imageCache.has(src)) {
319
+ console.log(`[SnapDOM - fetchImage] Cache hit for: ${src}`);
289
320
  return Promise.resolve(imageCache.get(src));
290
321
  }
322
+ const isDataURI = src.startsWith("data:image/");
323
+ if (isDataURI) {
324
+ imageCache.set(src, src);
325
+ return Promise.resolve(src);
326
+ }
327
+ const isSVG = /\.svg(\?.*)?$/i.test(src);
328
+ if (isSVG) {
329
+ return (() => __async(null, null, function* () {
330
+ try {
331
+ const response = yield fetch(src, {
332
+ mode: "cors",
333
+ credentials: crossOriginValue === "use-credentials" ? "include" : "omit"
334
+ });
335
+ const svgText = yield response.text();
336
+ const encoded = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgText)}`;
337
+ imageCache.set(src, encoded);
338
+ return encoded;
339
+ } catch (e) {
340
+ return fetchWithFallback(src);
341
+ }
342
+ }))();
343
+ }
291
344
  return new Promise((resolve, reject) => {
292
345
  const timeoutId = setTimeout(() => {
346
+ console.log(`[SnapDOM - fetchImage] Timeout after ${timeout}ms for image: ${src}`);
293
347
  reject(new Error("Image load timed out"));
294
348
  }, timeout);
295
349
  const image = new Image();
296
- image.crossOrigin = crossOrigin;
350
+ image.crossOrigin = crossOriginValue;
297
351
  image.onload = () => __async(null, null, function* () {
298
352
  clearTimeout(timeoutId);
299
353
  try {
@@ -303,21 +357,30 @@
303
357
  canvas.height = image.height;
304
358
  const ctx = canvas.getContext("2d");
305
359
  ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
360
+ const dataURL = canvas.toDataURL("image/png");
361
+ imageCache.set(src, dataURL);
362
+ resolve(dataURL);
363
+ } catch (e) {
306
364
  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"));
365
+ const fallbackDataURL = yield fetchWithFallback(src);
366
+ imageCache.set(src, fallbackDataURL);
367
+ resolve(fallbackDataURL);
368
+ } catch (e2) {
369
+ reject(e2);
312
370
  }
371
+ }
372
+ });
373
+ image.onerror = () => __async(null, null, function* () {
374
+ clearTimeout(timeoutId);
375
+ console.error(`[SnapDOM - fetchImage] Image failed to load: ${src}`);
376
+ try {
377
+ const fallbackDataURL = yield fetchWithFallback(src);
378
+ imageCache.set(src, fallbackDataURL);
379
+ resolve(fallbackDataURL);
313
380
  } catch (e) {
314
381
  reject(e);
315
382
  }
316
383
  });
317
- image.onerror = (e) => {
318
- clearTimeout(timeoutId);
319
- reject(new Error("Failed to load image: " + (e.message || "Unknown error")));
320
- };
321
384
  image.src = src;
322
385
  });
323
386
  }
@@ -351,9 +414,9 @@
351
414
  return cleaned.trim().replace(/\s{2,}/g, " ");
352
415
  }
353
416
  function safeEncodeURI(uri) {
417
+ if (/%[0-9A-Fa-f]{2}/.test(uri)) return uri;
354
418
  try {
355
- const newURI = decodeURI(uri);
356
- return encodeURI(newURI);
419
+ return encodeURI(uri);
357
420
  } catch (e) {
358
421
  return uri;
359
422
  }
@@ -376,6 +439,20 @@
376
439
  }
377
440
 
378
441
  // src/modules/styles.js
442
+ var snapshotCache = /* @__PURE__ */ new WeakMap();
443
+ var snapshotKeyCache = /* @__PURE__ */ new Map();
444
+ function snapshotComputedStyleFull(style) {
445
+ const result = {};
446
+ for (let i = 0; i < style.length; i++) {
447
+ const prop = style[i];
448
+ let val = style.getPropertyValue(prop);
449
+ if ((prop === "background-image" || prop === "content") && val.includes("url(") && !val.includes("data:")) {
450
+ val = "none";
451
+ }
452
+ result[prop] = val;
453
+ }
454
+ return result;
455
+ }
379
456
  function inlineAllStyles(source, clone, styleMap, cache, compress) {
380
457
  var _a;
381
458
  if (source.tagName === "STYLE") return;
@@ -383,28 +460,71 @@
383
460
  cache.set(source, getStyle(source));
384
461
  }
385
462
  const style = cache.get(source);
386
- const snapshot = snapshotComputedStyle(style);
463
+ if (!snapshotCache.has(source)) {
464
+ const snapshot2 = snapshotComputedStyleFull(style);
465
+ snapshotCache.set(source, snapshot2);
466
+ }
467
+ const snapshot = snapshotCache.get(source);
468
+ const hash = Object.entries(snapshot).sort(([a], [b]) => a.localeCompare(b)).map(([prop, val]) => `${prop}:${val}`).join(";");
469
+ if (snapshotKeyCache.has(hash)) {
470
+ styleMap.set(clone, snapshotKeyCache.get(hash));
471
+ return;
472
+ }
387
473
  const tagName = ((_a = source.tagName) == null ? void 0 : _a.toLowerCase()) || "div";
388
474
  const key = getStyleKey(snapshot, tagName, compress);
475
+ snapshotKeyCache.set(hash, key);
389
476
  styleMap.set(clone, key);
390
477
  }
391
478
 
392
479
  // src/core/clone.js
393
- function deepClone(node, styleMap, styleCache, nodeMap, compress) {
394
- var _a;
395
- if (node.nodeType === Node.ELEMENT_NODE && node.getAttribute("data-capture") === "exclude") {
480
+ function isSlotElement(node) {
481
+ return node.nodeType === Node.ELEMENT_NODE && node.tagName === "SLOT";
482
+ }
483
+ function deepClone(node, styleMap, styleCache, nodeMap, compress, options = {}, originalRoot) {
484
+ var _a, _b;
485
+ if (node.nodeType === Node.TEXT_NODE) {
486
+ return node.cloneNode(true);
487
+ }
488
+ if (node.nodeType !== Node.ELEMENT_NODE) return node.cloneNode(true);
489
+ if (node.getAttribute("data-capture") === "exclude") {
396
490
  const spacer = document.createElement("div");
397
491
  const rect = node.getBoundingClientRect();
398
492
  spacer.style.cssText = `display: inline-block; width: ${rect.width}px; height: ${rect.height}px; visibility: hidden;`;
399
493
  return spacer;
400
494
  }
495
+ if (options.exclude && Array.isArray(options.exclude)) {
496
+ for (const selector of options.exclude) {
497
+ try {
498
+ if ((_a = node.matches) == null ? void 0 : _a.call(node, selector)) {
499
+ const spacer = document.createElement("div");
500
+ const rect = node.getBoundingClientRect();
501
+ spacer.style.cssText = `display: inline-block; width: ${rect.width}px; height: ${rect.height}px; visibility: hidden;`;
502
+ return spacer;
503
+ }
504
+ } catch (err) {
505
+ console.warn(`Invalid selector in exclude option: ${selector}`, err);
506
+ }
507
+ }
508
+ }
509
+ if (typeof options.filter === "function") {
510
+ try {
511
+ if (!options.filter(node, originalRoot || node)) {
512
+ const spacer = document.createElement("div");
513
+ const rect = node.getBoundingClientRect();
514
+ spacer.style.cssText = `display: inline-block; width: ${rect.width}px; height: ${rect.height}px; visibility: hidden;`;
515
+ return spacer;
516
+ }
517
+ } catch (err) {
518
+ console.warn("Error in filter function:", err);
519
+ }
520
+ }
401
521
  if (node.tagName === "IFRAME") {
402
522
  const fallback = document.createElement("div");
403
523
  fallback.textContent = "";
404
524
  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
525
  return fallback;
406
526
  }
407
- if (node.nodeType === Node.ELEMENT_NODE && node.getAttribute("data-capture") === "placeholder") {
527
+ if (node.getAttribute("data-capture") === "placeholder") {
408
528
  const clone2 = node.cloneNode(false);
409
529
  nodeMap.set(clone2, node);
410
530
  inlineAllStyles(node, clone2, styleMap, styleCache, compress);
@@ -425,14 +545,6 @@
425
545
  img.style.height = node.style.height || `${node.height}px`;
426
546
  return img;
427
547
  }
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
548
  const clone = node.cloneNode(false);
437
549
  nodeMap.set(clone, node);
438
550
  if (node instanceof HTMLInputElement) {
@@ -443,8 +555,10 @@
443
555
  if (node.checked) clone.setAttribute("checked", "");
444
556
  }
445
557
  } else if (node instanceof HTMLTextAreaElement) {
446
- clone.value = node.value;
558
+ const rect = node.getBoundingClientRect();
447
559
  clone.textContent = node.value;
560
+ clone.style.width = `${rect.width}px`;
561
+ clone.style.height = `${rect.height}px`;
448
562
  } else if (node instanceof HTMLSelectElement) {
449
563
  clone.value = node.value;
450
564
  Array.from(clone.options).forEach((opt) => {
@@ -456,21 +570,71 @@
456
570
  });
457
571
  }
458
572
  inlineAllStyles(node, clone, styleMap, styleCache, compress);
459
- const frag = document.createDocumentFragment();
460
- node.childNodes.forEach((child) => {
461
- const clonedChild = deepClone(child, styleMap, styleCache, nodeMap, compress);
462
- if (clonedChild) frag.appendChild(clonedChild);
463
- });
464
- clone.appendChild(frag);
465
- 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);
467
- const shadowFrag = document.createDocumentFragment();
468
- shadowContent.forEach((child) => shadowFrag.appendChild(child));
469
- clone.appendChild(shadowFrag);
573
+ if (isSlotElement(node)) {
574
+ const assigned = ((_b = node.assignedNodes) == null ? void 0 : _b.call(node, { flatten: true })) || [];
575
+ const nodesToClone = assigned.length > 0 ? assigned : Array.from(node.childNodes);
576
+ const fragment = document.createDocumentFragment();
577
+ for (const child of nodesToClone) {
578
+ const clonedChild = deepClone(child, styleMap, styleCache, nodeMap, compress, options, originalRoot || node);
579
+ if (clonedChild) fragment.appendChild(clonedChild);
580
+ }
581
+ return fragment;
582
+ } else if (node instanceof HTMLTextAreaElement) {
583
+ } else {
584
+ const baseChildren = node.shadowRoot ? node.shadowRoot.childNodes : node.childNodes;
585
+ for (const child of baseChildren) {
586
+ const clonedChild = deepClone(child, styleMap, styleCache, nodeMap, compress, options, originalRoot || node);
587
+ if (clonedChild) clone.appendChild(clonedChild);
588
+ }
589
+ if (node.shadowRoot && node.childNodes.length > 0 && !node.shadowRoot.querySelector("slot")) {
590
+ const lightDomContent = document.createDocumentFragment();
591
+ for (const child of node.childNodes) {
592
+ const clonedChild = deepClone(child, styleMap, styleCache, nodeMap, compress, options, originalRoot || node);
593
+ if (clonedChild) lightDomContent.appendChild(clonedChild);
594
+ }
595
+ clone.appendChild(lightDomContent);
596
+ }
470
597
  }
471
598
  return clone;
472
599
  }
473
600
 
601
+ // src/modules/iconFonts.js
602
+ var defaultIconFonts = [
603
+ // /uicons/i,
604
+ /font\s*awesome/i,
605
+ /material\s*icons/i,
606
+ /ionicons/i,
607
+ /glyphicons/i,
608
+ /feather/i,
609
+ /bootstrap\s*icons/i,
610
+ /remix\s*icons/i,
611
+ /heroicons/i,
612
+ /layui/i,
613
+ /lucide/i
614
+ ];
615
+ var userIconFonts = [];
616
+ function extendIconFonts(fonts) {
617
+ const list = Array.isArray(fonts) ? fonts : [fonts];
618
+ for (const f of list) {
619
+ if (f instanceof RegExp) {
620
+ userIconFonts.push(f);
621
+ } else if (typeof f === "string") {
622
+ userIconFonts.push(new RegExp(f, "i"));
623
+ } else {
624
+ console.warn("[snapdom] Ignored invalid iconFont value:", f);
625
+ }
626
+ }
627
+ }
628
+ function isIconFont(input) {
629
+ const text = typeof input === "string" ? input : "";
630
+ const candidates = [...defaultIconFonts, ...userIconFonts];
631
+ for (const rx of candidates) {
632
+ if (rx instanceof RegExp && rx.test(text)) return true;
633
+ }
634
+ if (/icon/i.test(text) || /glyph/i.test(text) || /symbols/i.test(text) || /feather/i.test(text) || /fontawesome/i.test(text)) return true;
635
+ return false;
636
+ }
637
+
474
638
  // src/modules/fonts.js
475
639
  function iconToImage(unicodeChar, fontFamily, fontWeight, fontSize = 32, color = "#000") {
476
640
  return __async(this, null, function* () {
@@ -513,7 +677,7 @@
513
677
  });
514
678
  }
515
679
  function embedCustomFonts() {
516
- return __async(this, arguments, function* ({ ignoreIconFonts = true, preCached = false } = {}) {
680
+ return __async(this, arguments, function* ({ preCached = false } = {}) {
517
681
  if (resourceCache.has("fonts-embed-css")) {
518
682
  if (preCached) {
519
683
  const style = document.createElement("style");
@@ -530,6 +694,7 @@
530
694
  const matches = Array.from(cssText.matchAll(importRegex));
531
695
  for (const match of matches) {
532
696
  const importUrl = match[1];
697
+ if (isIconFont(importUrl)) continue;
533
698
  if (!isStylesheetLoaded(importUrl)) {
534
699
  styleImports.push(importUrl);
535
700
  }
@@ -542,7 +707,7 @@
542
707
  try {
543
708
  const res = yield fetch(link.href);
544
709
  const cssText = yield res.text();
545
- if (ignoreIconFonts && (isIconFont(link.href) || isIconFont(cssText))) continue;
710
+ if (isIconFont(link.href) || isIconFont(cssText)) continue;
546
711
  const urlRegex = /url\((["']?)([^"')]+)\1\)/g;
547
712
  const inlinedCSS = yield Promise.all(
548
713
  Array.from(cssText.matchAll(urlRegex)).map((match) => __async(null, null, function* () {
@@ -552,7 +717,7 @@
552
717
  if (!url.startsWith("http") && !url.startsWith("data:")) {
553
718
  url = new URL(url, link.href).href;
554
719
  }
555
- if (ignoreIconFonts && isIconFont(url)) return null;
720
+ if (isIconFont(url)) return null;
556
721
  if (resourceCache.has(url)) {
557
722
  processedFontURLs.add(url);
558
723
  return { original: match[0], inlined: `url(${resourceCache.get(url)})` };
@@ -590,7 +755,8 @@
590
755
  for (const rule of sheet.cssRules) {
591
756
  if (rule.type === CSSRule.FONT_FACE_RULE) {
592
757
  const src = rule.style.getPropertyValue("src");
593
- if (!src) continue;
758
+ const family = rule.style.getPropertyValue("font-family");
759
+ if (!src || isIconFont(family)) continue;
594
760
  const urlRegex = /url\((["']?)([^"')]+)\1\)/g;
595
761
  let inlinedSrc = src;
596
762
  const matches = Array.from(src.matchAll(urlRegex));
@@ -601,7 +767,7 @@
601
767
  if (!url.startsWith("http") && !url.startsWith("data:")) {
602
768
  url = new URL(url, sheet.href || location.href).href;
603
769
  }
604
- if (ignoreIconFonts && isIconFont(url)) continue;
770
+ if (isIconFont(url)) continue;
605
771
  if (resourceCache.has(url)) {
606
772
  processedFontURLs.add(url);
607
773
  inlinedSrc = inlinedSrc.replace(match[0], `url(${resourceCache.get(url)})`);
@@ -624,7 +790,7 @@
624
790
  }
625
791
  }
626
792
  finalCSS += `@font-face {
627
- font-family: ${rule.style.getPropertyValue("font-family")};
793
+ font-family: ${family};
628
794
  src: ${inlinedSrc};
629
795
  font-style: ${rule.style.getPropertyValue("font-style") || "normal"};
630
796
  font-weight: ${rule.style.getPropertyValue("font-weight") || "normal"};
@@ -639,6 +805,7 @@
639
805
  }
640
806
  for (const font of document.fonts) {
641
807
  if (font.family && font.status === "loaded" && font._snapdomSrc) {
808
+ if (isIconFont(font.family)) continue;
642
809
  let b64 = font._snapdomSrc;
643
810
  if (!b64.startsWith("data:")) {
644
811
  if (resourceCache.has(font._snapdomSrc)) {
@@ -684,7 +851,7 @@
684
851
  }
685
852
 
686
853
  // src/modules/pseudo.js
687
- function inlinePseudoElements(source, clone, styleMap, styleCache, compress, embedFonts = false) {
854
+ function inlinePseudoElements(source, clone, styleMap, styleCache, compress, embedFonts = false, useProxy) {
688
855
  return __async(this, null, function* () {
689
856
  if (!(source instanceof Element) || !(clone instanceof Element)) return;
690
857
  for (const pseudo of ["::before", "::after", "::first-letter"]) {
@@ -731,23 +898,21 @@
731
898
  const snapshot = snapshotComputedStyle(style);
732
899
  const key = getStyleKey(snapshot, "span", compress);
733
900
  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
- );
901
+ const isIconFont2 = isIconFont(fontFamily);
737
902
  const cleanContent = parseContent(content);
738
- if (!embedFonts && isIconFont2 && cleanContent.length === 1) {
903
+ if (isIconFont2 && cleanContent.length === 1) {
739
904
  const imgEl = document.createElement("img");
740
905
  imgEl.src = yield iconToImage(cleanContent, fontFamily, fontWeight, fontSize, color);
741
- imgEl.style = "display:block;width:100%;height:100%;object-fit:contain;";
906
+ imgEl.style = `width:${fontSize}px;height:auto;object-fit:contain;`;
742
907
  pseudoEl.appendChild(imgEl);
743
908
  } else if (cleanContent.startsWith("url(")) {
744
909
  const rawUrl = extractURL(cleanContent);
745
910
  if (rawUrl && rawUrl.trim() !== "") {
746
911
  try {
747
912
  const imgEl = document.createElement("img");
748
- const dataUrl = yield fetchImage(safeEncodeURI(rawUrl));
913
+ const dataUrl = yield fetchImage(safeEncodeURI(rawUrl, { useProxy }));
749
914
  imgEl.src = dataUrl;
750
- imgEl.style = "display:block;width:100%;height:100%;object-fit:contain;";
915
+ imgEl.style = `width:${fontSize}px;height:auto;object-fit:contain;`;
751
916
  pseudoEl.appendChild(imgEl);
752
917
  } catch (e) {
753
918
  console.error(`[snapdom] Error in pseudo ${pseudo} for`, source, e);
@@ -786,31 +951,67 @@
786
951
  styleMap,
787
952
  styleCache,
788
953
  compress,
789
- embedFonts
954
+ embedFonts,
955
+ useProxy
790
956
  );
791
957
  }
792
958
  });
793
959
  }
794
960
 
961
+ // src/modules/svgDefs.js
962
+ function inlineExternalDef(root) {
963
+ if (!root) return;
964
+ const defsSources = document.querySelectorAll("svg > defs");
965
+ if (!defsSources.length) return;
966
+ root.querySelectorAll("svg").forEach((svg) => {
967
+ const uses = svg.querySelectorAll("use");
968
+ if (!uses.length) return;
969
+ const usedIds = /* @__PURE__ */ new Set();
970
+ uses.forEach((use) => {
971
+ const href = use.getAttribute("xlink:href") || use.getAttribute("href");
972
+ if (href && href.startsWith("#")) usedIds.add(href.slice(1));
973
+ });
974
+ if (!usedIds.size) return;
975
+ const defs = document.createElementNS("http://www.w3.org/2000/svg", "defs");
976
+ for (const id of usedIds) {
977
+ for (const source of defsSources) {
978
+ const def = source.querySelector(`#${CSS.escape(id)}`);
979
+ if (def) {
980
+ defs.appendChild(def.cloneNode(true));
981
+ break;
982
+ }
983
+ }
984
+ }
985
+ if (defs.childNodes.length) {
986
+ svg.insertBefore(defs, svg.firstChild);
987
+ }
988
+ });
989
+ }
990
+
795
991
  // src/core/prepare.js
796
- function prepareClone(element, compress = false, embedFonts = false) {
797
- return __async(this, null, function* () {
992
+ function prepareClone(_0) {
993
+ return __async(this, arguments, function* (element, compress = false, embedFonts = false, options = {}) {
798
994
  var _a;
799
995
  const styleMap = /* @__PURE__ */ new Map();
800
996
  const styleCache = /* @__PURE__ */ new WeakMap();
801
997
  const nodeMap = /* @__PURE__ */ new Map();
802
998
  let clone;
803
999
  try {
804
- clone = deepClone(element, styleMap, styleCache, nodeMap, compress);
1000
+ clone = deepClone(element, styleMap, styleCache, nodeMap, compress, options, element);
805
1001
  } catch (e) {
806
1002
  console.warn("deepClone failed:", e);
807
1003
  throw e;
808
1004
  }
809
1005
  try {
810
- yield inlinePseudoElements(element, clone, styleMap, styleCache, compress, embedFonts);
1006
+ yield inlinePseudoElements(element, clone, styleMap, styleCache, compress, embedFonts, options.useProxy);
811
1007
  } catch (e) {
812
1008
  console.warn("inlinePseudoElements failed:", e);
813
1009
  }
1010
+ try {
1011
+ inlineExternalDef(clone);
1012
+ } catch (e) {
1013
+ console.warn("inlineExternalDef failed:", e);
1014
+ }
814
1015
  let classCSS = "";
815
1016
  if (compress) {
816
1017
  const keyToClass = generateCSSClasses(styleMap);
@@ -863,6 +1064,12 @@
863
1064
  clone.style.clear = "none";
864
1065
  clone.style.transform = transform || "";
865
1066
  }
1067
+ for (const [cloneNode, originalNode] of nodeMap.entries()) {
1068
+ if (originalNode.tagName === "PRE") {
1069
+ cloneNode.style.marginTop = "0";
1070
+ cloneNode.style.marginBlockStart = "0";
1071
+ }
1072
+ }
866
1073
  return { clone, classCSS, styleCache };
867
1074
  });
868
1075
  }
@@ -874,8 +1081,7 @@
874
1081
  const processImg = (img) => __async(null, null, function* () {
875
1082
  const src = img.src;
876
1083
  try {
877
- const crossOrigin = options.crossOrigin ? options.crossOrigin(src) : "anonymous";
878
- const dataUrl = yield fetchImage(src, 3e3, crossOrigin);
1084
+ const dataUrl = yield fetchImage(src, { useProxy: options.useProxy });
879
1085
  img.src = dataUrl;
880
1086
  if (!img.width) img.width = img.naturalWidth || 100;
881
1087
  if (!img.height) img.height = img.naturalHeight || 100;
@@ -897,30 +1103,32 @@
897
1103
  function inlineBackgroundImages(_0, _1, _2) {
898
1104
  return __async(this, arguments, function* (source, clone, styleCache, options = {}) {
899
1105
  const queue = [[source, clone]];
1106
+ const imageProps = [
1107
+ "background-image",
1108
+ "mask",
1109
+ "mask-image",
1110
+ "-webkit-mask-image",
1111
+ "mask-source",
1112
+ "mask-box-image-source",
1113
+ "mask-border-source",
1114
+ "-webkit-mask-box-image-source"
1115
+ ];
900
1116
  while (queue.length) {
901
1117
  const [srcNode, cloneNode] = queue.shift();
902
1118
  const style = styleCache.get(srcNode) || getStyle(srcNode);
903
1119
  if (!styleCache.has(srcNode)) styleCache.set(srcNode, style);
904
- const bg = style.getPropertyValue("background-image");
905
- const bgColor = style.getPropertyValue("background-color");
906
- if (!bg || bg === "none") {
907
- const sChildren2 = Array.from(srcNode.children);
908
- const cChildren2 = Array.from(cloneNode.children);
909
- for (let i = 0; i < Math.min(sChildren2.length, cChildren2.length); i++) {
910
- queue.push([sChildren2[i], cChildren2[i]]);
1120
+ for (const prop of imageProps) {
1121
+ const val = style.getPropertyValue(prop);
1122
+ if (!val || val === "none") continue;
1123
+ const splits = splitBackgroundImage(val);
1124
+ const inlined = yield Promise.all(
1125
+ splits.map((entry) => inlineSingleBackgroundEntry(entry, options))
1126
+ );
1127
+ if (inlined.some((p) => p && p !== "none" && !/^url\(undefined/.test(p))) {
1128
+ cloneNode.style.setProperty(prop, inlined.join(", "));
911
1129
  }
912
- continue;
913
- }
914
- const bgSplits = splitBackgroundImage(bg);
915
- const newBgParts = yield Promise.all(
916
- bgSplits.map((entry) => inlineSingleBackgroundEntry(entry, options))
917
- );
918
- const hasRealBg = newBgParts.some(
919
- (p) => p && p !== "none" && !/^url\(undefined\)/.test(p)
920
- );
921
- if (hasRealBg) {
922
- cloneNode.style.backgroundImage = newBgParts.join(", ");
923
1130
  }
1131
+ const bgColor = style.getPropertyValue("background-color");
924
1132
  if (bgColor && bgColor !== "transparent" && bgColor !== "rgba(0, 0, 0, 0)") {
925
1133
  cloneNode.style.backgroundColor = bgColor;
926
1134
  }
@@ -937,14 +1145,13 @@
937
1145
  function captureDOM(_0) {
938
1146
  return __async(this, arguments, function* (element, options = {}) {
939
1147
  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;
1148
+ const { compress = true, embedFonts = false, fast = true, scale = 1, useProxy = "" } = options;
942
1149
  let clone, classCSS, styleCache;
943
1150
  let fontsCSS = "";
944
1151
  let baseCSS = "";
945
1152
  let dataURL;
946
1153
  let svgString;
947
- ({ clone, classCSS, styleCache } = yield prepareClone(element, compress, embedFonts));
1154
+ ({ clone, classCSS, styleCache } = yield prepareClone(element, compress, embedFonts, options));
948
1155
  yield new Promise((resolve) => {
949
1156
  idle(() => __async(null, null, function* () {
950
1157
  yield inlineImages(clone, options);
@@ -960,7 +1167,7 @@
960
1167
  if (embedFonts) {
961
1168
  yield new Promise((resolve) => {
962
1169
  idle(() => __async(null, null, function* () {
963
- fontsCSS = yield embedCustomFonts({ ignoreIconFonts: !embedFonts });
1170
+ fontsCSS = yield embedCustomFonts();
964
1171
  resolve();
965
1172
  }), { fast });
966
1173
  });
@@ -1073,10 +1280,27 @@
1073
1280
  return canvas;
1074
1281
  });
1075
1282
  }
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" });
1283
+ function toBlob(_0) {
1284
+ return __async(this, arguments, function* (url, {
1285
+ type = "svg",
1286
+ scale = 1,
1287
+ backgroundColor = "#fff",
1288
+ quality
1289
+ } = {}) {
1290
+ const mime = {
1291
+ jpg: "image/jpeg",
1292
+ jpeg: "image/jpeg",
1293
+ png: "image/png",
1294
+ webp: "image/webp"
1295
+ }[type] || "image/png";
1296
+ if (type === "svg") {
1297
+ const svgText = decodeURIComponent(url.split(",")[1]);
1298
+ return new Blob([svgText], { type: "image/svg+xml" });
1299
+ }
1300
+ const canvas = yield createBackground(url, { dpr: 1, scale }, backgroundColor);
1301
+ return new Promise((resolve) => {
1302
+ canvas.toBlob((blob) => resolve(blob), `${mime}`, quality);
1303
+ });
1080
1304
  });
1081
1305
  }
1082
1306
  function createBackground(_0, _1, _2) {
@@ -1136,6 +1360,9 @@
1136
1360
  return __async(this, arguments, function* (element, options = {}) {
1137
1361
  options = __spreadValues({ scale: 1 }, options);
1138
1362
  if (!element) throw new Error("Element cannot be null or undefined");
1363
+ if (options.iconFonts) {
1364
+ extendIconFonts(options.iconFonts);
1365
+ }
1139
1366
  return yield snapdom.capture(element, options);
1140
1367
  });
1141
1368
  }
@@ -1149,7 +1376,7 @@
1149
1376
  toRaw: () => url,
1150
1377
  toImg: () => toImg(url, { dpr, scale }),
1151
1378
  toCanvas: () => toCanvas(url, { dpr, scale }),
1152
- toBlob: () => toBlob(url),
1379
+ toBlob: (options2) => toBlob(url, __spreadValues({ dpr, scale }, options2)),
1153
1380
  toPng: (options2) => toRasterImg(url, __spreadValues({ dpr, scale }, options2), "png"),
1154
1381
  toJpg: (options2) => toRasterImg(url, __spreadValues({ dpr, scale }, options2), "jpeg"),
1155
1382
  toWebp: (options2) => toRasterImg(url, __spreadValues({ dpr, scale }, options2), "webp"),
@@ -1166,7 +1393,7 @@
1166
1393
  return (yield snapdom.capture(el, options)).toCanvas();
1167
1394
  });
1168
1395
  snapdom.toBlob = (el, options) => __async(null, null, function* () {
1169
- return (yield snapdom.capture(el, options)).toBlob();
1396
+ return (yield snapdom.capture(el, options)).toBlob(options);
1170
1397
  });
1171
1398
  snapdom.toPng = (el, options) => __async(null, null, function* () {
1172
1399
  return (yield snapdom.capture(el, options)).toPng(options);
@@ -1194,7 +1421,7 @@
1194
1421
  // src/api/preCache.js
1195
1422
  function preCache() {
1196
1423
  return __async(this, arguments, function* (root = document, options = {}) {
1197
- const { embedFonts = true, reset = false, crossOrigin: crossOriginFn } = options;
1424
+ const { embedFonts = true, reset = false } = options;
1198
1425
  if (reset) {
1199
1426
  imageCache.clear();
1200
1427
  bgCache.clear();
@@ -1213,9 +1440,8 @@
1213
1440
  for (const img of imgEls) {
1214
1441
  const src = img.src;
1215
1442
  if (!imageCache.has(src)) {
1216
- const crossOrigin = crossOriginFn ? crossOriginFn(src) : "anonymous";
1217
1443
  promises.push(
1218
- fetchImage(src, 3e3, crossOrigin).then((dataURL) => imageCache.set(src, dataURL)).catch(() => {
1444
+ fetchImage(src, { useProxy: options.useProxy }).then((dataURL) => imageCache.set(src, dataURL)).catch(() => {
1219
1445
  })
1220
1446
  );
1221
1447
  }
@@ -1228,7 +1454,7 @@
1228
1454
  const isUrl = entry.startsWith("url(");
1229
1455
  if (isUrl) {
1230
1456
  promises.push(
1231
- inlineSingleBackgroundEntry(entry, { crossOrigin: crossOriginFn, skipInline: true }).catch(() => {
1457
+ inlineSingleBackgroundEntry(entry, options).catch(() => {
1232
1458
  })
1233
1459
  );
1234
1460
  }
@@ -1236,7 +1462,7 @@
1236
1462
  }
1237
1463
  }
1238
1464
  if (embedFonts) {
1239
- yield embedCustomFonts({ ignoreIconFonts: !embedFonts, preCached: true });
1465
+ yield embedCustomFonts({ preCached: true });
1240
1466
  }
1241
1467
  yield Promise.all(promises);
1242
1468
  });