@zumer/snapdom 1.9.8 → 1.9.9

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.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
 
2
2
  /*
3
3
  * snapdom
4
- * v.1.9.8
4
+ * v.1.9.9
5
5
  * Author Juan Martin Muda
6
6
  * License MIT
7
7
  **/
@@ -18,18 +18,10 @@ var cache = {
18
18
  font: /* @__PURE__ */ new Set(),
19
19
  snapshot: /* @__PURE__ */ new WeakMap(),
20
20
  snapshotKey: /* @__PURE__ */ new Map(),
21
- preStyleMap: /* @__PURE__ */ new Map(),
22
- preStyle: /* @__PURE__ */ new WeakMap(),
23
- preNodeMap: /* @__PURE__ */ new Map(),
24
21
  reset: resetCache
25
22
  };
26
23
  function resetCache() {
27
24
  cache.computedStyle = /* @__PURE__ */ new WeakMap();
28
- cache.snapshot = /* @__PURE__ */ new WeakMap();
29
- cache.snapshotKey.clear();
30
- cache.preStyleMap.clear();
31
- cache.preStyle = /* @__PURE__ */ new WeakMap();
32
- cache.preNodeMap.clear();
33
25
  }
34
26
 
35
27
  // src/utils/cssTools.js
@@ -163,8 +155,8 @@ function generateDedupedBaseCSS(usedTagNames) {
163
155
  }
164
156
  return css;
165
157
  }
166
- function generateCSSClasses() {
167
- const keySet = new Set(cache.preStyleMap.values());
158
+ function generateCSSClasses(styleMap) {
159
+ const keySet = new Set(styleMap.values());
168
160
  const classMap = /* @__PURE__ */ new Map();
169
161
  let counter = 1;
170
162
  for (const key of keySet) {
@@ -234,7 +226,25 @@ function extractURL(value) {
234
226
  if (url.startsWith("#")) return null;
235
227
  return url;
236
228
  }
237
- function fetchImage(src, { timeout = 3e3, useProxy = "" } = {}) {
229
+ async function fetchResource(url, { useProxy = "" } = {}) {
230
+ async function doFetch(u) {
231
+ const res = await fetch(u);
232
+ if (!res.ok) throw new Error(`[snapdom] Failed to fetch resource: ${u}`);
233
+ return res;
234
+ }
235
+ try {
236
+ return await doFetch(url);
237
+ } catch (e) {
238
+ if (useProxy && typeof useProxy === "string") {
239
+ const proxied = useProxy.replace(/\/$/, "") + safeEncodeURI(url);
240
+ return doFetch(proxied);
241
+ }
242
+ throw e;
243
+ }
244
+ }
245
+ var _inflight = /* @__PURE__ */ new Map();
246
+ var _errorCache = /* @__PURE__ */ new Map();
247
+ function fetchImage(src, { timeout = 3e3, useProxy = "", errorTTL = 8e3 } = {}) {
238
248
  function getCrossOriginMode(url) {
239
249
  try {
240
250
  const parsed = new URL(url, window.location.href);
@@ -243,105 +253,157 @@ function fetchImage(src, { timeout = 3e3, useProxy = "" } = {}) {
243
253
  return "anonymous";
244
254
  }
245
255
  }
246
- async function fetchWithFallback(url) {
247
- const fetchBlobAsDataURL = (fetchUrl) => fetch(fetchUrl, {
248
- mode: "cors",
249
- credentials: getCrossOriginMode(fetchUrl) === "use-credentials" ? "include" : "omit"
250
- }).then((r) => r.blob()).then((blob) => new Promise((resolve, reject) => {
251
- const reader = new FileReader();
252
- reader.onloadend = () => {
253
- const base64 = reader.result;
254
- if (typeof base64 !== "string" || !base64.startsWith("data:image/")) {
255
- reject(new Error("Invalid image data URL"));
256
- return;
257
- }
258
- resolve(base64);
259
- };
260
- reader.onerror = () => reject(new Error("FileReader error"));
261
- reader.readAsDataURL(blob);
262
- }));
256
+ const ok = (data) => ({ ok: true, data });
257
+ const fail = (e) => ({ ok: false, error: e instanceof Error ? e : new Error(String(e)) });
258
+ function fetchBlobAsDataURLSafe(fetchUrl) {
263
259
  try {
264
- return await fetchBlobAsDataURL(url);
260
+ return fetch(fetchUrl, {
261
+ mode: "cors",
262
+ credentials: getCrossOriginMode(fetchUrl) === "use-credentials" ? "include" : "omit"
263
+ }).then((r) => {
264
+ if (!r.ok) return fail(new Error("HTTP " + r.status));
265
+ return r.blob().then((blob) => new Promise((resolve) => {
266
+ const reader = new FileReader();
267
+ reader.onloadend = () => {
268
+ const base64 = reader.result;
269
+ if (typeof base64 !== "string" || !base64.startsWith("data:image/")) {
270
+ resolve(fail(new Error("Invalid image data URL")));
271
+ } else {
272
+ resolve(ok(base64));
273
+ }
274
+ };
275
+ reader.onerror = () => resolve(fail(new Error("FileReader error")));
276
+ reader.readAsDataURL(blob);
277
+ }));
278
+ }).catch((e) => fail(e));
265
279
  } catch (e) {
280
+ return Promise.resolve(fail(e));
281
+ }
282
+ }
283
+ function fetchWithFallbackOnceSafe(url) {
284
+ return fetchBlobAsDataURLSafe(url).then((r) => {
285
+ if (r.ok) return r;
266
286
  if (useProxy && typeof useProxy === "string") {
267
287
  const proxied = useProxy.replace(/\/$/, "") + safeEncodeURI(url);
268
- try {
269
- return await fetchBlobAsDataURL(proxied);
270
- } catch {
271
- throw new Error("[SnapDOM - fetchImage] CORS restrictions prevented image capture (even via proxy)");
272
- }
273
- } else {
274
- throw new Error("[SnapDOM - fetchImage] Fetch fallback failed and no proxy provided");
288
+ return fetchBlobAsDataURLSafe(proxied).then((r2) => {
289
+ if (r2.ok) return r2;
290
+ return fail(new Error("[SnapDOM - fetchImage] Fetch failed and no proxy provided"));
291
+ });
275
292
  }
276
- }
293
+ return fail(new Error("[SnapDOM - fetchImage] Fetch failed and no proxy provided"));
294
+ });
277
295
  }
278
- const crossOriginValue = getCrossOriginMode(src);
279
- if (cache.image.has(src)) {
280
- return Promise.resolve(cache.image.get(src));
296
+ const now = Date.now();
297
+ const until = _errorCache.get(src);
298
+ if (until && until > now) {
299
+ const pr = Promise.reject(new Error("[SnapDOM - fetchImage] Recently failed (cooldown)."));
300
+ pr.catch(() => {
301
+ });
302
+ return pr;
281
303
  }
282
- const isDataURI = src.startsWith("data:image/");
283
- if (isDataURI) {
304
+ if (_inflight.has(src)) return _inflight.get(src);
305
+ const crossOriginValue = getCrossOriginMode(src);
306
+ if (cache.image.has(src)) return Promise.resolve(cache.image.get(src));
307
+ if (src.startsWith("data:image/")) {
284
308
  cache.image.set(src, src);
285
309
  return Promise.resolve(src);
286
310
  }
287
- const isSVG = /\.svg(\?.*)?$/i.test(src);
288
- if (isSVG) {
289
- return (async () => {
290
- try {
291
- const response = await fetch(src, {
292
- mode: "cors",
293
- credentials: crossOriginValue === "use-credentials" ? "include" : "omit"
294
- });
295
- const svgText = await response.text();
296
- const encoded = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgText)}`;
297
- cache.image.set(src, encoded);
298
- return encoded;
299
- } catch {
300
- return fetchWithFallback(src);
301
- }
302
- })();
303
- }
304
- return new Promise((resolve, reject) => {
305
- const timeoutId = setTimeout(() => {
306
- reject(new Error("[SnapDOM - fetchImage] Image load timed out"));
307
- }, timeout);
308
- const image = new Image();
309
- image.crossOrigin = crossOriginValue;
310
- image.onload = async () => {
311
- clearTimeout(timeoutId);
312
- try {
313
- await image.decode();
314
- const canvas = document.createElement("canvas");
315
- canvas.width = image.width;
316
- canvas.height = image.height;
317
- const ctx = canvas.getContext("2d");
318
- ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
319
- const dataURL = canvas.toDataURL("image/png");
320
- cache.image.set(src, dataURL);
321
- resolve(dataURL);
322
- } catch {
311
+ if (/\.svg(\?.*)?$/i.test(src)) {
312
+ const p2 = (async () => {
313
+ const direct = await (async () => {
323
314
  try {
324
- const fallbackDataURL = await fetchWithFallback(src);
325
- cache.image.set(src, fallbackDataURL);
326
- resolve(fallbackDataURL);
315
+ const res = await fetch(src, {
316
+ mode: "cors",
317
+ credentials: crossOriginValue === "use-credentials" ? "include" : "omit"
318
+ });
319
+ if (!res.ok) return fail(new Error("HTTP " + res.status));
320
+ const svgText = await res.text();
321
+ return ok(`data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgText)}`);
327
322
  } catch (e) {
328
- reject(e);
323
+ return fail(e);
329
324
  }
325
+ })();
326
+ if (direct.ok) {
327
+ cache.image.set(src, direct.data);
328
+ return direct.data;
330
329
  }
331
- };
332
- image.onerror = async () => {
333
- clearTimeout(timeoutId);
334
- console.error(`[SnapDOM - fetchImage] Image failed to load: ${src}`);
335
- try {
336
- const fallbackDataURL = await fetchWithFallback(src);
337
- cache.image.set(src, fallbackDataURL);
338
- resolve(fallbackDataURL);
339
- } catch (e) {
340
- reject(e);
330
+ const via = await fetchWithFallbackOnceSafe(src);
331
+ if (via.ok) {
332
+ cache.image.set(src, via.data);
333
+ return via.data;
341
334
  }
335
+ _errorCache.set(src, now + errorTTL);
336
+ return Promise.reject(via.error);
337
+ })();
338
+ _inflight.set(src, p2);
339
+ p2.finally(() => _inflight.delete(src));
340
+ p2.catch(() => {
341
+ });
342
+ return p2;
343
+ }
344
+ const p = new Promise((resolve, reject) => {
345
+ let finished = false;
346
+ const img = new Image();
347
+ const finish = (fn) => (arg) => {
348
+ if (finished) return;
349
+ finished = true;
350
+ clearTimeout(timeoutId);
351
+ img.onload = img.onerror = null;
352
+ fn(arg);
353
+ };
354
+ const onSuccess = (d) => {
355
+ cache.image.set(src, d);
356
+ resolve(d);
342
357
  };
343
- image.src = src;
358
+ const onFinalError = (e) => {
359
+ _errorCache.set(src, Date.now() + errorTTL);
360
+ reject(e);
361
+ };
362
+ const timeoutId = setTimeout(
363
+ finish(() => {
364
+ fetchWithFallbackOnceSafe(src).then((r) => {
365
+ if (r.ok) onSuccess(r.data);
366
+ else onFinalError(new Error("Image load timed out"));
367
+ });
368
+ }),
369
+ timeout
370
+ );
371
+ img.crossOrigin = crossOriginValue;
372
+ img.onload = finish(() => {
373
+ Promise.resolve(img.decode()).then(() => {
374
+ try {
375
+ const canvas = document.createElement("canvas");
376
+ canvas.width = img.naturalWidth || img.width;
377
+ canvas.height = img.naturalHeight || img.height;
378
+ const ctx = canvas.getContext("2d");
379
+ ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
380
+ onSuccess(canvas.toDataURL("image/png"));
381
+ } catch {
382
+ fetchWithFallbackOnceSafe(src).then((r) => {
383
+ if (r.ok) onSuccess(r.data);
384
+ else onFinalError(r.error);
385
+ });
386
+ }
387
+ }).catch(() => {
388
+ fetchWithFallbackOnceSafe(src).then((r) => {
389
+ if (r.ok) onSuccess(r.data);
390
+ else onFinalError(r.error);
391
+ });
392
+ });
393
+ });
394
+ img.onerror = finish(() => {
395
+ fetchWithFallbackOnceSafe(src).then((r) => {
396
+ if (r.ok) onSuccess(r.data);
397
+ else onFinalError(r.error);
398
+ });
399
+ });
400
+ img.src = src;
344
401
  });
402
+ _inflight.set(src, p);
403
+ p.finally(() => _inflight.delete(src));
404
+ p.catch(() => {
405
+ });
406
+ return p;
345
407
  }
346
408
  function snapshotComputedStyle(style) {
347
409
  const snap = {};
@@ -398,6 +460,8 @@ function splitBackgroundImage(bg) {
398
460
  }
399
461
 
400
462
  // src/modules/styles.js
463
+ var snapshotCache = /* @__PURE__ */ new WeakMap();
464
+ var snapshotKeyCache = /* @__PURE__ */ new Map();
401
465
  function snapshotComputedStyleFull(style) {
402
466
  const result = {};
403
467
  const computedVisibility = style.getPropertyValue("visibility");
@@ -414,30 +478,42 @@ function snapshotComputedStyleFull(style) {
414
478
  }
415
479
  return result;
416
480
  }
417
- function inlineAllStyles(source, clone, compress) {
481
+ function inlineAllStyles(source, clone, styleMap, cache2, compress) {
418
482
  if (source.tagName === "STYLE") return;
419
- if (!cache.preStyle.has(source)) {
420
- cache.preStyle.set(source, getStyle(source));
483
+ if (!cache2.has(source)) {
484
+ cache2.set(source, getStyle(source));
421
485
  }
422
- const style = cache.preStyle.get(source);
423
- if (!cache.snapshot.has(source)) {
486
+ const style = cache2.get(source);
487
+ if (!snapshotCache.has(source)) {
424
488
  const snapshot2 = snapshotComputedStyleFull(style);
425
- cache.snapshot.set(source, snapshot2);
489
+ snapshotCache.set(source, snapshot2);
426
490
  }
427
- const snapshot = cache.snapshot.get(source);
491
+ const snapshot = snapshotCache.get(source);
428
492
  const hash = Object.entries(snapshot).sort(([a], [b]) => a.localeCompare(b)).map(([prop, val]) => `${prop}:${val}`).join(";");
429
- if (cache.snapshotKey.has(hash)) {
430
- cache.preStyleMap.set(clone, cache.snapshotKey.get(hash));
493
+ if (snapshotKeyCache.has(hash)) {
494
+ styleMap.set(clone, snapshotKeyCache.get(hash));
431
495
  return;
432
496
  }
433
497
  const tagName = source.tagName?.toLowerCase() || "div";
434
498
  const key = getStyleKey(snapshot, tagName, compress);
435
- cache.snapshotKey.set(hash, key);
436
- cache.preStyleMap.set(clone, key);
499
+ snapshotKeyCache.set(hash, key);
500
+ styleMap.set(clone, key);
437
501
  }
438
502
 
439
503
  // src/core/clone.js
440
- function deepClone(node, compress, options = {}, originalRoot) {
504
+ function freezeImgSrcset(original, cloned) {
505
+ try {
506
+ const chosen = original.currentSrc || original.src || "";
507
+ if (!chosen) return;
508
+ cloned.setAttribute("src", chosen);
509
+ cloned.removeAttribute("srcset");
510
+ cloned.removeAttribute("sizes");
511
+ cloned.loading = "eager";
512
+ cloned.decoding = "sync";
513
+ } catch {
514
+ }
515
+ }
516
+ function deepClone(node, styleMap, styleCache, nodeMap, compress, options = {}, originalRoot) {
441
517
  if (!node) throw new Error("Invalid node");
442
518
  const clonedAssignedNodes = /* @__PURE__ */ new Set();
443
519
  let pendingSelectValue = null;
@@ -486,8 +562,8 @@ function deepClone(node, compress, options = {}, originalRoot) {
486
562
  }
487
563
  if (node.getAttribute("data-capture") === "placeholder") {
488
564
  const clone2 = node.cloneNode(false);
489
- cache.preNodeMap.set(clone2, node);
490
- inlineAllStyles(node, clone2, compress);
565
+ nodeMap.set(clone2, node);
566
+ inlineAllStyles(node, clone2, styleMap, styleCache, compress);
491
567
  const placeholder = document.createElement("div");
492
568
  placeholder.textContent = node.getAttribute("data-placeholder-text") || "";
493
569
  placeholder.style.cssText = `color:#666;font-size:12px;text-align:center;line-height:1.4;padding:0.5em;box-sizing:border-box;`;
@@ -500,14 +576,17 @@ function deepClone(node, compress, options = {}, originalRoot) {
500
576
  img.src = dataURL;
501
577
  img.width = node.width;
502
578
  img.height = node.height;
503
- cache.preNodeMap.set(img, node);
504
- inlineAllStyles(node, img, compress);
579
+ nodeMap.set(img, node);
580
+ inlineAllStyles(node, img, styleMap, styleCache, compress);
505
581
  return img;
506
582
  }
507
583
  let clone;
508
584
  try {
509
585
  clone = node.cloneNode(false);
510
- cache.preNodeMap.set(clone, node);
586
+ nodeMap.set(clone, node);
587
+ if (node.tagName === "IMG") {
588
+ freezeImgSrcset(node, clone);
589
+ }
511
590
  } catch (err) {
512
591
  console.error("[Snapdom] Failed to clone node:", node, err);
513
592
  throw err;
@@ -532,7 +611,7 @@ function deepClone(node, compress, options = {}, originalRoot) {
532
611
  if (node instanceof HTMLSelectElement) {
533
612
  pendingSelectValue = node.value;
534
613
  }
535
- inlineAllStyles(node, clone, compress);
614
+ inlineAllStyles(node, clone, styleMap, styleCache, compress);
536
615
  if (node.shadowRoot) {
537
616
  const hasSlot = Array.from(node.shadowRoot.querySelectorAll("slot")).length > 0;
538
617
  if (hasSlot) {
@@ -540,8 +619,7 @@ function deepClone(node, compress, options = {}, originalRoot) {
540
619
  if (child.nodeType === Node.ELEMENT_NODE && child.tagName === "STYLE") {
541
620
  const cssText = child.textContent || "";
542
621
  if (cssText.trim() && compress) {
543
- if (!cache.preStyle) cache.preStyle = /* @__PURE__ */ new WeakMap();
544
- cache.preStyle.set(child, cssText);
622
+ styleCache.set(child, cssText);
545
623
  }
546
624
  }
547
625
  }
@@ -551,12 +629,11 @@ function deepClone(node, compress, options = {}, originalRoot) {
551
629
  if (child.nodeType === Node.ELEMENT_NODE && child.tagName === "STYLE") {
552
630
  const cssText = child.textContent || "";
553
631
  if (cssText.trim() && compress) {
554
- if (!cache.preStyle) cache.preStyle = /* @__PURE__ */ new WeakMap();
555
- cache.preStyle.set(child, cssText);
632
+ styleCache.set(child, cssText);
556
633
  }
557
634
  continue;
558
635
  }
559
- const clonedChild = deepClone(child, compress, options, originalRoot || node);
636
+ const clonedChild = deepClone(child, styleMap, styleCache, nodeMap, compress, options, originalRoot || node);
560
637
  if (clonedChild) shadowFrag.appendChild(clonedChild);
561
638
  }
562
639
  clone.appendChild(shadowFrag);
@@ -567,14 +644,14 @@ function deepClone(node, compress, options = {}, originalRoot) {
567
644
  const nodesToClone = assigned.length > 0 ? assigned : Array.from(node.childNodes);
568
645
  const fragment = document.createDocumentFragment();
569
646
  for (const child of nodesToClone) {
570
- const clonedChild = deepClone(child, compress, options, originalRoot || node);
647
+ const clonedChild = deepClone(child, styleMap, styleCache, nodeMap, compress, options, originalRoot || node);
571
648
  if (clonedChild) fragment.appendChild(clonedChild);
572
649
  }
573
650
  return fragment;
574
651
  }
575
652
  for (const child of node.childNodes) {
576
653
  if (clonedAssignedNodes.has(child)) continue;
577
- const clonedChild = deepClone(child, compress, options, originalRoot || node);
654
+ const clonedChild = deepClone(child, styleMap, styleCache, nodeMap, compress, options, originalRoot || node);
578
655
  if (clonedChild) clone.appendChild(clonedChild);
579
656
  }
580
657
  if (pendingSelectValue !== null && clone instanceof HTMLSelectElement) {
@@ -679,7 +756,7 @@ function injectLinkIfMissing(href) {
679
756
  document.head.appendChild(link);
680
757
  });
681
758
  }
682
- async function embedCustomFonts({ preCached = false } = {}) {
759
+ async function embedCustomFonts({ preCached = false, localFonts = [], useProxy = "" } = {}) {
683
760
  if (cache.resource.has("fonts-embed-css")) {
684
761
  if (preCached) {
685
762
  const style = document.createElement("style");
@@ -689,6 +766,15 @@ async function embedCustomFonts({ preCached = false } = {}) {
689
766
  }
690
767
  return cache.resource.get("fonts-embed-css");
691
768
  }
769
+ const loadedFonts = /* @__PURE__ */ new Set();
770
+ try {
771
+ for (const f of document.fonts) {
772
+ if (f.status === "loaded") {
773
+ loadedFonts.add(`${f.family}__${f.weight || "normal"}__${f.style || "normal"}`);
774
+ }
775
+ }
776
+ } catch {
777
+ }
692
778
  const importRegex = /@import\s+url\(["']?([^"')]+)["']?\)/g;
693
779
  const styleImports = [];
694
780
  for (const styleTag of document.querySelectorAll("style")) {
@@ -707,26 +793,48 @@ async function embedCustomFonts({ preCached = false } = {}) {
707
793
  let finalCSS = "";
708
794
  for (const link of links) {
709
795
  try {
710
- const res = await fetch(link.href);
796
+ const res = await fetchResource(link.href, { useProxy });
711
797
  const cssText = await res.text();
712
798
  if (isIconFont(link.href) || isIconFont(cssText)) continue;
713
- const urlRegex = /url\((["']?)([^"')]+)\1\)/g;
714
- const inlinedCSS = await Promise.all(
715
- Array.from(cssText.matchAll(urlRegex)).map(async (match) => {
799
+ const faceRegex = /@font-face[^{}]*{[^}]*}/g;
800
+ let cssFinal = cssText;
801
+ for (const face of cssText.match(faceRegex) || []) {
802
+ const famMatch = face.match(/font-family:\s*([^;]+);/i);
803
+ if (!famMatch) continue;
804
+ const family = famMatch[1].replace(/['"]/g, "").trim();
805
+ const weightMatch = face.match(/font-weight:\s*([^;]+);/i);
806
+ const styleMatch = face.match(/font-style:\s*([^;]+);/i);
807
+ const weight = weightMatch ? weightMatch[1].trim() : "normal";
808
+ const style = styleMatch ? styleMatch[1].trim() : "normal";
809
+ const key = `${family}__${weight}__${style}`;
810
+ const urlRegex = /url\((["']?)([^"')]+)\1\)/g;
811
+ const hasURL = /url\(/i.test(face);
812
+ const hasLocal = /local\(/i.test(face);
813
+ if (!hasURL && hasLocal) {
814
+ continue;
815
+ }
816
+ if (!loadedFonts.has(key)) {
817
+ cssFinal = cssFinal.replace(face, "");
818
+ continue;
819
+ }
820
+ let inlined = face;
821
+ const matches = Array.from(face.matchAll(urlRegex));
822
+ for (const match of matches) {
716
823
  let rawUrl = extractURL(match[0]);
717
- if (!rawUrl) return null;
824
+ if (!rawUrl) continue;
718
825
  let url = rawUrl;
719
826
  if (!url.startsWith("http") && !url.startsWith("data:")) {
720
827
  url = new URL(url, link.href).href;
721
828
  }
722
- if (isIconFont(url)) return null;
829
+ if (isIconFont(url)) continue;
723
830
  if (cache.resource.has(url)) {
724
831
  cache.font.add(url);
725
- return { original: match[0], inlined: `url(${cache.resource.get(url)})` };
832
+ inlined = inlined.replace(match[0], `url(${cache.resource.get(url)})`);
833
+ continue;
726
834
  }
727
- if (cache.font.has(url)) return null;
835
+ if (cache.font.has(url)) continue;
728
836
  try {
729
- const fontRes = await fetch(url);
837
+ const fontRes = await fetchResource(url, { useProxy });
730
838
  const blob = await fontRes.blob();
731
839
  const b64 = await new Promise((resolve) => {
732
840
  const reader = new FileReader();
@@ -735,16 +843,12 @@ async function embedCustomFonts({ preCached = false } = {}) {
735
843
  });
736
844
  cache.resource.set(url, b64);
737
845
  cache.font.add(url);
738
- return { original: match[0], inlined: `url(${b64})` };
846
+ inlined = inlined.replace(match[0], `url(${b64})`);
739
847
  } catch (e) {
740
848
  console.warn("[snapdom] Failed to fetch font resource:", url);
741
- return null;
742
849
  }
743
- })
744
- );
745
- let cssFinal = cssText;
746
- for (const r of inlinedCSS) {
747
- if (r) cssFinal = cssFinal.replace(r.original, r.inlined);
850
+ }
851
+ cssFinal = cssFinal.replace(face, inlined);
748
852
  }
749
853
  finalCSS += cssFinal + "\n";
750
854
  } catch (e) {
@@ -759,14 +863,18 @@ async function embedCustomFonts({ preCached = false } = {}) {
759
863
  const src = rule.style.getPropertyValue("src");
760
864
  const family = rule.style.getPropertyValue("font-family");
761
865
  if (!src || isIconFont(family)) continue;
866
+ const weightVal = rule.style.getPropertyValue("font-weight") || "normal";
867
+ const styleVal = rule.style.getPropertyValue("font-style") || "normal";
868
+ const key = `${family}__${weightVal}__${styleVal}`;
762
869
  const urlRegex = /url\((["']?)([^"')]+)\1\)/g;
763
870
  const localRegex = /local\((["']?)[^)]+?\1\)/g;
764
871
  const hasURL = !!src.match(urlRegex);
765
872
  const hasLocal = !!src.match(localRegex);
766
873
  if (!hasURL && hasLocal) {
767
- finalCSS += `@font-face{font-family:${family};src:${src};font-style:${rule.style.getPropertyValue("font-style") || "normal"};font-weight:${rule.style.getPropertyValue("font-weight") || "normal"};}`;
874
+ finalCSS += `@font-face{font-family:${family};src:${src};font-style:${styleVal};font-weight:${weightVal};}`;
768
875
  continue;
769
876
  }
877
+ if (!loadedFonts.has(key)) continue;
770
878
  let inlinedSrc = src;
771
879
  const matches = Array.from(src.matchAll(urlRegex));
772
880
  for (const match of matches) {
@@ -784,7 +892,7 @@ async function embedCustomFonts({ preCached = false } = {}) {
784
892
  }
785
893
  if (cache.font.has(url)) continue;
786
894
  try {
787
- const res = await fetch(url);
895
+ const res = await fetchResource(url, { useProxy });
788
896
  const blob = await res.blob();
789
897
  const b64 = await new Promise((resolve) => {
790
898
  const reader = new FileReader();
@@ -798,7 +906,7 @@ async function embedCustomFonts({ preCached = false } = {}) {
798
906
  console.warn("[snapdom] Failed to fetch font URL:", url);
799
907
  }
800
908
  }
801
- finalCSS += `@font-face{font-family:${family};src:${inlinedSrc};font-style:${rule.style.getPropertyValue("font-style") || "normal"};font-weight:${rule.style.getPropertyValue("font-weight") || "normal"};}`;
909
+ finalCSS += `@font-face{font-family:${family};src:${inlinedSrc};font-style:${styleVal};font-weight:${weightVal};}`;
802
910
  }
803
911
  }
804
912
  }
@@ -816,7 +924,7 @@ async function embedCustomFonts({ preCached = false } = {}) {
816
924
  cache.font.add(font._snapdomSrc);
817
925
  } else if (!cache.font.has(font._snapdomSrc)) {
818
926
  try {
819
- const res = await fetch(font._snapdomSrc);
927
+ const res = await fetchResource(font._snapdomSrc, { useProxy });
820
928
  const blob = await res.blob();
821
929
  b64 = await new Promise((resolve) => {
822
930
  const reader = new FileReader();
@@ -834,6 +942,32 @@ async function embedCustomFonts({ preCached = false } = {}) {
834
942
  finalCSS += `@font-face{font-family:'${font.family}';src:url(${b64});font-style:${font.style || "normal"};font-weight:${font.weight || "normal"};}`;
835
943
  }
836
944
  }
945
+ for (const font of localFonts) {
946
+ if (!font || typeof font !== "object") continue;
947
+ const { family, src, weight = "normal", style = "normal" } = font;
948
+ if (!family || !src) continue;
949
+ let b64 = src;
950
+ if (!b64.startsWith("data:")) {
951
+ try {
952
+ const res = await fetchResource(src, { useProxy });
953
+ const blob = await res.blob();
954
+ b64 = await new Promise((resolve) => {
955
+ const reader = new FileReader();
956
+ reader.onload = () => resolve(reader.result);
957
+ reader.readAsDataURL(blob);
958
+ });
959
+ cache.resource.set(src, b64);
960
+ cache.font.add(src);
961
+ } catch (e) {
962
+ console.warn("[snapdom] Failed to load local font:", src);
963
+ continue;
964
+ }
965
+ } else {
966
+ cache.resource.set(src, b64);
967
+ cache.font.add(src);
968
+ }
969
+ finalCSS += `@font-face{font-family:'${family}';src:url(${b64});font-style:${style};font-weight:${weight};}`;
970
+ }
837
971
  if (finalCSS) {
838
972
  cache.resource.set("fonts-embed-css", finalCSS);
839
973
  if (preCached) {
@@ -847,7 +981,7 @@ async function embedCustomFonts({ preCached = false } = {}) {
847
981
  }
848
982
 
849
983
  // src/modules/pseudo.js
850
- async function inlinePseudoElements(source, clone, options) {
984
+ async function inlinePseudoElements(source, clone, styleMap, styleCache, options) {
851
985
  if (!(source instanceof Element) || !(clone instanceof Element)) return;
852
986
  for (const pseudo of ["::before", "::after", "::first-letter"]) {
853
987
  try {
@@ -873,7 +1007,7 @@ async function inlinePseudoElements(source, clone, options) {
873
1007
  span.dataset.snapdomPseudo = "::first-letter";
874
1008
  const snapshot2 = snapshotComputedStyle(style);
875
1009
  const key2 = getStyleKey(snapshot2, "span", options);
876
- cache.preStyleMap.set(span, key2);
1010
+ styleMap.set(span, key2);
877
1011
  const restNode = document.createTextNode(rest);
878
1012
  clone.replaceChild(restNode, textNode);
879
1013
  clone.insertBefore(span, restNode);
@@ -907,7 +1041,7 @@ async function inlinePseudoElements(source, clone, options) {
907
1041
  pseudoEl.style.verticalAlign = "middle";
908
1042
  const snapshot = snapshotComputedStyle(style);
909
1043
  const key = getStyleKey(snapshot, "span", options);
910
- cache.preStyleMap.set(pseudoEl, key);
1044
+ styleMap.set(pseudoEl, key);
911
1045
  if (isIconFont2 && cleanContent.length === 1) {
912
1046
  const { dataUrl, width: width2, height: height2 } = await iconToImage(cleanContent, fontFamily, fontWeight, fontSize, color);
913
1047
  const imgEl = document.createElement("img");
@@ -956,7 +1090,7 @@ async function inlinePseudoElements(source, clone, options) {
956
1090
  const sChildren = Array.from(source.children);
957
1091
  const cChildren = Array.from(clone.children).filter((child) => !child.dataset.snapdomPseudo);
958
1092
  for (let i = 0; i < Math.min(sChildren.length, cChildren.length); i++) {
959
- await inlinePseudoElements(sChildren[i], cChildren[i], options);
1093
+ await inlinePseudoElements(sChildren[i], cChildren[i], styleMap, styleCache, options);
960
1094
  }
961
1095
  }
962
1096
 
@@ -1012,6 +1146,9 @@ function inlineExternalDefsAndSymbols(rootElement) {
1012
1146
 
1013
1147
  // src/core/prepare.js
1014
1148
  async function prepareClone(element, compress = false, embedFonts = false, options = {}) {
1149
+ const styleMap = /* @__PURE__ */ new Map();
1150
+ const styleCache = /* @__PURE__ */ new WeakMap();
1151
+ const nodeMap = /* @__PURE__ */ new Map();
1015
1152
  let clone;
1016
1153
  let classCSS = "";
1017
1154
  stabilizeLayout(element);
@@ -1021,20 +1158,21 @@ async function prepareClone(element, compress = false, embedFonts = false, optio
1021
1158
  console.warn("inlineExternal defs or symbol failed:", e);
1022
1159
  }
1023
1160
  try {
1024
- clone = deepClone(element, compress, options, element);
1161
+ clone = deepClone(element, styleMap, styleCache, nodeMap, compress, options, element);
1025
1162
  } catch (e) {
1026
1163
  console.warn("deepClone failed:", e);
1027
1164
  throw e;
1028
1165
  }
1029
1166
  try {
1030
- await inlinePseudoElements(element, clone, compress, embedFonts, options.useProxy);
1167
+ await inlinePseudoElements(element, clone, styleMap, styleCache, compress, embedFonts, options.useProxy);
1031
1168
  } catch (e) {
1032
1169
  console.warn("inlinePseudoElements failed:", e);
1033
1170
  }
1171
+ await resolveBlobUrlsInTree(clone);
1034
1172
  if (compress) {
1035
- const keyToClass = generateCSSClasses();
1173
+ const keyToClass = generateCSSClasses(styleMap);
1036
1174
  classCSS = Array.from(keyToClass.entries()).map(([key, className]) => `.${className}{${key}}`).join("");
1037
- for (const [node, key] of cache.preStyleMap.entries()) {
1175
+ for (const [node, key] of styleMap.entries()) {
1038
1176
  if (node.tagName === "STYLE") continue;
1039
1177
  if (node.getRootNode && node.getRootNode() instanceof ShadowRoot) {
1040
1178
  node.setAttribute("style", key.replace(/;/g, "; "));
@@ -1051,12 +1189,12 @@ async function prepareClone(element, compress = false, embedFonts = false, optio
1051
1189
  }
1052
1190
  }
1053
1191
  } else {
1054
- for (const [node, key] of cache.preStyleMap.entries()) {
1192
+ for (const [node, key] of styleMap.entries()) {
1055
1193
  if (node.tagName === "STYLE") continue;
1056
1194
  node.setAttribute("style", key.replace(/;/g, "; "));
1057
1195
  }
1058
1196
  }
1059
- for (const [cloneNode, originalNode] of cache.preNodeMap.entries()) {
1197
+ for (const [cloneNode, originalNode] of nodeMap.entries()) {
1060
1198
  const scrollX = originalNode.scrollLeft;
1061
1199
  const scrollY = originalNode.scrollTop;
1062
1200
  const hasScroll = scrollX || scrollY;
@@ -1075,9 +1213,9 @@ async function prepareClone(element, compress = false, embedFonts = false, optio
1075
1213
  cloneNode.appendChild(inner);
1076
1214
  }
1077
1215
  }
1078
- if (element === cache.preNodeMap.get(clone)) {
1079
- const computed = cache.preStyle.get(element) || window.getComputedStyle(element);
1080
- cache.preStyle.set(element, computed);
1216
+ if (element === nodeMap.get(clone)) {
1217
+ const computed = styleCache.get(element) || window.getComputedStyle(element);
1218
+ styleCache.set(element, computed);
1081
1219
  const transform = stripTranslate(computed.transform);
1082
1220
  clone.style.margin = "0";
1083
1221
  clone.style.position = "static";
@@ -1090,13 +1228,13 @@ async function prepareClone(element, compress = false, embedFonts = false, optio
1090
1228
  clone.style.clear = "none";
1091
1229
  clone.style.transform = transform || "";
1092
1230
  }
1093
- for (const [cloneNode, originalNode] of cache.preNodeMap.entries()) {
1231
+ for (const [cloneNode, originalNode] of nodeMap.entries()) {
1094
1232
  if (originalNode.tagName === "PRE") {
1095
1233
  cloneNode.style.marginTop = "0";
1096
1234
  cloneNode.style.marginBlockStart = "0";
1097
1235
  }
1098
1236
  }
1099
- return { clone, classCSS };
1237
+ return { clone, classCSS, styleCache };
1100
1238
  }
1101
1239
  function stabilizeLayout(element) {
1102
1240
  const style = getComputedStyle(element);
@@ -1110,11 +1248,140 @@ function stabilizeLayout(element) {
1110
1248
  element.style.border = `${outlineWidth} solid transparent`;
1111
1249
  }
1112
1250
  }
1251
+ var _blobToDataUrlCache = /* @__PURE__ */ new Map();
1252
+ async function blobUrlToDataUrl(blobUrl) {
1253
+ if (_blobToDataUrlCache.has(blobUrl)) return _blobToDataUrlCache.get(blobUrl);
1254
+ const res = await fetch(blobUrl);
1255
+ if (!res.ok) throw new Error(`[SnapDOM] HTTP ${res.status} on blob fetch (${blobUrl})`);
1256
+ const blob = await res.blob();
1257
+ const dataUrl = await new Promise((resolve, reject) => {
1258
+ const fr = new FileReader();
1259
+ fr.onloadend = () => {
1260
+ const v = fr.result;
1261
+ if (typeof v === "string" && v.startsWith("data:")) resolve(v);
1262
+ else reject(new Error("[SnapDOM] Invalid data URL from blob"));
1263
+ };
1264
+ fr.onerror = () => reject(new Error("[SnapDOM] FileReader error"));
1265
+ fr.readAsDataURL(blob);
1266
+ });
1267
+ _blobToDataUrlCache.set(blobUrl, dataUrl);
1268
+ return dataUrl;
1269
+ }
1270
+ var BLOB_URL_RE = /\bblob:[^)"'\s]+/g;
1271
+ async function replaceBlobUrlsInCssText(cssText) {
1272
+ if (!cssText || cssText.indexOf("blob:") === -1) return cssText;
1273
+ const uniques = Array.from(new Set(cssText.match(BLOB_URL_RE) || []));
1274
+ if (uniques.length === 0) return cssText;
1275
+ let out = cssText;
1276
+ for (const u of uniques) {
1277
+ try {
1278
+ const d = await blobUrlToDataUrl(u);
1279
+ out = out.split(u).join(d);
1280
+ } catch {
1281
+ }
1282
+ }
1283
+ return out;
1284
+ }
1285
+ function isBlobUrl(u) {
1286
+ return typeof u === "string" && u.startsWith("blob:");
1287
+ }
1288
+ function parseSrcset(srcset) {
1289
+ return (srcset || "").split(",").map((s) => s.trim()).filter(Boolean).map((item) => {
1290
+ const m = item.match(/^(\S+)(\s+.+)?$/);
1291
+ return m ? { url: m[1], desc: m[2] || "" } : null;
1292
+ }).filter(Boolean);
1293
+ }
1294
+ function stringifySrcset(parts) {
1295
+ return parts.map((p) => p.desc ? `${p.url} ${p.desc.trim()}` : p.url).join(", ");
1296
+ }
1297
+ async function resolveBlobUrlsInTree(root) {
1298
+ if (!root) return;
1299
+ const imgs = root.querySelectorAll ? root.querySelectorAll("img") : [];
1300
+ for (const img of imgs) {
1301
+ try {
1302
+ const srcAttr = img.getAttribute("src");
1303
+ const effective = srcAttr || img.currentSrc || "";
1304
+ if (isBlobUrl(effective)) {
1305
+ const data = await blobUrlToDataUrl(effective);
1306
+ img.setAttribute("src", data);
1307
+ }
1308
+ const srcset = img.getAttribute("srcset");
1309
+ if (srcset && srcset.includes("blob:")) {
1310
+ const parts = parseSrcset(srcset);
1311
+ let changed = false;
1312
+ for (const p of parts) {
1313
+ if (isBlobUrl(p.url)) {
1314
+ try {
1315
+ p.url = await blobUrlToDataUrl(p.url);
1316
+ changed = true;
1317
+ } catch {
1318
+ }
1319
+ }
1320
+ }
1321
+ if (changed) img.setAttribute("srcset", stringifySrcset(parts));
1322
+ }
1323
+ } catch {
1324
+ }
1325
+ }
1326
+ const svgImages = root.querySelectorAll ? root.querySelectorAll("image") : [];
1327
+ for (const node of svgImages) {
1328
+ try {
1329
+ const XLINK_NS = "http://www.w3.org/1999/xlink";
1330
+ const href = node.getAttribute("href") || node.getAttributeNS?.(XLINK_NS, "href");
1331
+ if (isBlobUrl(href)) {
1332
+ const d = await blobUrlToDataUrl(href);
1333
+ node.setAttribute("href", d);
1334
+ node.removeAttributeNS?.(XLINK_NS, "href");
1335
+ }
1336
+ } catch {
1337
+ }
1338
+ }
1339
+ const styled = root.querySelectorAll ? root.querySelectorAll("[style*='blob:']") : [];
1340
+ for (const el of styled) {
1341
+ try {
1342
+ const styleText = el.getAttribute("style");
1343
+ if (styleText && styleText.includes("blob:")) {
1344
+ const replaced = await replaceBlobUrlsInCssText(styleText);
1345
+ el.setAttribute("style", replaced);
1346
+ }
1347
+ } catch {
1348
+ }
1349
+ }
1350
+ const styleTags = root.querySelectorAll ? root.querySelectorAll("style") : [];
1351
+ for (const s of styleTags) {
1352
+ try {
1353
+ const css = s.textContent || "";
1354
+ if (css.includes("blob:")) {
1355
+ s.textContent = await replaceBlobUrlsInCssText(css);
1356
+ }
1357
+ } catch {
1358
+ }
1359
+ }
1360
+ const urlAttrs = ["poster"];
1361
+ for (const attr of urlAttrs) {
1362
+ const nodes = root.querySelectorAll ? root.querySelectorAll(`[${attr}^='blob:']`) : [];
1363
+ for (const n of nodes) {
1364
+ try {
1365
+ const u = n.getAttribute(attr);
1366
+ if (isBlobUrl(u)) {
1367
+ n.setAttribute(attr, await blobUrlToDataUrl(u));
1368
+ }
1369
+ } catch {
1370
+ }
1371
+ }
1372
+ }
1373
+ }
1113
1374
 
1114
1375
  // src/modules/images.js
1115
1376
  async function inlineImages(clone, options = {}) {
1116
1377
  const imgs = Array.from(clone.querySelectorAll("img"));
1117
1378
  const processImg = async (img) => {
1379
+ if (!img.getAttribute("src")) {
1380
+ const eff = img.currentSrc || img.src || "";
1381
+ if (eff) img.setAttribute("src", eff);
1382
+ }
1383
+ img.removeAttribute("srcset");
1384
+ img.removeAttribute("sizes");
1118
1385
  const src = img.src;
1119
1386
  try {
1120
1387
  const dataUrl = await fetchImage(src, { useProxy: options.useProxy });
@@ -1135,7 +1402,7 @@ async function inlineImages(clone, options = {}) {
1135
1402
  }
1136
1403
 
1137
1404
  // src/modules/background.js
1138
- async function inlineBackgroundImages(source, clone, options = {}) {
1405
+ async function inlineBackgroundImages(source, clone, styleCache, options = {}) {
1139
1406
  const queue = [[source, clone]];
1140
1407
  const imageProps = [
1141
1408
  "background-image",
@@ -1155,8 +1422,8 @@ async function inlineBackgroundImages(source, clone, options = {}) {
1155
1422
  ];
1156
1423
  while (queue.length) {
1157
1424
  const [srcNode, cloneNode] = queue.shift();
1158
- const style = cache.preStyle.get(srcNode) || getStyle(srcNode);
1159
- if (!cache.preStyle.has(srcNode)) cache.preStyle.set(srcNode, style);
1425
+ const style = styleCache.get(srcNode) || getStyle(srcNode);
1426
+ if (!styleCache.has(srcNode)) styleCache.set(srcNode, style);
1160
1427
  const hasBorderImage = (() => {
1161
1428
  const bi = style.getPropertyValue("border-image");
1162
1429
  const bis = style.getPropertyValue("border-image-source");
@@ -1192,13 +1459,13 @@ async function inlineBackgroundImages(source, clone, options = {}) {
1192
1459
  async function captureDOM(element, options = {}) {
1193
1460
  if (!element) throw new Error("Element cannot be null or undefined");
1194
1461
  cache.reset();
1195
- const { compress = true, embedFonts = false, fast = true, scale = 1, useProxy = "" } = options;
1196
- let clone, classCSS;
1462
+ const { compress = true, embedFonts = false, fast = true, scale = 1, useProxy = "", localFonts = [] } = options;
1463
+ let clone, classCSS, styleCache;
1197
1464
  let fontsCSS = "";
1198
1465
  let baseCSS = "";
1199
1466
  let dataURL;
1200
1467
  let svgString;
1201
- ({ clone, classCSS } = await prepareClone(element, compress, embedFonts, options));
1468
+ ({ clone, classCSS, styleCache } = await prepareClone(element, compress, embedFonts, options));
1202
1469
  await new Promise((resolve) => {
1203
1470
  idle(async () => {
1204
1471
  await inlineImages(clone, options);
@@ -1207,14 +1474,14 @@ async function captureDOM(element, options = {}) {
1207
1474
  });
1208
1475
  await new Promise((resolve) => {
1209
1476
  idle(async () => {
1210
- await inlineBackgroundImages(element, clone, options);
1477
+ await inlineBackgroundImages(element, clone, styleCache, options);
1211
1478
  resolve();
1212
1479
  }, { fast });
1213
1480
  });
1214
1481
  if (embedFonts) {
1215
1482
  await new Promise((resolve) => {
1216
1483
  idle(async () => {
1217
- fontsCSS = await embedCustomFonts();
1484
+ fontsCSS = await embedCustomFonts({ localFonts, useProxy });
1218
1485
  resolve();
1219
1486
  }, { fast });
1220
1487
  });
@@ -1449,12 +1716,21 @@ snapdom.download = async (el, options = {}) => {
1449
1716
 
1450
1717
  // src/api/preCache.js
1451
1718
  async function preCache(root = document, options = {}) {
1452
- const { embedFonts = true, reset = false } = options;
1719
+ const { embedFonts = true, reset = false, useProxy } = options;
1453
1720
  if (reset) {
1454
- cache.reset();
1721
+ cache.image.clear();
1722
+ cache.background.clear();
1723
+ cache.resource.clear();
1724
+ cache.defaultStyle.clear();
1725
+ cache.baseStyle.clear();
1726
+ cache.font.clear();
1727
+ cache.computedStyle = /* @__PURE__ */ new WeakMap();
1455
1728
  return;
1456
1729
  }
1457
- await document.fonts.ready;
1730
+ try {
1731
+ await document.fonts.ready;
1732
+ } catch {
1733
+ }
1458
1734
  precacheCommonTags();
1459
1735
  let imgEls = [], allEls = [];
1460
1736
  if (root?.querySelectorAll) {
@@ -1463,33 +1739,41 @@ async function preCache(root = document, options = {}) {
1463
1739
  }
1464
1740
  const promises = [];
1465
1741
  for (const img of imgEls) {
1466
- const src = img.src;
1742
+ const src = img?.src;
1743
+ if (!src) continue;
1467
1744
  if (!cache.image.has(src)) {
1468
- promises.push(
1469
- fetchImage(src, { useProxy: options.useProxy }).then((dataURL) => cache.image.set(src, dataURL)).catch(() => {
1470
- })
1471
- );
1745
+ const p = Promise.resolve().then(() => fetchImage(src, { useProxy })).then((dataURL) => {
1746
+ cache.image.set(src, dataURL);
1747
+ }).catch(() => {
1748
+ });
1749
+ promises.push(p);
1472
1750
  }
1473
1751
  }
1474
1752
  for (const el of allEls) {
1475
- const bg = getStyle(el).backgroundImage;
1753
+ let bg = "";
1754
+ try {
1755
+ bg = getStyle(el).backgroundImage;
1756
+ } catch {
1757
+ }
1476
1758
  if (bg && bg !== "none") {
1477
1759
  const bgSplits = splitBackgroundImage(bg);
1478
1760
  for (const entry of bgSplits) {
1479
- const isUrl = entry.startsWith("url(");
1480
- if (isUrl) {
1481
- promises.push(
1482
- inlineSingleBackgroundEntry(entry, options).catch(() => {
1483
- })
1484
- );
1761
+ if (entry.startsWith("url(")) {
1762
+ const p = Promise.resolve().then(() => inlineSingleBackgroundEntry(entry, { ...options, useProxy })).catch(() => {
1763
+ });
1764
+ promises.push(p);
1485
1765
  }
1486
1766
  }
1487
1767
  }
1488
1768
  }
1489
1769
  if (embedFonts) {
1490
- await embedCustomFonts({ preCached: true });
1770
+ try {
1771
+ await embedCustomFonts({ preCached: true, localFonts: options.localFonts, useProxy: options.useProxy });
1772
+ } catch {
1773
+ }
1774
+ ;
1491
1775
  }
1492
- await Promise.all(promises);
1776
+ await Promise.allSettled(promises);
1493
1777
  }
1494
1778
  export {
1495
1779
  preCache,