@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/README.md +63 -30
- package/dist/snapdom.js +294 -85
- package/dist/snapdom.min.js +13 -13
- package/dist/snapdom.min.mjs +10 -10
- package/dist/snapdom.mjs +289 -82
- package/package.json +1 -1
- package/types/snapdom.d.ts +6 -2
package/dist/snapdom.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
/*
|
|
3
3
|
* snapdom
|
|
4
|
-
* v.1.
|
|
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
|
|
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
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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 =
|
|
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
|
|
308
|
-
imageCache.set(src,
|
|
309
|
-
resolve(
|
|
310
|
-
} catch (
|
|
311
|
-
reject(
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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* ({
|
|
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 (
|
|
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 (
|
|
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
|
-
|
|
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 (
|
|
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: ${
|
|
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
|
|
735
|
-
fontFamily
|
|
736
|
-
);
|
|
889
|
+
const isIconFont2 = isIconFont(fontFamily);
|
|
737
890
|
const cleanContent = parseContent(content);
|
|
738
|
-
if (
|
|
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 =
|
|
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 =
|
|
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(
|
|
797
|
-
return __async(this,
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
1077
|
-
return __async(this,
|
|
1078
|
-
|
|
1079
|
-
|
|
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
|
|
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,
|
|
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,
|
|
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({
|
|
1448
|
+
yield embedCustomFonts({ preCached: true });
|
|
1240
1449
|
}
|
|
1241
1450
|
yield Promise.all(promises);
|
|
1242
1451
|
});
|