@zumer/snapdom 2.0.0-dev.2 → 2.0.0-dev.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/snapdom.js +718 -561
- package/dist/snapdom.min.js +8 -8
- package/dist/snapdom.min.mjs +8 -8
- package/dist/snapdom.mjs +711 -554
- package/package.json +1 -1
package/dist/snapdom.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
/*
|
|
3
3
|
* snapdom
|
|
4
|
-
* v.2.0.0-dev.
|
|
4
|
+
* v.2.0.0-dev.3
|
|
5
5
|
* Author Juan Martin Muda
|
|
6
6
|
* License MIT
|
|
7
7
|
**/
|
|
@@ -919,7 +919,7 @@ function resolveCSSVars(sourceEl, cloneEl) {
|
|
|
919
919
|
}
|
|
920
920
|
}
|
|
921
921
|
|
|
922
|
-
// src/
|
|
922
|
+
// src/utils/clone.helpers.js
|
|
923
923
|
function idleCallback(childList, callback, fast) {
|
|
924
924
|
return Promise.all(childList.map((child) => {
|
|
925
925
|
return new Promise((resolve) => {
|
|
@@ -1131,6 +1131,134 @@ async function rasterizeIframe(iframe, sessionCache, options) {
|
|
|
1131
1131
|
wrapper.appendChild(imgEl);
|
|
1132
1132
|
return wrapper;
|
|
1133
1133
|
}
|
|
1134
|
+
var _blobToDataUrlCache = /* @__PURE__ */ new Map();
|
|
1135
|
+
async function blobUrlToDataUrl(blobUrl) {
|
|
1136
|
+
if (cache.resource?.has(blobUrl)) return cache.resource.get(blobUrl);
|
|
1137
|
+
if (_blobToDataUrlCache.has(blobUrl)) return _blobToDataUrlCache.get(blobUrl);
|
|
1138
|
+
const p = (async () => {
|
|
1139
|
+
const r = await snapFetch(blobUrl, { as: "dataURL", silent: true });
|
|
1140
|
+
if (!r.ok || typeof r.data !== "string") {
|
|
1141
|
+
throw new Error(`[snapDOM] Failed to read blob URL: ${blobUrl}`);
|
|
1142
|
+
}
|
|
1143
|
+
cache.resource?.set(blobUrl, r.data);
|
|
1144
|
+
return r.data;
|
|
1145
|
+
})();
|
|
1146
|
+
_blobToDataUrlCache.set(blobUrl, p);
|
|
1147
|
+
try {
|
|
1148
|
+
const data = await p;
|
|
1149
|
+
_blobToDataUrlCache.set(blobUrl, data);
|
|
1150
|
+
return data;
|
|
1151
|
+
} catch (e) {
|
|
1152
|
+
_blobToDataUrlCache.delete(blobUrl);
|
|
1153
|
+
throw e;
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
var BLOB_URL_RE = /\bblob:[^)"'\s]+/g;
|
|
1157
|
+
async function replaceBlobUrlsInCssText(cssText) {
|
|
1158
|
+
if (!cssText || cssText.indexOf("blob:") === -1) return cssText;
|
|
1159
|
+
const uniques = Array.from(new Set(cssText.match(BLOB_URL_RE) || []));
|
|
1160
|
+
if (uniques.length === 0) return cssText;
|
|
1161
|
+
let out = cssText;
|
|
1162
|
+
for (const u of uniques) {
|
|
1163
|
+
try {
|
|
1164
|
+
const d = await blobUrlToDataUrl(u);
|
|
1165
|
+
out = out.split(u).join(d);
|
|
1166
|
+
} catch {
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
return out;
|
|
1170
|
+
}
|
|
1171
|
+
function isBlobUrl(u) {
|
|
1172
|
+
return typeof u === "string" && u.startsWith("blob:");
|
|
1173
|
+
}
|
|
1174
|
+
function parseSrcset(srcset) {
|
|
1175
|
+
return (srcset || "").split(",").map((s) => s.trim()).filter(Boolean).map((item) => {
|
|
1176
|
+
const m = item.match(/^(\S+)(\s+.+)?$/);
|
|
1177
|
+
return m ? { url: m[1], desc: m[2] || "" } : null;
|
|
1178
|
+
}).filter(Boolean);
|
|
1179
|
+
}
|
|
1180
|
+
function stringifySrcset(parts) {
|
|
1181
|
+
return parts.map((p) => p.desc ? `${p.url} ${p.desc.trim()}` : p.url).join(", ");
|
|
1182
|
+
}
|
|
1183
|
+
async function resolveBlobUrlsInTree(root) {
|
|
1184
|
+
if (!root) return;
|
|
1185
|
+
const imgs = root.querySelectorAll ? root.querySelectorAll("img") : [];
|
|
1186
|
+
for (const img of imgs) {
|
|
1187
|
+
try {
|
|
1188
|
+
const srcAttr = img.getAttribute("src");
|
|
1189
|
+
const effective = srcAttr || img.currentSrc || "";
|
|
1190
|
+
if (isBlobUrl(effective)) {
|
|
1191
|
+
const data = await blobUrlToDataUrl(effective);
|
|
1192
|
+
img.setAttribute("src", data);
|
|
1193
|
+
}
|
|
1194
|
+
const srcset = img.getAttribute("srcset");
|
|
1195
|
+
if (srcset && srcset.includes("blob:")) {
|
|
1196
|
+
const parts = parseSrcset(srcset);
|
|
1197
|
+
let changed = false;
|
|
1198
|
+
for (const p of parts) {
|
|
1199
|
+
if (isBlobUrl(p.url)) {
|
|
1200
|
+
try {
|
|
1201
|
+
p.url = await blobUrlToDataUrl(p.url);
|
|
1202
|
+
changed = true;
|
|
1203
|
+
} catch {
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
if (changed) img.setAttribute("srcset", stringifySrcset(parts));
|
|
1208
|
+
}
|
|
1209
|
+
} catch {
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
const svgImages = root.querySelectorAll ? root.querySelectorAll("image") : [];
|
|
1213
|
+
for (const node of svgImages) {
|
|
1214
|
+
try {
|
|
1215
|
+
const XLINK_NS = "http://www.w3.org/1999/xlink";
|
|
1216
|
+
const href = node.getAttribute("href") || node.getAttributeNS?.(XLINK_NS, "href");
|
|
1217
|
+
if (isBlobUrl(href)) {
|
|
1218
|
+
const d = await blobUrlToDataUrl(href);
|
|
1219
|
+
node.setAttribute("href", d);
|
|
1220
|
+
node.removeAttributeNS?.(XLINK_NS, "href");
|
|
1221
|
+
}
|
|
1222
|
+
} catch {
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
const styled = root.querySelectorAll ? root.querySelectorAll("[style*='blob:']") : [];
|
|
1226
|
+
for (const el of styled) {
|
|
1227
|
+
try {
|
|
1228
|
+
const styleText = el.getAttribute("style");
|
|
1229
|
+
if (styleText && styleText.includes("blob:")) {
|
|
1230
|
+
const replaced = await replaceBlobUrlsInCssText(styleText);
|
|
1231
|
+
el.setAttribute("style", replaced);
|
|
1232
|
+
}
|
|
1233
|
+
} catch {
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
const styleTags = root.querySelectorAll ? root.querySelectorAll("style") : [];
|
|
1237
|
+
for (const s of styleTags) {
|
|
1238
|
+
try {
|
|
1239
|
+
const css = s.textContent || "";
|
|
1240
|
+
if (css.includes("blob:")) {
|
|
1241
|
+
s.textContent = await replaceBlobUrlsInCssText(css);
|
|
1242
|
+
}
|
|
1243
|
+
} catch {
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
const urlAttrs = ["poster"];
|
|
1247
|
+
for (const attr of urlAttrs) {
|
|
1248
|
+
const nodes = root.querySelectorAll ? root.querySelectorAll(`[${attr}^='blob:']`) : [];
|
|
1249
|
+
for (const n of nodes) {
|
|
1250
|
+
try {
|
|
1251
|
+
const u = n.getAttribute(attr);
|
|
1252
|
+
if (isBlobUrl(u)) {
|
|
1253
|
+
n.setAttribute(attr, await blobUrlToDataUrl(u));
|
|
1254
|
+
}
|
|
1255
|
+
} catch {
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
// src/core/clone.js
|
|
1134
1262
|
async function deepClone(node, sessionCache, options) {
|
|
1135
1263
|
if (!node) throw new Error("Invalid node");
|
|
1136
1264
|
const clonedAssignedNodes = /* @__PURE__ */ new Set();
|
|
@@ -2595,6 +2723,152 @@ function resolveCountersInContent(raw, node, ctx) {
|
|
|
2595
2723
|
}
|
|
2596
2724
|
|
|
2597
2725
|
// src/modules/pseudo.js
|
|
2726
|
+
var __preflightMemo = /* @__PURE__ */ new WeakMap();
|
|
2727
|
+
var CSS_RULE_SCAN_BUDGET = 300;
|
|
2728
|
+
function preflightWithFp(doc, sessionCache) {
|
|
2729
|
+
const fp = styleFingerprint(doc);
|
|
2730
|
+
if (!sessionCache) return shouldProcessPseudos(doc);
|
|
2731
|
+
if (sessionCache.__pseudoPreflightFp !== fp) {
|
|
2732
|
+
sessionCache.__pseudoPreflight = shouldProcessPseudos(doc);
|
|
2733
|
+
sessionCache.__pseudoPreflightFp = fp;
|
|
2734
|
+
}
|
|
2735
|
+
return !!sessionCache.__pseudoPreflight;
|
|
2736
|
+
}
|
|
2737
|
+
function safeRules(sheet) {
|
|
2738
|
+
try {
|
|
2739
|
+
return sheet && sheet.cssRules ? sheet.cssRules : null;
|
|
2740
|
+
} catch {
|
|
2741
|
+
return null;
|
|
2742
|
+
}
|
|
2743
|
+
}
|
|
2744
|
+
function styleFingerprint(doc) {
|
|
2745
|
+
const nodes = doc.querySelectorAll('style,link[rel~="stylesheet"]');
|
|
2746
|
+
let fp = `n:${nodes.length}|`;
|
|
2747
|
+
let totalRules = 0;
|
|
2748
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
2749
|
+
const n = nodes[i];
|
|
2750
|
+
if (n.tagName === "STYLE") {
|
|
2751
|
+
const len = n.textContent ? n.textContent.length : 0;
|
|
2752
|
+
fp += `S${len}|`;
|
|
2753
|
+
const sheet = (
|
|
2754
|
+
/** @type {HTMLStyleElement} */
|
|
2755
|
+
n.sheet
|
|
2756
|
+
);
|
|
2757
|
+
const rules = sheet ? safeRules(sheet) : null;
|
|
2758
|
+
if (rules) totalRules += rules.length;
|
|
2759
|
+
} else {
|
|
2760
|
+
const href = n.getAttribute("href") || "";
|
|
2761
|
+
const media = n.getAttribute("media") || "all";
|
|
2762
|
+
fp += `L${href}|m:${media}|`;
|
|
2763
|
+
const sheet = (
|
|
2764
|
+
/** @type {HTMLLinkElement} */
|
|
2765
|
+
n.sheet
|
|
2766
|
+
);
|
|
2767
|
+
const rules = sheet ? safeRules(sheet) : null;
|
|
2768
|
+
if (rules) totalRules += rules.length;
|
|
2769
|
+
}
|
|
2770
|
+
}
|
|
2771
|
+
const ass = (
|
|
2772
|
+
/** @type {any} */
|
|
2773
|
+
doc.adoptedStyleSheets
|
|
2774
|
+
);
|
|
2775
|
+
fp += `ass:${Array.isArray(ass) ? ass.length : 0}|tr:${totalRules}`;
|
|
2776
|
+
return fp;
|
|
2777
|
+
}
|
|
2778
|
+
function sheetHasNeedles(sheet, needles, state) {
|
|
2779
|
+
const rules = safeRules(sheet);
|
|
2780
|
+
if (!rules) return false;
|
|
2781
|
+
for (let i = 0; i < rules.length; i++) {
|
|
2782
|
+
if (state.budget <= 0) return false;
|
|
2783
|
+
const rule = rules[i];
|
|
2784
|
+
const css = rule && rule.cssText ? rule.cssText : "";
|
|
2785
|
+
state.budget--;
|
|
2786
|
+
for (const k of needles) {
|
|
2787
|
+
if (css.includes(k)) return true;
|
|
2788
|
+
}
|
|
2789
|
+
if (rule && rule.cssRules && rule.cssRules.length) {
|
|
2790
|
+
for (let j = 0; j < rule.cssRules.length && state.budget > 0; j++) {
|
|
2791
|
+
const inner = rule.cssRules[j];
|
|
2792
|
+
const innerCss = inner && inner.cssText ? inner.cssText : "";
|
|
2793
|
+
state.budget--;
|
|
2794
|
+
for (const k of needles) {
|
|
2795
|
+
if (innerCss.includes(k)) return true;
|
|
2796
|
+
}
|
|
2797
|
+
}
|
|
2798
|
+
}
|
|
2799
|
+
if (state.budget <= 0) return false;
|
|
2800
|
+
}
|
|
2801
|
+
return false;
|
|
2802
|
+
}
|
|
2803
|
+
function shouldProcessPseudos(doc = document) {
|
|
2804
|
+
const fp = styleFingerprint(doc);
|
|
2805
|
+
const memo = __preflightMemo.get(doc);
|
|
2806
|
+
if (memo && memo.fingerprint === fp) return memo.result;
|
|
2807
|
+
const NEEDLES = [
|
|
2808
|
+
// double-colon
|
|
2809
|
+
"::before",
|
|
2810
|
+
"::after",
|
|
2811
|
+
"::first-letter",
|
|
2812
|
+
// single-colon robustness
|
|
2813
|
+
":before",
|
|
2814
|
+
":after",
|
|
2815
|
+
":first-letter",
|
|
2816
|
+
// counters
|
|
2817
|
+
"counter(",
|
|
2818
|
+
"counters(",
|
|
2819
|
+
"counter-increment",
|
|
2820
|
+
"counter-reset"
|
|
2821
|
+
];
|
|
2822
|
+
const styleEls = doc.querySelectorAll("style");
|
|
2823
|
+
for (let i = 0; i < styleEls.length; i++) {
|
|
2824
|
+
const t = styleEls[i].textContent || "";
|
|
2825
|
+
for (const k of NEEDLES) if (t.includes(k)) {
|
|
2826
|
+
__preflightMemo.set(doc, { fingerprint: fp, result: true });
|
|
2827
|
+
return true;
|
|
2828
|
+
}
|
|
2829
|
+
}
|
|
2830
|
+
const ass = (
|
|
2831
|
+
/** @type {any} */
|
|
2832
|
+
doc.adoptedStyleSheets
|
|
2833
|
+
);
|
|
2834
|
+
if (Array.isArray(ass) && ass.length) {
|
|
2835
|
+
const state = { budget: CSS_RULE_SCAN_BUDGET };
|
|
2836
|
+
try {
|
|
2837
|
+
for (const sheet of ass) {
|
|
2838
|
+
if (sheetHasNeedles(sheet, NEEDLES, state)) {
|
|
2839
|
+
__preflightMemo.set(doc, { fingerprint: fp, result: true });
|
|
2840
|
+
return true;
|
|
2841
|
+
}
|
|
2842
|
+
}
|
|
2843
|
+
} catch {
|
|
2844
|
+
}
|
|
2845
|
+
}
|
|
2846
|
+
{
|
|
2847
|
+
const nodes = doc.querySelectorAll('style,link[rel~="stylesheet"]');
|
|
2848
|
+
const state = { budget: CSS_RULE_SCAN_BUDGET };
|
|
2849
|
+
for (let i = 0; i < nodes.length && state.budget > 0; i++) {
|
|
2850
|
+
const n = nodes[i];
|
|
2851
|
+
let sheet = null;
|
|
2852
|
+
if (n.tagName === "STYLE") {
|
|
2853
|
+
sheet = /** @type {HTMLStyleElement} */
|
|
2854
|
+
n.sheet || null;
|
|
2855
|
+
} else {
|
|
2856
|
+
sheet = /** @type {HTMLLinkElement} */
|
|
2857
|
+
n.sheet || null;
|
|
2858
|
+
}
|
|
2859
|
+
if (sheet && sheetHasNeedles(sheet, NEEDLES, state)) {
|
|
2860
|
+
__preflightMemo.set(doc, { fingerprint: fp, result: true });
|
|
2861
|
+
return true;
|
|
2862
|
+
}
|
|
2863
|
+
}
|
|
2864
|
+
}
|
|
2865
|
+
if (doc.querySelector('[style*="counter("], [style*="counters("]')) {
|
|
2866
|
+
__preflightMemo.set(doc, { fingerprint: fp, result: true });
|
|
2867
|
+
return true;
|
|
2868
|
+
}
|
|
2869
|
+
__preflightMemo.set(doc, { fingerprint: fp, result: false });
|
|
2870
|
+
return false;
|
|
2871
|
+
}
|
|
2598
2872
|
var __siblingCounters = /* @__PURE__ */ new WeakMap();
|
|
2599
2873
|
var __pseudoEpoch = -1;
|
|
2600
2874
|
function unquoteDoubleStrings2(s) {
|
|
@@ -2693,6 +2967,10 @@ function resolvePseudoContentAndIncs(node, pseudo, baseCtx) {
|
|
|
2693
2967
|
}
|
|
2694
2968
|
async function inlinePseudoElements(source, clone, sessionCache, options) {
|
|
2695
2969
|
if (!(source instanceof Element) || !(clone instanceof Element)) return;
|
|
2970
|
+
const doc = source.ownerDocument || document;
|
|
2971
|
+
if (!preflightWithFp(doc, sessionCache)) {
|
|
2972
|
+
return;
|
|
2973
|
+
}
|
|
2696
2974
|
const epoch = cache?.session?.__counterEpoch ?? 0;
|
|
2697
2975
|
if (__pseudoEpoch !== epoch) {
|
|
2698
2976
|
__siblingCounters = /* @__PURE__ */ new WeakMap();
|
|
@@ -2709,7 +2987,7 @@ async function inlinePseudoElements(source, clone, sessionCache, options) {
|
|
|
2709
2987
|
for (const pseudo of ["::before", "::after", "::first-letter"]) {
|
|
2710
2988
|
try {
|
|
2711
2989
|
const style = getStyle(source, pseudo);
|
|
2712
|
-
if (!style
|
|
2990
|
+
if (!style) continue;
|
|
2713
2991
|
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";
|
|
2714
2992
|
if (isEmptyPseudo) continue;
|
|
2715
2993
|
if (pseudo === "::first-letter") {
|
|
@@ -2736,7 +3014,8 @@ async function inlinePseudoElements(source, clone, sessionCache, options) {
|
|
|
2736
3014
|
clone.insertBefore(span, restNode);
|
|
2737
3015
|
continue;
|
|
2738
3016
|
}
|
|
2739
|
-
const rawContent = style.content;
|
|
3017
|
+
const rawContent = style.content ?? "";
|
|
3018
|
+
const isNoExplicitContent = rawContent === "" || rawContent === "none" || rawContent === "normal";
|
|
2740
3019
|
const { text: cleanContent, incs } = resolvePseudoContentAndIncs(source, pseudo, counterCtx);
|
|
2741
3020
|
const bg = style.backgroundImage;
|
|
2742
3021
|
const bgColor = style.backgroundColor;
|
|
@@ -2748,7 +3027,7 @@ async function inlinePseudoElements(source, clone, sessionCache, options) {
|
|
|
2748
3027
|
const borderWidth = parseFloat(style.borderWidth);
|
|
2749
3028
|
const transform = style.transform;
|
|
2750
3029
|
const isIconFont22 = isIconFont2(fontFamily);
|
|
2751
|
-
const hasExplicitContent =
|
|
3030
|
+
const hasExplicitContent = !isNoExplicitContent && cleanContent !== "";
|
|
2752
3031
|
const hasBg = bg && bg !== "none";
|
|
2753
3032
|
const hasBgColor = bgColor && bgColor !== "transparent" && bgColor !== "rgba(0, 0, 0, 0)";
|
|
2754
3033
|
const hasBorder = borderStyle && borderStyle !== "none" && borderWidth > 0;
|
|
@@ -2770,8 +3049,6 @@ async function inlinePseudoElements(source, clone, sessionCache, options) {
|
|
|
2770
3049
|
}
|
|
2771
3050
|
const pseudoEl = document.createElement("span");
|
|
2772
3051
|
pseudoEl.dataset.snapdomPseudo = pseudo;
|
|
2773
|
-
pseudoEl.style.display = "inline";
|
|
2774
|
-
pseudoEl.style.verticalAlign = "baseline";
|
|
2775
3052
|
pseudoEl.style.pointerEvents = "none";
|
|
2776
3053
|
const snapshot = snapshotComputedStyle(style);
|
|
2777
3054
|
const key = getStyleKey(snapshot, "span");
|
|
@@ -3063,6 +3340,20 @@ function _childrenWithoutPlaceholders(el, phAttr) {
|
|
|
3063
3340
|
return out;
|
|
3064
3341
|
}
|
|
3065
3342
|
|
|
3343
|
+
// src/utils/prepare.helpers.js
|
|
3344
|
+
function stabilizeLayout(element) {
|
|
3345
|
+
const style = getComputedStyle(element);
|
|
3346
|
+
const outlineStyle = style.outlineStyle;
|
|
3347
|
+
const outlineWidth = style.outlineWidth;
|
|
3348
|
+
const borderStyle = style.borderStyle;
|
|
3349
|
+
const borderWidth = style.borderWidth;
|
|
3350
|
+
const outlineVisible = outlineStyle !== "none" && parseFloat(outlineWidth) > 0;
|
|
3351
|
+
const borderAbsent = borderStyle === "none" || parseFloat(borderWidth) === 0;
|
|
3352
|
+
if (outlineVisible && borderAbsent) {
|
|
3353
|
+
element.style.border = `${outlineWidth} solid transparent`;
|
|
3354
|
+
}
|
|
3355
|
+
}
|
|
3356
|
+
|
|
3066
3357
|
// src/core/prepare.js
|
|
3067
3358
|
async function prepareClone(element, options = {}) {
|
|
3068
3359
|
const sessionCache = {
|
|
@@ -3163,199 +3454,61 @@ async function prepareClone(element, options = {}) {
|
|
|
3163
3454
|
}
|
|
3164
3455
|
return { clone, classCSS, styleCache: sessionCache.styleCache };
|
|
3165
3456
|
}
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
const
|
|
3170
|
-
const
|
|
3171
|
-
const
|
|
3172
|
-
const
|
|
3173
|
-
const
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3457
|
+
|
|
3458
|
+
// src/modules/images.js
|
|
3459
|
+
function extractImageDimensions(img) {
|
|
3460
|
+
const dsW = parseInt(img.dataset?.snapdomWidth || "", 10) || 0;
|
|
3461
|
+
const dsH = parseInt(img.dataset?.snapdomHeight || "", 10) || 0;
|
|
3462
|
+
const attrW = parseInt(img.getAttribute("width") || "", 10) || 0;
|
|
3463
|
+
const attrH = parseInt(img.getAttribute("height") || "", 10) || 0;
|
|
3464
|
+
const styleW = parseFloat(img.style?.width || "") || 0;
|
|
3465
|
+
const styleH = parseFloat(img.style?.height || "") || 0;
|
|
3466
|
+
const w = dsW || styleW || attrW || img.width || img.naturalWidth || 100;
|
|
3467
|
+
const h = dsH || styleH || attrH || img.height || img.naturalHeight || 100;
|
|
3468
|
+
return { width: w, height: h };
|
|
3177
3469
|
}
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
if (!r.ok || typeof r.data !== "string") {
|
|
3185
|
-
throw new Error(`[snapDOM] Failed to read blob URL: ${blobUrl}`);
|
|
3470
|
+
async function inlineImages(clone, options = {}) {
|
|
3471
|
+
const imgs = Array.from(clone.querySelectorAll("img"));
|
|
3472
|
+
const processImg = async (img) => {
|
|
3473
|
+
if (!img.getAttribute("src")) {
|
|
3474
|
+
const eff = img.currentSrc || img.src || "";
|
|
3475
|
+
if (eff) img.setAttribute("src", eff);
|
|
3186
3476
|
}
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
throw e;
|
|
3198
|
-
}
|
|
3199
|
-
}
|
|
3200
|
-
var BLOB_URL_RE = /\bblob:[^)"'\s]+/g;
|
|
3201
|
-
async function replaceBlobUrlsInCssText(cssText) {
|
|
3202
|
-
if (!cssText || cssText.indexOf("blob:") === -1) return cssText;
|
|
3203
|
-
const uniques = Array.from(new Set(cssText.match(BLOB_URL_RE) || []));
|
|
3204
|
-
if (uniques.length === 0) return cssText;
|
|
3205
|
-
let out = cssText;
|
|
3206
|
-
for (const u of uniques) {
|
|
3207
|
-
try {
|
|
3208
|
-
const d = await blobUrlToDataUrl(u);
|
|
3209
|
-
out = out.split(u).join(d);
|
|
3210
|
-
} catch {
|
|
3211
|
-
}
|
|
3212
|
-
}
|
|
3213
|
-
return out;
|
|
3214
|
-
}
|
|
3215
|
-
function isBlobUrl(u) {
|
|
3216
|
-
return typeof u === "string" && u.startsWith("blob:");
|
|
3217
|
-
}
|
|
3218
|
-
function parseSrcset(srcset) {
|
|
3219
|
-
return (srcset || "").split(",").map((s) => s.trim()).filter(Boolean).map((item) => {
|
|
3220
|
-
const m = item.match(/^(\S+)(\s+.+)?$/);
|
|
3221
|
-
return m ? { url: m[1], desc: m[2] || "" } : null;
|
|
3222
|
-
}).filter(Boolean);
|
|
3223
|
-
}
|
|
3224
|
-
function stringifySrcset(parts) {
|
|
3225
|
-
return parts.map((p) => p.desc ? `${p.url} ${p.desc.trim()}` : p.url).join(", ");
|
|
3226
|
-
}
|
|
3227
|
-
async function resolveBlobUrlsInTree(root) {
|
|
3228
|
-
if (!root) return;
|
|
3229
|
-
const imgs = root.querySelectorAll ? root.querySelectorAll("img") : [];
|
|
3230
|
-
for (const img of imgs) {
|
|
3231
|
-
try {
|
|
3232
|
-
const srcAttr = img.getAttribute("src");
|
|
3233
|
-
const effective = srcAttr || img.currentSrc || "";
|
|
3234
|
-
if (isBlobUrl(effective)) {
|
|
3235
|
-
const data = await blobUrlToDataUrl(effective);
|
|
3236
|
-
img.setAttribute("src", data);
|
|
3237
|
-
}
|
|
3238
|
-
const srcset = img.getAttribute("srcset");
|
|
3239
|
-
if (srcset && srcset.includes("blob:")) {
|
|
3240
|
-
const parts = parseSrcset(srcset);
|
|
3241
|
-
let changed = false;
|
|
3242
|
-
for (const p of parts) {
|
|
3243
|
-
if (isBlobUrl(p.url)) {
|
|
3244
|
-
try {
|
|
3245
|
-
p.url = await blobUrlToDataUrl(p.url);
|
|
3246
|
-
changed = true;
|
|
3247
|
-
} catch {
|
|
3248
|
-
}
|
|
3249
|
-
}
|
|
3250
|
-
}
|
|
3251
|
-
if (changed) img.setAttribute("srcset", stringifySrcset(parts));
|
|
3252
|
-
}
|
|
3253
|
-
} catch {
|
|
3254
|
-
}
|
|
3255
|
-
}
|
|
3256
|
-
const svgImages = root.querySelectorAll ? root.querySelectorAll("image") : [];
|
|
3257
|
-
for (const node of svgImages) {
|
|
3258
|
-
try {
|
|
3259
|
-
const XLINK_NS = "http://www.w3.org/1999/xlink";
|
|
3260
|
-
const href = node.getAttribute("href") || node.getAttributeNS?.(XLINK_NS, "href");
|
|
3261
|
-
if (isBlobUrl(href)) {
|
|
3262
|
-
const d = await blobUrlToDataUrl(href);
|
|
3263
|
-
node.setAttribute("href", d);
|
|
3264
|
-
node.removeAttributeNS?.(XLINK_NS, "href");
|
|
3265
|
-
}
|
|
3266
|
-
} catch {
|
|
3267
|
-
}
|
|
3268
|
-
}
|
|
3269
|
-
const styled = root.querySelectorAll ? root.querySelectorAll("[style*='blob:']") : [];
|
|
3270
|
-
for (const el of styled) {
|
|
3271
|
-
try {
|
|
3272
|
-
const styleText = el.getAttribute("style");
|
|
3273
|
-
if (styleText && styleText.includes("blob:")) {
|
|
3274
|
-
const replaced = await replaceBlobUrlsInCssText(styleText);
|
|
3275
|
-
el.setAttribute("style", replaced);
|
|
3276
|
-
}
|
|
3277
|
-
} catch {
|
|
3278
|
-
}
|
|
3279
|
-
}
|
|
3280
|
-
const styleTags = root.querySelectorAll ? root.querySelectorAll("style") : [];
|
|
3281
|
-
for (const s of styleTags) {
|
|
3282
|
-
try {
|
|
3283
|
-
const css = s.textContent || "";
|
|
3284
|
-
if (css.includes("blob:")) {
|
|
3285
|
-
s.textContent = await replaceBlobUrlsInCssText(css);
|
|
3286
|
-
}
|
|
3287
|
-
} catch {
|
|
3288
|
-
}
|
|
3289
|
-
}
|
|
3290
|
-
const urlAttrs = ["poster"];
|
|
3291
|
-
for (const attr of urlAttrs) {
|
|
3292
|
-
const nodes = root.querySelectorAll ? root.querySelectorAll(`[${attr}^='blob:']`) : [];
|
|
3293
|
-
for (const n of nodes) {
|
|
3294
|
-
try {
|
|
3295
|
-
const u = n.getAttribute(attr);
|
|
3296
|
-
if (isBlobUrl(u)) {
|
|
3297
|
-
n.setAttribute(attr, await blobUrlToDataUrl(u));
|
|
3298
|
-
}
|
|
3299
|
-
} catch {
|
|
3300
|
-
}
|
|
3301
|
-
}
|
|
3302
|
-
}
|
|
3303
|
-
}
|
|
3304
|
-
|
|
3305
|
-
// src/modules/images.js
|
|
3306
|
-
async function inlineImages(clone, options = {}) {
|
|
3307
|
-
const imgs = Array.from(clone.querySelectorAll("img"));
|
|
3308
|
-
const processImg = async (img) => {
|
|
3309
|
-
if (!img.getAttribute("src")) {
|
|
3310
|
-
const eff = img.currentSrc || img.src || "";
|
|
3311
|
-
if (eff) img.setAttribute("src", eff);
|
|
3312
|
-
}
|
|
3313
|
-
img.removeAttribute("srcset");
|
|
3314
|
-
img.removeAttribute("sizes");
|
|
3315
|
-
const src = img.src || "";
|
|
3316
|
-
if (!src) return;
|
|
3317
|
-
const r = await snapFetch(src, { as: "dataURL", useProxy: options.useProxy });
|
|
3318
|
-
if (r.ok && typeof r.data === "string" && r.data.startsWith("data:")) {
|
|
3319
|
-
img.src = r.data;
|
|
3320
|
-
if (!img.width) img.width = img.naturalWidth || 100;
|
|
3321
|
-
if (!img.height) img.height = img.naturalHeight || 100;
|
|
3322
|
-
return;
|
|
3477
|
+
img.removeAttribute("srcset");
|
|
3478
|
+
img.removeAttribute("sizes");
|
|
3479
|
+
const src = img.src || "";
|
|
3480
|
+
if (!src) return;
|
|
3481
|
+
const r = await snapFetch(src, { as: "dataURL", useProxy: options.useProxy });
|
|
3482
|
+
if (r.ok && typeof r.data === "string" && r.data.startsWith("data:")) {
|
|
3483
|
+
img.src = r.data;
|
|
3484
|
+
if (!img.width) img.width = img.naturalWidth || 100;
|
|
3485
|
+
if (!img.height) img.height = img.naturalHeight || 100;
|
|
3486
|
+
return;
|
|
3323
3487
|
}
|
|
3488
|
+
const { width: fbW, height: fbH } = extractImageDimensions(img);
|
|
3324
3489
|
const { fallbackURL } = options || {};
|
|
3325
3490
|
if (fallbackURL) {
|
|
3326
3491
|
try {
|
|
3327
|
-
const dsW = parseInt(img.dataset?.snapdomWidth || "", 10) || 0;
|
|
3328
|
-
const dsH = parseInt(img.dataset?.snapdomHeight || "", 10) || 0;
|
|
3329
|
-
const attrW = parseInt(img.getAttribute("width") || "", 10) || 0;
|
|
3330
|
-
const attrH = parseInt(img.getAttribute("height") || "", 10) || 0;
|
|
3331
|
-
const styleW = parseFloat(img.style?.width || "") || 0;
|
|
3332
|
-
const styleH = parseFloat(img.style?.height || "") || 0;
|
|
3333
|
-
const fbW = dsW || styleW || attrW || img.width || void 0;
|
|
3334
|
-
const fbH = dsH || styleH || attrH || img.height || void 0;
|
|
3335
3492
|
const fallbackUrl = typeof fallbackURL === "function" ? await fallbackURL({ width: fbW, height: fbH, src, element: img }) : fallbackURL;
|
|
3336
3493
|
if (fallbackUrl) {
|
|
3337
3494
|
const fallbackData = await snapFetch(fallbackUrl, { as: "dataURL", useProxy: options.useProxy });
|
|
3338
3495
|
img.src = fallbackData.data;
|
|
3339
|
-
if (!img.width
|
|
3340
|
-
if (!img.height
|
|
3341
|
-
if (!img.width) img.width = img.naturalWidth || 100;
|
|
3342
|
-
if (!img.height) img.height = img.naturalHeight || 100;
|
|
3496
|
+
if (!img.width) img.width = fbW;
|
|
3497
|
+
if (!img.height) img.height = fbH;
|
|
3343
3498
|
return;
|
|
3344
3499
|
}
|
|
3345
3500
|
} catch {
|
|
3346
3501
|
}
|
|
3347
3502
|
}
|
|
3348
|
-
const w = img.width || img.naturalWidth || 100;
|
|
3349
|
-
const h = img.height || img.naturalHeight || 100;
|
|
3350
3503
|
if (options.placeholders !== false) {
|
|
3351
3504
|
const fallback = document.createElement("div");
|
|
3352
3505
|
fallback.style.cssText = [
|
|
3353
|
-
`width:${
|
|
3354
|
-
`height:${
|
|
3506
|
+
`width:${fbW}px`,
|
|
3507
|
+
`height:${fbH}px`,
|
|
3355
3508
|
"background:#ccc",
|
|
3356
3509
|
"display:inline-block",
|
|
3357
3510
|
"text-align:center",
|
|
3358
|
-
`line-height:${
|
|
3511
|
+
`line-height:${fbH}px`,
|
|
3359
3512
|
"color:#666",
|
|
3360
3513
|
"font-size:12px",
|
|
3361
3514
|
"overflow:hidden"
|
|
@@ -3364,7 +3517,7 @@ async function inlineImages(clone, options = {}) {
|
|
|
3364
3517
|
img.replaceWith(fallback);
|
|
3365
3518
|
} else {
|
|
3366
3519
|
const spacer = document.createElement("div");
|
|
3367
|
-
spacer.style.cssText = `display:inline-block;width:${
|
|
3520
|
+
spacer.style.cssText = `display:inline-block;width:${fbW}px;height:${fbH}px;visibility:hidden;`;
|
|
3368
3521
|
img.replaceWith(spacer);
|
|
3369
3522
|
}
|
|
3370
3523
|
};
|
|
@@ -3604,7 +3757,7 @@ function attachSessionPlugins(context, localDefs, force = false) {
|
|
|
3604
3757
|
return context;
|
|
3605
3758
|
}
|
|
3606
3759
|
|
|
3607
|
-
// src/
|
|
3760
|
+
// src/utils/capture.helpers.js
|
|
3608
3761
|
function stripRootShadows(originalEl, cloneRoot) {
|
|
3609
3762
|
if (!originalEl || !cloneRoot || !cloneRoot.style) return;
|
|
3610
3763
|
const cs = getComputedStyle(originalEl);
|
|
@@ -3671,283 +3824,134 @@ function sanitizeCloneForXHTML(root, opts = {}) {
|
|
|
3671
3824
|
sanitizeAttributesForXHTML(root, opts);
|
|
3672
3825
|
removeAllComments(root);
|
|
3673
3826
|
}
|
|
3674
|
-
|
|
3675
|
-
if (!element) throw new Error("Element cannot be null or undefined");
|
|
3676
|
-
applyCachePolicy(options.cache);
|
|
3677
|
-
const fast = options.fast;
|
|
3678
|
-
const outerTransforms = options.outerTransforms !== false;
|
|
3679
|
-
const outerShadows = !!options.outerShadows;
|
|
3680
|
-
let state = { element, options, plugins: options.plugins };
|
|
3681
|
-
let clone, classCSS, styleCache;
|
|
3682
|
-
let fontsCSS = "";
|
|
3683
|
-
let baseCSS = "";
|
|
3684
|
-
let dataURL;
|
|
3685
|
-
let svgString;
|
|
3686
|
-
let rootTransform2D = null;
|
|
3687
|
-
await runHook("beforeSnap", state);
|
|
3688
|
-
await runHook("beforeClone", state);
|
|
3689
|
-
const undoClamp = lineClamp(state.element);
|
|
3827
|
+
function authorHasExplicitSize(el) {
|
|
3690
3828
|
try {
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3829
|
+
const s = el.getAttribute?.("style") || "";
|
|
3830
|
+
return /\b(height|width|block-size|inline-size)\s*:/.test(s);
|
|
3831
|
+
} catch {
|
|
3832
|
+
return false;
|
|
3833
|
+
}
|
|
3834
|
+
}
|
|
3835
|
+
function isReplacedElement(el) {
|
|
3836
|
+
return el instanceof HTMLImageElement || el instanceof HTMLCanvasElement || el instanceof HTMLVideoElement || el instanceof HTMLIFrameElement || el instanceof SVGElement || el instanceof HTMLObjectElement || el instanceof HTMLEmbedElement;
|
|
3837
|
+
}
|
|
3838
|
+
function shouldShrinkBox(srcEl, cs) {
|
|
3839
|
+
if (!(srcEl instanceof Element)) return false;
|
|
3840
|
+
if (authorHasExplicitSize(srcEl)) return false;
|
|
3841
|
+
if (isReplacedElement(srcEl)) return false;
|
|
3842
|
+
const pos = cs.position;
|
|
3843
|
+
if (pos === "absolute" || pos === "fixed" || pos === "sticky") return false;
|
|
3844
|
+
const disp = cs.display || "";
|
|
3845
|
+
if (disp.includes("flex") || disp.includes("grid") || disp.startsWith("table")) return false;
|
|
3846
|
+
if (cs.transform && cs.transform !== "none") return false;
|
|
3847
|
+
return true;
|
|
3848
|
+
}
|
|
3849
|
+
function shrinkAutoSizeBoxes(sourceRoot, cloneRoot, styleCache = /* @__PURE__ */ new Map()) {
|
|
3850
|
+
function walk(src, cln) {
|
|
3851
|
+
if (!(src instanceof Element) || !(cln instanceof Element)) return;
|
|
3852
|
+
const lostKids = src.childElementCount > cln.childElementCount;
|
|
3853
|
+
const cs = styleCache.get(src) || getComputedStyle(src);
|
|
3854
|
+
if (!styleCache.has(src)) styleCache.set(src, cs);
|
|
3855
|
+
if (lostKids && shouldShrinkBox(src, cs)) {
|
|
3856
|
+
if (!cln.style.height) cln.style.height = "auto";
|
|
3857
|
+
if (!cln.style.width) cln.style.width = "auto";
|
|
3858
|
+
cln.style.removeProperty("block-size");
|
|
3859
|
+
cln.style.removeProperty("inline-size");
|
|
3860
|
+
if (!cln.style.minHeight) cln.style.minHeight = "0";
|
|
3861
|
+
if (!cln.style.minWidth) cln.style.minWidth = "0";
|
|
3862
|
+
if (!cln.style.maxHeight) cln.style.maxHeight = "none";
|
|
3863
|
+
if (!cln.style.maxWidth) cln.style.maxWidth = "none";
|
|
3864
|
+
const oy = cs.overflowY || cs.overflowBlock || "visible";
|
|
3865
|
+
const ox = cs.overflowX || cs.overflowInline || "visible";
|
|
3866
|
+
if (oy !== "visible" || ox !== "visible") {
|
|
3867
|
+
cln.style.overflow = "visible";
|
|
3868
|
+
}
|
|
3694
3869
|
}
|
|
3695
|
-
|
|
3696
|
-
|
|
3870
|
+
const sKids = Array.from(src.children);
|
|
3871
|
+
const cKids = Array.from(cln.children);
|
|
3872
|
+
for (let i = 0; i < Math.min(sKids.length, cKids.length); i++) {
|
|
3873
|
+
walk(sKids[i], cKids[i]);
|
|
3697
3874
|
}
|
|
3698
|
-
} finally {
|
|
3699
|
-
undoClamp();
|
|
3700
3875
|
}
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3876
|
+
walk(sourceRoot, cloneRoot);
|
|
3877
|
+
}
|
|
3878
|
+
function isInNormalFlow(el) {
|
|
3879
|
+
const cs = getComputedStyle(el);
|
|
3880
|
+
if (cs.display === "none") return false;
|
|
3881
|
+
if (cs.position === "absolute" || cs.position === "fixed" || cs.position === "sticky") return false;
|
|
3882
|
+
if ((cs.cssFloat || cs.float || "none") !== "none") return false;
|
|
3883
|
+
if (cs.transform && cs.transform !== "none") return false;
|
|
3884
|
+
return true;
|
|
3885
|
+
}
|
|
3886
|
+
function willBeExcluded(el, options) {
|
|
3887
|
+
if (!(el instanceof Element)) return false;
|
|
3888
|
+
if (el.getAttribute("data-capture") === "exclude" && options?.excludeMode === "remove") return true;
|
|
3889
|
+
if (Array.isArray(options?.exclude)) {
|
|
3890
|
+
for (const sel of options.exclude) {
|
|
3891
|
+
try {
|
|
3892
|
+
if (el.matches(sel)) return options.excludeMode === "remove";
|
|
3893
|
+
} catch {
|
|
3894
|
+
}
|
|
3709
3895
|
}
|
|
3710
3896
|
}
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
const required = collectUsedFontVariants(state.element);
|
|
3731
|
-
const usedCodepoints = collectUsedCodepoints(state.element);
|
|
3732
|
-
if (isSafari()) {
|
|
3733
|
-
const families = new Set(
|
|
3734
|
-
Array.from(required).map((k) => String(k).split("__")[0]).filter(Boolean)
|
|
3735
|
-
);
|
|
3736
|
-
await ensureFontsReady(families, 1);
|
|
3737
|
-
}
|
|
3738
|
-
fontsCSS = await embedCustomFonts({
|
|
3739
|
-
required,
|
|
3740
|
-
usedCodepoints,
|
|
3741
|
-
preCached: false,
|
|
3742
|
-
exclude: state.options.excludeFonts,
|
|
3743
|
-
useProxy: state.options.useProxy
|
|
3744
|
-
});
|
|
3745
|
-
resolve();
|
|
3746
|
-
}, { fast });
|
|
3747
|
-
});
|
|
3897
|
+
return false;
|
|
3898
|
+
}
|
|
3899
|
+
function estimateKeptHeight(container, options) {
|
|
3900
|
+
const csC = getComputedStyle(container);
|
|
3901
|
+
const rC = container.getBoundingClientRect();
|
|
3902
|
+
let minTop = Infinity;
|
|
3903
|
+
let maxBottom = -Infinity;
|
|
3904
|
+
let found = false;
|
|
3905
|
+
const kids = Array.from(container.children);
|
|
3906
|
+
for (const k of kids) {
|
|
3907
|
+
if (willBeExcluded(k, options)) continue;
|
|
3908
|
+
if (!isInNormalFlow(k)) continue;
|
|
3909
|
+
const rk = k.getBoundingClientRect();
|
|
3910
|
+
const top = rk.top - rC.top;
|
|
3911
|
+
const bottom = rk.bottom - rC.top;
|
|
3912
|
+
if (bottom <= top) continue;
|
|
3913
|
+
if (top < minTop) minTop = top;
|
|
3914
|
+
if (bottom > maxBottom) maxBottom = bottom;
|
|
3915
|
+
found = true;
|
|
3748
3916
|
}
|
|
3749
|
-
const
|
|
3750
|
-
const
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
idle(() => {
|
|
3756
|
-
baseCSS = generateDedupedBaseCSS(usedTags);
|
|
3757
|
-
cache.baseStyle.set(tagKey, baseCSS);
|
|
3758
|
-
resolve();
|
|
3759
|
-
}, { fast });
|
|
3760
|
-
});
|
|
3761
|
-
}
|
|
3762
|
-
state = { fontsCSS, baseCSS, ...state };
|
|
3763
|
-
await runHook("beforeRender", state);
|
|
3764
|
-
await new Promise((resolve) => {
|
|
3765
|
-
idle(() => {
|
|
3766
|
-
const csEl = getComputedStyle(state.element);
|
|
3767
|
-
const rect = state.element.getBoundingClientRect();
|
|
3768
|
-
let w0 = Math.max(1, limitDecimals(state.element.offsetWidth || parseFloat(csEl.width) || rect.width || 1));
|
|
3769
|
-
let h0 = Math.max(1, limitDecimals(state.element.offsetHeight || parseFloat(csEl.height) || rect.height || 1));
|
|
3770
|
-
if (state.options?.excludeMode === "remove") {
|
|
3771
|
-
const hEst = estimateKeptHeight(state.element, state.options);
|
|
3772
|
-
const EPS = 1;
|
|
3773
|
-
if (Number.isFinite(hEst) && hEst > 0) {
|
|
3774
|
-
h0 = Math.max(1, Math.min(h0, limitDecimals(hEst + EPS)));
|
|
3775
|
-
}
|
|
3776
|
-
}
|
|
3777
|
-
const coerceNum = (v, def = NaN) => {
|
|
3778
|
-
const n = typeof v === "string" ? parseFloat(v) : v;
|
|
3779
|
-
return Number.isFinite(n) ? n : def;
|
|
3780
|
-
};
|
|
3781
|
-
const optW = coerceNum(state.options.width);
|
|
3782
|
-
const optH = coerceNum(state.options.height);
|
|
3783
|
-
let w = w0, h = h0;
|
|
3784
|
-
const hasW = Number.isFinite(optW);
|
|
3785
|
-
const hasH = Number.isFinite(optH);
|
|
3786
|
-
const aspect0 = h0 > 0 ? w0 / h0 : 1;
|
|
3787
|
-
if (hasW && hasH) {
|
|
3788
|
-
w = Math.max(1, limitDecimals(optW));
|
|
3789
|
-
h = Math.max(1, limitDecimals(optH));
|
|
3790
|
-
} else if (hasW) {
|
|
3791
|
-
w = Math.max(1, limitDecimals(optW));
|
|
3792
|
-
h = Math.max(1, limitDecimals(w / (aspect0 || 1)));
|
|
3793
|
-
} else if (hasH) {
|
|
3794
|
-
h = Math.max(1, limitDecimals(optH));
|
|
3795
|
-
w = Math.max(1, limitDecimals(h * (aspect0 || 1)));
|
|
3796
|
-
} else {
|
|
3797
|
-
w = w0;
|
|
3798
|
-
h = h0;
|
|
3799
|
-
}
|
|
3800
|
-
let minX = 0, minY = 0, maxX = w0, maxY = h0;
|
|
3801
|
-
if (!outerTransforms && rootTransform2D && Number.isFinite(rootTransform2D.a)) {
|
|
3802
|
-
const M2 = {
|
|
3803
|
-
a: rootTransform2D.a,
|
|
3804
|
-
b: rootTransform2D.b || 0,
|
|
3805
|
-
c: rootTransform2D.c || 0,
|
|
3806
|
-
d: rootTransform2D.d || 1,
|
|
3807
|
-
e: 0,
|
|
3808
|
-
f: 0
|
|
3809
|
-
};
|
|
3810
|
-
const bb2 = bboxWithOriginFull(w0, h0, M2, 0, 0);
|
|
3811
|
-
minX = limitDecimals(bb2.minX);
|
|
3812
|
-
minY = limitDecimals(bb2.minY);
|
|
3813
|
-
maxX = limitDecimals(bb2.maxX);
|
|
3814
|
-
maxY = limitDecimals(bb2.maxY);
|
|
3815
|
-
} else {
|
|
3816
|
-
const useTFBBox = outerTransforms && hasTFBBox(state.element);
|
|
3817
|
-
if (useTFBBox) {
|
|
3818
|
-
const baseTransform2 = csEl.transform && csEl.transform !== "none" ? csEl.transform : "";
|
|
3819
|
-
const ind2 = readIndividualTransforms(state.element);
|
|
3820
|
-
const TOTAL = readTotalTransformMatrix({
|
|
3821
|
-
baseTransform: baseTransform2,
|
|
3822
|
-
rotate: ind2.rotate || "0deg",
|
|
3823
|
-
scale: ind2.scale,
|
|
3824
|
-
translate: ind2.translate
|
|
3825
|
-
});
|
|
3826
|
-
const { ox: ox2, oy: oy2 } = parseTransformOriginPx(csEl, w0, h0);
|
|
3827
|
-
const M = TOTAL.is2D ? TOTAL : new DOMMatrix(TOTAL.toString());
|
|
3828
|
-
const bb = bboxWithOriginFull(w0, h0, M, ox2, oy2);
|
|
3829
|
-
minX = limitDecimals(bb.minX);
|
|
3830
|
-
minY = limitDecimals(bb.minY);
|
|
3831
|
-
maxX = limitDecimals(bb.maxX);
|
|
3832
|
-
maxY = limitDecimals(bb.maxY);
|
|
3833
|
-
}
|
|
3834
|
-
}
|
|
3835
|
-
const bleedShadow = parseBoxShadow(csEl);
|
|
3836
|
-
const bleedBlur = parseFilterBlur(csEl);
|
|
3837
|
-
const bleedOutline = parseOutline(csEl);
|
|
3838
|
-
const drop = parseFilterDropShadows(csEl);
|
|
3839
|
-
const bleed = !outerShadows ? { top: 0, right: 0, bottom: 0, left: 0 } : {
|
|
3840
|
-
top: limitDecimals(bleedShadow.top + bleedBlur.top + bleedOutline.top + drop.bleed.top),
|
|
3841
|
-
right: limitDecimals(bleedShadow.right + bleedBlur.right + bleedOutline.right + drop.bleed.right),
|
|
3842
|
-
bottom: limitDecimals(bleedShadow.bottom + bleedBlur.bottom + bleedOutline.bottom + drop.bleed.bottom),
|
|
3843
|
-
left: limitDecimals(bleedShadow.left + bleedBlur.left + bleedOutline.left + drop.bleed.left)
|
|
3844
|
-
};
|
|
3845
|
-
minX = limitDecimals(minX - bleed.left);
|
|
3846
|
-
minY = limitDecimals(minY - bleed.top);
|
|
3847
|
-
maxX = limitDecimals(maxX + bleed.right);
|
|
3848
|
-
maxY = limitDecimals(maxY + bleed.bottom);
|
|
3849
|
-
const vbW0 = Math.max(1, limitDecimals(maxX - minX));
|
|
3850
|
-
const vbH0 = Math.max(1, limitDecimals(maxY - minY));
|
|
3851
|
-
const scaleW = hasW || hasH ? limitDecimals(w / w0) : 1;
|
|
3852
|
-
const scaleH = hasH || hasW ? limitDecimals(h / h0) : 1;
|
|
3853
|
-
const outW = Math.max(1, limitDecimals(vbW0 * scaleW));
|
|
3854
|
-
const outH = Math.max(1, limitDecimals(vbH0 * scaleH));
|
|
3855
|
-
const svgNS = "http://www.w3.org/2000/svg";
|
|
3856
|
-
const basePad = isSafari() ? 1 : 0;
|
|
3857
|
-
const extraPad = !outerTransforms ? 1 : 0;
|
|
3858
|
-
const pad = limitDecimals(basePad + extraPad);
|
|
3859
|
-
const fo = document.createElementNS(svgNS, "foreignObject");
|
|
3860
|
-
const vbMinX = limitDecimals(minX);
|
|
3861
|
-
const vbMinY = limitDecimals(minY);
|
|
3862
|
-
fo.setAttribute("x", String(limitDecimals(-(vbMinX - pad))));
|
|
3863
|
-
fo.setAttribute("y", String(limitDecimals(-(vbMinY - pad))));
|
|
3864
|
-
fo.setAttribute("width", String(limitDecimals(w0 + pad * 2)));
|
|
3865
|
-
fo.setAttribute("height", String(limitDecimals(h0 + pad * 2)));
|
|
3866
|
-
fo.style.overflow = "visible";
|
|
3867
|
-
const styleTag = document.createElement("style");
|
|
3868
|
-
styleTag.textContent = state.baseCSS + state.fontsCSS + "svg{overflow:visible;} foreignObject{overflow:visible;}" + state.classCSS;
|
|
3869
|
-
fo.appendChild(styleTag);
|
|
3870
|
-
const container = document.createElement("div");
|
|
3871
|
-
container.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
|
|
3872
|
-
container.style.width = `${limitDecimals(w0)}px`;
|
|
3873
|
-
container.style.height = `${limitDecimals(h0)}px`;
|
|
3874
|
-
container.style.overflow = "visible";
|
|
3875
|
-
state.clone.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
|
|
3876
|
-
container.appendChild(state.clone);
|
|
3877
|
-
fo.appendChild(container);
|
|
3878
|
-
const serializer = new XMLSerializer();
|
|
3879
|
-
const foString = serializer.serializeToString(fo);
|
|
3880
|
-
const vbW = limitDecimals(vbW0 + pad * 2);
|
|
3881
|
-
const vbH = limitDecimals(vbH0 + pad * 2);
|
|
3882
|
-
const wantsSize = hasW || hasH;
|
|
3883
|
-
options.meta = { w0, h0, vbW, vbH, targetW: w, targetH: h };
|
|
3884
|
-
const svgOutW = isSafari() && wantsSize ? vbW : limitDecimals(outW + pad * 2);
|
|
3885
|
-
const svgOutH = isSafari() && wantsSize ? vbH : limitDecimals(outH + pad * 2);
|
|
3886
|
-
const svgHeader = `<svg xmlns="${svgNS}" width="${svgOutW}" height="${svgOutH}" viewBox="0 0 ${vbW} ${vbH}">`;
|
|
3887
|
-
const svgFooter = "</svg>";
|
|
3888
|
-
svgString = svgHeader + foString + svgFooter;
|
|
3889
|
-
dataURL = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgString)}`;
|
|
3890
|
-
state = { svgString, dataURL, ...state };
|
|
3891
|
-
resolve();
|
|
3892
|
-
}, { fast });
|
|
3893
|
-
});
|
|
3894
|
-
await runHook("afterRender", state);
|
|
3895
|
-
const sandbox = document.getElementById("snapdom-sandbox");
|
|
3896
|
-
if (sandbox && sandbox.style.position === "absolute") sandbox.remove();
|
|
3897
|
-
return state.dataURL;
|
|
3917
|
+
const contentSpan = found ? Math.max(0, maxBottom - minTop) : 0;
|
|
3918
|
+
const bt = parseFloat(csC.borderTopWidth) || 0;
|
|
3919
|
+
const bb = parseFloat(csC.borderBottomWidth) || 0;
|
|
3920
|
+
const pt = parseFloat(csC.paddingTop) || 0;
|
|
3921
|
+
const pb = parseFloat(csC.paddingBottom) || 0;
|
|
3922
|
+
return bt + bb + pt + pb + contentSpan;
|
|
3898
3923
|
}
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3924
|
+
var limitDecimals = (v, n = 3) => Number.isFinite(v) ? Math.round(v * 10 ** n) / 10 ** n : v;
|
|
3925
|
+
|
|
3926
|
+
// src/utils/transforms.helpers.js
|
|
3927
|
+
function parseBoxShadow(cs) {
|
|
3928
|
+
const v = cs.boxShadow || "";
|
|
3929
|
+
if (!v || v === "none") return { top: 0, right: 0, bottom: 0, left: 0 };
|
|
3930
|
+
const parts = v.split(/\),(?=(?:[^()]*\([^()]*\))*[^()]*$)/).map((s) => s.trim());
|
|
3931
|
+
let t = 0, r = 0, b2 = 0, l = 0;
|
|
3932
|
+
for (const part of parts) {
|
|
3933
|
+
const nums = part.match(/-?\d+(\.\d+)?px/g)?.map((n) => parseFloat(n)) || [];
|
|
3934
|
+
if (nums.length < 2) continue;
|
|
3935
|
+
const [ox2, oy2, blur = 0, spread = 0] = nums;
|
|
3936
|
+
const extX = Math.abs(ox2) + blur + spread;
|
|
3937
|
+
const extY = Math.abs(oy2) + blur + spread;
|
|
3938
|
+
r = Math.max(r, extX + Math.max(ox2, 0));
|
|
3939
|
+
l = Math.max(l, extX + Math.max(-ox2, 0));
|
|
3940
|
+
b2 = Math.max(b2, extY + Math.max(oy2, 0));
|
|
3941
|
+
t = Math.max(t, extY + Math.max(-oy2, 0));
|
|
3905
3942
|
}
|
|
3943
|
+
return { top: Math.ceil(t), right: Math.ceil(r), bottom: Math.ceil(b2), left: Math.ceil(l) };
|
|
3906
3944
|
}
|
|
3907
|
-
function
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
if (!(srcEl instanceof Element)) return false;
|
|
3912
|
-
if (authorHasExplicitSize(srcEl)) return false;
|
|
3913
|
-
if (isReplacedElement(srcEl)) return false;
|
|
3914
|
-
const pos = cs.position;
|
|
3915
|
-
if (pos === "absolute" || pos === "fixed" || pos === "sticky") return false;
|
|
3916
|
-
const disp = cs.display || "";
|
|
3917
|
-
if (disp.includes("flex") || disp.includes("grid") || disp.startsWith("table")) return false;
|
|
3918
|
-
if (cs.transform && cs.transform !== "none") return false;
|
|
3919
|
-
return true;
|
|
3945
|
+
function parseFilterBlur(cs) {
|
|
3946
|
+
const m = (cs.filter || "").match(/blur\(\s*([0-9.]+)px\s*\)/);
|
|
3947
|
+
const b2 = m ? Math.ceil(parseFloat(m[1]) || 0) : 0;
|
|
3948
|
+
return { top: b2, right: b2, bottom: b2, left: b2 };
|
|
3920
3949
|
}
|
|
3921
|
-
function
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
const cs = styleCache.get(src) || getComputedStyle(src);
|
|
3926
|
-
if (!styleCache.has(src)) styleCache.set(src, cs);
|
|
3927
|
-
if (lostKids && shouldShrinkBox(src, cs)) {
|
|
3928
|
-
if (!cln.style.height) cln.style.height = "auto";
|
|
3929
|
-
if (!cln.style.width) cln.style.width = "auto";
|
|
3930
|
-
cln.style.removeProperty("block-size");
|
|
3931
|
-
cln.style.removeProperty("inline-size");
|
|
3932
|
-
if (!cln.style.minHeight) cln.style.minHeight = "0";
|
|
3933
|
-
if (!cln.style.minWidth) cln.style.minWidth = "0";
|
|
3934
|
-
if (!cln.style.maxHeight) cln.style.maxHeight = "none";
|
|
3935
|
-
if (!cln.style.maxWidth) cln.style.maxWidth = "none";
|
|
3936
|
-
const oy = cs.overflowY || cs.overflowBlock || "visible";
|
|
3937
|
-
const ox = cs.overflowX || cs.overflowInline || "visible";
|
|
3938
|
-
if (oy !== "visible" || ox !== "visible") {
|
|
3939
|
-
cln.style.overflow = "visible";
|
|
3940
|
-
}
|
|
3941
|
-
}
|
|
3942
|
-
const sKids = Array.from(src.children);
|
|
3943
|
-
const cKids = Array.from(cln.children);
|
|
3944
|
-
for (let i = 0; i < Math.min(sKids.length, cKids.length); i++) {
|
|
3945
|
-
walk(sKids[i], cKids[i]);
|
|
3946
|
-
}
|
|
3947
|
-
}
|
|
3948
|
-
walk(sourceRoot, cloneRoot);
|
|
3950
|
+
function parseOutline(cs) {
|
|
3951
|
+
if ((cs.outlineStyle || "none") === "none") return { top: 0, right: 0, bottom: 0, left: 0 };
|
|
3952
|
+
const w2 = Math.ceil(parseFloat(cs.outlineWidth || "0") || 0);
|
|
3953
|
+
return { top: w2, right: w2, bottom: w2, left: w2 };
|
|
3949
3954
|
}
|
|
3950
|
-
var limitDecimals = (v, n = 3) => Number.isFinite(v) ? Math.round(v * 10 ** n) / 10 ** n : v;
|
|
3951
3955
|
function parseFilterDropShadows(cs) {
|
|
3952
3956
|
const raw = `${cs.filter || ""} ${cs.webkitFilter || ""}`.trim();
|
|
3953
3957
|
if (!raw || raw === "none") {
|
|
@@ -4036,34 +4040,6 @@ function normalizeRootTransforms(originalEl, cloneRoot) {
|
|
|
4036
4040
|
return null;
|
|
4037
4041
|
}
|
|
4038
4042
|
}
|
|
4039
|
-
function parseBoxShadow(cs) {
|
|
4040
|
-
const v = cs.boxShadow || "";
|
|
4041
|
-
if (!v || v === "none") return { top: 0, right: 0, bottom: 0, left: 0 };
|
|
4042
|
-
const parts = v.split(/\),(?=(?:[^()]*\([^()]*\))*[^()]*$)/).map((s) => s.trim());
|
|
4043
|
-
let t = 0, r = 0, b2 = 0, l = 0;
|
|
4044
|
-
for (const part of parts) {
|
|
4045
|
-
const nums = part.match(/-?\d+(\.\d+)?px/g)?.map((n) => parseFloat(n)) || [];
|
|
4046
|
-
if (nums.length < 2) continue;
|
|
4047
|
-
const [ox2, oy2, blur = 0, spread = 0] = nums;
|
|
4048
|
-
const extX = Math.abs(ox2) + blur + spread;
|
|
4049
|
-
const extY = Math.abs(oy2) + blur + spread;
|
|
4050
|
-
r = Math.max(r, extX + Math.max(ox2, 0));
|
|
4051
|
-
l = Math.max(l, extX + Math.max(-ox2, 0));
|
|
4052
|
-
b2 = Math.max(b2, extY + Math.max(oy2, 0));
|
|
4053
|
-
t = Math.max(t, extY + Math.max(-oy2, 0));
|
|
4054
|
-
}
|
|
4055
|
-
return { top: Math.ceil(t), right: Math.ceil(r), bottom: Math.ceil(b2), left: Math.ceil(l) };
|
|
4056
|
-
}
|
|
4057
|
-
function parseFilterBlur(cs) {
|
|
4058
|
-
const m = (cs.filter || "").match(/blur\(\s*([0-9.]+)px\s*\)/);
|
|
4059
|
-
const b2 = m ? Math.ceil(parseFloat(m[1]) || 0) : 0;
|
|
4060
|
-
return { top: b2, right: b2, bottom: b2, left: b2 };
|
|
4061
|
-
}
|
|
4062
|
-
function parseOutline(cs) {
|
|
4063
|
-
if ((cs.outlineStyle || "none") === "none") return { top: 0, right: 0, bottom: 0, left: 0 };
|
|
4064
|
-
const w2 = Math.ceil(parseFloat(cs.outlineWidth || "0") || 0);
|
|
4065
|
-
return { top: w2, right: w2, bottom: w2, left: w2 };
|
|
4066
|
-
}
|
|
4067
4043
|
function bboxWithOriginFull(w2, h2, M, ox2, oy2) {
|
|
4068
4044
|
const a2 = M.a, b2 = M.b, c2 = M.c, d2 = M.d, e2 = M.e || 0, f2 = M.f || 0;
|
|
4069
4045
|
function pt(x, y) {
|
|
@@ -4083,17 +4059,24 @@ function bboxWithOriginFull(w2, h2, M, ox2, oy2) {
|
|
|
4083
4059
|
}
|
|
4084
4060
|
return { minX: minX2, minY: minY2, maxX: maxX2, maxY: maxY2, width: maxX2 - minX2, height: maxY2 - minY2 };
|
|
4085
4061
|
}
|
|
4086
|
-
function
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
return
|
|
4096
|
-
|
|
4062
|
+
function parseTransformOriginPx(cs, w, h) {
|
|
4063
|
+
const raw = (cs.transformOrigin || "0 0").trim().split(/\s+/);
|
|
4064
|
+
const [oxRaw, oyRaw] = [raw[0] || "0", raw[1] || "0"];
|
|
4065
|
+
const toPx = (token, size) => {
|
|
4066
|
+
const t = token.toLowerCase();
|
|
4067
|
+
if (t === "left" || t === "top") return 0;
|
|
4068
|
+
if (t === "center") return size / 2;
|
|
4069
|
+
if (t === "right") return size;
|
|
4070
|
+
if (t === "bottom") return size;
|
|
4071
|
+
if (t.endsWith("px")) return parseFloat(t) || 0;
|
|
4072
|
+
if (t.endsWith("%")) return (parseFloat(t) || 0) * size / 100;
|
|
4073
|
+
if (/^-?\d+(\.\d+)?$/.test(t)) return parseFloat(t) || 0;
|
|
4074
|
+
return 0;
|
|
4075
|
+
};
|
|
4076
|
+
return {
|
|
4077
|
+
ox: toPx(oxRaw, w),
|
|
4078
|
+
oy: toPx(oyRaw, h)
|
|
4079
|
+
};
|
|
4097
4080
|
}
|
|
4098
4081
|
function readIndividualTransforms(el) {
|
|
4099
4082
|
const out = { rotate: "0deg", scale: null, translate: null };
|
|
@@ -4150,35 +4133,6 @@ function readIndividualTransforms(el) {
|
|
|
4150
4133
|
out.translate = cs.translate && cs.translate !== "none" ? cs.translate : null;
|
|
4151
4134
|
return out;
|
|
4152
4135
|
}
|
|
4153
|
-
function hasBBoxAffectingTransform(el) {
|
|
4154
|
-
const cs = getComputedStyle(el);
|
|
4155
|
-
const t = cs.transform || "none";
|
|
4156
|
-
const hasMatrix = t !== "none" && !/^matrix\(\s*1\s*,\s*0\s*,\s*0\s*,\s*1\s*,\s*0\s*,\s*0\s*\)$/i.test(t);
|
|
4157
|
-
if (hasMatrix) return true;
|
|
4158
|
-
const r = cs.rotate && cs.rotate !== "none" && cs.rotate !== "0deg";
|
|
4159
|
-
const s = cs.scale && cs.scale !== "none" && cs.scale !== "1";
|
|
4160
|
-
const tr = cs.translate && cs.translate !== "none" && cs.translate !== "0px 0px";
|
|
4161
|
-
return Boolean(r || s || tr);
|
|
4162
|
-
}
|
|
4163
|
-
function parseTransformOriginPx(cs, w, h) {
|
|
4164
|
-
const raw = (cs.transformOrigin || "0 0").trim().split(/\s+/);
|
|
4165
|
-
const [oxRaw, oyRaw] = [raw[0] || "0", raw[1] || "0"];
|
|
4166
|
-
const toPx = (token, size) => {
|
|
4167
|
-
const t = token.toLowerCase();
|
|
4168
|
-
if (t === "left" || t === "top") return 0;
|
|
4169
|
-
if (t === "center") return size / 2;
|
|
4170
|
-
if (t === "right") return size;
|
|
4171
|
-
if (t === "bottom") return size;
|
|
4172
|
-
if (t.endsWith("px")) return parseFloat(t) || 0;
|
|
4173
|
-
if (t.endsWith("%")) return (parseFloat(t) || 0) * size / 100;
|
|
4174
|
-
if (/^-?\d+(\.\d+)?$/.test(t)) return parseFloat(t) || 0;
|
|
4175
|
-
return 0;
|
|
4176
|
-
};
|
|
4177
|
-
return {
|
|
4178
|
-
ox: toPx(oxRaw, w),
|
|
4179
|
-
oy: toPx(oyRaw, h)
|
|
4180
|
-
};
|
|
4181
|
-
}
|
|
4182
4136
|
var __measureHost = null;
|
|
4183
4137
|
function getMeasureHost() {
|
|
4184
4138
|
if (__measureHost) return __measureHost;
|
|
@@ -4213,51 +4167,254 @@ function readTotalTransformMatrix(t) {
|
|
|
4213
4167
|
host.removeChild(tmp);
|
|
4214
4168
|
return M;
|
|
4215
4169
|
}
|
|
4216
|
-
function
|
|
4170
|
+
function hasBBoxAffectingTransform(el) {
|
|
4217
4171
|
const cs = getComputedStyle(el);
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
if (
|
|
4221
|
-
|
|
4222
|
-
|
|
4172
|
+
const t = cs.transform || "none";
|
|
4173
|
+
const hasMatrix = t !== "none" && !/^matrix\(\s*1\s*,\s*0\s*,\s*0\s*,\s*1\s*,\s*0\s*,\s*0\s*\)$/i.test(t);
|
|
4174
|
+
if (hasMatrix) return true;
|
|
4175
|
+
const r = cs.rotate && cs.rotate !== "none" && cs.rotate !== "0deg";
|
|
4176
|
+
const s = cs.scale && cs.scale !== "none" && cs.scale !== "1";
|
|
4177
|
+
const tr = cs.translate && cs.translate !== "none" && cs.translate !== "0px 0px";
|
|
4178
|
+
return Boolean(r || s || tr);
|
|
4223
4179
|
}
|
|
4224
|
-
function
|
|
4225
|
-
|
|
4226
|
-
if (
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
} catch {
|
|
4232
|
-
}
|
|
4233
|
-
}
|
|
4180
|
+
function matrixFromComputed(el) {
|
|
4181
|
+
const tr = getComputedStyle(el).transform;
|
|
4182
|
+
if (!tr || tr === "none") return new DOMMatrix();
|
|
4183
|
+
try {
|
|
4184
|
+
return new DOMMatrix(tr);
|
|
4185
|
+
} catch {
|
|
4186
|
+
return new WebKitCSSMatrix(tr);
|
|
4234
4187
|
}
|
|
4235
|
-
return false;
|
|
4236
4188
|
}
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
const
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4189
|
+
|
|
4190
|
+
// src/core/capture.js
|
|
4191
|
+
async function captureDOM(element, options) {
|
|
4192
|
+
if (!element) throw new Error("Element cannot be null or undefined");
|
|
4193
|
+
applyCachePolicy(options.cache);
|
|
4194
|
+
const fast = options.fast;
|
|
4195
|
+
const outerTransforms = options.outerTransforms !== false;
|
|
4196
|
+
const outerShadows = !!options.outerShadows;
|
|
4197
|
+
let state = { element, options, plugins: options.plugins };
|
|
4198
|
+
let clone, classCSS, styleCache;
|
|
4199
|
+
let fontsCSS = "";
|
|
4200
|
+
let baseCSS = "";
|
|
4201
|
+
let dataURL;
|
|
4202
|
+
let svgString;
|
|
4203
|
+
let rootTransform2D = null;
|
|
4204
|
+
await runHook("beforeSnap", state);
|
|
4205
|
+
await runHook("beforeClone", state);
|
|
4206
|
+
const undoClamp = lineClamp(state.element);
|
|
4207
|
+
try {
|
|
4208
|
+
({ clone, classCSS, styleCache } = await prepareClone(state.element, state.options));
|
|
4209
|
+
if (!outerTransforms && clone) {
|
|
4210
|
+
rootTransform2D = normalizeRootTransforms(state.element, clone);
|
|
4211
|
+
}
|
|
4212
|
+
if (!outerShadows && clone) {
|
|
4213
|
+
stripRootShadows(state.element, clone);
|
|
4214
|
+
}
|
|
4215
|
+
} finally {
|
|
4216
|
+
undoClamp();
|
|
4254
4217
|
}
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4218
|
+
state = { clone, classCSS, styleCache, ...state };
|
|
4219
|
+
await runHook("afterClone", state);
|
|
4220
|
+
sanitizeCloneForXHTML(state.clone);
|
|
4221
|
+
if (state.options?.excludeMode === "remove") {
|
|
4222
|
+
try {
|
|
4223
|
+
shrinkAutoSizeBoxes(state.element, state.clone, state.styleCache);
|
|
4224
|
+
} catch (e) {
|
|
4225
|
+
console.warn("[snapdom] shrink pass failed:", e);
|
|
4226
|
+
}
|
|
4227
|
+
}
|
|
4228
|
+
try {
|
|
4229
|
+
await ligatureIconToImage(state.clone, state.element);
|
|
4230
|
+
} catch {
|
|
4231
|
+
}
|
|
4232
|
+
await new Promise((resolve) => {
|
|
4233
|
+
idle(async () => {
|
|
4234
|
+
await inlineImages(state.clone, state.options);
|
|
4235
|
+
resolve();
|
|
4236
|
+
}, { fast });
|
|
4237
|
+
});
|
|
4238
|
+
await new Promise((resolve) => {
|
|
4239
|
+
idle(async () => {
|
|
4240
|
+
await inlineBackgroundImages(state.element, state.clone, state.styleCache, state.options);
|
|
4241
|
+
resolve();
|
|
4242
|
+
}, { fast });
|
|
4243
|
+
});
|
|
4244
|
+
if (options.embedFonts) {
|
|
4245
|
+
await new Promise((resolve) => {
|
|
4246
|
+
idle(async () => {
|
|
4247
|
+
const required = collectUsedFontVariants(state.element);
|
|
4248
|
+
const usedCodepoints = collectUsedCodepoints(state.element);
|
|
4249
|
+
if (isSafari()) {
|
|
4250
|
+
const families = new Set(
|
|
4251
|
+
Array.from(required).map((k) => String(k).split("__")[0]).filter(Boolean)
|
|
4252
|
+
);
|
|
4253
|
+
await ensureFontsReady(families, 1);
|
|
4254
|
+
}
|
|
4255
|
+
fontsCSS = await embedCustomFonts({
|
|
4256
|
+
required,
|
|
4257
|
+
usedCodepoints,
|
|
4258
|
+
preCached: false,
|
|
4259
|
+
exclude: state.options.excludeFonts,
|
|
4260
|
+
useProxy: state.options.useProxy
|
|
4261
|
+
});
|
|
4262
|
+
resolve();
|
|
4263
|
+
}, { fast });
|
|
4264
|
+
});
|
|
4265
|
+
}
|
|
4266
|
+
const usedTags = collectUsedTagNames(state.clone).sort();
|
|
4267
|
+
const tagKey = usedTags.join(",");
|
|
4268
|
+
if (cache.baseStyle.has(tagKey)) {
|
|
4269
|
+
baseCSS = cache.baseStyle.get(tagKey);
|
|
4270
|
+
} else {
|
|
4271
|
+
await new Promise((resolve) => {
|
|
4272
|
+
idle(() => {
|
|
4273
|
+
baseCSS = generateDedupedBaseCSS(usedTags);
|
|
4274
|
+
cache.baseStyle.set(tagKey, baseCSS);
|
|
4275
|
+
resolve();
|
|
4276
|
+
}, { fast });
|
|
4277
|
+
});
|
|
4278
|
+
}
|
|
4279
|
+
state = { fontsCSS, baseCSS, ...state };
|
|
4280
|
+
await runHook("beforeRender", state);
|
|
4281
|
+
await new Promise((resolve) => {
|
|
4282
|
+
idle(() => {
|
|
4283
|
+
const csEl = getComputedStyle(state.element);
|
|
4284
|
+
const rect = state.element.getBoundingClientRect();
|
|
4285
|
+
let w0 = Math.max(1, limitDecimals(state.element.offsetWidth || parseFloat(csEl.width) || rect.width || 1));
|
|
4286
|
+
let h0 = Math.max(1, limitDecimals(state.element.offsetHeight || parseFloat(csEl.height) || rect.height || 1));
|
|
4287
|
+
if (state.options?.excludeMode === "remove") {
|
|
4288
|
+
const hEst = estimateKeptHeight(state.element, state.options);
|
|
4289
|
+
const EPS = 1;
|
|
4290
|
+
if (Number.isFinite(hEst) && hEst > 0) {
|
|
4291
|
+
h0 = Math.max(1, Math.min(h0, limitDecimals(hEst + EPS)));
|
|
4292
|
+
}
|
|
4293
|
+
}
|
|
4294
|
+
const coerceNum = (v, def = NaN) => {
|
|
4295
|
+
const n = typeof v === "string" ? parseFloat(v) : v;
|
|
4296
|
+
return Number.isFinite(n) ? n : def;
|
|
4297
|
+
};
|
|
4298
|
+
const optW = coerceNum(state.options.width);
|
|
4299
|
+
const optH = coerceNum(state.options.height);
|
|
4300
|
+
let w = w0, h = h0;
|
|
4301
|
+
const hasW = Number.isFinite(optW);
|
|
4302
|
+
const hasH = Number.isFinite(optH);
|
|
4303
|
+
const aspect0 = h0 > 0 ? w0 / h0 : 1;
|
|
4304
|
+
if (hasW && hasH) {
|
|
4305
|
+
w = Math.max(1, limitDecimals(optW));
|
|
4306
|
+
h = Math.max(1, limitDecimals(optH));
|
|
4307
|
+
} else if (hasW) {
|
|
4308
|
+
w = Math.max(1, limitDecimals(optW));
|
|
4309
|
+
h = Math.max(1, limitDecimals(w / (aspect0 || 1)));
|
|
4310
|
+
} else if (hasH) {
|
|
4311
|
+
h = Math.max(1, limitDecimals(optH));
|
|
4312
|
+
w = Math.max(1, limitDecimals(h * (aspect0 || 1)));
|
|
4313
|
+
} else {
|
|
4314
|
+
w = w0;
|
|
4315
|
+
h = h0;
|
|
4316
|
+
}
|
|
4317
|
+
let minX = 0, minY = 0, maxX = w0, maxY = h0;
|
|
4318
|
+
if (!outerTransforms && rootTransform2D && Number.isFinite(rootTransform2D.a)) {
|
|
4319
|
+
const M2 = {
|
|
4320
|
+
a: rootTransform2D.a,
|
|
4321
|
+
b: rootTransform2D.b || 0,
|
|
4322
|
+
c: rootTransform2D.c || 0,
|
|
4323
|
+
d: rootTransform2D.d || 1,
|
|
4324
|
+
e: 0,
|
|
4325
|
+
f: 0
|
|
4326
|
+
};
|
|
4327
|
+
const bb2 = bboxWithOriginFull(w0, h0, M2, 0, 0);
|
|
4328
|
+
minX = limitDecimals(bb2.minX);
|
|
4329
|
+
minY = limitDecimals(bb2.minY);
|
|
4330
|
+
maxX = limitDecimals(bb2.maxX);
|
|
4331
|
+
maxY = limitDecimals(bb2.maxY);
|
|
4332
|
+
} else {
|
|
4333
|
+
const useTFBBox = outerTransforms && hasTFBBox(state.element);
|
|
4334
|
+
if (useTFBBox) {
|
|
4335
|
+
const baseTransform2 = csEl.transform && csEl.transform !== "none" ? csEl.transform : "";
|
|
4336
|
+
const ind2 = readIndividualTransforms(state.element);
|
|
4337
|
+
const TOTAL = readTotalTransformMatrix({
|
|
4338
|
+
baseTransform: baseTransform2,
|
|
4339
|
+
rotate: ind2.rotate || "0deg",
|
|
4340
|
+
scale: ind2.scale,
|
|
4341
|
+
translate: ind2.translate
|
|
4342
|
+
});
|
|
4343
|
+
const { ox: ox2, oy: oy2 } = parseTransformOriginPx(csEl, w0, h0);
|
|
4344
|
+
const M = TOTAL.is2D ? TOTAL : new DOMMatrix(TOTAL.toString());
|
|
4345
|
+
const bb = bboxWithOriginFull(w0, h0, M, ox2, oy2);
|
|
4346
|
+
minX = limitDecimals(bb.minX);
|
|
4347
|
+
minY = limitDecimals(bb.minY);
|
|
4348
|
+
maxX = limitDecimals(bb.maxX);
|
|
4349
|
+
maxY = limitDecimals(bb.maxY);
|
|
4350
|
+
}
|
|
4351
|
+
}
|
|
4352
|
+
const bleedShadow = parseBoxShadow(csEl);
|
|
4353
|
+
const bleedBlur = parseFilterBlur(csEl);
|
|
4354
|
+
const bleedOutline = parseOutline(csEl);
|
|
4355
|
+
const drop = parseFilterDropShadows(csEl);
|
|
4356
|
+
const bleed = !outerShadows ? { top: 0, right: 0, bottom: 0, left: 0 } : {
|
|
4357
|
+
top: limitDecimals(bleedShadow.top + bleedBlur.top + bleedOutline.top + drop.bleed.top),
|
|
4358
|
+
right: limitDecimals(bleedShadow.right + bleedBlur.right + bleedOutline.right + drop.bleed.right),
|
|
4359
|
+
bottom: limitDecimals(bleedShadow.bottom + bleedBlur.bottom + bleedOutline.bottom + drop.bleed.bottom),
|
|
4360
|
+
left: limitDecimals(bleedShadow.left + bleedBlur.left + bleedOutline.left + drop.bleed.left)
|
|
4361
|
+
};
|
|
4362
|
+
minX = limitDecimals(minX - bleed.left);
|
|
4363
|
+
minY = limitDecimals(minY - bleed.top);
|
|
4364
|
+
maxX = limitDecimals(maxX + bleed.right);
|
|
4365
|
+
maxY = limitDecimals(maxY + bleed.bottom);
|
|
4366
|
+
const vbW0 = Math.max(1, limitDecimals(maxX - minX));
|
|
4367
|
+
const vbH0 = Math.max(1, limitDecimals(maxY - minY));
|
|
4368
|
+
const scaleW = hasW || hasH ? limitDecimals(w / w0) : 1;
|
|
4369
|
+
const scaleH = hasH || hasW ? limitDecimals(h / h0) : 1;
|
|
4370
|
+
const outW = Math.max(1, limitDecimals(vbW0 * scaleW));
|
|
4371
|
+
const outH = Math.max(1, limitDecimals(vbH0 * scaleH));
|
|
4372
|
+
const svgNS = "http://www.w3.org/2000/svg";
|
|
4373
|
+
const basePad = isSafari() ? 1 : 0;
|
|
4374
|
+
const extraPad = !outerTransforms ? 1 : 0;
|
|
4375
|
+
const pad = limitDecimals(basePad + extraPad);
|
|
4376
|
+
const fo = document.createElementNS(svgNS, "foreignObject");
|
|
4377
|
+
const vbMinX = limitDecimals(minX);
|
|
4378
|
+
const vbMinY = limitDecimals(minY);
|
|
4379
|
+
fo.setAttribute("x", String(limitDecimals(-(vbMinX - pad))));
|
|
4380
|
+
fo.setAttribute("y", String(limitDecimals(-(vbMinY - pad))));
|
|
4381
|
+
fo.setAttribute("width", String(limitDecimals(w0 + pad * 2)));
|
|
4382
|
+
fo.setAttribute("height", String(limitDecimals(h0 + pad * 2)));
|
|
4383
|
+
fo.style.overflow = "visible";
|
|
4384
|
+
const styleTag = document.createElement("style");
|
|
4385
|
+
styleTag.textContent = state.baseCSS + state.fontsCSS + "svg{overflow:visible;} foreignObject{overflow:visible;}" + state.classCSS;
|
|
4386
|
+
fo.appendChild(styleTag);
|
|
4387
|
+
const container = document.createElement("div");
|
|
4388
|
+
container.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
|
|
4389
|
+
container.style.width = `${limitDecimals(w0)}px`;
|
|
4390
|
+
container.style.height = `${limitDecimals(h0)}px`;
|
|
4391
|
+
container.style.overflow = "visible";
|
|
4392
|
+
state.clone.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
|
|
4393
|
+
container.appendChild(state.clone);
|
|
4394
|
+
fo.appendChild(container);
|
|
4395
|
+
const serializer = new XMLSerializer();
|
|
4396
|
+
const foString = serializer.serializeToString(fo);
|
|
4397
|
+
const vbW = limitDecimals(vbW0 + pad * 2);
|
|
4398
|
+
const vbH = limitDecimals(vbH0 + pad * 2);
|
|
4399
|
+
const wantsSize = hasW || hasH;
|
|
4400
|
+
options.meta = { w0, h0, vbW, vbH, targetW: w, targetH: h };
|
|
4401
|
+
const svgOutW = isSafari() && wantsSize ? vbW : limitDecimals(outW + pad * 2);
|
|
4402
|
+
const svgOutH = isSafari() && wantsSize ? vbH : limitDecimals(outH + pad * 2);
|
|
4403
|
+
const svgHeader = `<svg xmlns="${svgNS}" width="${svgOutW}" height="${svgOutH}" viewBox="0 0 ${vbW} ${vbH}">`;
|
|
4404
|
+
const svgFooter = "</svg>";
|
|
4405
|
+
svgString = svgHeader + foString + svgFooter;
|
|
4406
|
+
dataURL = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgString)}`;
|
|
4407
|
+
state = { svgString, dataURL, ...state };
|
|
4408
|
+
resolve();
|
|
4409
|
+
}, { fast });
|
|
4410
|
+
});
|
|
4411
|
+
await runHook("afterRender", state);
|
|
4412
|
+
const sandbox = document.getElementById("snapdom-sandbox");
|
|
4413
|
+
if (sandbox && sandbox.style.position === "absolute") sandbox.remove();
|
|
4414
|
+
return state.dataURL;
|
|
4415
|
+
}
|
|
4416
|
+
function hasTFBBox(el) {
|
|
4417
|
+
return hasBBoxAffectingTransform(el);
|
|
4261
4418
|
}
|
|
4262
4419
|
|
|
4263
4420
|
// src/core/context.js
|