@zumer/snapdom 1.9.7 → 1.9.8
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 +52 -6
- package/dist/snapdom.js +185 -102
- package/dist/snapdom.min.js +3 -3
- package/dist/snapdom.min.mjs +3 -3
- package/dist/snapdom.mjs +175 -100
- package/package.json +1 -1
- package/types/snapdom.d.ts +1 -0
package/README.md
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
# snapDOM
|
|
2
|
-
|
|
3
1
|
<p align="center">
|
|
4
2
|
<a href="http://zumerlab.github.io/snapdom">
|
|
5
|
-
<img src="https://raw.githubusercontent.com/zumerlab/snapdom/main/docs/assets/
|
|
3
|
+
<img src="https://raw.githubusercontent.com/zumerlab/snapdom/main/docs/assets/newhero.png" width="70%">
|
|
6
4
|
</a>
|
|
7
5
|
</p>
|
|
8
6
|
|
|
@@ -28,13 +26,15 @@
|
|
|
28
26
|
</a>
|
|
29
27
|
</p>
|
|
30
28
|
|
|
29
|
+
# snapDOM
|
|
30
|
+
|
|
31
31
|
**snapDOM** is a fast and accurate DOM-to-image capture tool built for **Zumly**, a zoom-based view transition framework.
|
|
32
32
|
|
|
33
33
|
It captures any HTML element as a scalable SVG image, preserving styles, fonts, background images, pseudo-elements, and even shadow DOM. It also supports export to raster image formats and canvas.
|
|
34
34
|
|
|
35
35
|
* 📸 Full DOM capture
|
|
36
36
|
* 🎨 Embedded styles, pseudo-elements, and fonts
|
|
37
|
-
* 🖼️ Export to SVG, PNG, JPG, WebP, or
|
|
37
|
+
* 🖼️ Export to SVG, PNG, JPG, WebP, `canvas`, or Blob
|
|
38
38
|
* ⚡ Ultra fast, no dependencies
|
|
39
39
|
* 📦 100% based on standard Web APIs
|
|
40
40
|
|
|
@@ -54,6 +54,12 @@ npm i @zumer/snapdom
|
|
|
54
54
|
yarn add @zumer/snapdom
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
+
Then import it in your code:
|
|
58
|
+
|
|
59
|
+
```js
|
|
60
|
+
import { snapdom } from '@zumer/snapdom';
|
|
61
|
+
```
|
|
62
|
+
|
|
57
63
|
### CDN
|
|
58
64
|
|
|
59
65
|
```html
|
|
@@ -146,6 +152,7 @@ All capture methods accept an `options` object:
|
|
|
146
152
|
| `fast` | boolean | `true` | Skips idle delay for faster results |
|
|
147
153
|
| `embedFonts` | boolean | `false` | Inlines fonts (icon fonts always embedded) |
|
|
148
154
|
| `scale` | number | `1` | Output scale multiplier |
|
|
155
|
+
| `dpr` | number | `devicePixelRatio` | Device pixel ratio |
|
|
149
156
|
| `width` | number | - | Output specific width size |
|
|
150
157
|
| `height` | number | - | Output specific height size |
|
|
151
158
|
| `backgroundColor` | string | `"#fff"` | Fallback color for JPG/WebP |
|
|
@@ -217,7 +224,7 @@ const result = await snapdom(element, {
|
|
|
217
224
|
|
|
218
225
|
### `preCache()` – Optional helper
|
|
219
226
|
|
|
220
|
-
The `preCache()` function can be used to load external resources (like images and fonts) in advance. It is specially useful when the element to
|
|
227
|
+
The `preCache()` function can be used to load external resources (like images and fonts) in advance. It is specially useful when the element to capture is big and complex.
|
|
221
228
|
|
|
222
229
|
```js
|
|
223
230
|
import { preCache } from '@zumer/snapdom';
|
|
@@ -250,7 +257,7 @@ import { snapdom, preCache } from './snapdom.mjs';
|
|
|
250
257
|
|
|
251
258
|
## Limitations
|
|
252
259
|
|
|
253
|
-
* External images
|
|
260
|
+
* External images should be CORS-accessible (use `useProxy` option for handling CORS denied)
|
|
254
261
|
* Iframes are not supported
|
|
255
262
|
* When WebP format is used on Safari, it will fallback to PNG rendering.
|
|
256
263
|
* `@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)
|
|
@@ -311,6 +318,36 @@ npm install
|
|
|
311
318
|
npm run test:benchmark
|
|
312
319
|
```
|
|
313
320
|
|
|
321
|
+
|
|
322
|
+
## Roadmap
|
|
323
|
+
|
|
324
|
+
Planned improvements for future versions of SnapDOM:
|
|
325
|
+
|
|
326
|
+
* [ ] **Implement plugin system**
|
|
327
|
+
SnapDOM will support external plugins to extend or override internal behavior (e.g. custom node transformers, exporters, or filters).
|
|
328
|
+
|
|
329
|
+
* [ ] **Refactor to modular architecture**
|
|
330
|
+
Internal logic will be split into smaller, focused modules to improve maintainability and code reuse.
|
|
331
|
+
|
|
332
|
+
* [X] **Decouple internal logic from global options**
|
|
333
|
+
Functions will be redesigned to avoid relying directly on `options`. A centralized capture context will improve clarity, autonomy, and testability. See [`next` branch](https://github.com/zumerlab/snapdom/tree/main)
|
|
334
|
+
|
|
335
|
+
* [ ] **Expose cache control**
|
|
336
|
+
Users will be able to manually clear image and font caches or configure their own caching strategies.
|
|
337
|
+
|
|
338
|
+
* [ ] **Auto font preloading**
|
|
339
|
+
Required fonts will be automatically detected and preloaded before capture, reducing the need for manual `preCache()` calls.
|
|
340
|
+
|
|
341
|
+
* [ ] **Document plugin development**
|
|
342
|
+
A full guide will be provided for creating and registering custom SnapDOM plugins.
|
|
343
|
+
|
|
344
|
+
* [ ] **Make export utilities tree-shakeable**
|
|
345
|
+
Export functions like `toPng`, `toJpg`, `toBlob`, etc. will be restructured into independent modules to support tree shaking and minimal builds.
|
|
346
|
+
|
|
347
|
+
Have ideas or feature requests?
|
|
348
|
+
Feel free to share suggestions or feedback in [GitHub Discussions](https://github.com/zumerlab/snapdom/discussions).
|
|
349
|
+
|
|
350
|
+
|
|
314
351
|
## Development
|
|
315
352
|
|
|
316
353
|
To contribute or build snapDOM locally:
|
|
@@ -351,16 +388,21 @@ For detailed contribution guidelines, please see [CONTRIBUTING](https://github.c
|
|
|
351
388
|
<a href="https://github.com/tinchox5" title="tinchox5"><img src="https://avatars.githubusercontent.com/u/11557901?v=4&s=100" style="border-radius:10px; width:60px; height:60px; object-fit:cover; margin:5px;" alt="tinchox5"/></a>
|
|
352
389
|
<a href="https://github.com/tarwin" title="tarwin"><img src="https://avatars.githubusercontent.com/u/646149?v=4&s=100" style="border-radius:10px; width:60px; height:60px; object-fit:cover; margin:5px;" alt="tarwin"/></a>
|
|
353
390
|
<a href="https://github.com/17biubiu" title="17biubiu"><img src="https://avatars.githubusercontent.com/u/13295895?v=4&s=100" style="border-radius:10px; width:60px; height:60px; object-fit:cover; margin:5px;" alt="17biubiu"/></a>
|
|
391
|
+
<a href="https://github.com/av01d" title="av01d"><img src="https://avatars.githubusercontent.com/u/6247646?v=4&s=100" style="border-radius:10px; width:60px; height:60px; object-fit:cover; margin:5px;" alt="av01d"/></a>
|
|
392
|
+
<a href="https://github.com/CHOYSEN" title="CHOYSEN"><img src="https://avatars.githubusercontent.com/u/25995358?v=4&s=100" style="border-radius:10px; width:60px; height:60px; object-fit:cover; margin:5px;" alt="CHOYSEN"/></a>
|
|
354
393
|
<a href="https://github.com/pedrocateexte" title="pedrocateexte"><img src="https://avatars.githubusercontent.com/u/207524750?v=4&s=100" style="border-radius:10px; width:60px; height:60px; object-fit:cover; margin:5px;" alt="pedrocateexte"/></a>
|
|
355
394
|
<a href="https://github.com/domialex" title="domialex"><img src="https://avatars.githubusercontent.com/u/4694217?v=4&s=100" style="border-radius:10px; width:60px; height:60px; object-fit:cover; margin:5px;" alt="domialex"/></a>
|
|
356
395
|
<a href="https://github.com/elliots" title="elliots"><img src="https://avatars.githubusercontent.com/u/622455?v=4&s=100" style="border-radius:10px; width:60px; height:60px; object-fit:cover; margin:5px;" alt="elliots"/></a>
|
|
357
396
|
<a href="https://github.com/jswhisperer" title="jswhisperer"><img src="https://avatars.githubusercontent.com/u/1177690?v=4&s=100" style="border-radius:10px; width:60px; height:60px; object-fit:cover; margin:5px;" alt="jswhisperer"/></a>
|
|
397
|
+
<a href="https://github.com/sharuzzaman" title="sharuzzaman"><img src="https://avatars.githubusercontent.com/u/7421941?v=4&s=100" style="border-radius:10px; width:60px; height:60px; object-fit:cover; margin:5px;" alt="sharuzzaman"/></a>
|
|
358
398
|
<a href="https://github.com/simon1uo" title="simon1uo"><img src="https://avatars.githubusercontent.com/u/60037549?v=4&s=100" style="border-radius:10px; width:60px; height:60px; object-fit:cover; margin:5px;" alt="simon1uo"/></a>
|
|
359
399
|
<a href="https://github.com/titoBouzout" title="titoBouzout"><img src="https://avatars.githubusercontent.com/u/64156?v=4&s=100" style="border-radius:10px; width:60px; height:60px; object-fit:cover; margin:5px;" alt="titoBouzout"/></a>
|
|
360
400
|
<a href="https://github.com/jhbae200" title="jhbae200"><img src="https://avatars.githubusercontent.com/u/20170610?v=4&s=100" style="border-radius:10px; width:60px; height:60px; object-fit:cover; margin:5px;" alt="jhbae200"/></a>
|
|
361
401
|
<a href="https://github.com/miusuncle" title="miusuncle"><img src="https://avatars.githubusercontent.com/u/7549857?v=4&s=100" style="border-radius:10px; width:60px; height:60px; object-fit:cover; margin:5px;" alt="miusuncle"/></a>
|
|
362
402
|
<a href="https://github.com/rbbydotdev" title="rbbydotdev"><img src="https://avatars.githubusercontent.com/u/101137670?v=4&s=100" style="border-radius:10px; width:60px; height:60px; object-fit:cover; margin:5px;" alt="rbbydotdev"/></a>
|
|
403
|
+
<a href="https://github.com/zhanghaotian2018" title="zhanghaotian2018"><img src="https://avatars.githubusercontent.com/u/169218899?v=4&s=100" style="border-radius:10px; width:60px; height:60px; object-fit:cover; margin:5px;" alt="zhanghaotian2018"/></a>
|
|
363
404
|
<a href="https://github.com/kohaiy" title="kohaiy"><img src="https://avatars.githubusercontent.com/u/15622127?v=4&s=100" style="border-radius:10px; width:60px; height:60px; object-fit:cover; margin:5px;" alt="kohaiy"/></a>
|
|
405
|
+
<a href="https://github.com/fu050409" title="fu050409"><img src="https://avatars.githubusercontent.com/u/46275354?v=4&s=100" style="border-radius:10px; width:60px; height:60px; object-fit:cover; margin:5px;" alt="fu050409"/></a>
|
|
364
406
|
</p>
|
|
365
407
|
<!-- CONTRIBUTORS:END -->
|
|
366
408
|
|
|
@@ -370,6 +412,10 @@ Special thanks to [@megaphonecolin](https://github.com/megaphonecolin) for suppo
|
|
|
370
412
|
|
|
371
413
|
If you'd like to support this project too, you can [become a sponsor](https://github.com/sponsors/tinchox5).
|
|
372
414
|
|
|
415
|
+
## Star History
|
|
416
|
+
|
|
417
|
+
[](https://www.star-history.com/#zumerlab/snapdom&Date)
|
|
418
|
+
|
|
373
419
|
## License
|
|
374
420
|
|
|
375
421
|
MIT © Zumerlab
|
package/dist/snapdom.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
/*
|
|
3
3
|
* snapdom
|
|
4
|
-
* v.1.9.
|
|
4
|
+
* v.1.9.8
|
|
5
5
|
* Author Juan Martin Muda
|
|
6
6
|
* License MIT
|
|
7
7
|
**/
|
|
@@ -130,7 +130,7 @@
|
|
|
130
130
|
if (cache.defaultStyle.has(tagName)) {
|
|
131
131
|
return cache.defaultStyle.get(tagName);
|
|
132
132
|
}
|
|
133
|
-
const skipTags = /* @__PURE__ */ new Set(["script", "style", "meta", "link", "noscript", "template"]);
|
|
133
|
+
const skipTags = /* @__PURE__ */ new Set(["script", "style", "meta", "link", "noscript", "template", "defs", "symbol", "title", "metadata", "desc"]);
|
|
134
134
|
if (skipTags.has(tagName)) {
|
|
135
135
|
const empty = {};
|
|
136
136
|
cache.defaultStyle.set(tagName, empty);
|
|
@@ -160,10 +160,14 @@
|
|
|
160
160
|
cache.defaultStyle.set(tagName, defaults);
|
|
161
161
|
return defaults;
|
|
162
162
|
}
|
|
163
|
+
var IGNORED_PROPS = /* @__PURE__ */ new Set([
|
|
164
|
+
"-webkit-locale"
|
|
165
|
+
]);
|
|
163
166
|
function getStyleKey(snapshot, tagName, compress = false) {
|
|
164
167
|
const entries = [];
|
|
165
168
|
const defaultStyles = getDefaultStyleForTag(tagName);
|
|
166
169
|
for (let [prop, value] of Object.entries(snapshot)) {
|
|
170
|
+
if (IGNORED_PROPS.has(prop)) continue;
|
|
167
171
|
if (!compress) {
|
|
168
172
|
if (value) {
|
|
169
173
|
entries.push(`${prop}:${value}`);
|
|
@@ -213,6 +217,7 @@
|
|
|
213
217
|
const classMap = /* @__PURE__ */ new Map();
|
|
214
218
|
let counter = 1;
|
|
215
219
|
for (const key of keySet) {
|
|
220
|
+
if (!key.trim()) continue;
|
|
216
221
|
classMap.set(key, `c${counter++}`);
|
|
217
222
|
}
|
|
218
223
|
return classMap;
|
|
@@ -448,6 +453,7 @@
|
|
|
448
453
|
// src/modules/styles.js
|
|
449
454
|
function snapshotComputedStyleFull(style) {
|
|
450
455
|
const result = {};
|
|
456
|
+
const computedVisibility = style.getPropertyValue("visibility");
|
|
451
457
|
for (let i = 0; i < style.length; i++) {
|
|
452
458
|
const prop = style[i];
|
|
453
459
|
let val = style.getPropertyValue(prop);
|
|
@@ -456,6 +462,9 @@
|
|
|
456
462
|
}
|
|
457
463
|
result[prop] = val;
|
|
458
464
|
}
|
|
465
|
+
if (computedVisibility === "hidden") {
|
|
466
|
+
result.opacity = "0";
|
|
467
|
+
}
|
|
459
468
|
return result;
|
|
460
469
|
}
|
|
461
470
|
function inlineAllStyles(source, clone, compress) {
|
|
@@ -678,25 +687,38 @@
|
|
|
678
687
|
return __async(this, null, function* () {
|
|
679
688
|
fontFamily = fontFamily.replace(/^['"]+|['"]+$/g, "");
|
|
680
689
|
const dpr = window.devicePixelRatio || 1;
|
|
681
|
-
|
|
682
|
-
const
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
690
|
+
yield document.fonts.ready;
|
|
691
|
+
const span = document.createElement("span");
|
|
692
|
+
span.textContent = unicodeChar;
|
|
693
|
+
span.style.position = "absolute";
|
|
694
|
+
span.style.visibility = "hidden";
|
|
695
|
+
span.style.fontFamily = `"${fontFamily}"`;
|
|
696
|
+
span.style.fontWeight = fontWeight || "normal";
|
|
697
|
+
span.style.fontSize = `${fontSize}px`;
|
|
698
|
+
span.style.lineHeight = "1";
|
|
699
|
+
span.style.whiteSpace = "nowrap";
|
|
700
|
+
span.style.padding = "0";
|
|
701
|
+
span.style.margin = "0";
|
|
702
|
+
document.body.appendChild(span);
|
|
703
|
+
const rect = span.getBoundingClientRect();
|
|
704
|
+
const width = Math.ceil(rect.width);
|
|
705
|
+
const height = Math.ceil(rect.height);
|
|
706
|
+
document.body.removeChild(span);
|
|
689
707
|
const canvas = document.createElement("canvas");
|
|
690
|
-
canvas.width =
|
|
691
|
-
canvas.height =
|
|
708
|
+
canvas.width = width * dpr;
|
|
709
|
+
canvas.height = height * dpr;
|
|
692
710
|
const ctx = canvas.getContext("2d");
|
|
693
711
|
ctx.scale(dpr, dpr);
|
|
694
|
-
ctx.font =
|
|
712
|
+
ctx.font = fontWeight ? `${fontWeight} ${fontSize}px "${fontFamily}"` : `${fontSize}px "${fontFamily}"`;
|
|
695
713
|
ctx.textAlign = "left";
|
|
696
|
-
ctx.textBaseline = "
|
|
714
|
+
ctx.textBaseline = "top";
|
|
697
715
|
ctx.fillStyle = color;
|
|
698
|
-
ctx.fillText(unicodeChar, 0,
|
|
699
|
-
return
|
|
716
|
+
ctx.fillText(unicodeChar, 0, 0);
|
|
717
|
+
return {
|
|
718
|
+
dataUrl: canvas.toDataURL(),
|
|
719
|
+
width,
|
|
720
|
+
height
|
|
721
|
+
};
|
|
700
722
|
});
|
|
701
723
|
}
|
|
702
724
|
function isStylesheetLoaded(href) {
|
|
@@ -884,19 +906,25 @@
|
|
|
884
906
|
}
|
|
885
907
|
|
|
886
908
|
// src/modules/pseudo.js
|
|
887
|
-
function inlinePseudoElements(source, clone,
|
|
909
|
+
function inlinePseudoElements(source, clone, options) {
|
|
888
910
|
return __async(this, null, function* () {
|
|
911
|
+
var _a;
|
|
889
912
|
if (!(source instanceof Element) || !(clone instanceof Element)) return;
|
|
890
913
|
for (const pseudo of ["::before", "::after", "::first-letter"]) {
|
|
891
914
|
try {
|
|
892
915
|
const style = getStyle(source, pseudo);
|
|
893
916
|
if (!style || typeof style[Symbol.iterator] !== "function") continue;
|
|
917
|
+
const isEmptyPseudo = style.content === "none" && style.backgroundImage === "none" && style.backgroundColor === "transparent" && (style.borderStyle === "none" || parseFloat(style.borderWidth) === 0) && (!style.transform || style.transform === "none") && style.display === "inline";
|
|
918
|
+
if (isEmptyPseudo) continue;
|
|
894
919
|
if (pseudo === "::first-letter") {
|
|
895
920
|
const normal = getComputedStyle(source);
|
|
896
921
|
const isMeaningful = style.color !== normal.color || style.fontSize !== normal.fontSize || style.fontWeight !== normal.fontWeight;
|
|
897
922
|
if (!isMeaningful) continue;
|
|
898
923
|
const textNode = Array.from(clone.childNodes).find(
|
|
899
|
-
(n) =>
|
|
924
|
+
(n) => {
|
|
925
|
+
var _a2;
|
|
926
|
+
return n.nodeType === Node.TEXT_NODE && ((_a2 = n.textContent) == null ? void 0 : _a2.trim().length) > 0;
|
|
927
|
+
}
|
|
900
928
|
);
|
|
901
929
|
if (!textNode) continue;
|
|
902
930
|
const text = textNode.textContent;
|
|
@@ -908,57 +936,55 @@
|
|
|
908
936
|
span.textContent = first;
|
|
909
937
|
span.dataset.snapdomPseudo = "::first-letter";
|
|
910
938
|
const snapshot2 = snapshotComputedStyle(style);
|
|
911
|
-
const key2 = getStyleKey(snapshot2, "span",
|
|
939
|
+
const key2 = getStyleKey(snapshot2, "span", options);
|
|
912
940
|
cache.preStyleMap.set(span, key2);
|
|
913
941
|
const restNode = document.createTextNode(rest);
|
|
914
942
|
clone.replaceChild(restNode, textNode);
|
|
915
943
|
clone.insertBefore(span, restNode);
|
|
916
944
|
continue;
|
|
917
945
|
}
|
|
918
|
-
const content = style.
|
|
919
|
-
const
|
|
920
|
-
const
|
|
921
|
-
const
|
|
922
|
-
const
|
|
923
|
-
const
|
|
924
|
-
const
|
|
925
|
-
const
|
|
926
|
-
const
|
|
927
|
-
const
|
|
928
|
-
const
|
|
929
|
-
const
|
|
946
|
+
const content = style.content;
|
|
947
|
+
const cleanContent = /counter\s*\(|counters\s*\(/.test(content) ? "- " : parseContent(content);
|
|
948
|
+
const bg = style.backgroundImage;
|
|
949
|
+
const bgColor = style.backgroundColor;
|
|
950
|
+
const fontFamily = style.fontFamily;
|
|
951
|
+
const fontSize = parseInt(style.fontSize) || 32;
|
|
952
|
+
const fontWeight = parseInt(style.fontWeight) || false;
|
|
953
|
+
const color = style.color || "#000";
|
|
954
|
+
const display = style.display;
|
|
955
|
+
const width = parseFloat(style.width);
|
|
956
|
+
const height = parseFloat(style.height);
|
|
957
|
+
const borderStyle = style.borderStyle;
|
|
958
|
+
const borderWidth = parseFloat(style.borderWidth);
|
|
959
|
+
const transform = style.transform;
|
|
930
960
|
const isIconFont2 = isIconFont(fontFamily);
|
|
931
|
-
|
|
932
|
-
if (/counter\s*\(|counters\s*\(/.test(content)) {
|
|
933
|
-
cleanContent = "- ";
|
|
934
|
-
} else {
|
|
935
|
-
cleanContent = parseContent(content);
|
|
936
|
-
}
|
|
937
|
-
const hasContent = content !== "none";
|
|
938
|
-
const hasExplicitContent = hasContent && cleanContent !== "";
|
|
961
|
+
const hasExplicitContent = content !== "none" && cleanContent !== "";
|
|
939
962
|
const hasBg = bg && bg !== "none";
|
|
940
963
|
const hasBgColor = bgColor && bgColor !== "transparent" && bgColor !== "rgba(0, 0, 0, 0)";
|
|
941
964
|
const hasBox = display !== "inline" && (width > 0 || height > 0);
|
|
942
|
-
const hasBorder = borderStyle && borderStyle !== "none";
|
|
965
|
+
const hasBorder = borderStyle && borderStyle !== "none" && borderWidth > 0;
|
|
943
966
|
const hasTransform = transform && transform !== "none";
|
|
944
967
|
const shouldRender = hasExplicitContent || hasBg || hasBgColor || hasBox || hasBorder || hasTransform;
|
|
945
968
|
if (!shouldRender) continue;
|
|
946
969
|
const pseudoEl = document.createElement("span");
|
|
947
970
|
pseudoEl.dataset.snapdomPseudo = pseudo;
|
|
971
|
+
pseudoEl.style.verticalAlign = "middle";
|
|
948
972
|
const snapshot = snapshotComputedStyle(style);
|
|
949
|
-
const key = getStyleKey(snapshot, "span",
|
|
973
|
+
const key = getStyleKey(snapshot, "span", options);
|
|
950
974
|
cache.preStyleMap.set(pseudoEl, key);
|
|
951
975
|
if (isIconFont2 && cleanContent.length === 1) {
|
|
976
|
+
const { dataUrl, width: width2, height: height2 } = yield iconToImage(cleanContent, fontFamily, fontWeight, fontSize, color);
|
|
952
977
|
const imgEl = document.createElement("img");
|
|
953
|
-
imgEl.src =
|
|
954
|
-
imgEl.style = `width:${fontSize}px;
|
|
978
|
+
imgEl.src = dataUrl;
|
|
979
|
+
imgEl.style = `height:${fontSize}px;width:${width2 / height2 * fontSize}px;object-fit:contain;`;
|
|
955
980
|
pseudoEl.appendChild(imgEl);
|
|
981
|
+
clone.dataset.snapdomHasIcon = "true";
|
|
956
982
|
} else if (cleanContent.startsWith("url(")) {
|
|
957
983
|
const rawUrl = extractURL(cleanContent);
|
|
958
|
-
if (rawUrl
|
|
984
|
+
if (rawUrl == null ? void 0 : rawUrl.trim()) {
|
|
959
985
|
try {
|
|
960
986
|
const imgEl = document.createElement("img");
|
|
961
|
-
const dataUrl = yield fetchImage(safeEncodeURI(rawUrl,
|
|
987
|
+
const dataUrl = yield fetchImage(safeEncodeURI(rawUrl), options);
|
|
962
988
|
imgEl.src = dataUrl;
|
|
963
989
|
imgEl.style = `width:${fontSize}px;height:auto;object-fit:contain;`;
|
|
964
990
|
pseudoEl.appendChild(imgEl);
|
|
@@ -972,19 +998,21 @@
|
|
|
972
998
|
if (hasBg) {
|
|
973
999
|
try {
|
|
974
1000
|
const bgSplits = splitBackgroundImage(bg);
|
|
975
|
-
const newBgParts = yield Promise.all(
|
|
976
|
-
bgSplits.map((entry) => inlineSingleBackgroundEntry(entry))
|
|
977
|
-
);
|
|
1001
|
+
const newBgParts = yield Promise.all(bgSplits.map(inlineSingleBackgroundEntry));
|
|
978
1002
|
pseudoEl.style.backgroundImage = newBgParts.join(", ");
|
|
979
1003
|
} catch (e) {
|
|
980
1004
|
console.warn(`[snapdom] Failed to inline background-image for ${pseudo}`, e);
|
|
981
1005
|
}
|
|
982
1006
|
}
|
|
983
1007
|
if (hasBgColor) pseudoEl.style.backgroundColor = bgColor;
|
|
984
|
-
const hasContent2 = pseudoEl.childNodes.length > 0 || pseudoEl.textContent
|
|
1008
|
+
const hasContent2 = pseudoEl.childNodes.length > 0 || ((_a = pseudoEl.textContent) == null ? void 0 : _a.trim()) !== "";
|
|
985
1009
|
const hasVisibleBox = hasContent2 || hasBg || hasBgColor || hasBox || hasBorder || hasTransform;
|
|
986
1010
|
if (!hasVisibleBox) continue;
|
|
987
|
-
pseudo === "::before"
|
|
1011
|
+
if (pseudo === "::before") {
|
|
1012
|
+
clone.insertBefore(pseudoEl, clone.firstChild);
|
|
1013
|
+
} else {
|
|
1014
|
+
clone.appendChild(pseudoEl);
|
|
1015
|
+
}
|
|
988
1016
|
} catch (e) {
|
|
989
1017
|
console.warn(`[snapdom] Failed to capture ${pseudo} for`, source, e);
|
|
990
1018
|
}
|
|
@@ -992,53 +1020,73 @@
|
|
|
992
1020
|
const sChildren = Array.from(source.children);
|
|
993
1021
|
const cChildren = Array.from(clone.children).filter((child) => !child.dataset.snapdomPseudo);
|
|
994
1022
|
for (let i = 0; i < Math.min(sChildren.length, cChildren.length); i++) {
|
|
995
|
-
yield inlinePseudoElements(
|
|
996
|
-
sChildren[i],
|
|
997
|
-
cChildren[i],
|
|
998
|
-
compress,
|
|
999
|
-
embedFonts,
|
|
1000
|
-
useProxy
|
|
1001
|
-
);
|
|
1023
|
+
yield inlinePseudoElements(sChildren[i], cChildren[i], options);
|
|
1002
1024
|
}
|
|
1003
1025
|
});
|
|
1004
1026
|
}
|
|
1005
1027
|
|
|
1006
1028
|
// src/modules/svgDefs.js
|
|
1007
|
-
function
|
|
1008
|
-
if (!
|
|
1009
|
-
const
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1029
|
+
function inlineExternalDefsAndSymbols(rootElement) {
|
|
1030
|
+
if (!rootElement) return;
|
|
1031
|
+
const usedIds = /* @__PURE__ */ new Set();
|
|
1032
|
+
rootElement.querySelectorAll("use").forEach((use) => {
|
|
1033
|
+
const href = use.getAttribute("xlink:href") || use.getAttribute("href");
|
|
1034
|
+
if (href && href.startsWith("#")) {
|
|
1035
|
+
usedIds.add(href.slice(1));
|
|
1036
|
+
}
|
|
1037
|
+
});
|
|
1038
|
+
if (!usedIds.size) return;
|
|
1039
|
+
const allGlobal = Array.from(document.querySelectorAll("svg > symbol, svg > defs"));
|
|
1040
|
+
const globalSymbols = allGlobal.filter((el) => el.tagName.toLowerCase() === "symbol");
|
|
1041
|
+
const globalDefs = allGlobal.filter((el) => el.tagName.toLowerCase() === "defs");
|
|
1042
|
+
let container = rootElement.querySelector("svg.inline-defs-container");
|
|
1043
|
+
if (!container) {
|
|
1044
|
+
container = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
1045
|
+
container.setAttribute("aria-hidden", "true");
|
|
1046
|
+
container.setAttribute("style", "position: absolute; width: 0; height: 0; overflow: hidden;");
|
|
1047
|
+
container.classList.add("inline-defs-container");
|
|
1048
|
+
rootElement.insertBefore(container, rootElement.firstChild);
|
|
1049
|
+
}
|
|
1050
|
+
const existingIds = /* @__PURE__ */ new Set();
|
|
1051
|
+
rootElement.querySelectorAll("symbol[id], defs > *[id]").forEach((el) => {
|
|
1052
|
+
existingIds.add(el.id);
|
|
1053
|
+
});
|
|
1054
|
+
usedIds.forEach((id) => {
|
|
1055
|
+
if (existingIds.has(id)) return;
|
|
1056
|
+
const symbol = globalSymbols.find((sym) => sym.id === id);
|
|
1057
|
+
if (symbol) {
|
|
1058
|
+
container.appendChild(symbol.cloneNode(true));
|
|
1059
|
+
existingIds.add(id);
|
|
1060
|
+
return;
|
|
1061
|
+
}
|
|
1062
|
+
for (const defs of globalDefs) {
|
|
1063
|
+
const defEl = defs.querySelector(`#${CSS.escape(id)}`);
|
|
1064
|
+
if (defEl) {
|
|
1065
|
+
let defsContainer = container.querySelector("defs");
|
|
1066
|
+
if (!defsContainer) {
|
|
1067
|
+
defsContainer = document.createElementNS("http://www.w3.org/2000/svg", "defs");
|
|
1068
|
+
container.appendChild(defsContainer);
|
|
1027
1069
|
}
|
|
1070
|
+
defsContainer.appendChild(defEl.cloneNode(true));
|
|
1071
|
+
existingIds.add(id);
|
|
1072
|
+
break;
|
|
1028
1073
|
}
|
|
1029
1074
|
}
|
|
1030
|
-
if (defs.childNodes.length) {
|
|
1031
|
-
svg.insertBefore(defs, svg.firstChild);
|
|
1032
|
-
}
|
|
1033
1075
|
});
|
|
1034
1076
|
}
|
|
1035
1077
|
|
|
1036
1078
|
// src/core/prepare.js
|
|
1037
1079
|
function prepareClone(_0) {
|
|
1038
1080
|
return __async(this, arguments, function* (element, compress = false, embedFonts = false, options = {}) {
|
|
1039
|
-
var _a;
|
|
1081
|
+
var _a, _b;
|
|
1040
1082
|
let clone;
|
|
1041
1083
|
let classCSS = "";
|
|
1084
|
+
stabilizeLayout(element);
|
|
1085
|
+
try {
|
|
1086
|
+
inlineExternalDefsAndSymbols(element);
|
|
1087
|
+
} catch (e) {
|
|
1088
|
+
console.warn("inlineExternal defs or symbol failed:", e);
|
|
1089
|
+
}
|
|
1042
1090
|
try {
|
|
1043
1091
|
clone = deepClone(element, compress, options, element);
|
|
1044
1092
|
} catch (e) {
|
|
@@ -1050,21 +1098,24 @@
|
|
|
1050
1098
|
} catch (e) {
|
|
1051
1099
|
console.warn("inlinePseudoElements failed:", e);
|
|
1052
1100
|
}
|
|
1053
|
-
try {
|
|
1054
|
-
inlineExternalDef(clone);
|
|
1055
|
-
} catch (e) {
|
|
1056
|
-
console.warn("inlineExternalDef failed:", e);
|
|
1057
|
-
}
|
|
1058
1101
|
if (compress) {
|
|
1059
1102
|
const keyToClass = generateCSSClasses();
|
|
1060
1103
|
classCSS = Array.from(keyToClass.entries()).map(([key, className]) => `.${className}{${key}}`).join("");
|
|
1061
1104
|
for (const [node, key] of cache.preStyleMap.entries()) {
|
|
1062
1105
|
if (node.tagName === "STYLE") continue;
|
|
1106
|
+
if (node.getRootNode && node.getRootNode() instanceof ShadowRoot) {
|
|
1107
|
+
node.setAttribute("style", key.replace(/;/g, "; "));
|
|
1108
|
+
continue;
|
|
1109
|
+
}
|
|
1063
1110
|
const className = keyToClass.get(key);
|
|
1064
1111
|
if (className) node.classList.add(className);
|
|
1065
1112
|
const bgImage = (_a = node.style) == null ? void 0 : _a.backgroundImage;
|
|
1066
|
-
node.
|
|
1113
|
+
const hasIcon = (_b = node.dataset) == null ? void 0 : _b.snapdomHasIcon;
|
|
1067
1114
|
if (bgImage && bgImage !== "none") node.style.backgroundImage = bgImage;
|
|
1115
|
+
if (hasIcon) {
|
|
1116
|
+
node.style.verticalAlign = "middle";
|
|
1117
|
+
node.style.display = "inline";
|
|
1118
|
+
}
|
|
1068
1119
|
}
|
|
1069
1120
|
} else {
|
|
1070
1121
|
for (const [node, key] of cache.preStyleMap.entries()) {
|
|
@@ -1115,6 +1166,18 @@
|
|
|
1115
1166
|
return { clone, classCSS };
|
|
1116
1167
|
});
|
|
1117
1168
|
}
|
|
1169
|
+
function stabilizeLayout(element) {
|
|
1170
|
+
const style = getComputedStyle(element);
|
|
1171
|
+
const outlineStyle = style.outlineStyle;
|
|
1172
|
+
const outlineWidth = style.outlineWidth;
|
|
1173
|
+
const borderStyle = style.borderStyle;
|
|
1174
|
+
const borderWidth = style.borderWidth;
|
|
1175
|
+
const outlineVisible = outlineStyle !== "none" && parseFloat(outlineWidth) > 0;
|
|
1176
|
+
const borderAbsent = borderStyle === "none" || parseFloat(borderWidth) === 0;
|
|
1177
|
+
if (outlineVisible && borderAbsent) {
|
|
1178
|
+
element.style.border = `${outlineWidth} solid transparent`;
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1118
1181
|
|
|
1119
1182
|
// src/modules/images.js
|
|
1120
1183
|
function inlineImages(_0) {
|
|
@@ -1153,13 +1216,27 @@
|
|
|
1153
1216
|
"mask-source",
|
|
1154
1217
|
"mask-box-image-source",
|
|
1155
1218
|
"mask-border-source",
|
|
1156
|
-
"-webkit-mask-box-image-source"
|
|
1219
|
+
"-webkit-mask-box-image-source",
|
|
1220
|
+
"border-image",
|
|
1221
|
+
"border-image-source",
|
|
1222
|
+
"border-image-slice",
|
|
1223
|
+
"border-image-width",
|
|
1224
|
+
"border-image-outset",
|
|
1225
|
+
"border-image-repeat"
|
|
1157
1226
|
];
|
|
1158
1227
|
while (queue.length) {
|
|
1159
1228
|
const [srcNode, cloneNode] = queue.shift();
|
|
1160
1229
|
const style = cache.preStyle.get(srcNode) || getStyle(srcNode);
|
|
1161
1230
|
if (!cache.preStyle.has(srcNode)) cache.preStyle.set(srcNode, style);
|
|
1231
|
+
const hasBorderImage = (() => {
|
|
1232
|
+
const bi = style.getPropertyValue("border-image");
|
|
1233
|
+
const bis = style.getPropertyValue("border-image-source");
|
|
1234
|
+
return bi && bi !== "none" || bis && bis !== "none";
|
|
1235
|
+
})();
|
|
1162
1236
|
for (const prop of imageProps) {
|
|
1237
|
+
if (["border-image-slice", "border-image-width", "border-image-outset", "border-image-repeat"].includes(prop) && !hasBorderImage) {
|
|
1238
|
+
continue;
|
|
1239
|
+
}
|
|
1163
1240
|
const val = style.getPropertyValue(prop);
|
|
1164
1241
|
if (!val || val === "none") continue;
|
|
1165
1242
|
const splits = splitBackgroundImage(val);
|
|
@@ -1288,13 +1365,15 @@
|
|
|
1288
1365
|
}
|
|
1289
1366
|
|
|
1290
1367
|
// src/api/snapdom.js
|
|
1291
|
-
function toImg(_0
|
|
1292
|
-
return __async(this, arguments, function* (url, {
|
|
1368
|
+
function toImg(_0) {
|
|
1369
|
+
return __async(this, arguments, function* (url, { scale = 1 } = {}) {
|
|
1293
1370
|
const img = new Image();
|
|
1294
1371
|
img.src = url;
|
|
1295
1372
|
yield img.decode();
|
|
1296
|
-
|
|
1297
|
-
|
|
1373
|
+
if (scale !== 1) {
|
|
1374
|
+
img.style.width = `${img.naturalWidth * scale}px`;
|
|
1375
|
+
img.style.height = `${img.naturalHeight * scale}px`;
|
|
1376
|
+
}
|
|
1298
1377
|
return img;
|
|
1299
1378
|
});
|
|
1300
1379
|
}
|
|
@@ -1319,8 +1398,8 @@
|
|
|
1319
1398
|
if (appended) img.remove();
|
|
1320
1399
|
throw new Error("Image failed to load or has no dimensions");
|
|
1321
1400
|
}
|
|
1322
|
-
const width = img.
|
|
1323
|
-
const height = img.
|
|
1401
|
+
const width = img.naturalWidth * scale;
|
|
1402
|
+
const height = img.naturalHeight * scale;
|
|
1324
1403
|
const canvas = document.createElement("canvas");
|
|
1325
1404
|
canvas.width = Math.ceil(width * dpr);
|
|
1326
1405
|
canvas.height = Math.ceil(height * dpr);
|
|
@@ -1422,20 +1501,24 @@
|
|
|
1422
1501
|
});
|
|
1423
1502
|
}
|
|
1424
1503
|
snapdom.capture = (_0, ..._1) => __async(null, [_0, ..._1], function* (el, options = {}) {
|
|
1504
|
+
var _a;
|
|
1425
1505
|
const url = yield captureDOM(el, options);
|
|
1426
|
-
const dpr = window.devicePixelRatio || 1;
|
|
1506
|
+
const dpr = (_a = options.dpr) != null ? _a : window.devicePixelRatio || 1;
|
|
1427
1507
|
const scale = options.scale || 1;
|
|
1428
1508
|
return {
|
|
1429
1509
|
url,
|
|
1430
1510
|
options,
|
|
1431
1511
|
toRaw: () => url,
|
|
1432
|
-
toImg: () => toImg(url, { dpr, scale }),
|
|
1433
|
-
toCanvas: () => toCanvas(url, { dpr, scale }),
|
|
1434
|
-
toBlob: (
|
|
1435
|
-
toPng: (
|
|
1436
|
-
toJpg: (
|
|
1437
|
-
toWebp: (
|
|
1438
|
-
download: (
|
|
1512
|
+
toImg: (opts = {}) => toImg(url, __spreadValues({ dpr, scale }, opts)),
|
|
1513
|
+
toCanvas: (opts = {}) => toCanvas(url, __spreadValues({ dpr, scale }, opts)),
|
|
1514
|
+
toBlob: (opts = {}) => toBlob(url, __spreadValues({ dpr, scale }, opts)),
|
|
1515
|
+
toPng: (opts = {}) => toRasterImg(url, __spreadValues({ dpr, scale }, opts), "png"),
|
|
1516
|
+
toJpg: (opts = {}) => toRasterImg(url, __spreadValues({ dpr, scale }, opts), "jpeg"),
|
|
1517
|
+
toWebp: (opts = {}) => toRasterImg(url, __spreadValues({ dpr, scale }, opts), "webp"),
|
|
1518
|
+
download: (_b = {}) => {
|
|
1519
|
+
var _c = _b, { format = "png", filename = "snapDOM", backgroundColor } = _c, opts = __objRest(_c, ["format", "filename", "backgroundColor"]);
|
|
1520
|
+
return download(url, __spreadValues({ dpr, scale, format, filename, backgroundColor }, opts));
|
|
1521
|
+
}
|
|
1439
1522
|
};
|
|
1440
1523
|
});
|
|
1441
1524
|
snapdom.toRaw = (el, options) => __async(null, null, function* () {
|