@zumer/snapdom 2.0.0-dev.1 → 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/README.md +5 -2
- package/dist/snapdom.js +901 -515
- package/dist/snapdom.min.js +7 -7
- package/dist/snapdom.min.mjs +8 -8
- package/dist/snapdom.mjs +878 -498
- 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
|
**/
|
|
@@ -328,19 +328,6 @@ async function snapFetch(url, options = {}) {
|
|
|
328
328
|
}
|
|
329
329
|
|
|
330
330
|
// src/utils/image.js
|
|
331
|
-
function createBackground(baseCanvas, backgroundColor) {
|
|
332
|
-
if (!backgroundColor || !baseCanvas.width || !baseCanvas.height) {
|
|
333
|
-
return baseCanvas;
|
|
334
|
-
}
|
|
335
|
-
const temp = document.createElement("canvas");
|
|
336
|
-
temp.width = baseCanvas.width;
|
|
337
|
-
temp.height = baseCanvas.height;
|
|
338
|
-
const ctx = temp.getContext("2d");
|
|
339
|
-
ctx.fillStyle = backgroundColor;
|
|
340
|
-
ctx.fillRect(0, 0, temp.width, temp.height);
|
|
341
|
-
ctx.drawImage(baseCanvas, 0, 0);
|
|
342
|
-
return temp;
|
|
343
|
-
}
|
|
344
331
|
async function inlineSingleBackgroundEntry(entry, options = {}) {
|
|
345
332
|
const isGradient = /^((repeating-)?(linear|radial|conic)-gradient)\(/i.test(entry);
|
|
346
333
|
if (isGradient || entry.trim() === "none") {
|
|
@@ -616,11 +603,20 @@ function idle(fn, { fast = false } = {}) {
|
|
|
616
603
|
}
|
|
617
604
|
}
|
|
618
605
|
function isSafari() {
|
|
619
|
-
|
|
620
|
-
const
|
|
621
|
-
const
|
|
622
|
-
const
|
|
623
|
-
|
|
606
|
+
if (typeof navigator === "undefined") return false;
|
|
607
|
+
const ua = navigator.userAgent || "";
|
|
608
|
+
const uaLower = ua.toLowerCase();
|
|
609
|
+
const isSafariUA = uaLower.includes("safari") && !uaLower.includes("chrome") && !uaLower.includes("crios") && // Chrome on iOS
|
|
610
|
+
!uaLower.includes("fxios") && // Firefox on iOS
|
|
611
|
+
!uaLower.includes("android");
|
|
612
|
+
const isWebKit = /applewebkit/i.test(ua);
|
|
613
|
+
const isMobile = /mobile/i.test(ua);
|
|
614
|
+
const missingSafariToken = !/safari/i.test(ua);
|
|
615
|
+
const isUIWebView = isWebKit && isMobile && missingSafariToken;
|
|
616
|
+
const isWeChatUA = /(micromessenger|wxwork|wecom|windowswechat|macwechat)/i.test(ua);
|
|
617
|
+
const isBaiduUA = /(baiduboxapp|baidubrowser|baidusearch|baiduboxlite)/i.test(uaLower);
|
|
618
|
+
const isIOSWebKit = /ipad|iphone|ipod/.test(uaLower) && isWebKit;
|
|
619
|
+
return isSafariUA || isUIWebView || isWeChatUA || isBaiduUA || isIOSWebKit;
|
|
624
620
|
}
|
|
625
621
|
|
|
626
622
|
// src/modules/styles.js
|
|
@@ -665,6 +661,22 @@ function snapshotComputedStyleFull(style, options = {}) {
|
|
|
665
661
|
}
|
|
666
662
|
out[prop] = val;
|
|
667
663
|
}
|
|
664
|
+
const EXTRA_TEXT_DECORATION_PROPS = [
|
|
665
|
+
"text-decoration-line",
|
|
666
|
+
"text-decoration-color",
|
|
667
|
+
"text-decoration-style",
|
|
668
|
+
"text-decoration-thickness",
|
|
669
|
+
"text-underline-offset",
|
|
670
|
+
"text-decoration-skip-ink"
|
|
671
|
+
];
|
|
672
|
+
for (const prop of EXTRA_TEXT_DECORATION_PROPS) {
|
|
673
|
+
if (out[prop]) continue;
|
|
674
|
+
try {
|
|
675
|
+
const v = style.getPropertyValue(prop);
|
|
676
|
+
if (v) out[prop] = v;
|
|
677
|
+
} catch {
|
|
678
|
+
}
|
|
679
|
+
}
|
|
668
680
|
if (options.embedFonts) {
|
|
669
681
|
const EXTRA_FONT_PROPS = [
|
|
670
682
|
"font-feature-settings",
|
|
@@ -907,7 +919,7 @@ function resolveCSSVars(sourceEl, cloneEl) {
|
|
|
907
919
|
}
|
|
908
920
|
}
|
|
909
921
|
|
|
910
|
-
// src/
|
|
922
|
+
// src/utils/clone.helpers.js
|
|
911
923
|
function idleCallback(childList, callback, fast) {
|
|
912
924
|
return Promise.all(childList.map((child) => {
|
|
913
925
|
return new Promise((resolve) => {
|
|
@@ -1119,6 +1131,134 @@ async function rasterizeIframe(iframe, sessionCache, options) {
|
|
|
1119
1131
|
wrapper.appendChild(imgEl);
|
|
1120
1132
|
return wrapper;
|
|
1121
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
|
|
1122
1262
|
async function deepClone(node, sessionCache, options) {
|
|
1123
1263
|
if (!node) throw new Error("Invalid node");
|
|
1124
1264
|
const clonedAssignedNodes = /* @__PURE__ */ new Set();
|
|
@@ -1421,17 +1561,19 @@ var defaultIconFonts = [
|
|
|
1421
1561
|
/layui/i,
|
|
1422
1562
|
/lucide/i
|
|
1423
1563
|
];
|
|
1564
|
+
var ICON_FONT_URLS = Object.assign({
|
|
1565
|
+
materialIconsFilled: "https://fonts.gstatic.com/s/materialicons/v48/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.woff2",
|
|
1566
|
+
materialIconsOutlined: "https://fonts.gstatic.com/s/materialiconsoutlined/v110/gok-H7zzDkdnRel8-DQ6KAXJ69wP1tGnf4ZGhUcel5euIg.woff2",
|
|
1567
|
+
materialIconsRound: "https://fonts.gstatic.com/s/materialiconsround/v109/LDItaoyNOAY6Uewc665JcIzCKsKc_M9flwmPq_HTTw.woff2",
|
|
1568
|
+
materialIconsSharp: "https://fonts.gstatic.com/s/materialiconssharp/v110/oPWQ_lt5nv4pWNJpghLP75WiFR4kLh3kvmvRImcycg.woff2"
|
|
1569
|
+
}, typeof window !== "undefined" && window.__SNAPDOM_ICON_FONTS__ || {});
|
|
1424
1570
|
var userIconFonts = [];
|
|
1425
1571
|
function extendIconFonts(fonts) {
|
|
1426
1572
|
const list = Array.isArray(fonts) ? fonts : [fonts];
|
|
1427
1573
|
for (const f of list) {
|
|
1428
|
-
if (f instanceof RegExp)
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
userIconFonts.push(new RegExp(f, "i"));
|
|
1432
|
-
} else {
|
|
1433
|
-
console.warn("[snapdom] Ignored invalid iconFont value:", f);
|
|
1434
|
-
}
|
|
1574
|
+
if (f instanceof RegExp) userIconFonts.push(f);
|
|
1575
|
+
else if (typeof f === "string") userIconFonts.push(new RegExp(f, "i"));
|
|
1576
|
+
else console.warn("[snapdom] Ignored invalid iconFont value:", f);
|
|
1435
1577
|
}
|
|
1436
1578
|
}
|
|
1437
1579
|
function isIconFont2(input) {
|
|
@@ -1447,10 +1589,62 @@ function isMaterialFamily(family = "") {
|
|
|
1447
1589
|
const s = String(family).toLowerCase();
|
|
1448
1590
|
return /\bmaterial\s*icons\b/.test(s) || /\bmaterial\s*symbols\b/.test(s);
|
|
1449
1591
|
}
|
|
1592
|
+
var loadedCanvasFamilies = /* @__PURE__ */ new Map();
|
|
1593
|
+
function parseAxes(variation = "") {
|
|
1594
|
+
const out = /* @__PURE__ */ Object.create(null);
|
|
1595
|
+
const v = String(variation || "");
|
|
1596
|
+
const rx = /['"]?\s*([A-Za-z]{3,4})\s*['"]?\s*([+-]?\d+(?:\.\d+)?)\s*/g;
|
|
1597
|
+
let m;
|
|
1598
|
+
while (m = rx.exec(v)) out[m[1].toUpperCase()] = Number(m[2]);
|
|
1599
|
+
return out;
|
|
1600
|
+
}
|
|
1601
|
+
async function ensureLigatureCanvasFont(cssFamily, className, axes) {
|
|
1602
|
+
const fam = String(cssFamily || "");
|
|
1603
|
+
const lowerFam = fam.toLowerCase();
|
|
1604
|
+
const cls = String(className || "").toLowerCase();
|
|
1605
|
+
if (/\bmaterial\s*icons\b/.test(lowerFam) && !/\bsymbols\b/.test(lowerFam)) {
|
|
1606
|
+
return { familyForMeasure: fam, familyForCanvas: fam };
|
|
1607
|
+
}
|
|
1608
|
+
const isSymbols = /\bmaterial\s*symbols\b/.test(lowerFam);
|
|
1609
|
+
if (!isSymbols) {
|
|
1610
|
+
return { familyForMeasure: fam, familyForCanvas: fam };
|
|
1611
|
+
}
|
|
1612
|
+
const FILL = axes && (axes.FILL ?? axes.fill);
|
|
1613
|
+
let style = "outlined";
|
|
1614
|
+
if (/\brounded\b/.test(cls) || /\bround\b/.test(cls)) style = "rounded";
|
|
1615
|
+
else if (/\bsharp\b/.test(cls)) style = "sharp";
|
|
1616
|
+
else if (/\boutlined\b/.test(cls)) style = "outlined";
|
|
1617
|
+
const filled = FILL === 1;
|
|
1618
|
+
let pick = null;
|
|
1619
|
+
if (filled) {
|
|
1620
|
+
if (style === "outlined" && ICON_FONT_URLS.materialIconsFilled) {
|
|
1621
|
+
pick = { url: ICON_FONT_URLS.materialIconsFilled, alias: "snapdom-mi-filled" };
|
|
1622
|
+
} else if (style === "rounded" && ICON_FONT_URLS.materialIconsRound) {
|
|
1623
|
+
pick = { url: ICON_FONT_URLS.materialIconsRound, alias: "snapdom-mi-round" };
|
|
1624
|
+
} else if (style === "sharp" && ICON_FONT_URLS.materialIconsSharp) {
|
|
1625
|
+
pick = { url: ICON_FONT_URLS.materialIconsSharp, alias: "snapdom-mi-sharp" };
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
if (!pick) {
|
|
1629
|
+
return { familyForMeasure: fam, familyForCanvas: fam };
|
|
1630
|
+
}
|
|
1631
|
+
if (!loadedCanvasFamilies.has(pick.alias)) {
|
|
1632
|
+
try {
|
|
1633
|
+
const ff = new FontFace(pick.alias, `url(${pick.url})`, { style: "normal", weight: "400" });
|
|
1634
|
+
document.fonts.add(ff);
|
|
1635
|
+
await ff.load();
|
|
1636
|
+
loadedCanvasFamilies.set(pick.alias, true);
|
|
1637
|
+
} catch {
|
|
1638
|
+
return { familyForMeasure: fam, familyForCanvas: fam };
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
const quoted = `"${pick.alias}"`;
|
|
1642
|
+
return { familyForMeasure: quoted, familyForCanvas: quoted };
|
|
1643
|
+
}
|
|
1450
1644
|
async function ensureMaterialFontsReady(family = "Material Icons", px = 24) {
|
|
1451
1645
|
try {
|
|
1452
1646
|
await Promise.all([
|
|
1453
|
-
document.fonts.load(`400 ${px}px "${family.replace(/["']/g, "")}"`),
|
|
1647
|
+
document.fonts.load(`400 ${px}px "${String(family).replace(/["']/g, "")}"`),
|
|
1454
1648
|
document.fonts.ready
|
|
1455
1649
|
]);
|
|
1456
1650
|
} catch {
|
|
@@ -1458,10 +1652,8 @@ async function ensureMaterialFontsReady(family = "Material Icons", px = 24) {
|
|
|
1458
1652
|
}
|
|
1459
1653
|
function resolvePaintColor(cs) {
|
|
1460
1654
|
let fill = cs.getPropertyValue("-webkit-text-fill-color")?.trim() || "";
|
|
1461
|
-
const isTransparent = /rgba?\(\s*0\s*,\s*0\s*,\s*0\s*,\s*0\s*\)/i.test(fill);
|
|
1462
|
-
if (fill && !isTransparent && fill.toLowerCase() !== "currentcolor")
|
|
1463
|
-
return fill;
|
|
1464
|
-
}
|
|
1655
|
+
const isTransparent = /^transparent$/i.test(fill) || /rgba?\(\s*0\s*,\s*0\s*,\s*0\s*,\s*0\s*\)/i.test(fill);
|
|
1656
|
+
if (fill && !isTransparent && fill.toLowerCase() !== "currentcolor") return fill;
|
|
1465
1657
|
const c = cs.color?.trim();
|
|
1466
1658
|
return c && c !== "inherit" ? c : "#000";
|
|
1467
1659
|
}
|
|
@@ -1470,18 +1662,21 @@ async function materialIconToImage(ligatureText, {
|
|
|
1470
1662
|
weight = "normal",
|
|
1471
1663
|
fontSize = 32,
|
|
1472
1664
|
color = "#000",
|
|
1473
|
-
variation = ""
|
|
1665
|
+
variation = "",
|
|
1666
|
+
className = ""
|
|
1474
1667
|
} = {}) {
|
|
1475
1668
|
const fam = String(family || "").replace(/^['"]+|['"]+$/g, "");
|
|
1476
1669
|
const dpr = window.devicePixelRatio || 1;
|
|
1477
|
-
|
|
1670
|
+
const axes = parseAxes(variation);
|
|
1671
|
+
const { familyForMeasure, familyForCanvas } = await ensureLigatureCanvasFont(fam, className, axes);
|
|
1672
|
+
await ensureMaterialFontsReady(familyForCanvas.replace(/^["']+|["']+$/g, ""), fontSize);
|
|
1478
1673
|
const span = document.createElement("span");
|
|
1479
1674
|
span.textContent = ligatureText;
|
|
1480
1675
|
span.style.position = "absolute";
|
|
1481
1676
|
span.style.visibility = "hidden";
|
|
1482
1677
|
span.style.left = "-99999px";
|
|
1483
1678
|
span.style.whiteSpace = "nowrap";
|
|
1484
|
-
span.style.fontFamily =
|
|
1679
|
+
span.style.fontFamily = familyForMeasure;
|
|
1485
1680
|
span.style.fontWeight = String(weight || "normal");
|
|
1486
1681
|
span.style.fontSize = `${fontSize}px`;
|
|
1487
1682
|
span.style.lineHeight = "1";
|
|
@@ -1489,7 +1684,6 @@ async function materialIconToImage(ligatureText, {
|
|
|
1489
1684
|
span.style.padding = "0";
|
|
1490
1685
|
span.style.fontFeatureSettings = "'liga' 1";
|
|
1491
1686
|
span.style.fontVariantLigatures = "normal";
|
|
1492
|
-
if (variation) span.style.fontVariationSettings = variation;
|
|
1493
1687
|
span.style.color = color;
|
|
1494
1688
|
document.body.appendChild(span);
|
|
1495
1689
|
const rect = span.getBoundingClientRect();
|
|
@@ -1501,7 +1695,7 @@ async function materialIconToImage(ligatureText, {
|
|
|
1501
1695
|
canvas.height = height * dpr;
|
|
1502
1696
|
const ctx = canvas.getContext("2d");
|
|
1503
1697
|
ctx.scale(dpr, dpr);
|
|
1504
|
-
ctx.font = `${weight ? `${weight} ` : ""}${fontSize}px
|
|
1698
|
+
ctx.font = `${weight ? `${weight} ` : ""}${fontSize}px ${familyForCanvas}`;
|
|
1505
1699
|
ctx.textAlign = "left";
|
|
1506
1700
|
ctx.textBaseline = "top";
|
|
1507
1701
|
ctx.fillStyle = color;
|
|
@@ -1529,21 +1723,23 @@ async function ligatureIconToImage(cloneRoot, sourceRoot) {
|
|
|
1529
1723
|
const el = cloneNodes[i];
|
|
1530
1724
|
const src = sourceNodes[i] || null;
|
|
1531
1725
|
try {
|
|
1532
|
-
const
|
|
1533
|
-
const family =
|
|
1726
|
+
const cs = src ? getComputedStyle(src) : getComputedStyle(el);
|
|
1727
|
+
const family = cs.fontFamily || "Material Icons";
|
|
1534
1728
|
if (!isMaterialFamily(family)) continue;
|
|
1535
1729
|
const text = (src || el).textContent.trim();
|
|
1536
1730
|
if (!text) continue;
|
|
1537
|
-
const size = parseInt(
|
|
1538
|
-
const weight =
|
|
1539
|
-
const color = resolvePaintColor(
|
|
1540
|
-
const variation =
|
|
1731
|
+
const size = parseInt(cs.fontSize, 10) || 24;
|
|
1732
|
+
const weight = cs.fontWeight && cs.fontWeight !== "normal" ? cs.fontWeight : "normal";
|
|
1733
|
+
const color = resolvePaintColor(cs);
|
|
1734
|
+
const variation = cs.fontVariationSettings && cs.fontVariationSettings !== "normal" ? cs.fontVariationSettings : "";
|
|
1735
|
+
const className = (src || el).className || "";
|
|
1541
1736
|
const { dataUrl, width, height } = await materialIconToImage(text, {
|
|
1542
1737
|
family,
|
|
1543
1738
|
weight,
|
|
1544
1739
|
fontSize: size,
|
|
1545
1740
|
color,
|
|
1546
|
-
variation
|
|
1741
|
+
variation,
|
|
1742
|
+
className
|
|
1547
1743
|
});
|
|
1548
1744
|
el.textContent = "";
|
|
1549
1745
|
const img = el.ownerDocument.createElement("img");
|
|
@@ -1909,7 +2105,7 @@ function dedupeFontFaces(cssText) {
|
|
|
1909
2105
|
const styleSpec = (block.match(/font-style:\s*([^;]+);/i)?.[1] || "normal").trim();
|
|
1910
2106
|
const stretchSpec = (block.match(/font-stretch:\s*([^;]+);/i)?.[1] || "100%").trim();
|
|
1911
2107
|
const urange = (block.match(/unicode-range:\s*([^;]+);/i)?.[1] || "").trim();
|
|
1912
|
-
const srcRaw = (block.match(/src\s*:\s*([^;]+)
|
|
2108
|
+
const srcRaw = (block.match(/src\s*:\s*([^;}]+)[;}]/i)?.[1] || "").trim();
|
|
1913
2109
|
const urls = extractSrcUrls(srcRaw, location.href);
|
|
1914
2110
|
const srcPart = urls.length ? urls.map((u) => String(u).toLowerCase()).sort().join("|") : srcRaw.toLowerCase();
|
|
1915
2111
|
const key = [
|
|
@@ -2117,7 +2313,7 @@ async function embedCustomFonts({
|
|
|
2117
2313
|
const styleSpec = (face.match(/font-style:\s*([^;]+);/i)?.[1] || "normal").trim();
|
|
2118
2314
|
const stretchSpec = (face.match(/font-stretch:\s*([^;]+);/i)?.[1] || "100%").trim();
|
|
2119
2315
|
const urange = (face.match(/unicode-range:\s*([^;]+);/i)?.[1] || "").trim();
|
|
2120
|
-
const srcRaw = (face.match(/src\s*:\s*([^;]+)
|
|
2316
|
+
const srcRaw = (face.match(/src\s*:\s*([^;}]+)[;}]/i)?.[1] || "").trim();
|
|
2121
2317
|
const srcUrls = extractSrcUrls(srcRaw, link.href);
|
|
2122
2318
|
if (!faceMatchesRequired(family, styleSpec, weightSpec, stretchSpec)) continue;
|
|
2123
2319
|
const ranges = parseUnicodeRange(urange);
|
|
@@ -2527,6 +2723,152 @@ function resolveCountersInContent(raw, node, ctx) {
|
|
|
2527
2723
|
}
|
|
2528
2724
|
|
|
2529
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
|
+
}
|
|
2530
2872
|
var __siblingCounters = /* @__PURE__ */ new WeakMap();
|
|
2531
2873
|
var __pseudoEpoch = -1;
|
|
2532
2874
|
function unquoteDoubleStrings2(s) {
|
|
@@ -2625,6 +2967,10 @@ function resolvePseudoContentAndIncs(node, pseudo, baseCtx) {
|
|
|
2625
2967
|
}
|
|
2626
2968
|
async function inlinePseudoElements(source, clone, sessionCache, options) {
|
|
2627
2969
|
if (!(source instanceof Element) || !(clone instanceof Element)) return;
|
|
2970
|
+
const doc = source.ownerDocument || document;
|
|
2971
|
+
if (!preflightWithFp(doc, sessionCache)) {
|
|
2972
|
+
return;
|
|
2973
|
+
}
|
|
2628
2974
|
const epoch = cache?.session?.__counterEpoch ?? 0;
|
|
2629
2975
|
if (__pseudoEpoch !== epoch) {
|
|
2630
2976
|
__siblingCounters = /* @__PURE__ */ new WeakMap();
|
|
@@ -2641,7 +2987,7 @@ async function inlinePseudoElements(source, clone, sessionCache, options) {
|
|
|
2641
2987
|
for (const pseudo of ["::before", "::after", "::first-letter"]) {
|
|
2642
2988
|
try {
|
|
2643
2989
|
const style = getStyle(source, pseudo);
|
|
2644
|
-
if (!style
|
|
2990
|
+
if (!style) continue;
|
|
2645
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";
|
|
2646
2992
|
if (isEmptyPseudo) continue;
|
|
2647
2993
|
if (pseudo === "::first-letter") {
|
|
@@ -2668,7 +3014,8 @@ async function inlinePseudoElements(source, clone, sessionCache, options) {
|
|
|
2668
3014
|
clone.insertBefore(span, restNode);
|
|
2669
3015
|
continue;
|
|
2670
3016
|
}
|
|
2671
|
-
const rawContent = style.content;
|
|
3017
|
+
const rawContent = style.content ?? "";
|
|
3018
|
+
const isNoExplicitContent = rawContent === "" || rawContent === "none" || rawContent === "normal";
|
|
2672
3019
|
const { text: cleanContent, incs } = resolvePseudoContentAndIncs(source, pseudo, counterCtx);
|
|
2673
3020
|
const bg = style.backgroundImage;
|
|
2674
3021
|
const bgColor = style.backgroundColor;
|
|
@@ -2680,7 +3027,7 @@ async function inlinePseudoElements(source, clone, sessionCache, options) {
|
|
|
2680
3027
|
const borderWidth = parseFloat(style.borderWidth);
|
|
2681
3028
|
const transform = style.transform;
|
|
2682
3029
|
const isIconFont22 = isIconFont2(fontFamily);
|
|
2683
|
-
const hasExplicitContent =
|
|
3030
|
+
const hasExplicitContent = !isNoExplicitContent && cleanContent !== "";
|
|
2684
3031
|
const hasBg = bg && bg !== "none";
|
|
2685
3032
|
const hasBgColor = bgColor && bgColor !== "transparent" && bgColor !== "rgba(0, 0, 0, 0)";
|
|
2686
3033
|
const hasBorder = borderStyle && borderStyle !== "none" && borderWidth > 0;
|
|
@@ -2702,8 +3049,6 @@ async function inlinePseudoElements(source, clone, sessionCache, options) {
|
|
|
2702
3049
|
}
|
|
2703
3050
|
const pseudoEl = document.createElement("span");
|
|
2704
3051
|
pseudoEl.dataset.snapdomPseudo = pseudo;
|
|
2705
|
-
pseudoEl.style.display = "inline";
|
|
2706
|
-
pseudoEl.style.verticalAlign = "baseline";
|
|
2707
3052
|
pseudoEl.style.pointerEvents = "none";
|
|
2708
3053
|
const snapshot = snapshotComputedStyle(style);
|
|
2709
3054
|
const key = getStyleKey(snapshot, "span");
|
|
@@ -2995,6 +3340,20 @@ function _childrenWithoutPlaceholders(el, phAttr) {
|
|
|
2995
3340
|
return out;
|
|
2996
3341
|
}
|
|
2997
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
|
+
|
|
2998
3357
|
// src/core/prepare.js
|
|
2999
3358
|
async function prepareClone(element, options = {}) {
|
|
3000
3359
|
const sessionCache = {
|
|
@@ -3095,152 +3454,25 @@ async function prepareClone(element, options = {}) {
|
|
|
3095
3454
|
}
|
|
3096
3455
|
return { clone, classCSS, styleCache: sessionCache.styleCache };
|
|
3097
3456
|
}
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
const
|
|
3102
|
-
const
|
|
3103
|
-
const
|
|
3104
|
-
const
|
|
3105
|
-
const
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
}
|
|
3110
|
-
var _blobToDataUrlCache = /* @__PURE__ */ new Map();
|
|
3111
|
-
async function blobUrlToDataUrl(blobUrl) {
|
|
3112
|
-
if (cache.resource?.has(blobUrl)) return cache.resource.get(blobUrl);
|
|
3113
|
-
if (_blobToDataUrlCache.has(blobUrl)) return _blobToDataUrlCache.get(blobUrl);
|
|
3114
|
-
const p = (async () => {
|
|
3115
|
-
const r = await snapFetch(blobUrl, { as: "dataURL", silent: true });
|
|
3116
|
-
if (!r.ok || typeof r.data !== "string") {
|
|
3117
|
-
throw new Error(`[snapDOM] Failed to read blob URL: ${blobUrl}`);
|
|
3118
|
-
}
|
|
3119
|
-
cache.resource?.set(blobUrl, r.data);
|
|
3120
|
-
return r.data;
|
|
3121
|
-
})();
|
|
3122
|
-
_blobToDataUrlCache.set(blobUrl, p);
|
|
3123
|
-
try {
|
|
3124
|
-
const data = await p;
|
|
3125
|
-
_blobToDataUrlCache.set(blobUrl, data);
|
|
3126
|
-
return data;
|
|
3127
|
-
} catch (e) {
|
|
3128
|
-
_blobToDataUrlCache.delete(blobUrl);
|
|
3129
|
-
throw e;
|
|
3130
|
-
}
|
|
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 };
|
|
3131
3469
|
}
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
for (const u of uniques) {
|
|
3139
|
-
try {
|
|
3140
|
-
const d = await blobUrlToDataUrl(u);
|
|
3141
|
-
out = out.split(u).join(d);
|
|
3142
|
-
} catch {
|
|
3143
|
-
}
|
|
3144
|
-
}
|
|
3145
|
-
return out;
|
|
3146
|
-
}
|
|
3147
|
-
function isBlobUrl(u) {
|
|
3148
|
-
return typeof u === "string" && u.startsWith("blob:");
|
|
3149
|
-
}
|
|
3150
|
-
function parseSrcset(srcset) {
|
|
3151
|
-
return (srcset || "").split(",").map((s) => s.trim()).filter(Boolean).map((item) => {
|
|
3152
|
-
const m = item.match(/^(\S+)(\s+.+)?$/);
|
|
3153
|
-
return m ? { url: m[1], desc: m[2] || "" } : null;
|
|
3154
|
-
}).filter(Boolean);
|
|
3155
|
-
}
|
|
3156
|
-
function stringifySrcset(parts) {
|
|
3157
|
-
return parts.map((p) => p.desc ? `${p.url} ${p.desc.trim()}` : p.url).join(", ");
|
|
3158
|
-
}
|
|
3159
|
-
async function resolveBlobUrlsInTree(root) {
|
|
3160
|
-
if (!root) return;
|
|
3161
|
-
const imgs = root.querySelectorAll ? root.querySelectorAll("img") : [];
|
|
3162
|
-
for (const img of imgs) {
|
|
3163
|
-
try {
|
|
3164
|
-
const srcAttr = img.getAttribute("src");
|
|
3165
|
-
const effective = srcAttr || img.currentSrc || "";
|
|
3166
|
-
if (isBlobUrl(effective)) {
|
|
3167
|
-
const data = await blobUrlToDataUrl(effective);
|
|
3168
|
-
img.setAttribute("src", data);
|
|
3169
|
-
}
|
|
3170
|
-
const srcset = img.getAttribute("srcset");
|
|
3171
|
-
if (srcset && srcset.includes("blob:")) {
|
|
3172
|
-
const parts = parseSrcset(srcset);
|
|
3173
|
-
let changed = false;
|
|
3174
|
-
for (const p of parts) {
|
|
3175
|
-
if (isBlobUrl(p.url)) {
|
|
3176
|
-
try {
|
|
3177
|
-
p.url = await blobUrlToDataUrl(p.url);
|
|
3178
|
-
changed = true;
|
|
3179
|
-
} catch {
|
|
3180
|
-
}
|
|
3181
|
-
}
|
|
3182
|
-
}
|
|
3183
|
-
if (changed) img.setAttribute("srcset", stringifySrcset(parts));
|
|
3184
|
-
}
|
|
3185
|
-
} catch {
|
|
3186
|
-
}
|
|
3187
|
-
}
|
|
3188
|
-
const svgImages = root.querySelectorAll ? root.querySelectorAll("image") : [];
|
|
3189
|
-
for (const node of svgImages) {
|
|
3190
|
-
try {
|
|
3191
|
-
const XLINK_NS = "http://www.w3.org/1999/xlink";
|
|
3192
|
-
const href = node.getAttribute("href") || node.getAttributeNS?.(XLINK_NS, "href");
|
|
3193
|
-
if (isBlobUrl(href)) {
|
|
3194
|
-
const d = await blobUrlToDataUrl(href);
|
|
3195
|
-
node.setAttribute("href", d);
|
|
3196
|
-
node.removeAttributeNS?.(XLINK_NS, "href");
|
|
3197
|
-
}
|
|
3198
|
-
} catch {
|
|
3199
|
-
}
|
|
3200
|
-
}
|
|
3201
|
-
const styled = root.querySelectorAll ? root.querySelectorAll("[style*='blob:']") : [];
|
|
3202
|
-
for (const el of styled) {
|
|
3203
|
-
try {
|
|
3204
|
-
const styleText = el.getAttribute("style");
|
|
3205
|
-
if (styleText && styleText.includes("blob:")) {
|
|
3206
|
-
const replaced = await replaceBlobUrlsInCssText(styleText);
|
|
3207
|
-
el.setAttribute("style", replaced);
|
|
3208
|
-
}
|
|
3209
|
-
} catch {
|
|
3210
|
-
}
|
|
3211
|
-
}
|
|
3212
|
-
const styleTags = root.querySelectorAll ? root.querySelectorAll("style") : [];
|
|
3213
|
-
for (const s of styleTags) {
|
|
3214
|
-
try {
|
|
3215
|
-
const css = s.textContent || "";
|
|
3216
|
-
if (css.includes("blob:")) {
|
|
3217
|
-
s.textContent = await replaceBlobUrlsInCssText(css);
|
|
3218
|
-
}
|
|
3219
|
-
} catch {
|
|
3220
|
-
}
|
|
3221
|
-
}
|
|
3222
|
-
const urlAttrs = ["poster"];
|
|
3223
|
-
for (const attr of urlAttrs) {
|
|
3224
|
-
const nodes = root.querySelectorAll ? root.querySelectorAll(`[${attr}^='blob:']`) : [];
|
|
3225
|
-
for (const n of nodes) {
|
|
3226
|
-
try {
|
|
3227
|
-
const u = n.getAttribute(attr);
|
|
3228
|
-
if (isBlobUrl(u)) {
|
|
3229
|
-
n.setAttribute(attr, await blobUrlToDataUrl(u));
|
|
3230
|
-
}
|
|
3231
|
-
} catch {
|
|
3232
|
-
}
|
|
3233
|
-
}
|
|
3234
|
-
}
|
|
3235
|
-
}
|
|
3236
|
-
|
|
3237
|
-
// src/modules/images.js
|
|
3238
|
-
async function inlineImages(clone, options = {}) {
|
|
3239
|
-
const imgs = Array.from(clone.querySelectorAll("img"));
|
|
3240
|
-
const processImg = async (img) => {
|
|
3241
|
-
if (!img.getAttribute("src")) {
|
|
3242
|
-
const eff = img.currentSrc || img.src || "";
|
|
3243
|
-
if (eff) img.setAttribute("src", eff);
|
|
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);
|
|
3244
3476
|
}
|
|
3245
3477
|
img.removeAttribute("srcset");
|
|
3246
3478
|
img.removeAttribute("sizes");
|
|
@@ -3253,41 +3485,30 @@ async function inlineImages(clone, options = {}) {
|
|
|
3253
3485
|
if (!img.height) img.height = img.naturalHeight || 100;
|
|
3254
3486
|
return;
|
|
3255
3487
|
}
|
|
3488
|
+
const { width: fbW, height: fbH } = extractImageDimensions(img);
|
|
3256
3489
|
const { fallbackURL } = options || {};
|
|
3257
3490
|
if (fallbackURL) {
|
|
3258
3491
|
try {
|
|
3259
|
-
const dsW = parseInt(img.dataset?.snapdomWidth || "", 10) || 0;
|
|
3260
|
-
const dsH = parseInt(img.dataset?.snapdomHeight || "", 10) || 0;
|
|
3261
|
-
const attrW = parseInt(img.getAttribute("width") || "", 10) || 0;
|
|
3262
|
-
const attrH = parseInt(img.getAttribute("height") || "", 10) || 0;
|
|
3263
|
-
const styleW = parseFloat(img.style?.width || "") || 0;
|
|
3264
|
-
const styleH = parseFloat(img.style?.height || "") || 0;
|
|
3265
|
-
const fbW = dsW || styleW || attrW || img.width || void 0;
|
|
3266
|
-
const fbH = dsH || styleH || attrH || img.height || void 0;
|
|
3267
3492
|
const fallbackUrl = typeof fallbackURL === "function" ? await fallbackURL({ width: fbW, height: fbH, src, element: img }) : fallbackURL;
|
|
3268
3493
|
if (fallbackUrl) {
|
|
3269
3494
|
const fallbackData = await snapFetch(fallbackUrl, { as: "dataURL", useProxy: options.useProxy });
|
|
3270
3495
|
img.src = fallbackData.data;
|
|
3271
|
-
if (!img.width
|
|
3272
|
-
if (!img.height
|
|
3273
|
-
if (!img.width) img.width = img.naturalWidth || 100;
|
|
3274
|
-
if (!img.height) img.height = img.naturalHeight || 100;
|
|
3496
|
+
if (!img.width) img.width = fbW;
|
|
3497
|
+
if (!img.height) img.height = fbH;
|
|
3275
3498
|
return;
|
|
3276
3499
|
}
|
|
3277
3500
|
} catch {
|
|
3278
3501
|
}
|
|
3279
3502
|
}
|
|
3280
|
-
const w = img.width || img.naturalWidth || 100;
|
|
3281
|
-
const h = img.height || img.naturalHeight || 100;
|
|
3282
3503
|
if (options.placeholders !== false) {
|
|
3283
3504
|
const fallback = document.createElement("div");
|
|
3284
3505
|
fallback.style.cssText = [
|
|
3285
|
-
`width:${
|
|
3286
|
-
`height:${
|
|
3506
|
+
`width:${fbW}px`,
|
|
3507
|
+
`height:${fbH}px`,
|
|
3287
3508
|
"background:#ccc",
|
|
3288
3509
|
"display:inline-block",
|
|
3289
3510
|
"text-align:center",
|
|
3290
|
-
`line-height:${
|
|
3511
|
+
`line-height:${fbH}px`,
|
|
3291
3512
|
"color:#666",
|
|
3292
3513
|
"font-size:12px",
|
|
3293
3514
|
"overflow:hidden"
|
|
@@ -3296,7 +3517,7 @@ async function inlineImages(clone, options = {}) {
|
|
|
3296
3517
|
img.replaceWith(fallback);
|
|
3297
3518
|
} else {
|
|
3298
3519
|
const spacer = document.createElement("div");
|
|
3299
|
-
spacer.style.cssText = `display:inline-block;width:${
|
|
3520
|
+
spacer.style.cssText = `display:inline-block;width:${fbW}px;height:${fbH}px;visibility:hidden;`;
|
|
3300
3521
|
img.replaceWith(spacer);
|
|
3301
3522
|
}
|
|
3302
3523
|
};
|
|
@@ -3536,7 +3757,7 @@ function attachSessionPlugins(context, localDefs, force = false) {
|
|
|
3536
3757
|
return context;
|
|
3537
3758
|
}
|
|
3538
3759
|
|
|
3539
|
-
// src/
|
|
3760
|
+
// src/utils/capture.helpers.js
|
|
3540
3761
|
function stripRootShadows(originalEl, cloneRoot) {
|
|
3541
3762
|
if (!originalEl || !cloneRoot || !cloneRoot.style) return;
|
|
3542
3763
|
const cs = getComputedStyle(originalEl);
|
|
@@ -3559,217 +3780,178 @@ function stripRootShadows(originalEl, cloneRoot) {
|
|
|
3559
3780
|
} catch {
|
|
3560
3781
|
}
|
|
3561
3782
|
}
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
const
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3783
|
+
function removeAllComments(root) {
|
|
3784
|
+
const it = document.createTreeWalker(root, NodeFilter.SHOW_COMMENT);
|
|
3785
|
+
const toRemove = [];
|
|
3786
|
+
while (it.nextNode()) toRemove.push(it.currentNode);
|
|
3787
|
+
for (const n of toRemove) n.remove();
|
|
3788
|
+
}
|
|
3789
|
+
function sanitizeAttributesForXHTML(root, opts = {}) {
|
|
3790
|
+
const { stripFrameworkDirectives = true } = opts;
|
|
3791
|
+
const ALLOWED_PREFIXES = /* @__PURE__ */ new Set(["xml", "xlink"]);
|
|
3792
|
+
const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
|
|
3793
|
+
while (walker.nextNode()) {
|
|
3794
|
+
const el = walker.currentNode;
|
|
3795
|
+
for (const attr of Array.from(el.attributes)) {
|
|
3796
|
+
const name = attr.name;
|
|
3797
|
+
if (name.includes("@")) {
|
|
3798
|
+
el.removeAttribute(name);
|
|
3799
|
+
continue;
|
|
3800
|
+
}
|
|
3801
|
+
if (name.includes(":")) {
|
|
3802
|
+
const prefix = name.split(":", 1)[0];
|
|
3803
|
+
if (!ALLOWED_PREFIXES.has(prefix)) {
|
|
3804
|
+
el.removeAttribute(name);
|
|
3805
|
+
continue;
|
|
3806
|
+
}
|
|
3807
|
+
}
|
|
3808
|
+
if (!stripFrameworkDirectives) continue;
|
|
3809
|
+
if (name.startsWith("x-") || // Alpine
|
|
3810
|
+
name.startsWith("v-") || // Vue
|
|
3811
|
+
name.startsWith(":") || // Vue/Alpine shorthand
|
|
3812
|
+
name.startsWith("on:") || // Svelte
|
|
3813
|
+
name.startsWith("bind:") || // Svelte
|
|
3814
|
+
name.startsWith("let:") || // Svelte
|
|
3815
|
+
name.startsWith("class:")) {
|
|
3816
|
+
el.removeAttribute(name);
|
|
3817
|
+
continue;
|
|
3818
|
+
}
|
|
3585
3819
|
}
|
|
3586
|
-
} finally {
|
|
3587
|
-
undoClamp();
|
|
3588
3820
|
}
|
|
3589
|
-
|
|
3590
|
-
|
|
3821
|
+
}
|
|
3822
|
+
function sanitizeCloneForXHTML(root, opts = {}) {
|
|
3823
|
+
if (!root) return;
|
|
3824
|
+
sanitizeAttributesForXHTML(root, opts);
|
|
3825
|
+
removeAllComments(root);
|
|
3826
|
+
}
|
|
3827
|
+
function authorHasExplicitSize(el) {
|
|
3591
3828
|
try {
|
|
3592
|
-
|
|
3829
|
+
const s = el.getAttribute?.("style") || "";
|
|
3830
|
+
return /\b(height|width|block-size|inline-size)\s*:/.test(s);
|
|
3593
3831
|
} catch {
|
|
3832
|
+
return false;
|
|
3594
3833
|
}
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
if (
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
baseCSS = generateDedupedBaseCSS(usedTags);
|
|
3637
|
-
cache.baseStyle.set(tagKey, baseCSS);
|
|
3638
|
-
resolve();
|
|
3639
|
-
}, { fast });
|
|
3640
|
-
});
|
|
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
|
+
}
|
|
3869
|
+
}
|
|
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]);
|
|
3874
|
+
}
|
|
3641
3875
|
}
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
if (hasW && hasH) {
|
|
3661
|
-
w = Math.max(1, limitDecimals(optW));
|
|
3662
|
-
h = Math.max(1, limitDecimals(optH));
|
|
3663
|
-
} else if (hasW) {
|
|
3664
|
-
w = Math.max(1, limitDecimals(optW));
|
|
3665
|
-
h = Math.max(1, limitDecimals(w / (aspect0 || 1)));
|
|
3666
|
-
} else if (hasH) {
|
|
3667
|
-
h = Math.max(1, limitDecimals(optH));
|
|
3668
|
-
w = Math.max(1, limitDecimals(h * (aspect0 || 1)));
|
|
3669
|
-
} else {
|
|
3670
|
-
w = w0;
|
|
3671
|
-
h = h0;
|
|
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 {
|
|
3672
3894
|
}
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
const bb = bboxWithOriginFull(w0, h0, M, ox2, oy2);
|
|
3702
|
-
minX = limitDecimals(bb.minX);
|
|
3703
|
-
minY = limitDecimals(bb.minY);
|
|
3704
|
-
maxX = limitDecimals(bb.maxX);
|
|
3705
|
-
maxY = limitDecimals(bb.maxY);
|
|
3706
|
-
}
|
|
3707
|
-
}
|
|
3708
|
-
const bleedShadow = parseBoxShadow(csEl);
|
|
3709
|
-
const bleedBlur = parseFilterBlur(csEl);
|
|
3710
|
-
const bleedOutline = parseOutline(csEl);
|
|
3711
|
-
const drop = parseFilterDropShadows(csEl);
|
|
3712
|
-
const bleed = !outerShadows ? { top: 0, right: 0, bottom: 0, left: 0 } : {
|
|
3713
|
-
top: limitDecimals(bleedShadow.top + bleedBlur.top + bleedOutline.top + drop.bleed.top),
|
|
3714
|
-
right: limitDecimals(bleedShadow.right + bleedBlur.right + bleedOutline.right + drop.bleed.right),
|
|
3715
|
-
bottom: limitDecimals(bleedShadow.bottom + bleedBlur.bottom + bleedOutline.bottom + drop.bleed.bottom),
|
|
3716
|
-
left: limitDecimals(bleedShadow.left + bleedBlur.left + bleedOutline.left + drop.bleed.left)
|
|
3717
|
-
};
|
|
3718
|
-
minX = limitDecimals(minX - bleed.left);
|
|
3719
|
-
minY = limitDecimals(minY - bleed.top);
|
|
3720
|
-
maxX = limitDecimals(maxX + bleed.right);
|
|
3721
|
-
maxY = limitDecimals(maxY + bleed.bottom);
|
|
3722
|
-
const vbW0 = Math.max(1, limitDecimals(maxX - minX));
|
|
3723
|
-
const vbH0 = Math.max(1, limitDecimals(maxY - minY));
|
|
3724
|
-
const scaleW = hasW || hasH ? limitDecimals(w / w0) : 1;
|
|
3725
|
-
const scaleH = hasH || hasW ? limitDecimals(h / h0) : 1;
|
|
3726
|
-
const outW = Math.max(1, limitDecimals(vbW0 * scaleW));
|
|
3727
|
-
const outH = Math.max(1, limitDecimals(vbH0 * scaleH));
|
|
3728
|
-
const svgNS = "http://www.w3.org/2000/svg";
|
|
3729
|
-
const basePad = isSafari() ? 1 : 0;
|
|
3730
|
-
const extraPad = !outerTransforms ? 1 : 0;
|
|
3731
|
-
const pad = limitDecimals(basePad + extraPad);
|
|
3732
|
-
const fo = document.createElementNS(svgNS, "foreignObject");
|
|
3733
|
-
const vbMinX = limitDecimals(minX);
|
|
3734
|
-
const vbMinY = limitDecimals(minY);
|
|
3735
|
-
fo.setAttribute("x", String(limitDecimals(-(vbMinX - pad))));
|
|
3736
|
-
fo.setAttribute("y", String(limitDecimals(-(vbMinY - pad))));
|
|
3737
|
-
fo.setAttribute("width", String(limitDecimals(w0 + pad * 2)));
|
|
3738
|
-
fo.setAttribute("height", String(limitDecimals(h0 + pad * 2)));
|
|
3739
|
-
fo.style.overflow = "visible";
|
|
3740
|
-
const styleTag = document.createElement("style");
|
|
3741
|
-
styleTag.textContent = state.baseCSS + state.fontsCSS + "svg{overflow:visible;} foreignObject{overflow:visible;}" + state.classCSS;
|
|
3742
|
-
fo.appendChild(styleTag);
|
|
3743
|
-
const container = document.createElement("div");
|
|
3744
|
-
container.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
|
|
3745
|
-
container.style.width = `${limitDecimals(w0)}px`;
|
|
3746
|
-
container.style.height = `${limitDecimals(h0)}px`;
|
|
3747
|
-
container.style.overflow = "visible";
|
|
3748
|
-
state.clone.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
|
|
3749
|
-
container.appendChild(state.clone);
|
|
3750
|
-
fo.appendChild(container);
|
|
3751
|
-
const serializer = new XMLSerializer();
|
|
3752
|
-
const foString = serializer.serializeToString(fo);
|
|
3753
|
-
const vbW = limitDecimals(vbW0 + pad * 2);
|
|
3754
|
-
const vbH = limitDecimals(vbH0 + pad * 2);
|
|
3755
|
-
const wantsSize = hasW || hasH;
|
|
3756
|
-
options.meta = { w0, h0, vbW, vbH, targetW: w, targetH: h };
|
|
3757
|
-
const svgOutW = isSafari() && wantsSize ? vbW : limitDecimals(outW + pad * 2);
|
|
3758
|
-
const svgOutH = isSafari() && wantsSize ? vbH : limitDecimals(outH + pad * 2);
|
|
3759
|
-
const svgHeader = `<svg xmlns="${svgNS}" width="${svgOutW}" height="${svgOutH}" viewBox="0 0 ${vbW} ${vbH}">`;
|
|
3760
|
-
const svgFooter = "</svg>";
|
|
3761
|
-
svgString = svgHeader + foString + svgFooter;
|
|
3762
|
-
dataURL = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgString)}`;
|
|
3763
|
-
state = { svgString, dataURL, ...state };
|
|
3764
|
-
resolve();
|
|
3765
|
-
}, { fast });
|
|
3766
|
-
});
|
|
3767
|
-
await runHook("afterRender", state);
|
|
3768
|
-
const sandbox = document.getElementById("snapdom-sandbox");
|
|
3769
|
-
if (sandbox && sandbox.style.position === "absolute") sandbox.remove();
|
|
3770
|
-
return state.dataURL;
|
|
3895
|
+
}
|
|
3896
|
+
}
|
|
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;
|
|
3916
|
+
}
|
|
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;
|
|
3771
3923
|
}
|
|
3772
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));
|
|
3942
|
+
}
|
|
3943
|
+
return { top: Math.ceil(t), right: Math.ceil(r), bottom: Math.ceil(b2), left: Math.ceil(l) };
|
|
3944
|
+
}
|
|
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 };
|
|
3949
|
+
}
|
|
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 };
|
|
3954
|
+
}
|
|
3773
3955
|
function parseFilterDropShadows(cs) {
|
|
3774
3956
|
const raw = `${cs.filter || ""} ${cs.webkitFilter || ""}`.trim();
|
|
3775
3957
|
if (!raw || raw === "none") {
|
|
@@ -3858,34 +4040,6 @@ function normalizeRootTransforms(originalEl, cloneRoot) {
|
|
|
3858
4040
|
return null;
|
|
3859
4041
|
}
|
|
3860
4042
|
}
|
|
3861
|
-
function parseBoxShadow(cs) {
|
|
3862
|
-
const v = cs.boxShadow || "";
|
|
3863
|
-
if (!v || v === "none") return { top: 0, right: 0, bottom: 0, left: 0 };
|
|
3864
|
-
const parts = v.split(/\),(?=(?:[^()]*\([^()]*\))*[^()]*$)/).map((s) => s.trim());
|
|
3865
|
-
let t = 0, r = 0, b2 = 0, l = 0;
|
|
3866
|
-
for (const part of parts) {
|
|
3867
|
-
const nums = part.match(/-?\d+(\.\d+)?px/g)?.map((n) => parseFloat(n)) || [];
|
|
3868
|
-
if (nums.length < 2) continue;
|
|
3869
|
-
const [ox2, oy2, blur = 0, spread = 0] = nums;
|
|
3870
|
-
const extX = Math.abs(ox2) + blur + spread;
|
|
3871
|
-
const extY = Math.abs(oy2) + blur + spread;
|
|
3872
|
-
r = Math.max(r, extX + Math.max(ox2, 0));
|
|
3873
|
-
l = Math.max(l, extX + Math.max(-ox2, 0));
|
|
3874
|
-
b2 = Math.max(b2, extY + Math.max(oy2, 0));
|
|
3875
|
-
t = Math.max(t, extY + Math.max(-oy2, 0));
|
|
3876
|
-
}
|
|
3877
|
-
return { top: Math.ceil(t), right: Math.ceil(r), bottom: Math.ceil(b2), left: Math.ceil(l) };
|
|
3878
|
-
}
|
|
3879
|
-
function parseFilterBlur(cs) {
|
|
3880
|
-
const m = (cs.filter || "").match(/blur\(\s*([0-9.]+)px\s*\)/);
|
|
3881
|
-
const b2 = m ? Math.ceil(parseFloat(m[1]) || 0) : 0;
|
|
3882
|
-
return { top: b2, right: b2, bottom: b2, left: b2 };
|
|
3883
|
-
}
|
|
3884
|
-
function parseOutline(cs) {
|
|
3885
|
-
if ((cs.outlineStyle || "none") === "none") return { top: 0, right: 0, bottom: 0, left: 0 };
|
|
3886
|
-
const w2 = Math.ceil(parseFloat(cs.outlineWidth || "0") || 0);
|
|
3887
|
-
return { top: w2, right: w2, bottom: w2, left: w2 };
|
|
3888
|
-
}
|
|
3889
4043
|
function bboxWithOriginFull(w2, h2, M, ox2, oy2) {
|
|
3890
4044
|
const a2 = M.a, b2 = M.b, c2 = M.c, d2 = M.d, e2 = M.e || 0, f2 = M.f || 0;
|
|
3891
4045
|
function pt(x, y) {
|
|
@@ -3905,17 +4059,24 @@ function bboxWithOriginFull(w2, h2, M, ox2, oy2) {
|
|
|
3905
4059
|
}
|
|
3906
4060
|
return { minX: minX2, minY: minY2, maxX: maxX2, maxY: maxY2, width: maxX2 - minX2, height: maxY2 - minY2 };
|
|
3907
4061
|
}
|
|
3908
|
-
function
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
return
|
|
3918
|
-
|
|
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
|
+
};
|
|
3919
4080
|
}
|
|
3920
4081
|
function readIndividualTransforms(el) {
|
|
3921
4082
|
const out = { rotate: "0deg", scale: null, translate: null };
|
|
@@ -3972,35 +4133,6 @@ function readIndividualTransforms(el) {
|
|
|
3972
4133
|
out.translate = cs.translate && cs.translate !== "none" ? cs.translate : null;
|
|
3973
4134
|
return out;
|
|
3974
4135
|
}
|
|
3975
|
-
function hasBBoxAffectingTransform(el) {
|
|
3976
|
-
const cs = getComputedStyle(el);
|
|
3977
|
-
const t = cs.transform || "none";
|
|
3978
|
-
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);
|
|
3979
|
-
if (hasMatrix) return true;
|
|
3980
|
-
const r = cs.rotate && cs.rotate !== "none" && cs.rotate !== "0deg";
|
|
3981
|
-
const s = cs.scale && cs.scale !== "none" && cs.scale !== "1";
|
|
3982
|
-
const tr = cs.translate && cs.translate !== "none" && cs.translate !== "0px 0px";
|
|
3983
|
-
return Boolean(r || s || tr);
|
|
3984
|
-
}
|
|
3985
|
-
function parseTransformOriginPx(cs, w, h) {
|
|
3986
|
-
const raw = (cs.transformOrigin || "0 0").trim().split(/\s+/);
|
|
3987
|
-
const [oxRaw, oyRaw] = [raw[0] || "0", raw[1] || "0"];
|
|
3988
|
-
const toPx = (token, size) => {
|
|
3989
|
-
const t = token.toLowerCase();
|
|
3990
|
-
if (t === "left" || t === "top") return 0;
|
|
3991
|
-
if (t === "center") return size / 2;
|
|
3992
|
-
if (t === "right") return size;
|
|
3993
|
-
if (t === "bottom") return size;
|
|
3994
|
-
if (t.endsWith("px")) return parseFloat(t) || 0;
|
|
3995
|
-
if (t.endsWith("%")) return (parseFloat(t) || 0) * size / 100;
|
|
3996
|
-
if (/^-?\d+(\.\d+)?$/.test(t)) return parseFloat(t) || 0;
|
|
3997
|
-
return 0;
|
|
3998
|
-
};
|
|
3999
|
-
return {
|
|
4000
|
-
ox: toPx(oxRaw, w),
|
|
4001
|
-
oy: toPx(oyRaw, h)
|
|
4002
|
-
};
|
|
4003
|
-
}
|
|
4004
4136
|
var __measureHost = null;
|
|
4005
4137
|
function getMeasureHost() {
|
|
4006
4138
|
if (__measureHost) return __measureHost;
|
|
@@ -4035,6 +4167,255 @@ function readTotalTransformMatrix(t) {
|
|
|
4035
4167
|
host.removeChild(tmp);
|
|
4036
4168
|
return M;
|
|
4037
4169
|
}
|
|
4170
|
+
function hasBBoxAffectingTransform(el) {
|
|
4171
|
+
const cs = getComputedStyle(el);
|
|
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);
|
|
4179
|
+
}
|
|
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);
|
|
4187
|
+
}
|
|
4188
|
+
}
|
|
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();
|
|
4217
|
+
}
|
|
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);
|
|
4418
|
+
}
|
|
4038
4419
|
|
|
4039
4420
|
// src/core/context.js
|
|
4040
4421
|
function normalizeCachePolicy(v) {
|
|
@@ -4191,7 +4572,7 @@ function maybeConvertBoxShadowForSafari(url) {
|
|
|
4191
4572
|
}
|
|
4192
4573
|
}
|
|
4193
4574
|
async function toCanvas(url, options) {
|
|
4194
|
-
let { width: optW, height: optH, scale = 1, dpr = 1, meta = {} } = options;
|
|
4575
|
+
let { width: optW, height: optH, scale = 1, dpr = 1, meta = {}, backgroundColor } = options;
|
|
4195
4576
|
url = maybeConvertBoxShadowForSafari(url);
|
|
4196
4577
|
const img = new Image();
|
|
4197
4578
|
img.loading = "eager";
|
|
@@ -4230,6 +4611,12 @@ async function toCanvas(url, options) {
|
|
|
4230
4611
|
canvas.style.height = `${outH}px`;
|
|
4231
4612
|
const ctx = canvas.getContext("2d");
|
|
4232
4613
|
if (dpr !== 1) ctx.scale(dpr, dpr);
|
|
4614
|
+
if (backgroundColor) {
|
|
4615
|
+
ctx.save();
|
|
4616
|
+
ctx.fillStyle = backgroundColor;
|
|
4617
|
+
ctx.fillRect(0, 0, outW, outH);
|
|
4618
|
+
ctx.restore();
|
|
4619
|
+
}
|
|
4233
4620
|
ctx.drawImage(img, 0, 0, outW, outH);
|
|
4234
4621
|
return canvas;
|
|
4235
4622
|
}
|
|
@@ -4237,12 +4624,11 @@ async function toCanvas(url, options) {
|
|
|
4237
4624
|
// src/modules/rasterize.js
|
|
4238
4625
|
async function rasterize(url, options) {
|
|
4239
4626
|
const canvas = await toCanvas(url, options);
|
|
4240
|
-
const finalCanvas = options.backgroundColor ? createBackground(canvas, options.backgroundColor) : canvas;
|
|
4241
4627
|
const img = new Image();
|
|
4242
|
-
img.src =
|
|
4628
|
+
img.src = canvas.toDataURL(`image/${options.format}`, options.quality);
|
|
4243
4629
|
await img.decode();
|
|
4244
|
-
img.style.width = `${
|
|
4245
|
-
img.style.height = `${
|
|
4630
|
+
img.style.width = `${canvas.width / options.dpr}px`;
|
|
4631
|
+
img.style.height = `${canvas.height / options.dpr}px`;
|
|
4246
4632
|
return img;
|
|
4247
4633
|
}
|
|
4248
4634
|
|
|
@@ -4302,13 +4688,8 @@ async function toBlob(url, options) {
|
|
|
4302
4688
|
return new Blob([svgText], { type: "image/svg+xml" });
|
|
4303
4689
|
}
|
|
4304
4690
|
const canvas = await toCanvas(url, options);
|
|
4305
|
-
const finalCanvas = options.backgroundColor ? createBackground(canvas, options.backgroundColor) : canvas;
|
|
4306
4691
|
return new Promise(
|
|
4307
|
-
(resolve) =>
|
|
4308
|
-
(blob) => resolve(blob),
|
|
4309
|
-
`image/${type}`,
|
|
4310
|
-
options.quality
|
|
4311
|
-
)
|
|
4692
|
+
(resolve) => canvas.toBlob((blob) => resolve(blob), `image/${type}`, options.quality)
|
|
4312
4693
|
);
|
|
4313
4694
|
}
|
|
4314
4695
|
|
|
@@ -4326,9 +4707,8 @@ async function download(url, options) {
|
|
|
4326
4707
|
return;
|
|
4327
4708
|
}
|
|
4328
4709
|
const canvas = await toCanvas(url, options);
|
|
4329
|
-
const finalCanvas = options.backgroundColor ? createBackground(canvas, options.backgroundColor) : canvas;
|
|
4330
4710
|
const a = document.createElement("a");
|
|
4331
|
-
a.href =
|
|
4711
|
+
a.href = canvas.toDataURL(`image/${options.format}`, options.quality);
|
|
4332
4712
|
a.download = options.filename;
|
|
4333
4713
|
a.click();
|
|
4334
4714
|
}
|