@zumer/snapdom 1.3.0 → 1.7.1
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 +36 -9
- package/dist/snapdom.js +120 -27
- package/dist/snapdom.min.js +15 -3
- package/dist/snapdom.min.mjs +15 -3
- package/dist/snapdom.mjs +120 -27
- package/package.json +1 -1
- package/types/snapdom.d.ts +2 -0
package/README.md
CHANGED
|
@@ -122,14 +122,34 @@ Returns an object with reusable export methods:
|
|
|
122
122
|
|
|
123
123
|
All capture methods accept an `options` object:
|
|
124
124
|
|
|
125
|
-
| Option | Type
|
|
126
|
-
| ----------------- |
|
|
127
|
-
| `compress` | boolean
|
|
128
|
-
| `fast` | boolean
|
|
129
|
-
| `embedFonts` | boolean
|
|
130
|
-
| `scale` | number
|
|
131
|
-
| `backgroundColor` | string
|
|
132
|
-
| `quality` | number
|
|
125
|
+
| Option | Type | Default | Description |
|
|
126
|
+
| ----------------- | -------- | -------- | ------------------------------------------ |
|
|
127
|
+
| `compress` | boolean | `true` | Removes redundant styles |
|
|
128
|
+
| `fast` | boolean | `true` | Skips idle delay for faster results |
|
|
129
|
+
| `embedFonts` | boolean | `false` | Inlines fonts (icon fonts always embedded) |
|
|
130
|
+
| `scale` | number | `1` | Output scale multiplier |
|
|
131
|
+
| `backgroundColor` | string | `"#fff"` | Fallback color for JPG/WebP |
|
|
132
|
+
| `quality` | number | `1` | Quality for JPG/WebP (0 to 1) |
|
|
133
|
+
| `crossOrigin` | function | - | Function to determine CORS mode per image URL |
|
|
134
|
+
|
|
135
|
+
### Cross-Origin Images
|
|
136
|
+
|
|
137
|
+
By default, snapDOM loads images with `crossOrigin="anonymous"`. You can customize this behavior using the `crossOrigin` option:
|
|
138
|
+
|
|
139
|
+
```js
|
|
140
|
+
const result = await snapdom(element, {
|
|
141
|
+
crossOrigin: (url) => {
|
|
142
|
+
// Use credentials for same-origin images
|
|
143
|
+
if (url.startsWith(window.location.origin)) {
|
|
144
|
+
return "use-credentials";
|
|
145
|
+
}
|
|
146
|
+
// Use anonymous for cross-origin images
|
|
147
|
+
return "anonymous";
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
This is useful when your images require authentication or when dealing with credentialed requests.
|
|
133
153
|
|
|
134
154
|
### Download options
|
|
135
155
|
|
|
@@ -164,6 +184,7 @@ import { snapdom, preCache } from './snapdom.mjs';
|
|
|
164
184
|
|
|
165
185
|
* `embedFonts` *(boolean, default: true)* — Inlines non-icon fonts during preload.
|
|
166
186
|
* `reset` *(boolean, default: false)* — Clears all existing internal caches.
|
|
187
|
+
* `crossOrigin` *(function)* — Function to determine CORS mode per image URL during preload.
|
|
167
188
|
|
|
168
189
|
|
|
169
190
|
## Features
|
|
@@ -177,9 +198,10 @@ import { snapdom, preCache } from './snapdom.mjs';
|
|
|
177
198
|
|
|
178
199
|
## Limitations
|
|
179
200
|
|
|
180
|
-
* External images must be CORS-accessible
|
|
201
|
+
* External images must be CORS-accessible (use `crossOrigin` option for credentialed requests)
|
|
181
202
|
* Iframes are not supported
|
|
182
203
|
* When WebP format is used on Safari, it will fallback to PNG rendering.
|
|
204
|
+
* `@font-face` CSS rule is well supported, but if need to use JS `FontFace()`, see this workaround [`#43`](https://github.com/zumerlab/snapdom/issues/43)
|
|
183
205
|
|
|
184
206
|
## Benchmarks
|
|
185
207
|
|
|
@@ -215,6 +237,11 @@ npm run test:benchmark
|
|
|
215
237
|
|
|
216
238
|
They execute in **headless Chromium** using real DOM nodes.
|
|
217
239
|
|
|
240
|
+
## Contributors 🙌
|
|
241
|
+
|
|
242
|
+
<a href="https://github.com/zumerlab/snapdom/graphs/contributors">
|
|
243
|
+
<img src="https://contrib.rocks/image?repo=zumerlab/snapdom" />
|
|
244
|
+
</a>
|
|
218
245
|
|
|
219
246
|
## License
|
|
220
247
|
|
package/dist/snapdom.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
/*
|
|
3
3
|
* snapdom
|
|
4
|
-
* v.1.
|
|
4
|
+
* v.1.7.1
|
|
5
5
|
* Author Juan Martin Muda
|
|
6
6
|
* License MIT
|
|
7
7
|
**/
|
|
@@ -209,7 +209,7 @@
|
|
|
209
209
|
];
|
|
210
210
|
return iconFontPatterns.some((rx) => rx.test(familyOrUrl));
|
|
211
211
|
}
|
|
212
|
-
function fetchImage(src, timeout = 3e3) {
|
|
212
|
+
function fetchImage(src, timeout = 3e3, crossOrigin = "anonymous") {
|
|
213
213
|
if (imageCache.has(src)) {
|
|
214
214
|
return Promise.resolve(imageCache.get(src));
|
|
215
215
|
}
|
|
@@ -218,7 +218,7 @@
|
|
|
218
218
|
reject(new Error("Image load timed out"));
|
|
219
219
|
}, timeout);
|
|
220
220
|
const image = new Image();
|
|
221
|
-
image.crossOrigin =
|
|
221
|
+
image.crossOrigin = crossOrigin;
|
|
222
222
|
image.onload = async () => {
|
|
223
223
|
clearTimeout(timeoutId);
|
|
224
224
|
try {
|
|
@@ -375,7 +375,7 @@
|
|
|
375
375
|
ctx.fillText(unicodeChar, 0, ascent);
|
|
376
376
|
return canvas.toDataURL();
|
|
377
377
|
}
|
|
378
|
-
async function embedCustomFonts({ ignoreIconFonts = true, preCached = false }) {
|
|
378
|
+
async function embedCustomFonts({ ignoreIconFonts = true, preCached = false } = {}) {
|
|
379
379
|
const links = Array.from(document.querySelectorAll('link[rel="stylesheet"]')).filter((link) => link.href);
|
|
380
380
|
let finalCSS = "";
|
|
381
381
|
for (const link of links) {
|
|
@@ -416,7 +416,7 @@
|
|
|
416
416
|
resourceCache.set(url, b64);
|
|
417
417
|
return { original: match[0], inlined: `url(${b64})` };
|
|
418
418
|
} catch (err) {
|
|
419
|
-
console.warn("
|
|
419
|
+
console.warn("[snapdom] Failed to fetch font:", url);
|
|
420
420
|
return null;
|
|
421
421
|
}
|
|
422
422
|
})
|
|
@@ -429,7 +429,90 @@
|
|
|
429
429
|
}
|
|
430
430
|
finalCSS += cssFinal + "\n";
|
|
431
431
|
} catch (e) {
|
|
432
|
-
console.warn("
|
|
432
|
+
console.warn("[snapdom] Failed to fetch CSS:", link.href);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
for (const sheet of document.styleSheets) {
|
|
436
|
+
try {
|
|
437
|
+
if (!sheet.href || links.every((link) => link.href !== sheet.href)) {
|
|
438
|
+
for (const rule of sheet.cssRules) {
|
|
439
|
+
if (rule.type === CSSRule.FONT_FACE_RULE) {
|
|
440
|
+
const src = rule.style.getPropertyValue("src");
|
|
441
|
+
if (!src) continue;
|
|
442
|
+
const urlRegex = /url\(([^)]+)\)/g;
|
|
443
|
+
let inlinedSrc = src;
|
|
444
|
+
const matches = Array.from(src.matchAll(urlRegex));
|
|
445
|
+
for (const match of matches) {
|
|
446
|
+
let rawUrl = match[1].trim().replace(/^["']|["']$/g, "");
|
|
447
|
+
if (!rawUrl) continue;
|
|
448
|
+
let url = rawUrl;
|
|
449
|
+
if (!url.startsWith("http") && !url.startsWith("data:")) {
|
|
450
|
+
url = new URL(url, sheet.href || location.href).href;
|
|
451
|
+
}
|
|
452
|
+
if (ignoreIconFonts && isIconFont(url)) {
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
if (resourceCache.has(url)) {
|
|
456
|
+
inlinedSrc = inlinedSrc.replace(match[0], `url(${resourceCache.get(url)})`);
|
|
457
|
+
continue;
|
|
458
|
+
}
|
|
459
|
+
try {
|
|
460
|
+
const res = await fetch(url);
|
|
461
|
+
const blob = await res.blob();
|
|
462
|
+
const b64 = await new Promise((resolve) => {
|
|
463
|
+
const reader = new FileReader();
|
|
464
|
+
reader.onload = () => resolve(reader.result);
|
|
465
|
+
reader.readAsDataURL(blob);
|
|
466
|
+
});
|
|
467
|
+
resourceCache.set(url, b64);
|
|
468
|
+
inlinedSrc = inlinedSrc.replace(match[0], `url(${b64})`);
|
|
469
|
+
} catch (e) {
|
|
470
|
+
console.warn("[snapdom] Failed to fetch font URL:", url);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
finalCSS += `@font-face {
|
|
474
|
+
font-family: ${rule.style.getPropertyValue("font-family")};
|
|
475
|
+
src: ${inlinedSrc};
|
|
476
|
+
font-style: ${rule.style.getPropertyValue("font-style") || "normal"};
|
|
477
|
+
font-weight: ${rule.style.getPropertyValue("font-weight") || "normal"};
|
|
478
|
+
}
|
|
479
|
+
`;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
} catch (e) {
|
|
484
|
+
console.warn("[snapdom] Cannot access stylesheet", sheet.href, e);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
for (const font of document.fonts) {
|
|
488
|
+
if (font.family && font.status === "loaded" && font._snapdomSrc) {
|
|
489
|
+
let b64 = font._snapdomSrc;
|
|
490
|
+
if (!b64.startsWith("data:")) {
|
|
491
|
+
if (resourceCache.has(font._snapdomSrc)) {
|
|
492
|
+
b64 = resourceCache.get(font._snapdomSrc);
|
|
493
|
+
} else {
|
|
494
|
+
try {
|
|
495
|
+
const res = await fetch(font._snapdomSrc);
|
|
496
|
+
const blob = await res.blob();
|
|
497
|
+
b64 = await new Promise((resolve) => {
|
|
498
|
+
const reader = new FileReader();
|
|
499
|
+
reader.onload = () => resolve(reader.result);
|
|
500
|
+
reader.readAsDataURL(blob);
|
|
501
|
+
});
|
|
502
|
+
resourceCache.set(font._snapdomSrc, b64);
|
|
503
|
+
} catch (e) {
|
|
504
|
+
console.warn("[snapdom] Failed to fetch dynamic font src:", font._snapdomSrc);
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
finalCSS += `@font-face {
|
|
510
|
+
font-family: '${font.family}';
|
|
511
|
+
src: url(${b64});
|
|
512
|
+
font-style: ${font.style || "normal"};
|
|
513
|
+
font-weight: ${font.weight || "normal"};
|
|
514
|
+
}
|
|
515
|
+
`;
|
|
433
516
|
}
|
|
434
517
|
}
|
|
435
518
|
if (finalCSS && preCached) {
|
|
@@ -588,12 +671,13 @@
|
|
|
588
671
|
}
|
|
589
672
|
|
|
590
673
|
// src/modules/images.js
|
|
591
|
-
async function inlineImages(clone) {
|
|
674
|
+
async function inlineImages(clone, options = {}) {
|
|
592
675
|
const imgs = Array.from(clone.querySelectorAll("img"));
|
|
593
676
|
const processImg = async (img) => {
|
|
594
677
|
const src = img.src;
|
|
595
678
|
try {
|
|
596
|
-
const
|
|
679
|
+
const crossOrigin = options.crossOrigin ? options.crossOrigin(src) : "anonymous";
|
|
680
|
+
const dataUrl = await fetchImage(src, 3e3, crossOrigin);
|
|
597
681
|
img.src = dataUrl;
|
|
598
682
|
if (!img.width) img.width = img.naturalWidth || 100;
|
|
599
683
|
if (!img.height) img.height = img.naturalHeight || 100;
|
|
@@ -611,29 +695,36 @@
|
|
|
611
695
|
}
|
|
612
696
|
|
|
613
697
|
// src/modules/background.js
|
|
614
|
-
async function inlineBackgroundImages(source, clone, styleCache) {
|
|
698
|
+
async function inlineBackgroundImages(source, clone, styleCache, options = {}) {
|
|
615
699
|
const queue = [[source, clone]];
|
|
616
700
|
while (queue.length) {
|
|
617
701
|
const [srcNode, cloneNode] = queue.shift();
|
|
618
702
|
const style = styleCache.get(srcNode) || getStyle(srcNode);
|
|
619
703
|
if (!styleCache.has(srcNode)) styleCache.set(srcNode, style);
|
|
620
704
|
const bg = style.getPropertyValue("background-image");
|
|
621
|
-
const
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
dataUrl
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
705
|
+
const bgSplits = bg.split(",");
|
|
706
|
+
for (let i = 0; i < bgSplits.length; i++) {
|
|
707
|
+
const rawUrl = extractURL(bgSplits[i]);
|
|
708
|
+
if (rawUrl) {
|
|
709
|
+
try {
|
|
710
|
+
let bgUrl = encodeURI(rawUrl);
|
|
711
|
+
let dataUrl;
|
|
712
|
+
if (bgCache.has(bgUrl)) {
|
|
713
|
+
dataUrl = bgCache.get(bgUrl);
|
|
714
|
+
} else {
|
|
715
|
+
const crossOrigin = options.crossOrigin ? options.crossOrigin(bgUrl) : "anonymous";
|
|
716
|
+
dataUrl = await fetchImage(bgUrl, 3e3, crossOrigin);
|
|
717
|
+
bgCache.set(bgUrl, dataUrl);
|
|
718
|
+
}
|
|
719
|
+
bgSplits[i] = `url(${dataUrl})`;
|
|
720
|
+
} catch {
|
|
721
|
+
bgSplits[i] = "none";
|
|
631
722
|
}
|
|
632
|
-
cloneNode.style.backgroundImage = `url(${dataUrl})`;
|
|
633
|
-
} catch {
|
|
634
|
-
cloneNode.style.backgroundImage = "none";
|
|
635
723
|
}
|
|
636
724
|
}
|
|
725
|
+
if (bgSplits.some((s) => s.includes("url("))) {
|
|
726
|
+
cloneNode.style.backgroundImage = bgSplits.join(",");
|
|
727
|
+
}
|
|
637
728
|
const sChildren = Array.from(srcNode.children);
|
|
638
729
|
const cChildren = Array.from(cloneNode.children);
|
|
639
730
|
for (let i = 0; i < Math.min(sChildren.length, cChildren.length); i++) {
|
|
@@ -654,13 +745,13 @@
|
|
|
654
745
|
({ clone, classCSS, styleCache } = await prepareClone(element, compress, embedFonts));
|
|
655
746
|
await new Promise((resolve) => {
|
|
656
747
|
idle(async () => {
|
|
657
|
-
await inlineImages(clone);
|
|
748
|
+
await inlineImages(clone, options);
|
|
658
749
|
resolve();
|
|
659
750
|
}, { fast });
|
|
660
751
|
});
|
|
661
752
|
await new Promise((resolve) => {
|
|
662
753
|
idle(async () => {
|
|
663
|
-
await inlineBackgroundImages(element, clone, styleCache);
|
|
754
|
+
await inlineBackgroundImages(element, clone, styleCache, options);
|
|
664
755
|
resolve();
|
|
665
756
|
}, { fast });
|
|
666
757
|
});
|
|
@@ -842,7 +933,7 @@
|
|
|
842
933
|
|
|
843
934
|
// src/api/preCache.js
|
|
844
935
|
async function preCache(root = document, options = {}) {
|
|
845
|
-
const { embedFonts = true, reset = false,
|
|
936
|
+
const { embedFonts = true, reset = false, crossOrigin: crossOriginFn } = options;
|
|
846
937
|
if (reset) {
|
|
847
938
|
imageCache.clear();
|
|
848
939
|
bgCache.clear();
|
|
@@ -862,8 +953,9 @@
|
|
|
862
953
|
for (const img of imgEls) {
|
|
863
954
|
const src = img.src;
|
|
864
955
|
if (!imageCache.has(src)) {
|
|
956
|
+
const crossOrigin = crossOriginFn ? crossOriginFn(src) : "anonymous";
|
|
865
957
|
promises.push(
|
|
866
|
-
fetchImage(src).then((dataURL) => imageCache.set(src, dataURL)).catch(() => {
|
|
958
|
+
fetchImage(src, 3e3, crossOrigin).then((dataURL) => imageCache.set(src, dataURL)).catch(() => {
|
|
867
959
|
})
|
|
868
960
|
);
|
|
869
961
|
}
|
|
@@ -872,8 +964,9 @@
|
|
|
872
964
|
const bg = getComputedStyle(el).backgroundImage;
|
|
873
965
|
const url = extractURL(bg);
|
|
874
966
|
if (url && !bgCache.has(url)) {
|
|
967
|
+
const crossOrigin = crossOriginFn ? crossOriginFn(url) : "anonymous";
|
|
875
968
|
promises.push(
|
|
876
|
-
fetchImage(url).then((dataURL) => bgCache.set(url, dataURL)).catch(() => {
|
|
969
|
+
fetchImage(url, 3e3, crossOrigin).then((dataURL) => bgCache.set(url, dataURL)).catch(() => {
|
|
877
970
|
})
|
|
878
971
|
);
|
|
879
972
|
}
|
package/dist/snapdom.min.js
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
-
(()=>{var
|
|
2
|
-
`;return o}function J(e){let
|
|
3
|
-
`}catch{console.warn("
|
|
1
|
+
(()=>{var R=new Map,T=new Map,x=new Map,I=new Map,k=new Map,N=new WeakMap;var ce=["div","span","p","a","img","ul","li","button","input","select","textarea","label","section","article","header","footer","nav","main","aside","h1","h2","h3","h4","h5","h6","svg","path","circle","rect","line","g","table","thead","tbody","tr","td","th"];function H(){for(let e of ce)z(e)}function z(e){if(I.has(e))return I.get(e);if(new Set(["script","style","meta","link","noscript","template"]).has(e)){let s={};return I.set(e,s),s}let o=document.getElementById("snapdom-sandbox");o||(o=document.createElement("div"),o.id="snapdom-sandbox",o.style.position="absolute",o.style.left="-9999px",o.style.top="-9999px",o.style.width="0",o.style.height="0",o.style.overflow="hidden",document.body.appendChild(o));let r=document.createElement(e);r.style.all="initial",o.appendChild(r);let t=getComputedStyle(r),a={};for(let s of t)a[s]=t.getPropertyValue(s);return o.removeChild(r),I.set(e,a),a}function M(e,n,o=!1){let r=[],t=z(n);for(let[a,s]of Object.entries(e))if(!o)s&&r.push(`${a}:${s}`);else{let i=t[a];s&&s!==i&&r.push(`${a}:${s}`)}return r.sort().join(";")}function Y(e){let n=new Set;return e.nodeType!==Node.ELEMENT_NODE&&e.nodeType!==Node.DOCUMENT_FRAGMENT_NODE?[]:(e.tagName&&n.add(e.tagName.toLowerCase()),typeof e.querySelectorAll=="function"&&e.querySelectorAll("*").forEach(o=>n.add(o.tagName.toLowerCase())),Array.from(n))}function K(e){let n=new Map;for(let r of e){let t=I.get(r);if(!t)continue;let a=Object.entries(t).map(([s,i])=>`${s}:${i};`).sort().join("");n.has(a)||n.set(a,[]),n.get(a).push(r)}let o="";for(let[r,t]of n.entries())o+=`${t.join(",")} { ${r} }
|
|
2
|
+
`;return o}function J(e){let n=new Set(e.values()),o=new Map,r=1;for(let t of n)o.set(t,`c${r++}`);return o}function L(e,{fast:n=!1}={}){if(n)return e();"requestIdleCallback"in window?requestIdleCallback(e,{timeout:50}):setTimeout(e,1)}function U(e,n=null){if(!(e instanceof Element))return window.getComputedStyle(e,n);let o=N.get(e);if(o||(o=new Map,N.set(e,o)),!o.has(n)){let r=window.getComputedStyle(e,n);o.set(n,r)}return o.get(n)}function X(e){let n=e.replace(/^['"]|['"]$/g,"");if(n.startsWith("\\"))try{return String.fromCharCode(parseInt(n.replace("\\",""),16))}catch{return n}return n}function $(e){let n=e.indexOf("url(");if(n===-1)return null;let o=e.slice(n+4).trim();return o.endsWith(")")&&(o=o.slice(0,-1).trim()),(o.startsWith('"')&&o.endsWith('"')||o.startsWith("'")&&o.endsWith("'"))&&(o=o.slice(1,-1)),o}function P(e){return[/font\s*awesome/i,/material\s*icons/i,/ionicons/i,/glyphicons/i,/feather/i,/bootstrap\s*icons/i,/remix\s*icons/i,/heroicons/i].some(o=>o.test(e))}function S(e,n=3e3,o="anonymous"){return R.has(e)?Promise.resolve(R.get(e)):new Promise((r,t)=>{let a=setTimeout(()=>{t(new Error("Image load timed out"))},n),s=new Image;s.crossOrigin=o,s.onload=async()=>{clearTimeout(a);try{await s.decode();let i=document.createElement("canvas");i.width=s.width,i.height=s.height,i.getContext("2d").drawImage(s,0,0,i.width,i.height);try{let l=i.toDataURL("image/png");R.set(e,l),r(l)}catch{t(new Error("CORS restrictions prevented image capture"))}}catch(i){t(i)}},s.onerror=i=>{clearTimeout(a),t(new Error("Failed to load image: "+(i.message||"Unknown error")))},s.src=e})}function O(e){let n={};for(let o of e)n[o]=e.getPropertyValue(o);return n}function B(){return/^((?!chrome|android).)*safari/i.test(navigator.userAgent)}function _(e,n,o,r,t){if(e.tagName==="STYLE")return;r.has(e)||r.set(e,U(e));let a=r.get(e),s=O(a),i=e.tagName?.toLowerCase()||"div",c=M(s,i,t);o.set(n,c)}function D(e,n,o,r,t){if(e.nodeType===Node.ELEMENT_NODE&&e.getAttribute("data-capture")==="exclude"){let i=document.createElement("div"),c=e.getBoundingClientRect();return i.style.cssText=`display: inline-block; width: ${c.width}px; height: ${c.height}px; visibility: hidden;`,i}if(e.tagName==="IFRAME"){let i=document.createElement("div");return i.textContent="",i.style.cssText=`width: ${e.offsetWidth}px; height: ${e.offsetHeight}px; background: repeating-linear-gradient(45deg, #ddd, #ddd 5px, #f9f9f9 5px, #f9f9f9 10px);display: flex;align-items: center;justify-content: center;font-size: 12px;color: #555; border: 1px solid #aaa;`,i}if(e.nodeType===Node.ELEMENT_NODE&&e.getAttribute("data-capture")==="placeholder"){let i=e.cloneNode(!1);r.set(i,e),_(e,i,n,o,t);let c=document.createElement("div");return c.textContent=e.getAttribute("data-placeholder-text")||"",c.style.cssText="color: #666;font-size: 12px;text-align: center;line-height: 1.4;padding: 0.5em;box-sizing: border-box;",i.appendChild(c),i}if(e.tagName==="CANVAS"){let i=e.toDataURL(),c=document.createElement("img");return c.src=i,c.width=e.width,c.height=e.height,c.style.display="inline-block",c.style.width=`${e.width}px`,c.style.height=`${e.height}px`,c}if(e.nodeType===Node.TEXT_NODE){if(e.parentElement?.shadowRoot){let i=e.parentElement.tagName.toLowerCase();if(customElements.get(i))return null}return e.cloneNode(!0)}if(e.nodeType!==Node.ELEMENT_NODE)return e.cloneNode(!0);let a=e.cloneNode(!1);r.set(a,e),e instanceof HTMLInputElement?(a.value=e.value,a.setAttribute("value",e.value),e.checked!==void 0&&(a.checked=e.checked,e.checked&&a.setAttribute("checked",""))):e instanceof HTMLTextAreaElement?(a.value=e.value,a.textContent=e.value):e instanceof HTMLSelectElement&&(a.value=e.value,Array.from(a.options).forEach(i=>{i.value===e.value?i.setAttribute("selected",""):i.removeAttribute("selected")})),_(e,a,n,o,t);let s=document.createDocumentFragment();if(e.childNodes.forEach(i=>{let c=D(i,n,o,r,t);c&&s.appendChild(c)}),a.appendChild(s),e.shadowRoot){let i=Array.from(e.shadowRoot.children).filter(l=>l.tagName!=="STYLE").map(l=>D(l,n,o,r)).filter(Boolean),c=document.createDocumentFragment();i.forEach(l=>c.appendChild(l)),a.appendChild(c)}return a}async function G(e,n,o,r=32,t="#000"){n=n.replace(/^['"]+|['"]+$/g,"");let a=window.devicePixelRatio||1,i=document.createElement("canvas").getContext("2d");i.font=o?`${o} ${r}px "${n}"`:`${r}px "${n}"`;let c=i.measureText(e),l=c.actualBoundingBoxAscent||r*.8,d=c.actualBoundingBoxDescent||r*.2,h=l+d,f=c.width,p=document.createElement("canvas");p.width=Math.ceil(f*a),p.height=Math.ceil(h*a);let m=p.getContext("2d");return m.scale(a,a),m.font=i.font,m.textAlign="left",m.textBaseline="alphabetic",m.fillStyle=t,m.fillText(e,0,l),p.toDataURL()}async function W({ignoreIconFonts:e=!0,preCached:n=!1}={}){let o=Array.from(document.querySelectorAll('link[rel="stylesheet"]')).filter(t=>t.href),r="";for(let t of o)try{let s=await(await fetch(t.href)).text();if(e&&(P(t.href)||P(s)))continue;let i=/url\(([^)]+)\)/g,c=await Promise.all(Array.from(s.matchAll(i)).map(async d=>{let h=$(d[0]);if(!h)return null;let f=h;f.startsWith("http")||(f=new URL(f,t.href).href);try{f=encodeURI(f)}catch{console.warn("[snapdom] Failed to encode font URL:",f)}if(e&&P(f))return null;if(x.has(f))return{original:d[0],inlined:`url(${x.get(f)})`};try{let m=await(await fetch(f)).blob(),g=await new Promise(y=>{let C=new FileReader;C.onload=()=>y(C.result),C.readAsDataURL(m)});return x.set(f,g),{original:d[0],inlined:`url(${g})`}}catch{return console.warn("[snapdom] Failed to fetch font:",f),null}})),l=s;for(let d of c)d&&(l=l.replace(d.original,d.inlined));r+=l+`
|
|
3
|
+
`}catch{console.warn("[snapdom] Failed to fetch CSS:",t.href)}for(let t of document.styleSheets)try{if(!t.href||o.every(a=>a.href!==t.href)){for(let a of t.cssRules)if(a.type===CSSRule.FONT_FACE_RULE){let s=a.style.getPropertyValue("src");if(!s)continue;let i=/url\(([^)]+)\)/g,c=s,l=Array.from(s.matchAll(i));for(let d of l){let h=d[1].trim().replace(/^["']|["']$/g,"");if(!h)continue;let f=h;if(!f.startsWith("http")&&!f.startsWith("data:")&&(f=new URL(f,t.href||location.href).href),!(e&&P(f))){if(x.has(f)){c=c.replace(d[0],`url(${x.get(f)})`);continue}try{let m=await(await fetch(f)).blob(),g=await new Promise(y=>{let C=new FileReader;C.onload=()=>y(C.result),C.readAsDataURL(m)});x.set(f,g),c=c.replace(d[0],`url(${g})`)}catch{console.warn("[snapdom] Failed to fetch font URL:",f)}}}r+=`@font-face {
|
|
4
|
+
font-family: ${a.style.getPropertyValue("font-family")};
|
|
5
|
+
src: ${c};
|
|
6
|
+
font-style: ${a.style.getPropertyValue("font-style")||"normal"};
|
|
7
|
+
font-weight: ${a.style.getPropertyValue("font-weight")||"normal"};
|
|
8
|
+
}
|
|
9
|
+
`}}}catch(a){console.warn("[snapdom] Cannot access stylesheet",t.href,a)}for(let t of document.fonts)if(t.family&&t.status==="loaded"&&t._snapdomSrc){let a=t._snapdomSrc;if(!a.startsWith("data:"))if(x.has(t._snapdomSrc))a=x.get(t._snapdomSrc);else try{let i=await(await fetch(t._snapdomSrc)).blob();a=await new Promise(c=>{let l=new FileReader;l.onload=()=>c(l.result),l.readAsDataURL(i)}),x.set(t._snapdomSrc,a)}catch{console.warn("[snapdom] Failed to fetch dynamic font src:",t._snapdomSrc);continue}r+=`@font-face {
|
|
10
|
+
font-family: '${t.family}';
|
|
11
|
+
src: url(${a});
|
|
12
|
+
font-style: ${t.style||"normal"};
|
|
13
|
+
font-weight: ${t.weight||"normal"};
|
|
14
|
+
}
|
|
15
|
+
`}if(r&&n){let t=document.createElement("style");t.setAttribute("data-snapdom","embedFonts"),t.textContent=r,document.head.appendChild(t)}return r}async function V(e,n,o,r,t,a=!1){if(!(e instanceof Element)||!(n instanceof Element))return;for(let c of["::before","::after"])try{let l=U(e,c);if(!l)continue;let d=l.getPropertyValue("content"),h=l.getPropertyValue("background-image"),f=d&&d!=="none"&&d!=='""'&&d!=="''",p=h&&h.startsWith("url(");if(f||p){let m=l.getPropertyValue("font-family"),g=parseInt(l.getPropertyValue("font-size"))||32,y=parseInt(l.getPropertyValue("font-weight"))||!1,C=l.getPropertyValue("color")||"#000",w=document.createElement("span");w.dataset.snapdomPseudo=c;let A=O(l),F=M(A,"span",t);o.set(w,F);let j=m&&/font.*awesome|material|bootstrap|glyphicons|ionicons|remixicon|simple-line-icons|octicons|feather|typicons|weathericons/i.test(m),E=X(d);if(!a&&j&&E.length===1){let b=document.createElement("img");b.src=await G(E,m,y,g,C),b.style="display:block;width:100%;height:100%;object-fit:contain;",w.appendChild(b)}else if(E.startsWith("url(")){let b=$(E);if(b)try{let v=document.createElement("img"),ie=await S(encodeURI(b));v.src=ie,v.style="display:block;width:100%;height:100%;object-fit:contain;",w.appendChild(v)}catch(v){console.error(`[snapdom] Error in pseudo ${c} for`,e,v)}}else!j&&E&&E!=="none"&&(w.textContent=E);if(p&&h){let b=$(h);if(b)try{let v=await S(encodeURI(b));w.style.backgroundImage=`url(${v})`}catch(v){console.warn(`[snapdom] Failed to inline background-image for ${c}`,v)}}if(!(w.childNodes.length>0||w.textContent&&w.textContent.trim()!=="")&&!p)continue;c==="::before"?n.insertBefore(w,n.firstChild):n.appendChild(w)}}catch(l){console.warn(`[snapdom] Failed to capture ${c} for`,e,l)}let s=Array.from(e.children),i=Array.from(n.children).filter(c=>!c.dataset.snapdomPseudo);for(let c=0;c<Math.min(s.length,i.length);c++)await V(s[c],i[c],o,r,t,a)}async function Q(e,n=!1,o=!1){let r=new Map,t=new WeakMap,a=new Map,s;try{s=D(e,r,t,a,n)}catch(c){throw console.warn("deepClone failed:",c),c}try{await V(e,s,r,t,n,o)}catch(c){console.warn("inlinePseudoElements failed:",c)}let i="";if(n){let c=J(r);i=Array.from(c.entries()).map(([l,d])=>`.${d}{${l}}`).join("");for(let[l,d]of r.entries()){if(l.tagName==="STYLE")continue;let h=c.get(d);h&&l.classList.add(h);let f=l.style?.backgroundImage;l.removeAttribute("style"),f&&f!=="none"&&(l.style.backgroundImage=f)}}else for(let[c,l]of r.entries())c.tagName!=="STYLE"&&c.setAttribute("style",l.replace(/;/g,"; "));for(let[c,l]of a.entries()){let d=l.scrollLeft,h=l.scrollTop;if((d||h)&&c instanceof HTMLElement){c.style.overflow="hidden",c.style.scrollbarWidth="none",c.style.msOverflowStyle="none";let p=document.createElement("div");for(p.style.transform=`translate(${-d}px, ${-h}px)`,p.style.willChange="transform",p.style.display="inline-block",p.style.width="100%";c.firstChild;)p.appendChild(c.firstChild);c.appendChild(p)}}return{clone:s,classCSS:i,styleCache:t}}async function Z(e,n={}){let o=Array.from(e.querySelectorAll("img")),r=async t=>{let a=t.src;try{let s=n.crossOrigin?n.crossOrigin(a):"anonymous",i=await S(a,3e3,s);t.src=i,t.width||(t.width=t.naturalWidth||100),t.height||(t.height=t.naturalHeight||100)}catch{let s=document.createElement("div");s.style=`width: ${t.width||100}px; height: ${t.height||100}px; background: #ccc; display: inline-block; text-align: center; line-height: ${t.height||100}px; color: #666; font-size: 12px;`,s.innerText="img",t.replaceWith(s)}};for(let t=0;t<o.length;t+=4){let a=o.slice(t,t+4).map(r);await Promise.allSettled(a)}}async function ee(e,n,o,r={}){let t=[[e,n]];for(;t.length;){let[a,s]=t.shift(),i=o.get(a)||U(a);o.has(a)||o.set(a,i);let l=i.getPropertyValue("background-image").split(",");for(let f=0;f<l.length;f++){let p=$(l[f]);if(p)try{let m=encodeURI(p),g;if(T.has(m))g=T.get(m);else{let y=r.crossOrigin?r.crossOrigin(m):"anonymous";g=await S(m,3e3,y),T.set(m,g)}l[f]=`url(${g})`}catch{l[f]="none"}}l.some(f=>f.includes("url("))&&(s.style.backgroundImage=l.join(","));let d=Array.from(a.children),h=Array.from(s.children);for(let f=0;f<Math.min(d.length,h.length);f++)t.push([d[f],h[f]])}}async function te(e,n={}){if(!e)throw new Error("Element cannot be null or undefined");let{compress:o=!0,embedFonts:r=!1,fast:t=!0,scale:a=1}=n,s,i,c,l="",d="",h,f;if({clone:s,classCSS:i,styleCache:c}=await Q(e,o,r),await new Promise(m=>{L(async()=>{await Z(s,n),m()},{fast:t})}),await new Promise(m=>{L(async()=>{await ee(e,s,c,n),m()},{fast:t})}),r&&await new Promise(m=>{L(async()=>{l=await W({ignoreIconFonts:!r}),m()},{fast:t})}),o){let m=Y(s).sort(),g=m.join(",");k.has(g)?d=k.get(g):await new Promise(y=>{L(()=>{d=K(m),k.set(g,d),y()},{fast:t})})}await new Promise(m=>{L(()=>{let g=e.getBoundingClientRect(),y=Math.ceil(g.width),C=Math.ceil(g.height);s.setAttribute("xmlns","http://www.w3.org/1999/xhtml"),s.style.transformOrigin="top left",a!==1&&B()&&(s.style.scale=`${a}`);let w="http://www.w3.org/2000/svg",A=document.createElementNS(w,"foreignObject");A.setAttribute("width","100%"),A.setAttribute("height","100%");let F=document.createElement("style");F.textContent=d+l+"svg{overflow:visible;}"+i,A.appendChild(F),A.appendChild(s);let E=new XMLSerializer().serializeToString(A);f=`<svg xmlns="${w}" width="${y}" height="${C}" viewBox="0 0 ${y} ${C}">`+E+"</svg>",h=`data:image/svg+xml;charset=utf-8,${encodeURIComponent(f)}`,m()},{fast:t})});let p=document.getElementById("snapdom-sandbox");return p&&p.style.position==="absolute"&&p.remove(),h}async function le(e,{dpr:n=1,scale:o=1}){let r=new Image;return r.src=e,await r.decode(),B?(r.width=r.width*o,r.height=r.height*o):(r.width=r.width/o,r.height=r.height/o),r}async function ne(e,{dpr:n=1,scale:o=1}={}){let r=new Image;r.src=e,await r.decode();let t=document.createElement("canvas"),a=r.width*o,s=r.height*o;t.width=Math.ceil(a*n),t.height=Math.ceil(s*n);let i=t.getContext("2d");return i.scale(n,n),i.drawImage(r,0,0,a,s),t.style.width=`${a}px`,t.style.height=`${s}px`,t}async function oe(e){let n=decodeURIComponent(e.split(",")[1]);return new Blob([n],{type:"image/svg+xml"})}async function ae(e,{dpr:n=1,scale:o=1},r){let t=await ne(e,{dpr:n,scale:o});if(!r)return t;let a=document.createElement("canvas");a.width=t.width,a.height=t.height;let s=a.getContext("2d");return s.fillStyle=r,s.fillRect(0,0,a.width,a.height),s.drawImage(t,0,0),a}async function q(e,{dpr:n=1,scale:o=1,backgroundColor:r="#fff",quality:t},a="png"){let s=await ae(e,{dpr:n,scale:o},r),i=new Image;return i.src=s.toDataURL(`image/${a}`,t),await i.decode(),i.style.width=`${s.width/n}px`,i.style.height=`${s.height/n}px`,i}async function fe(e,{dpr:n=1,scale:o=1,backgroundColor:r="#fff",format:t="png",filename:a="capture"}={}){if(t==="svg"){let f=await oe(e),p=URL.createObjectURL(f),m=document.createElement("a");m.href=p,m.download=`${a}.svg`,m.click(),URL.revokeObjectURL(p);return}let s=["jpg","jpeg","webp"].includes(t)?"#fff":void 0,c=await ae(e,{dpr:n,scale:o},r??s),l={jpg:"image/jpeg",jpeg:"image/jpeg",png:"image/png",webp:"image/webp"}[t]||"image/png",d=c.toDataURL(l),h=document.createElement("a");h.href=d,h.download=`${a}.${t}`,h.click()}async function u(e,n={}){if(n={scale:1,...n},!e)throw new Error("Element cannot be null or undefined");return await u.capture(e,n)}u.capture=async(e,n={})=>{let o=await te(e,n),r=window.devicePixelRatio||1,t=n.scale||1;return{url:o,options:n,toRaw:()=>o,toImg:()=>le(o,{dpr:r,scale:t}),toCanvas:()=>ne(o,{dpr:r,scale:t}),toBlob:()=>oe(o),toPng:a=>q(o,{dpr:r,scale:t,...a},"png"),toJpg:a=>q(o,{dpr:r,scale:t,...a},"jpeg"),toWebp:a=>q(o,{dpr:r,scale:t,...a},"webp"),download:({format:a="png",filename:s="capture",backgroundColor:i}={})=>fe(o,{dpr:r,scale:t,backgroundColor:i,format:a,filename:s})}};u.toRaw=async(e,n)=>(await u.capture(e,n)).toRaw();u.toImg=async(e,n)=>(await u.capture(e,n)).toImg();u.toCanvas=async(e,n)=>(await u.capture(e,n)).toCanvas();u.toBlob=async(e,n)=>(await u.capture(e,n)).toBlob();u.toPng=async(e,n)=>(await u.capture(e,n)).toPng(n);u.toJpg=async(e,n)=>(await u.capture(e,n)).toJpg(n);u.toWebp=async(e,n)=>(await u.capture(e,n)).toWebp(n);u.download=async(e,n={})=>{let{format:o="png",filename:r="capture",backgroundColor:t,...a}=n;return await(await u.capture(e,a)).download({format:o,filename:r,backgroundColor:t})};async function re(e=document,n={}){let{embedFonts:o=!0,reset:r=!1,crossOrigin:t}=n;if(r){R.clear(),T.clear(),x.clear(),k.clear(),N.clear();return}await document.fonts.ready,H();let a=[],s=[];e?.querySelectorAll&&(a=Array.from(e.querySelectorAll("img[src]")),s=Array.from(e.querySelectorAll("*")));let i=[];for(let c of a){let l=c.src;if(!R.has(l)){let d=t?t(l):"anonymous";i.push(S(l,3e3,d).then(h=>R.set(l,h)).catch(()=>{}))}}for(let c of s){let l=getComputedStyle(c).backgroundImage,d=$(l);if(d&&!T.has(d)){let h=t?t(d):"anonymous";i.push(S(d,3e3,h).then(f=>T.set(d,f)).catch(()=>{}))}}o&&await W({ignoreIconFonts:!o,preCached:!0}),await Promise.all(i)}window.snapdom=u;window.preCache=re;})();
|
package/dist/snapdom.min.mjs
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
-
var
|
|
2
|
-
`;return o}function J(e){let
|
|
3
|
-
`}catch{console.warn("
|
|
1
|
+
var R=new Map,T=new Map,C=new Map,I=new Map,k=new Map,N=new WeakMap;var ie=["div","span","p","a","img","ul","li","button","input","select","textarea","label","section","article","header","footer","nav","main","aside","h1","h2","h3","h4","h5","h6","svg","path","circle","rect","line","g","table","thead","tbody","tr","td","th"];function H(){for(let e of ie)z(e)}function z(e){if(I.has(e))return I.get(e);if(new Set(["script","style","meta","link","noscript","template"]).has(e)){let s={};return I.set(e,s),s}let o=document.getElementById("snapdom-sandbox");o||(o=document.createElement("div"),o.id="snapdom-sandbox",o.style.position="absolute",o.style.left="-9999px",o.style.top="-9999px",o.style.width="0",o.style.height="0",o.style.overflow="hidden",document.body.appendChild(o));let r=document.createElement(e);r.style.all="initial",o.appendChild(r);let t=getComputedStyle(r),a={};for(let s of t)a[s]=t.getPropertyValue(s);return o.removeChild(r),I.set(e,a),a}function M(e,n,o=!1){let r=[],t=z(n);for(let[a,s]of Object.entries(e))if(!o)s&&r.push(`${a}:${s}`);else{let i=t[a];s&&s!==i&&r.push(`${a}:${s}`)}return r.sort().join(";")}function Y(e){let n=new Set;return e.nodeType!==Node.ELEMENT_NODE&&e.nodeType!==Node.DOCUMENT_FRAGMENT_NODE?[]:(e.tagName&&n.add(e.tagName.toLowerCase()),typeof e.querySelectorAll=="function"&&e.querySelectorAll("*").forEach(o=>n.add(o.tagName.toLowerCase())),Array.from(n))}function K(e){let n=new Map;for(let r of e){let t=I.get(r);if(!t)continue;let a=Object.entries(t).map(([s,i])=>`${s}:${i};`).sort().join("");n.has(a)||n.set(a,[]),n.get(a).push(r)}let o="";for(let[r,t]of n.entries())o+=`${t.join(",")} { ${r} }
|
|
2
|
+
`;return o}function J(e){let n=new Set(e.values()),o=new Map,r=1;for(let t of n)o.set(t,`c${r++}`);return o}function L(e,{fast:n=!1}={}){if(n)return e();"requestIdleCallback"in window?requestIdleCallback(e,{timeout:50}):setTimeout(e,1)}function U(e,n=null){if(!(e instanceof Element))return window.getComputedStyle(e,n);let o=N.get(e);if(o||(o=new Map,N.set(e,o)),!o.has(n)){let r=window.getComputedStyle(e,n);o.set(n,r)}return o.get(n)}function X(e){let n=e.replace(/^['"]|['"]$/g,"");if(n.startsWith("\\"))try{return String.fromCharCode(parseInt(n.replace("\\",""),16))}catch{return n}return n}function $(e){let n=e.indexOf("url(");if(n===-1)return null;let o=e.slice(n+4).trim();return o.endsWith(")")&&(o=o.slice(0,-1).trim()),(o.startsWith('"')&&o.endsWith('"')||o.startsWith("'")&&o.endsWith("'"))&&(o=o.slice(1,-1)),o}function P(e){return[/font\s*awesome/i,/material\s*icons/i,/ionicons/i,/glyphicons/i,/feather/i,/bootstrap\s*icons/i,/remix\s*icons/i,/heroicons/i].some(o=>o.test(e))}function S(e,n=3e3,o="anonymous"){return R.has(e)?Promise.resolve(R.get(e)):new Promise((r,t)=>{let a=setTimeout(()=>{t(new Error("Image load timed out"))},n),s=new Image;s.crossOrigin=o,s.onload=async()=>{clearTimeout(a);try{await s.decode();let i=document.createElement("canvas");i.width=s.width,i.height=s.height,i.getContext("2d").drawImage(s,0,0,i.width,i.height);try{let l=i.toDataURL("image/png");R.set(e,l),r(l)}catch{t(new Error("CORS restrictions prevented image capture"))}}catch(i){t(i)}},s.onerror=i=>{clearTimeout(a),t(new Error("Failed to load image: "+(i.message||"Unknown error")))},s.src=e})}function O(e){let n={};for(let o of e)n[o]=e.getPropertyValue(o);return n}function B(){return/^((?!chrome|android).)*safari/i.test(navigator.userAgent)}function _(e,n,o,r,t){if(e.tagName==="STYLE")return;r.has(e)||r.set(e,U(e));let a=r.get(e),s=O(a),i=e.tagName?.toLowerCase()||"div",c=M(s,i,t);o.set(n,c)}function D(e,n,o,r,t){if(e.nodeType===Node.ELEMENT_NODE&&e.getAttribute("data-capture")==="exclude"){let i=document.createElement("div"),c=e.getBoundingClientRect();return i.style.cssText=`display: inline-block; width: ${c.width}px; height: ${c.height}px; visibility: hidden;`,i}if(e.tagName==="IFRAME"){let i=document.createElement("div");return i.textContent="",i.style.cssText=`width: ${e.offsetWidth}px; height: ${e.offsetHeight}px; background: repeating-linear-gradient(45deg, #ddd, #ddd 5px, #f9f9f9 5px, #f9f9f9 10px);display: flex;align-items: center;justify-content: center;font-size: 12px;color: #555; border: 1px solid #aaa;`,i}if(e.nodeType===Node.ELEMENT_NODE&&e.getAttribute("data-capture")==="placeholder"){let i=e.cloneNode(!1);r.set(i,e),_(e,i,n,o,t);let c=document.createElement("div");return c.textContent=e.getAttribute("data-placeholder-text")||"",c.style.cssText="color: #666;font-size: 12px;text-align: center;line-height: 1.4;padding: 0.5em;box-sizing: border-box;",i.appendChild(c),i}if(e.tagName==="CANVAS"){let i=e.toDataURL(),c=document.createElement("img");return c.src=i,c.width=e.width,c.height=e.height,c.style.display="inline-block",c.style.width=`${e.width}px`,c.style.height=`${e.height}px`,c}if(e.nodeType===Node.TEXT_NODE){if(e.parentElement?.shadowRoot){let i=e.parentElement.tagName.toLowerCase();if(customElements.get(i))return null}return e.cloneNode(!0)}if(e.nodeType!==Node.ELEMENT_NODE)return e.cloneNode(!0);let a=e.cloneNode(!1);r.set(a,e),e instanceof HTMLInputElement?(a.value=e.value,a.setAttribute("value",e.value),e.checked!==void 0&&(a.checked=e.checked,e.checked&&a.setAttribute("checked",""))):e instanceof HTMLTextAreaElement?(a.value=e.value,a.textContent=e.value):e instanceof HTMLSelectElement&&(a.value=e.value,Array.from(a.options).forEach(i=>{i.value===e.value?i.setAttribute("selected",""):i.removeAttribute("selected")})),_(e,a,n,o,t);let s=document.createDocumentFragment();if(e.childNodes.forEach(i=>{let c=D(i,n,o,r,t);c&&s.appendChild(c)}),a.appendChild(s),e.shadowRoot){let i=Array.from(e.shadowRoot.children).filter(l=>l.tagName!=="STYLE").map(l=>D(l,n,o,r)).filter(Boolean),c=document.createDocumentFragment();i.forEach(l=>c.appendChild(l)),a.appendChild(c)}return a}async function G(e,n,o,r=32,t="#000"){n=n.replace(/^['"]+|['"]+$/g,"");let a=window.devicePixelRatio||1,i=document.createElement("canvas").getContext("2d");i.font=o?`${o} ${r}px "${n}"`:`${r}px "${n}"`;let c=i.measureText(e),l=c.actualBoundingBoxAscent||r*.8,d=c.actualBoundingBoxDescent||r*.2,h=l+d,f=c.width,p=document.createElement("canvas");p.width=Math.ceil(f*a),p.height=Math.ceil(h*a);let m=p.getContext("2d");return m.scale(a,a),m.font=i.font,m.textAlign="left",m.textBaseline="alphabetic",m.fillStyle=t,m.fillText(e,0,l),p.toDataURL()}async function W({ignoreIconFonts:e=!0,preCached:n=!1}={}){let o=Array.from(document.querySelectorAll('link[rel="stylesheet"]')).filter(t=>t.href),r="";for(let t of o)try{let s=await(await fetch(t.href)).text();if(e&&(P(t.href)||P(s)))continue;let i=/url\(([^)]+)\)/g,c=await Promise.all(Array.from(s.matchAll(i)).map(async d=>{let h=$(d[0]);if(!h)return null;let f=h;f.startsWith("http")||(f=new URL(f,t.href).href);try{f=encodeURI(f)}catch{console.warn("[snapdom] Failed to encode font URL:",f)}if(e&&P(f))return null;if(C.has(f))return{original:d[0],inlined:`url(${C.get(f)})`};try{let m=await(await fetch(f)).blob(),u=await new Promise(y=>{let x=new FileReader;x.onload=()=>y(x.result),x.readAsDataURL(m)});return C.set(f,u),{original:d[0],inlined:`url(${u})`}}catch{return console.warn("[snapdom] Failed to fetch font:",f),null}})),l=s;for(let d of c)d&&(l=l.replace(d.original,d.inlined));r+=l+`
|
|
3
|
+
`}catch{console.warn("[snapdom] Failed to fetch CSS:",t.href)}for(let t of document.styleSheets)try{if(!t.href||o.every(a=>a.href!==t.href)){for(let a of t.cssRules)if(a.type===CSSRule.FONT_FACE_RULE){let s=a.style.getPropertyValue("src");if(!s)continue;let i=/url\(([^)]+)\)/g,c=s,l=Array.from(s.matchAll(i));for(let d of l){let h=d[1].trim().replace(/^["']|["']$/g,"");if(!h)continue;let f=h;if(!f.startsWith("http")&&!f.startsWith("data:")&&(f=new URL(f,t.href||location.href).href),!(e&&P(f))){if(C.has(f)){c=c.replace(d[0],`url(${C.get(f)})`);continue}try{let m=await(await fetch(f)).blob(),u=await new Promise(y=>{let x=new FileReader;x.onload=()=>y(x.result),x.readAsDataURL(m)});C.set(f,u),c=c.replace(d[0],`url(${u})`)}catch{console.warn("[snapdom] Failed to fetch font URL:",f)}}}r+=`@font-face {
|
|
4
|
+
font-family: ${a.style.getPropertyValue("font-family")};
|
|
5
|
+
src: ${c};
|
|
6
|
+
font-style: ${a.style.getPropertyValue("font-style")||"normal"};
|
|
7
|
+
font-weight: ${a.style.getPropertyValue("font-weight")||"normal"};
|
|
8
|
+
}
|
|
9
|
+
`}}}catch(a){console.warn("[snapdom] Cannot access stylesheet",t.href,a)}for(let t of document.fonts)if(t.family&&t.status==="loaded"&&t._snapdomSrc){let a=t._snapdomSrc;if(!a.startsWith("data:"))if(C.has(t._snapdomSrc))a=C.get(t._snapdomSrc);else try{let i=await(await fetch(t._snapdomSrc)).blob();a=await new Promise(c=>{let l=new FileReader;l.onload=()=>c(l.result),l.readAsDataURL(i)}),C.set(t._snapdomSrc,a)}catch{console.warn("[snapdom] Failed to fetch dynamic font src:",t._snapdomSrc);continue}r+=`@font-face {
|
|
10
|
+
font-family: '${t.family}';
|
|
11
|
+
src: url(${a});
|
|
12
|
+
font-style: ${t.style||"normal"};
|
|
13
|
+
font-weight: ${t.weight||"normal"};
|
|
14
|
+
}
|
|
15
|
+
`}if(r&&n){let t=document.createElement("style");t.setAttribute("data-snapdom","embedFonts"),t.textContent=r,document.head.appendChild(t)}return r}async function V(e,n,o,r,t,a=!1){if(!(e instanceof Element)||!(n instanceof Element))return;for(let c of["::before","::after"])try{let l=U(e,c);if(!l)continue;let d=l.getPropertyValue("content"),h=l.getPropertyValue("background-image"),f=d&&d!=="none"&&d!=='""'&&d!=="''",p=h&&h.startsWith("url(");if(f||p){let m=l.getPropertyValue("font-family"),u=parseInt(l.getPropertyValue("font-size"))||32,y=parseInt(l.getPropertyValue("font-weight"))||!1,x=l.getPropertyValue("color")||"#000",w=document.createElement("span");w.dataset.snapdomPseudo=c;let A=O(l),F=M(A,"span",t);o.set(w,F);let j=m&&/font.*awesome|material|bootstrap|glyphicons|ionicons|remixicon|simple-line-icons|octicons|feather|typicons|weathericons/i.test(m),E=X(d);if(!a&&j&&E.length===1){let b=document.createElement("img");b.src=await G(E,m,y,u,x),b.style="display:block;width:100%;height:100%;object-fit:contain;",w.appendChild(b)}else if(E.startsWith("url(")){let b=$(E);if(b)try{let v=document.createElement("img"),se=await S(encodeURI(b));v.src=se,v.style="display:block;width:100%;height:100%;object-fit:contain;",w.appendChild(v)}catch(v){console.error(`[snapdom] Error in pseudo ${c} for`,e,v)}}else!j&&E&&E!=="none"&&(w.textContent=E);if(p&&h){let b=$(h);if(b)try{let v=await S(encodeURI(b));w.style.backgroundImage=`url(${v})`}catch(v){console.warn(`[snapdom] Failed to inline background-image for ${c}`,v)}}if(!(w.childNodes.length>0||w.textContent&&w.textContent.trim()!=="")&&!p)continue;c==="::before"?n.insertBefore(w,n.firstChild):n.appendChild(w)}}catch(l){console.warn(`[snapdom] Failed to capture ${c} for`,e,l)}let s=Array.from(e.children),i=Array.from(n.children).filter(c=>!c.dataset.snapdomPseudo);for(let c=0;c<Math.min(s.length,i.length);c++)await V(s[c],i[c],o,r,t,a)}async function Q(e,n=!1,o=!1){let r=new Map,t=new WeakMap,a=new Map,s;try{s=D(e,r,t,a,n)}catch(c){throw console.warn("deepClone failed:",c),c}try{await V(e,s,r,t,n,o)}catch(c){console.warn("inlinePseudoElements failed:",c)}let i="";if(n){let c=J(r);i=Array.from(c.entries()).map(([l,d])=>`.${d}{${l}}`).join("");for(let[l,d]of r.entries()){if(l.tagName==="STYLE")continue;let h=c.get(d);h&&l.classList.add(h);let f=l.style?.backgroundImage;l.removeAttribute("style"),f&&f!=="none"&&(l.style.backgroundImage=f)}}else for(let[c,l]of r.entries())c.tagName!=="STYLE"&&c.setAttribute("style",l.replace(/;/g,"; "));for(let[c,l]of a.entries()){let d=l.scrollLeft,h=l.scrollTop;if((d||h)&&c instanceof HTMLElement){c.style.overflow="hidden",c.style.scrollbarWidth="none",c.style.msOverflowStyle="none";let p=document.createElement("div");for(p.style.transform=`translate(${-d}px, ${-h}px)`,p.style.willChange="transform",p.style.display="inline-block",p.style.width="100%";c.firstChild;)p.appendChild(c.firstChild);c.appendChild(p)}}return{clone:s,classCSS:i,styleCache:t}}async function Z(e,n={}){let o=Array.from(e.querySelectorAll("img")),r=async t=>{let a=t.src;try{let s=n.crossOrigin?n.crossOrigin(a):"anonymous",i=await S(a,3e3,s);t.src=i,t.width||(t.width=t.naturalWidth||100),t.height||(t.height=t.naturalHeight||100)}catch{let s=document.createElement("div");s.style=`width: ${t.width||100}px; height: ${t.height||100}px; background: #ccc; display: inline-block; text-align: center; line-height: ${t.height||100}px; color: #666; font-size: 12px;`,s.innerText="img",t.replaceWith(s)}};for(let t=0;t<o.length;t+=4){let a=o.slice(t,t+4).map(r);await Promise.allSettled(a)}}async function ee(e,n,o,r={}){let t=[[e,n]];for(;t.length;){let[a,s]=t.shift(),i=o.get(a)||U(a);o.has(a)||o.set(a,i);let l=i.getPropertyValue("background-image").split(",");for(let f=0;f<l.length;f++){let p=$(l[f]);if(p)try{let m=encodeURI(p),u;if(T.has(m))u=T.get(m);else{let y=r.crossOrigin?r.crossOrigin(m):"anonymous";u=await S(m,3e3,y),T.set(m,u)}l[f]=`url(${u})`}catch{l[f]="none"}}l.some(f=>f.includes("url("))&&(s.style.backgroundImage=l.join(","));let d=Array.from(a.children),h=Array.from(s.children);for(let f=0;f<Math.min(d.length,h.length);f++)t.push([d[f],h[f]])}}async function te(e,n={}){if(!e)throw new Error("Element cannot be null or undefined");let{compress:o=!0,embedFonts:r=!1,fast:t=!0,scale:a=1}=n,s,i,c,l="",d="",h,f;if({clone:s,classCSS:i,styleCache:c}=await Q(e,o,r),await new Promise(m=>{L(async()=>{await Z(s,n),m()},{fast:t})}),await new Promise(m=>{L(async()=>{await ee(e,s,c,n),m()},{fast:t})}),r&&await new Promise(m=>{L(async()=>{l=await W({ignoreIconFonts:!r}),m()},{fast:t})}),o){let m=Y(s).sort(),u=m.join(",");k.has(u)?d=k.get(u):await new Promise(y=>{L(()=>{d=K(m),k.set(u,d),y()},{fast:t})})}await new Promise(m=>{L(()=>{let u=e.getBoundingClientRect(),y=Math.ceil(u.width),x=Math.ceil(u.height);s.setAttribute("xmlns","http://www.w3.org/1999/xhtml"),s.style.transformOrigin="top left",a!==1&&B()&&(s.style.scale=`${a}`);let w="http://www.w3.org/2000/svg",A=document.createElementNS(w,"foreignObject");A.setAttribute("width","100%"),A.setAttribute("height","100%");let F=document.createElement("style");F.textContent=d+l+"svg{overflow:visible;}"+i,A.appendChild(F),A.appendChild(s);let E=new XMLSerializer().serializeToString(A);f=`<svg xmlns="${w}" width="${y}" height="${x}" viewBox="0 0 ${y} ${x}">`+E+"</svg>",h=`data:image/svg+xml;charset=utf-8,${encodeURIComponent(f)}`,m()},{fast:t})});let p=document.getElementById("snapdom-sandbox");return p&&p.style.position==="absolute"&&p.remove(),h}async function ce(e,{dpr:n=1,scale:o=1}){let r=new Image;return r.src=e,await r.decode(),B?(r.width=r.width*o,r.height=r.height*o):(r.width=r.width/o,r.height=r.height/o),r}async function ne(e,{dpr:n=1,scale:o=1}={}){let r=new Image;r.src=e,await r.decode();let t=document.createElement("canvas"),a=r.width*o,s=r.height*o;t.width=Math.ceil(a*n),t.height=Math.ceil(s*n);let i=t.getContext("2d");return i.scale(n,n),i.drawImage(r,0,0,a,s),t.style.width=`${a}px`,t.style.height=`${s}px`,t}async function oe(e){let n=decodeURIComponent(e.split(",")[1]);return new Blob([n],{type:"image/svg+xml"})}async function ae(e,{dpr:n=1,scale:o=1},r){let t=await ne(e,{dpr:n,scale:o});if(!r)return t;let a=document.createElement("canvas");a.width=t.width,a.height=t.height;let s=a.getContext("2d");return s.fillStyle=r,s.fillRect(0,0,a.width,a.height),s.drawImage(t,0,0),a}async function q(e,{dpr:n=1,scale:o=1,backgroundColor:r="#fff",quality:t},a="png"){let s=await ae(e,{dpr:n,scale:o},r),i=new Image;return i.src=s.toDataURL(`image/${a}`,t),await i.decode(),i.style.width=`${s.width/n}px`,i.style.height=`${s.height/n}px`,i}async function le(e,{dpr:n=1,scale:o=1,backgroundColor:r="#fff",format:t="png",filename:a="capture"}={}){if(t==="svg"){let f=await oe(e),p=URL.createObjectURL(f),m=document.createElement("a");m.href=p,m.download=`${a}.svg`,m.click(),URL.revokeObjectURL(p);return}let s=["jpg","jpeg","webp"].includes(t)?"#fff":void 0,c=await ae(e,{dpr:n,scale:o},r??s),l={jpg:"image/jpeg",jpeg:"image/jpeg",png:"image/png",webp:"image/webp"}[t]||"image/png",d=c.toDataURL(l),h=document.createElement("a");h.href=d,h.download=`${a}.${t}`,h.click()}async function g(e,n={}){if(n={scale:1,...n},!e)throw new Error("Element cannot be null or undefined");return await g.capture(e,n)}g.capture=async(e,n={})=>{let o=await te(e,n),r=window.devicePixelRatio||1,t=n.scale||1;return{url:o,options:n,toRaw:()=>o,toImg:()=>ce(o,{dpr:r,scale:t}),toCanvas:()=>ne(o,{dpr:r,scale:t}),toBlob:()=>oe(o),toPng:a=>q(o,{dpr:r,scale:t,...a},"png"),toJpg:a=>q(o,{dpr:r,scale:t,...a},"jpeg"),toWebp:a=>q(o,{dpr:r,scale:t,...a},"webp"),download:({format:a="png",filename:s="capture",backgroundColor:i}={})=>le(o,{dpr:r,scale:t,backgroundColor:i,format:a,filename:s})}};g.toRaw=async(e,n)=>(await g.capture(e,n)).toRaw();g.toImg=async(e,n)=>(await g.capture(e,n)).toImg();g.toCanvas=async(e,n)=>(await g.capture(e,n)).toCanvas();g.toBlob=async(e,n)=>(await g.capture(e,n)).toBlob();g.toPng=async(e,n)=>(await g.capture(e,n)).toPng(n);g.toJpg=async(e,n)=>(await g.capture(e,n)).toJpg(n);g.toWebp=async(e,n)=>(await g.capture(e,n)).toWebp(n);g.download=async(e,n={})=>{let{format:o="png",filename:r="capture",backgroundColor:t,...a}=n;return await(await g.capture(e,a)).download({format:o,filename:r,backgroundColor:t})};async function fe(e=document,n={}){let{embedFonts:o=!0,reset:r=!1,crossOrigin:t}=n;if(r){R.clear(),T.clear(),C.clear(),k.clear(),N.clear();return}await document.fonts.ready,H();let a=[],s=[];e?.querySelectorAll&&(a=Array.from(e.querySelectorAll("img[src]")),s=Array.from(e.querySelectorAll("*")));let i=[];for(let c of a){let l=c.src;if(!R.has(l)){let d=t?t(l):"anonymous";i.push(S(l,3e3,d).then(h=>R.set(l,h)).catch(()=>{}))}}for(let c of s){let l=getComputedStyle(c).backgroundImage,d=$(l);if(d&&!T.has(d)){let h=t?t(d):"anonymous";i.push(S(d,3e3,h).then(f=>T.set(d,f)).catch(()=>{}))}}o&&await W({ignoreIconFonts:!o,preCached:!0}),await Promise.all(i)}export{fe as preCache,g as snapdom};
|
package/dist/snapdom.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
/*
|
|
3
3
|
* snapdom
|
|
4
|
-
* v.1.
|
|
4
|
+
* v.1.7.1
|
|
5
5
|
* Author Juan Martin Muda
|
|
6
6
|
* License MIT
|
|
7
7
|
**/
|
|
@@ -209,7 +209,7 @@ function isIconFont(familyOrUrl) {
|
|
|
209
209
|
];
|
|
210
210
|
return iconFontPatterns.some((rx) => rx.test(familyOrUrl));
|
|
211
211
|
}
|
|
212
|
-
function fetchImage(src, timeout = 3e3) {
|
|
212
|
+
function fetchImage(src, timeout = 3e3, crossOrigin = "anonymous") {
|
|
213
213
|
if (imageCache.has(src)) {
|
|
214
214
|
return Promise.resolve(imageCache.get(src));
|
|
215
215
|
}
|
|
@@ -218,7 +218,7 @@ function fetchImage(src, timeout = 3e3) {
|
|
|
218
218
|
reject(new Error("Image load timed out"));
|
|
219
219
|
}, timeout);
|
|
220
220
|
const image = new Image();
|
|
221
|
-
image.crossOrigin =
|
|
221
|
+
image.crossOrigin = crossOrigin;
|
|
222
222
|
image.onload = async () => {
|
|
223
223
|
clearTimeout(timeoutId);
|
|
224
224
|
try {
|
|
@@ -375,7 +375,7 @@ async function iconToImage(unicodeChar, fontFamily, fontWeight, fontSize = 32, c
|
|
|
375
375
|
ctx.fillText(unicodeChar, 0, ascent);
|
|
376
376
|
return canvas.toDataURL();
|
|
377
377
|
}
|
|
378
|
-
async function embedCustomFonts({ ignoreIconFonts = true, preCached = false }) {
|
|
378
|
+
async function embedCustomFonts({ ignoreIconFonts = true, preCached = false } = {}) {
|
|
379
379
|
const links = Array.from(document.querySelectorAll('link[rel="stylesheet"]')).filter((link) => link.href);
|
|
380
380
|
let finalCSS = "";
|
|
381
381
|
for (const link of links) {
|
|
@@ -416,7 +416,7 @@ async function embedCustomFonts({ ignoreIconFonts = true, preCached = false }) {
|
|
|
416
416
|
resourceCache.set(url, b64);
|
|
417
417
|
return { original: match[0], inlined: `url(${b64})` };
|
|
418
418
|
} catch (err) {
|
|
419
|
-
console.warn("
|
|
419
|
+
console.warn("[snapdom] Failed to fetch font:", url);
|
|
420
420
|
return null;
|
|
421
421
|
}
|
|
422
422
|
})
|
|
@@ -429,7 +429,90 @@ async function embedCustomFonts({ ignoreIconFonts = true, preCached = false }) {
|
|
|
429
429
|
}
|
|
430
430
|
finalCSS += cssFinal + "\n";
|
|
431
431
|
} catch (e) {
|
|
432
|
-
console.warn("
|
|
432
|
+
console.warn("[snapdom] Failed to fetch CSS:", link.href);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
for (const sheet of document.styleSheets) {
|
|
436
|
+
try {
|
|
437
|
+
if (!sheet.href || links.every((link) => link.href !== sheet.href)) {
|
|
438
|
+
for (const rule of sheet.cssRules) {
|
|
439
|
+
if (rule.type === CSSRule.FONT_FACE_RULE) {
|
|
440
|
+
const src = rule.style.getPropertyValue("src");
|
|
441
|
+
if (!src) continue;
|
|
442
|
+
const urlRegex = /url\(([^)]+)\)/g;
|
|
443
|
+
let inlinedSrc = src;
|
|
444
|
+
const matches = Array.from(src.matchAll(urlRegex));
|
|
445
|
+
for (const match of matches) {
|
|
446
|
+
let rawUrl = match[1].trim().replace(/^["']|["']$/g, "");
|
|
447
|
+
if (!rawUrl) continue;
|
|
448
|
+
let url = rawUrl;
|
|
449
|
+
if (!url.startsWith("http") && !url.startsWith("data:")) {
|
|
450
|
+
url = new URL(url, sheet.href || location.href).href;
|
|
451
|
+
}
|
|
452
|
+
if (ignoreIconFonts && isIconFont(url)) {
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
if (resourceCache.has(url)) {
|
|
456
|
+
inlinedSrc = inlinedSrc.replace(match[0], `url(${resourceCache.get(url)})`);
|
|
457
|
+
continue;
|
|
458
|
+
}
|
|
459
|
+
try {
|
|
460
|
+
const res = await fetch(url);
|
|
461
|
+
const blob = await res.blob();
|
|
462
|
+
const b64 = await new Promise((resolve) => {
|
|
463
|
+
const reader = new FileReader();
|
|
464
|
+
reader.onload = () => resolve(reader.result);
|
|
465
|
+
reader.readAsDataURL(blob);
|
|
466
|
+
});
|
|
467
|
+
resourceCache.set(url, b64);
|
|
468
|
+
inlinedSrc = inlinedSrc.replace(match[0], `url(${b64})`);
|
|
469
|
+
} catch (e) {
|
|
470
|
+
console.warn("[snapdom] Failed to fetch font URL:", url);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
finalCSS += `@font-face {
|
|
474
|
+
font-family: ${rule.style.getPropertyValue("font-family")};
|
|
475
|
+
src: ${inlinedSrc};
|
|
476
|
+
font-style: ${rule.style.getPropertyValue("font-style") || "normal"};
|
|
477
|
+
font-weight: ${rule.style.getPropertyValue("font-weight") || "normal"};
|
|
478
|
+
}
|
|
479
|
+
`;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
} catch (e) {
|
|
484
|
+
console.warn("[snapdom] Cannot access stylesheet", sheet.href, e);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
for (const font of document.fonts) {
|
|
488
|
+
if (font.family && font.status === "loaded" && font._snapdomSrc) {
|
|
489
|
+
let b64 = font._snapdomSrc;
|
|
490
|
+
if (!b64.startsWith("data:")) {
|
|
491
|
+
if (resourceCache.has(font._snapdomSrc)) {
|
|
492
|
+
b64 = resourceCache.get(font._snapdomSrc);
|
|
493
|
+
} else {
|
|
494
|
+
try {
|
|
495
|
+
const res = await fetch(font._snapdomSrc);
|
|
496
|
+
const blob = await res.blob();
|
|
497
|
+
b64 = await new Promise((resolve) => {
|
|
498
|
+
const reader = new FileReader();
|
|
499
|
+
reader.onload = () => resolve(reader.result);
|
|
500
|
+
reader.readAsDataURL(blob);
|
|
501
|
+
});
|
|
502
|
+
resourceCache.set(font._snapdomSrc, b64);
|
|
503
|
+
} catch (e) {
|
|
504
|
+
console.warn("[snapdom] Failed to fetch dynamic font src:", font._snapdomSrc);
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
finalCSS += `@font-face {
|
|
510
|
+
font-family: '${font.family}';
|
|
511
|
+
src: url(${b64});
|
|
512
|
+
font-style: ${font.style || "normal"};
|
|
513
|
+
font-weight: ${font.weight || "normal"};
|
|
514
|
+
}
|
|
515
|
+
`;
|
|
433
516
|
}
|
|
434
517
|
}
|
|
435
518
|
if (finalCSS && preCached) {
|
|
@@ -588,12 +671,13 @@ async function prepareClone(element, compress = false, embedFonts = false) {
|
|
|
588
671
|
}
|
|
589
672
|
|
|
590
673
|
// src/modules/images.js
|
|
591
|
-
async function inlineImages(clone) {
|
|
674
|
+
async function inlineImages(clone, options = {}) {
|
|
592
675
|
const imgs = Array.from(clone.querySelectorAll("img"));
|
|
593
676
|
const processImg = async (img) => {
|
|
594
677
|
const src = img.src;
|
|
595
678
|
try {
|
|
596
|
-
const
|
|
679
|
+
const crossOrigin = options.crossOrigin ? options.crossOrigin(src) : "anonymous";
|
|
680
|
+
const dataUrl = await fetchImage(src, 3e3, crossOrigin);
|
|
597
681
|
img.src = dataUrl;
|
|
598
682
|
if (!img.width) img.width = img.naturalWidth || 100;
|
|
599
683
|
if (!img.height) img.height = img.naturalHeight || 100;
|
|
@@ -611,29 +695,36 @@ async function inlineImages(clone) {
|
|
|
611
695
|
}
|
|
612
696
|
|
|
613
697
|
// src/modules/background.js
|
|
614
|
-
async function inlineBackgroundImages(source, clone, styleCache) {
|
|
698
|
+
async function inlineBackgroundImages(source, clone, styleCache, options = {}) {
|
|
615
699
|
const queue = [[source, clone]];
|
|
616
700
|
while (queue.length) {
|
|
617
701
|
const [srcNode, cloneNode] = queue.shift();
|
|
618
702
|
const style = styleCache.get(srcNode) || getStyle(srcNode);
|
|
619
703
|
if (!styleCache.has(srcNode)) styleCache.set(srcNode, style);
|
|
620
704
|
const bg = style.getPropertyValue("background-image");
|
|
621
|
-
const
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
dataUrl
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
705
|
+
const bgSplits = bg.split(",");
|
|
706
|
+
for (let i = 0; i < bgSplits.length; i++) {
|
|
707
|
+
const rawUrl = extractURL(bgSplits[i]);
|
|
708
|
+
if (rawUrl) {
|
|
709
|
+
try {
|
|
710
|
+
let bgUrl = encodeURI(rawUrl);
|
|
711
|
+
let dataUrl;
|
|
712
|
+
if (bgCache.has(bgUrl)) {
|
|
713
|
+
dataUrl = bgCache.get(bgUrl);
|
|
714
|
+
} else {
|
|
715
|
+
const crossOrigin = options.crossOrigin ? options.crossOrigin(bgUrl) : "anonymous";
|
|
716
|
+
dataUrl = await fetchImage(bgUrl, 3e3, crossOrigin);
|
|
717
|
+
bgCache.set(bgUrl, dataUrl);
|
|
718
|
+
}
|
|
719
|
+
bgSplits[i] = `url(${dataUrl})`;
|
|
720
|
+
} catch {
|
|
721
|
+
bgSplits[i] = "none";
|
|
631
722
|
}
|
|
632
|
-
cloneNode.style.backgroundImage = `url(${dataUrl})`;
|
|
633
|
-
} catch {
|
|
634
|
-
cloneNode.style.backgroundImage = "none";
|
|
635
723
|
}
|
|
636
724
|
}
|
|
725
|
+
if (bgSplits.some((s) => s.includes("url("))) {
|
|
726
|
+
cloneNode.style.backgroundImage = bgSplits.join(",");
|
|
727
|
+
}
|
|
637
728
|
const sChildren = Array.from(srcNode.children);
|
|
638
729
|
const cChildren = Array.from(cloneNode.children);
|
|
639
730
|
for (let i = 0; i < Math.min(sChildren.length, cChildren.length); i++) {
|
|
@@ -654,13 +745,13 @@ async function captureDOM(element, options = {}) {
|
|
|
654
745
|
({ clone, classCSS, styleCache } = await prepareClone(element, compress, embedFonts));
|
|
655
746
|
await new Promise((resolve) => {
|
|
656
747
|
idle(async () => {
|
|
657
|
-
await inlineImages(clone);
|
|
748
|
+
await inlineImages(clone, options);
|
|
658
749
|
resolve();
|
|
659
750
|
}, { fast });
|
|
660
751
|
});
|
|
661
752
|
await new Promise((resolve) => {
|
|
662
753
|
idle(async () => {
|
|
663
|
-
await inlineBackgroundImages(element, clone, styleCache);
|
|
754
|
+
await inlineBackgroundImages(element, clone, styleCache, options);
|
|
664
755
|
resolve();
|
|
665
756
|
}, { fast });
|
|
666
757
|
});
|
|
@@ -842,7 +933,7 @@ snapdom.download = async (el, options = {}) => {
|
|
|
842
933
|
|
|
843
934
|
// src/api/preCache.js
|
|
844
935
|
async function preCache(root = document, options = {}) {
|
|
845
|
-
const { embedFonts = true, reset = false,
|
|
936
|
+
const { embedFonts = true, reset = false, crossOrigin: crossOriginFn } = options;
|
|
846
937
|
if (reset) {
|
|
847
938
|
imageCache.clear();
|
|
848
939
|
bgCache.clear();
|
|
@@ -862,8 +953,9 @@ async function preCache(root = document, options = {}) {
|
|
|
862
953
|
for (const img of imgEls) {
|
|
863
954
|
const src = img.src;
|
|
864
955
|
if (!imageCache.has(src)) {
|
|
956
|
+
const crossOrigin = crossOriginFn ? crossOriginFn(src) : "anonymous";
|
|
865
957
|
promises.push(
|
|
866
|
-
fetchImage(src).then((dataURL) => imageCache.set(src, dataURL)).catch(() => {
|
|
958
|
+
fetchImage(src, 3e3, crossOrigin).then((dataURL) => imageCache.set(src, dataURL)).catch(() => {
|
|
867
959
|
})
|
|
868
960
|
);
|
|
869
961
|
}
|
|
@@ -872,8 +964,9 @@ async function preCache(root = document, options = {}) {
|
|
|
872
964
|
const bg = getComputedStyle(el).backgroundImage;
|
|
873
965
|
const url = extractURL(bg);
|
|
874
966
|
if (url && !bgCache.has(url)) {
|
|
967
|
+
const crossOrigin = crossOriginFn ? crossOriginFn(url) : "anonymous";
|
|
875
968
|
promises.push(
|
|
876
|
-
fetchImage(url).then((dataURL) => bgCache.set(url, dataURL)).catch(() => {
|
|
969
|
+
fetchImage(url, 3e3, crossOrigin).then((dataURL) => bgCache.set(url, dataURL)).catch(() => {
|
|
877
970
|
})
|
|
878
971
|
);
|
|
879
972
|
}
|
package/package.json
CHANGED
package/types/snapdom.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ declare module "@zumer/snapdom" {
|
|
|
9
9
|
filename?: string;
|
|
10
10
|
dpr?: number;
|
|
11
11
|
quality?: number;
|
|
12
|
+
crossOrigin?: (url: string) => "anonymous" | "use-credentials";
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
export interface SnapResult {
|
|
@@ -51,6 +52,7 @@ declare module "@zumer/snapdom" {
|
|
|
51
52
|
root?: Document | HTMLElement,
|
|
52
53
|
options?: {
|
|
53
54
|
embedFonts?: boolean;
|
|
55
|
+
crossOrigin?: (url: string) => "anonymous" | "use-credentials";
|
|
54
56
|
}
|
|
55
57
|
): Promise<void>;
|
|
56
58
|
}
|