@zumer/snapdom 1.9.14 → 2.0.0-dev.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +253 -22
- package/dist/snapdom.js +771 -231
- package/dist/snapdom.min.js +7 -7
- package/dist/snapdom.min.mjs +7 -7
- package/dist/snapdom.mjs +733 -227
- package/package.json +1 -1
- package/types/snapdom.d.ts +269 -176
package/dist/snapdom.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
/*
|
|
3
3
|
* snapdom
|
|
4
|
-
* v.
|
|
4
|
+
* v.2.0.0-dev.1
|
|
5
5
|
* Author Juan Martin Muda
|
|
6
6
|
* License MIT
|
|
7
7
|
**/
|
|
@@ -23,6 +23,7 @@ var cache = {
|
|
|
23
23
|
}
|
|
24
24
|
};
|
|
25
25
|
function applyCachePolicy(policy = "soft") {
|
|
26
|
+
cache.session.__counterEpoch = (cache.session.__counterEpoch || 0) + 1;
|
|
26
27
|
switch (policy) {
|
|
27
28
|
case "auto": {
|
|
28
29
|
cache.session.styleMap = /* @__PURE__ */ new Map();
|
|
@@ -808,6 +809,27 @@ function stripHeightForWrappers(el, cs, snap) {
|
|
|
808
809
|
}
|
|
809
810
|
|
|
810
811
|
// src/modules/CSSVar.js
|
|
812
|
+
var KEY_PROPS = ["fill", "stroke", "color", "background-color", "stop-color"];
|
|
813
|
+
var __BASELINE_CACHE = /* @__PURE__ */ new Map();
|
|
814
|
+
function getBaselineComputed(tagName, ns) {
|
|
815
|
+
const key = ns + "::" + tagName.toLowerCase();
|
|
816
|
+
let entry = __BASELINE_CACHE.get(key);
|
|
817
|
+
if (entry) return entry;
|
|
818
|
+
const doc = document;
|
|
819
|
+
const el = ns === "http://www.w3.org/2000/svg" ? doc.createElementNS(ns, tagName) : doc.createElement(tagName);
|
|
820
|
+
const holder = doc.createElement("div");
|
|
821
|
+
holder.style.cssText = "position:absolute;left:-99999px;top:-99999px;contain:strict;display:block;";
|
|
822
|
+
holder.appendChild(el);
|
|
823
|
+
doc.documentElement.appendChild(holder);
|
|
824
|
+
const cs = getComputedStyle(el);
|
|
825
|
+
const base = {};
|
|
826
|
+
for (const p of KEY_PROPS) {
|
|
827
|
+
base[p] = cs.getPropertyValue(p) || "";
|
|
828
|
+
}
|
|
829
|
+
holder.remove();
|
|
830
|
+
__BASELINE_CACHE.set(key, base);
|
|
831
|
+
return base;
|
|
832
|
+
}
|
|
811
833
|
function resolveCSSVars(sourceEl, cloneEl) {
|
|
812
834
|
if (!(sourceEl instanceof Element) || !(cloneEl instanceof Element)) return;
|
|
813
835
|
const styleAttr = sourceEl.getAttribute?.("style");
|
|
@@ -822,42 +844,62 @@ function resolveCSSVars(sourceEl, cloneEl) {
|
|
|
822
844
|
}
|
|
823
845
|
}
|
|
824
846
|
}
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
847
|
+
let cs = null;
|
|
848
|
+
if (hasVar) {
|
|
849
|
+
try {
|
|
850
|
+
cs = getComputedStyle(sourceEl);
|
|
851
|
+
} catch {
|
|
852
|
+
}
|
|
831
853
|
}
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
854
|
+
if (hasVar) {
|
|
855
|
+
const author = sourceEl.style;
|
|
856
|
+
if (author && author.length) {
|
|
857
|
+
for (let i = 0; i < author.length; i++) {
|
|
858
|
+
const prop = author[i];
|
|
859
|
+
const val = author.getPropertyValue(prop);
|
|
860
|
+
if (!val || !val.includes("var(")) continue;
|
|
861
|
+
const resolved = cs && cs.getPropertyValue(prop);
|
|
862
|
+
if (resolved) {
|
|
863
|
+
try {
|
|
864
|
+
cloneEl.style.setProperty(prop, resolved.trim(), author.getPropertyPriority(prop));
|
|
865
|
+
} catch {
|
|
866
|
+
}
|
|
843
867
|
}
|
|
844
868
|
}
|
|
845
869
|
}
|
|
846
870
|
}
|
|
847
|
-
if (sourceEl.attributes?.length) {
|
|
871
|
+
if (hasVar && sourceEl.attributes?.length) {
|
|
848
872
|
const attrs = sourceEl.attributes;
|
|
849
873
|
for (let i = 0; i < attrs.length; i++) {
|
|
850
874
|
const a = attrs[i];
|
|
851
875
|
if (!a || typeof a.value !== "string" || !a.value.includes("var(")) continue;
|
|
852
876
|
const propName = a.name;
|
|
853
|
-
|
|
877
|
+
const resolved = cs && cs.getPropertyValue(propName);
|
|
878
|
+
if (resolved) {
|
|
879
|
+
try {
|
|
880
|
+
cloneEl.style.setProperty(propName, resolved.trim());
|
|
881
|
+
} catch {
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
if (!hasVar) {
|
|
887
|
+
if (!cs) {
|
|
854
888
|
try {
|
|
855
|
-
|
|
889
|
+
cs = getComputedStyle(sourceEl);
|
|
856
890
|
} catch {
|
|
891
|
+
cs = null;
|
|
857
892
|
}
|
|
858
|
-
|
|
893
|
+
}
|
|
894
|
+
if (!cs) return;
|
|
895
|
+
const ns = sourceEl.namespaceURI || "html";
|
|
896
|
+
const base = getBaselineComputed(sourceEl.tagName, ns);
|
|
897
|
+
for (const prop of KEY_PROPS) {
|
|
898
|
+
const v = cs.getPropertyValue(prop) || "";
|
|
899
|
+
const b = base[prop] || "";
|
|
900
|
+
if (v && v !== b) {
|
|
859
901
|
try {
|
|
860
|
-
cloneEl.style.setProperty(
|
|
902
|
+
cloneEl.style.setProperty(prop, v.trim());
|
|
861
903
|
} catch {
|
|
862
904
|
}
|
|
863
905
|
}
|
|
@@ -1180,11 +1222,50 @@ async function deepClone(node, sessionCache, options) {
|
|
|
1180
1222
|
return clone2;
|
|
1181
1223
|
}
|
|
1182
1224
|
if (node.tagName === "CANVAS") {
|
|
1183
|
-
|
|
1225
|
+
let url = "";
|
|
1226
|
+
try {
|
|
1227
|
+
const ctx = node.getContext("2d", { willReadFrequently: true });
|
|
1228
|
+
try {
|
|
1229
|
+
ctx && ctx.getImageData(0, 0, 1, 1);
|
|
1230
|
+
} catch {
|
|
1231
|
+
}
|
|
1232
|
+
await new Promise((r) => requestAnimationFrame(r));
|
|
1233
|
+
url = node.toDataURL("image/png");
|
|
1234
|
+
if (!url || url === "data:,") {
|
|
1235
|
+
try {
|
|
1236
|
+
ctx && ctx.getImageData(0, 0, 1, 1);
|
|
1237
|
+
} catch {
|
|
1238
|
+
}
|
|
1239
|
+
await new Promise((r) => requestAnimationFrame(r));
|
|
1240
|
+
url = node.toDataURL("image/png");
|
|
1241
|
+
if (!url || url === "data:,") {
|
|
1242
|
+
const scratch = document.createElement("canvas");
|
|
1243
|
+
scratch.width = node.width;
|
|
1244
|
+
scratch.height = node.height;
|
|
1245
|
+
const sctx = scratch.getContext("2d");
|
|
1246
|
+
if (sctx) {
|
|
1247
|
+
sctx.drawImage(node, 0, 0);
|
|
1248
|
+
url = scratch.toDataURL("image/png");
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
} catch {
|
|
1253
|
+
}
|
|
1184
1254
|
const img = document.createElement("img");
|
|
1185
|
-
|
|
1255
|
+
try {
|
|
1256
|
+
img.decoding = "sync";
|
|
1257
|
+
img.loading = "eager";
|
|
1258
|
+
} catch {
|
|
1259
|
+
}
|
|
1260
|
+
if (url) img.src = url;
|
|
1186
1261
|
img.width = node.width;
|
|
1187
1262
|
img.height = node.height;
|
|
1263
|
+
try {
|
|
1264
|
+
const cs = getComputedStyle(node);
|
|
1265
|
+
if (cs.width) img.style.width = cs.width;
|
|
1266
|
+
if (cs.height) img.style.height = cs.height;
|
|
1267
|
+
} catch {
|
|
1268
|
+
}
|
|
1188
1269
|
sessionCache.nodeMap.set(img, node);
|
|
1189
1270
|
inlineAllStyles(node, img, sessionCache, options);
|
|
1190
1271
|
return img;
|
|
@@ -1362,6 +1443,123 @@ function isIconFont2(input) {
|
|
|
1362
1443
|
if (/icon/i.test(text) || /glyph/i.test(text) || /symbols/i.test(text) || /feather/i.test(text) || /fontawesome/i.test(text)) return true;
|
|
1363
1444
|
return false;
|
|
1364
1445
|
}
|
|
1446
|
+
function isMaterialFamily(family = "") {
|
|
1447
|
+
const s = String(family).toLowerCase();
|
|
1448
|
+
return /\bmaterial\s*icons\b/.test(s) || /\bmaterial\s*symbols\b/.test(s);
|
|
1449
|
+
}
|
|
1450
|
+
async function ensureMaterialFontsReady(family = "Material Icons", px = 24) {
|
|
1451
|
+
try {
|
|
1452
|
+
await Promise.all([
|
|
1453
|
+
document.fonts.load(`400 ${px}px "${family.replace(/["']/g, "")}"`),
|
|
1454
|
+
document.fonts.ready
|
|
1455
|
+
]);
|
|
1456
|
+
} catch {
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
function resolvePaintColor(cs) {
|
|
1460
|
+
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
|
+
}
|
|
1465
|
+
const c = cs.color?.trim();
|
|
1466
|
+
return c && c !== "inherit" ? c : "#000";
|
|
1467
|
+
}
|
|
1468
|
+
async function materialIconToImage(ligatureText, {
|
|
1469
|
+
family = "Material Icons",
|
|
1470
|
+
weight = "normal",
|
|
1471
|
+
fontSize = 32,
|
|
1472
|
+
color = "#000",
|
|
1473
|
+
variation = ""
|
|
1474
|
+
} = {}) {
|
|
1475
|
+
const fam = String(family || "").replace(/^['"]+|['"]+$/g, "");
|
|
1476
|
+
const dpr = window.devicePixelRatio || 1;
|
|
1477
|
+
await ensureMaterialFontsReady(fam, fontSize);
|
|
1478
|
+
const span = document.createElement("span");
|
|
1479
|
+
span.textContent = ligatureText;
|
|
1480
|
+
span.style.position = "absolute";
|
|
1481
|
+
span.style.visibility = "hidden";
|
|
1482
|
+
span.style.left = "-99999px";
|
|
1483
|
+
span.style.whiteSpace = "nowrap";
|
|
1484
|
+
span.style.fontFamily = `"${fam}"`;
|
|
1485
|
+
span.style.fontWeight = String(weight || "normal");
|
|
1486
|
+
span.style.fontSize = `${fontSize}px`;
|
|
1487
|
+
span.style.lineHeight = "1";
|
|
1488
|
+
span.style.margin = "0";
|
|
1489
|
+
span.style.padding = "0";
|
|
1490
|
+
span.style.fontFeatureSettings = "'liga' 1";
|
|
1491
|
+
span.style.fontVariantLigatures = "normal";
|
|
1492
|
+
if (variation) span.style.fontVariationSettings = variation;
|
|
1493
|
+
span.style.color = color;
|
|
1494
|
+
document.body.appendChild(span);
|
|
1495
|
+
const rect = span.getBoundingClientRect();
|
|
1496
|
+
const width = Math.max(1, Math.ceil(rect.width));
|
|
1497
|
+
const height = Math.max(1, Math.ceil(rect.height));
|
|
1498
|
+
document.body.removeChild(span);
|
|
1499
|
+
const canvas = document.createElement("canvas");
|
|
1500
|
+
canvas.width = width * dpr;
|
|
1501
|
+
canvas.height = height * dpr;
|
|
1502
|
+
const ctx = canvas.getContext("2d");
|
|
1503
|
+
ctx.scale(dpr, dpr);
|
|
1504
|
+
ctx.font = `${weight ? `${weight} ` : ""}${fontSize}px "${fam}"`;
|
|
1505
|
+
ctx.textAlign = "left";
|
|
1506
|
+
ctx.textBaseline = "top";
|
|
1507
|
+
ctx.fillStyle = color;
|
|
1508
|
+
try {
|
|
1509
|
+
ctx.fontKerning = "normal";
|
|
1510
|
+
} catch {
|
|
1511
|
+
}
|
|
1512
|
+
ctx.fillText(ligatureText, 0, 0);
|
|
1513
|
+
return {
|
|
1514
|
+
dataUrl: canvas.toDataURL(),
|
|
1515
|
+
width,
|
|
1516
|
+
height
|
|
1517
|
+
};
|
|
1518
|
+
}
|
|
1519
|
+
async function ligatureIconToImage(cloneRoot, sourceRoot) {
|
|
1520
|
+
if (!(cloneRoot instanceof Element)) return 0;
|
|
1521
|
+
const selector = '.material-icons, [class*="material-symbols"]';
|
|
1522
|
+
const cloneNodes = Array.from(
|
|
1523
|
+
cloneRoot.querySelectorAll(selector)
|
|
1524
|
+
).filter((n) => n && n.textContent && n.textContent.trim());
|
|
1525
|
+
if (cloneNodes.length === 0) return 0;
|
|
1526
|
+
const sourceNodes = sourceRoot instanceof Element ? Array.from(sourceRoot.querySelectorAll(selector)).filter((n) => n && n.textContent && n.textContent.trim()) : [];
|
|
1527
|
+
let replaced = 0;
|
|
1528
|
+
for (let i = 0; i < cloneNodes.length; i++) {
|
|
1529
|
+
const el = cloneNodes[i];
|
|
1530
|
+
const src = sourceNodes[i] || null;
|
|
1531
|
+
try {
|
|
1532
|
+
const csSrc = src ? getComputedStyle(src) : getComputedStyle(el);
|
|
1533
|
+
const family = csSrc.fontFamily || "Material Icons";
|
|
1534
|
+
if (!isMaterialFamily(family)) continue;
|
|
1535
|
+
const text = (src || el).textContent.trim();
|
|
1536
|
+
if (!text) continue;
|
|
1537
|
+
const size = parseInt(csSrc.fontSize, 10) || 24;
|
|
1538
|
+
const weight = csSrc.fontWeight && csSrc.fontWeight !== "normal" ? csSrc.fontWeight : "normal";
|
|
1539
|
+
const color = resolvePaintColor(csSrc);
|
|
1540
|
+
const variation = csSrc.fontVariationSettings && csSrc.fontVariationSettings !== "normal" ? csSrc.fontVariationSettings : "";
|
|
1541
|
+
const { dataUrl, width, height } = await materialIconToImage(text, {
|
|
1542
|
+
family,
|
|
1543
|
+
weight,
|
|
1544
|
+
fontSize: size,
|
|
1545
|
+
color,
|
|
1546
|
+
variation
|
|
1547
|
+
});
|
|
1548
|
+
el.textContent = "";
|
|
1549
|
+
const img = el.ownerDocument.createElement("img");
|
|
1550
|
+
img.src = dataUrl;
|
|
1551
|
+
img.alt = text;
|
|
1552
|
+
img.style.height = `${size}px`;
|
|
1553
|
+
img.style.width = `${Math.max(1, Math.round(width / height * size))}px`;
|
|
1554
|
+
img.style.objectFit = "contain";
|
|
1555
|
+
img.style.verticalAlign = getComputedStyle(el).verticalAlign || "baseline";
|
|
1556
|
+
el.appendChild(img);
|
|
1557
|
+
replaced++;
|
|
1558
|
+
} catch {
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
return replaced;
|
|
1562
|
+
}
|
|
1365
1563
|
|
|
1366
1564
|
// src/modules/fonts.js
|
|
1367
1565
|
async function iconToImage(unicodeChar, fontFamily, fontWeight, fontSize = 32, color = "#000") {
|
|
@@ -2176,6 +2374,8 @@ function formatCounter(value, style) {
|
|
|
2176
2374
|
}
|
|
2177
2375
|
}
|
|
2178
2376
|
function buildCounterContext(root) {
|
|
2377
|
+
const getEpoch = () => cache?.session?.__counterEpoch ?? 0;
|
|
2378
|
+
let run = getEpoch();
|
|
2179
2379
|
const nodeCounters = /* @__PURE__ */ new WeakMap();
|
|
2180
2380
|
const rootEl = root instanceof Document ? root.documentElement : root;
|
|
2181
2381
|
const isLi = (el) => el && el.tagName === "LI";
|
|
@@ -2268,6 +2468,14 @@ function buildCounterContext(root) {
|
|
|
2268
2468
|
};
|
|
2269
2469
|
const empty = /* @__PURE__ */ new Map();
|
|
2270
2470
|
build(rootEl, empty, empty);
|
|
2471
|
+
function ensureFresh() {
|
|
2472
|
+
const now = getEpoch();
|
|
2473
|
+
if (now !== run) {
|
|
2474
|
+
run = now;
|
|
2475
|
+
const empty2 = /* @__PURE__ */ new Map();
|
|
2476
|
+
build(rootEl, empty2, empty2);
|
|
2477
|
+
}
|
|
2478
|
+
}
|
|
2271
2479
|
return {
|
|
2272
2480
|
/**
|
|
2273
2481
|
* Get top value for counter name at given node.
|
|
@@ -2275,6 +2483,7 @@ function buildCounterContext(root) {
|
|
|
2275
2483
|
* @param {string} name
|
|
2276
2484
|
*/
|
|
2277
2485
|
get(node, name) {
|
|
2486
|
+
ensureFresh();
|
|
2278
2487
|
const s = nodeCounters.get(node)?.get(name);
|
|
2279
2488
|
return s && s.length ? s[s.length - 1] : 0;
|
|
2280
2489
|
},
|
|
@@ -2284,6 +2493,7 @@ function buildCounterContext(root) {
|
|
|
2284
2493
|
* @param {string} name
|
|
2285
2494
|
*/
|
|
2286
2495
|
getStack(node, name) {
|
|
2496
|
+
ensureFresh();
|
|
2287
2497
|
const s = nodeCounters.get(node)?.get(name);
|
|
2288
2498
|
return s ? s.slice() : [];
|
|
2289
2499
|
}
|
|
@@ -2317,8 +2527,8 @@ function resolveCountersInContent(raw, node, ctx) {
|
|
|
2317
2527
|
}
|
|
2318
2528
|
|
|
2319
2529
|
// src/modules/pseudo.js
|
|
2320
|
-
var counterCtx = null;
|
|
2321
2530
|
var __siblingCounters = /* @__PURE__ */ new WeakMap();
|
|
2531
|
+
var __pseudoEpoch = -1;
|
|
2322
2532
|
function unquoteDoubleStrings2(s) {
|
|
2323
2533
|
return (s || "").replace(/"([^"]*)"/g, "$1");
|
|
2324
2534
|
}
|
|
@@ -2415,12 +2625,19 @@ function resolvePseudoContentAndIncs(node, pseudo, baseCtx) {
|
|
|
2415
2625
|
}
|
|
2416
2626
|
async function inlinePseudoElements(source, clone, sessionCache, options) {
|
|
2417
2627
|
if (!(source instanceof Element) || !(clone instanceof Element)) return;
|
|
2418
|
-
|
|
2628
|
+
const epoch = cache?.session?.__counterEpoch ?? 0;
|
|
2629
|
+
if (__pseudoEpoch !== epoch) {
|
|
2630
|
+
__siblingCounters = /* @__PURE__ */ new WeakMap();
|
|
2631
|
+
if (sessionCache) sessionCache.__counterCtx = null;
|
|
2632
|
+
__pseudoEpoch = epoch;
|
|
2633
|
+
}
|
|
2634
|
+
if (!sessionCache.__counterCtx) {
|
|
2419
2635
|
try {
|
|
2420
|
-
|
|
2636
|
+
sessionCache.__counterCtx = buildCounterContext(source.ownerDocument || document);
|
|
2421
2637
|
} catch {
|
|
2422
2638
|
}
|
|
2423
2639
|
}
|
|
2640
|
+
const counterCtx = sessionCache.__counterCtx;
|
|
2424
2641
|
for (const pseudo of ["::before", "::after", "::first-letter"]) {
|
|
2425
2642
|
try {
|
|
2426
2643
|
const style = getStyle(source, pseudo);
|
|
@@ -2485,7 +2702,8 @@ async function inlinePseudoElements(source, clone, sessionCache, options) {
|
|
|
2485
2702
|
}
|
|
2486
2703
|
const pseudoEl = document.createElement("span");
|
|
2487
2704
|
pseudoEl.dataset.snapdomPseudo = pseudo;
|
|
2488
|
-
pseudoEl.style.
|
|
2705
|
+
pseudoEl.style.display = "inline";
|
|
2706
|
+
pseudoEl.style.verticalAlign = "baseline";
|
|
2489
2707
|
pseudoEl.style.pointerEvents = "none";
|
|
2490
2708
|
const snapshot = snapshotComputedStyle(style);
|
|
2491
2709
|
const key = getStyleKey(snapshot, "span");
|
|
@@ -2513,9 +2731,23 @@ async function inlinePseudoElements(source, clone, sessionCache, options) {
|
|
|
2513
2731
|
} else if (!isIconFont22 && hasExplicitContent) {
|
|
2514
2732
|
pseudoEl.textContent = cleanContent;
|
|
2515
2733
|
}
|
|
2516
|
-
pseudoEl.style.
|
|
2517
|
-
if ("
|
|
2518
|
-
|
|
2734
|
+
pseudoEl.style.backgroundImage = "none";
|
|
2735
|
+
if ("maskImage" in pseudoEl.style) pseudoEl.style.maskImage = "none";
|
|
2736
|
+
if ("webkitMaskImage" in pseudoEl.style) pseudoEl.style.webkitMaskImage = "none";
|
|
2737
|
+
try {
|
|
2738
|
+
pseudoEl.style.backgroundRepeat = style.backgroundRepeat;
|
|
2739
|
+
pseudoEl.style.backgroundSize = style.backgroundSize;
|
|
2740
|
+
if (style.backgroundPositionX && style.backgroundPositionY) {
|
|
2741
|
+
pseudoEl.style.backgroundPositionX = style.backgroundPositionX;
|
|
2742
|
+
pseudoEl.style.backgroundPositionY = style.backgroundPositionY;
|
|
2743
|
+
} else {
|
|
2744
|
+
pseudoEl.style.backgroundPosition = style.backgroundPosition;
|
|
2745
|
+
}
|
|
2746
|
+
pseudoEl.style.backgroundOrigin = style.backgroundOrigin;
|
|
2747
|
+
pseudoEl.style.backgroundClip = style.backgroundClip;
|
|
2748
|
+
pseudoEl.style.backgroundAttachment = style.backgroundAttachment;
|
|
2749
|
+
pseudoEl.style.backgroundBlendMode = style.backgroundBlendMode;
|
|
2750
|
+
} catch {
|
|
2519
2751
|
}
|
|
2520
2752
|
if (hasBg) {
|
|
2521
2753
|
try {
|
|
@@ -2558,53 +2790,113 @@ async function inlinePseudoElements(source, clone, sessionCache, options) {
|
|
|
2558
2790
|
}
|
|
2559
2791
|
|
|
2560
2792
|
// src/modules/svgDefs.js
|
|
2561
|
-
function inlineExternalDefsAndSymbols(
|
|
2562
|
-
if (!
|
|
2563
|
-
const
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2793
|
+
function inlineExternalDefsAndSymbols(element, lookupRoot) {
|
|
2794
|
+
if (!element || !(element instanceof Element)) return;
|
|
2795
|
+
const doc = element.ownerDocument || document;
|
|
2796
|
+
const searchRoot = lookupRoot || doc;
|
|
2797
|
+
const svgRoots = element instanceof SVGSVGElement ? [element] : Array.from(element.querySelectorAll("svg"));
|
|
2798
|
+
if (svgRoots.length === 0) return;
|
|
2799
|
+
const URL_ID_RE = /url\(\s*#([^)]+)\)/g;
|
|
2800
|
+
const URL_ATTRS = [
|
|
2801
|
+
"fill",
|
|
2802
|
+
"stroke",
|
|
2803
|
+
"filter",
|
|
2804
|
+
"clip-path",
|
|
2805
|
+
"mask",
|
|
2806
|
+
"marker",
|
|
2807
|
+
"marker-start",
|
|
2808
|
+
"marker-mid",
|
|
2809
|
+
"marker-end"
|
|
2810
|
+
];
|
|
2811
|
+
const cssEscape = (s) => window.CSS && CSS.escape ? CSS.escape(s) : s.replace(/[^a-zA-Z0-9_-]/g, "\\$&");
|
|
2812
|
+
const globalExistingIds = new Set(
|
|
2813
|
+
Array.from(element.querySelectorAll("[id]")).map((n) => n.id)
|
|
2814
|
+
);
|
|
2815
|
+
const neededIds = /* @__PURE__ */ new Set();
|
|
2816
|
+
let sawAnyReference = false;
|
|
2817
|
+
const addUrlIdsFromValue = (val) => {
|
|
2818
|
+
if (!val) return;
|
|
2819
|
+
URL_ID_RE.lastIndex = 0;
|
|
2820
|
+
let m;
|
|
2821
|
+
while (m = URL_ID_RE.exec(val)) {
|
|
2822
|
+
sawAnyReference = true;
|
|
2823
|
+
const id = (m[1] || "").trim();
|
|
2824
|
+
if (id && !globalExistingIds.has(id)) neededIds.add(id);
|
|
2568
2825
|
}
|
|
2569
|
-
}
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
container.setAttribute("style", "position: absolute; width: 0; height: 0; overflow: hidden;");
|
|
2579
|
-
container.classList.add("inline-defs-container");
|
|
2580
|
-
rootElement.insertBefore(container, rootElement.firstChild);
|
|
2581
|
-
}
|
|
2582
|
-
const existingIds = /* @__PURE__ */ new Set();
|
|
2583
|
-
rootElement.querySelectorAll("symbol[id], defs > *[id]").forEach((el) => {
|
|
2584
|
-
existingIds.add(el.id);
|
|
2585
|
-
});
|
|
2586
|
-
usedIds.forEach((id) => {
|
|
2587
|
-
if (existingIds.has(id)) return;
|
|
2588
|
-
const symbol = globalSymbols.find((sym) => sym.id === id);
|
|
2589
|
-
if (symbol) {
|
|
2590
|
-
container.appendChild(symbol.cloneNode(true));
|
|
2591
|
-
existingIds.add(id);
|
|
2592
|
-
return;
|
|
2826
|
+
};
|
|
2827
|
+
const collectReferencesInSvg = (rootSvg) => {
|
|
2828
|
+
const uses = rootSvg.querySelectorAll('use[href^="#"], use[xlink\\:href^="#"]');
|
|
2829
|
+
for (const u of uses) {
|
|
2830
|
+
const href = u.getAttribute("href") || u.getAttribute("xlink:href");
|
|
2831
|
+
if (!href || !href.startsWith("#")) continue;
|
|
2832
|
+
sawAnyReference = true;
|
|
2833
|
+
const id = href.slice(1).trim();
|
|
2834
|
+
if (id && !globalExistingIds.has(id)) neededIds.add(id);
|
|
2593
2835
|
}
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2836
|
+
let query = '*[style*="url("],*[fill^="url("], *[stroke^="url("],*[filter^="url("], *[clip-path^="url("],*[mask^="url("],*[marker^="url("], *[marker-start^="url("],*[marker-mid^="url("], *[marker-end^="url("]';
|
|
2837
|
+
const candidates = rootSvg.querySelectorAll(query);
|
|
2838
|
+
for (const el of candidates) {
|
|
2839
|
+
addUrlIdsFromValue(el.getAttribute("style") || "");
|
|
2840
|
+
for (const a of URL_ATTRS) addUrlIdsFromValue(el.getAttribute(a));
|
|
2841
|
+
}
|
|
2842
|
+
};
|
|
2843
|
+
for (const svg of svgRoots) collectReferencesInSvg(svg);
|
|
2844
|
+
if (!sawAnyReference) return;
|
|
2845
|
+
let defsHost = element.querySelector("svg.inline-defs-container");
|
|
2846
|
+
if (!defsHost) {
|
|
2847
|
+
defsHost = doc.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
2848
|
+
defsHost.classList.add("inline-defs-container");
|
|
2849
|
+
defsHost.setAttribute("aria-hidden", "true");
|
|
2850
|
+
defsHost.setAttribute("style", "position:absolute;width:0;height:0;overflow:hidden");
|
|
2851
|
+
element.insertBefore(defsHost, element.firstChild || null);
|
|
2852
|
+
}
|
|
2853
|
+
let localDefs = defsHost.querySelector("defs") || null;
|
|
2854
|
+
const findGlobalById = (id) => {
|
|
2855
|
+
if (!id) return null;
|
|
2856
|
+
if (globalExistingIds.has(id)) return null;
|
|
2857
|
+
const esc = cssEscape(id);
|
|
2858
|
+
const tryFind = (sel) => {
|
|
2859
|
+
const el = searchRoot.querySelector(sel);
|
|
2860
|
+
return el && !element.contains(el) ? el : null;
|
|
2861
|
+
};
|
|
2862
|
+
return tryFind(`svg defs > *#${esc}`) || tryFind(`svg > symbol#${esc}`) || tryFind(`*#${esc}`);
|
|
2863
|
+
};
|
|
2864
|
+
if (!neededIds.size) return;
|
|
2865
|
+
const queued = new Set(neededIds);
|
|
2866
|
+
const inlined = /* @__PURE__ */ new Set();
|
|
2867
|
+
while (queued.size) {
|
|
2868
|
+
const id = queued.values().next().value;
|
|
2869
|
+
queued.delete(id);
|
|
2870
|
+
if (!id || globalExistingIds.has(id) || inlined.has(id)) continue;
|
|
2871
|
+
const source = findGlobalById(id);
|
|
2872
|
+
if (!source) {
|
|
2873
|
+
inlined.add(id);
|
|
2874
|
+
continue;
|
|
2875
|
+
}
|
|
2876
|
+
if (!localDefs) {
|
|
2877
|
+
localDefs = doc.createElementNS("http://www.w3.org/2000/svg", "defs");
|
|
2878
|
+
defsHost.appendChild(localDefs);
|
|
2879
|
+
}
|
|
2880
|
+
const clone = source.cloneNode(true);
|
|
2881
|
+
if (!clone.id) clone.setAttribute("id", id);
|
|
2882
|
+
localDefs.appendChild(clone);
|
|
2883
|
+
inlined.add(id);
|
|
2884
|
+
globalExistingIds.add(id);
|
|
2885
|
+
const walk = [clone, ...clone.querySelectorAll("*")];
|
|
2886
|
+
for (const node of walk) {
|
|
2887
|
+
const h = node.getAttribute?.("href") || node.getAttribute?.("xlink:href");
|
|
2888
|
+
if (h && h.startsWith("#")) {
|
|
2889
|
+
const ref = h.slice(1).trim();
|
|
2890
|
+
if (ref && !globalExistingIds.has(ref) && !inlined.has(ref)) queued.add(ref);
|
|
2891
|
+
}
|
|
2892
|
+
const style = node.getAttribute?.("style") || "";
|
|
2893
|
+
if (style) addUrlIdsFromValue(style);
|
|
2894
|
+
for (const a of URL_ATTRS) {
|
|
2895
|
+
const v = node.getAttribute?.(a);
|
|
2896
|
+
if (v) addUrlIdsFromValue(v);
|
|
2605
2897
|
}
|
|
2606
2898
|
}
|
|
2607
|
-
}
|
|
2899
|
+
}
|
|
2608
2900
|
}
|
|
2609
2901
|
|
|
2610
2902
|
// src/modules/changeCSS.js
|
|
@@ -3050,6 +3342,17 @@ async function inlineBackgroundImages(source, clone, styleCache, options = {}) {
|
|
|
3050
3342
|
"-webkit-mask-position-x",
|
|
3051
3343
|
"-webkit-mask-position-y"
|
|
3052
3344
|
];
|
|
3345
|
+
const BG_LAYOUT_PROPS = [
|
|
3346
|
+
"background-position",
|
|
3347
|
+
"background-position-x",
|
|
3348
|
+
"background-position-y",
|
|
3349
|
+
"background-size",
|
|
3350
|
+
"background-repeat",
|
|
3351
|
+
"background-origin",
|
|
3352
|
+
"background-clip",
|
|
3353
|
+
"background-attachment",
|
|
3354
|
+
"background-blend-mode"
|
|
3355
|
+
];
|
|
3053
3356
|
const BORDER_AUX_PROPS = [
|
|
3054
3357
|
"border-image-slice",
|
|
3055
3358
|
"border-image-width",
|
|
@@ -3065,6 +3368,11 @@ async function inlineBackgroundImages(source, clone, styleCache, options = {}) {
|
|
|
3065
3368
|
const bis = style.getPropertyValue("border-image-source");
|
|
3066
3369
|
return bi && bi !== "none" || bis && bis !== "none";
|
|
3067
3370
|
})();
|
|
3371
|
+
for (const prop of BG_LAYOUT_PROPS) {
|
|
3372
|
+
const v = style.getPropertyValue(prop);
|
|
3373
|
+
if (!v) continue;
|
|
3374
|
+
cloneNode.style.setProperty(prop, v);
|
|
3375
|
+
}
|
|
3068
3376
|
for (const prop of URL_PROPS) {
|
|
3069
3377
|
const val = style.getPropertyValue(prop);
|
|
3070
3378
|
if (!val || val === "none") continue;
|
|
@@ -3089,7 +3397,7 @@ async function inlineBackgroundImages(source, clone, styleCache, options = {}) {
|
|
|
3089
3397
|
}
|
|
3090
3398
|
}
|
|
3091
3399
|
const sChildren = Array.from(srcNode.children);
|
|
3092
|
-
const cChildren = Array.from(cloneNode.children);
|
|
3400
|
+
const cChildren = Array.from(cloneNode.children).filter((el) => !(el.dataset && el.dataset.snapdomPseudo));
|
|
3093
3401
|
for (let i = 0; i < Math.min(sChildren.length, cChildren.length); i++) {
|
|
3094
3402
|
queue.push([sChildren[i], cChildren[i]]);
|
|
3095
3403
|
}
|
|
@@ -3153,6 +3461,81 @@ function isPlainTextContainer(el) {
|
|
|
3153
3461
|
return Array.from(el.childNodes).some((n) => n.nodeType === Node.TEXT_NODE);
|
|
3154
3462
|
}
|
|
3155
3463
|
|
|
3464
|
+
// src/core/plugins.js
|
|
3465
|
+
var __plugins = [];
|
|
3466
|
+
function normalizePlugin(spec) {
|
|
3467
|
+
if (!spec) return null;
|
|
3468
|
+
if (Array.isArray(spec)) {
|
|
3469
|
+
const [factory, options] = spec;
|
|
3470
|
+
return typeof factory === "function" ? factory(options) : factory;
|
|
3471
|
+
}
|
|
3472
|
+
if (typeof spec === "object" && "plugin" in spec) {
|
|
3473
|
+
const { plugin, options } = spec;
|
|
3474
|
+
return typeof plugin === "function" ? plugin(options) : plugin;
|
|
3475
|
+
}
|
|
3476
|
+
if (typeof spec === "function") return spec();
|
|
3477
|
+
return spec;
|
|
3478
|
+
}
|
|
3479
|
+
function registerPlugins(...defs) {
|
|
3480
|
+
const flat = defs.flat();
|
|
3481
|
+
for (const d of flat) {
|
|
3482
|
+
const inst = normalizePlugin(d);
|
|
3483
|
+
if (!inst) continue;
|
|
3484
|
+
if (!__plugins.some((p) => p && p.name && inst.name && p.name === inst.name)) {
|
|
3485
|
+
__plugins.push(inst);
|
|
3486
|
+
}
|
|
3487
|
+
}
|
|
3488
|
+
}
|
|
3489
|
+
function getContextPlugins(context) {
|
|
3490
|
+
const arr = context && Array.isArray(context.plugins) ? context.plugins : __plugins;
|
|
3491
|
+
return arr || __plugins;
|
|
3492
|
+
}
|
|
3493
|
+
async function runHook(name, context, payload) {
|
|
3494
|
+
let acc = payload;
|
|
3495
|
+
const list = getContextPlugins(context);
|
|
3496
|
+
for (const p of list) {
|
|
3497
|
+
const fn = p && typeof p[name] === "function" ? p[name] : null;
|
|
3498
|
+
if (!fn) continue;
|
|
3499
|
+
const out = await fn(context, acc);
|
|
3500
|
+
if (typeof out !== "undefined") acc = out;
|
|
3501
|
+
}
|
|
3502
|
+
return acc;
|
|
3503
|
+
}
|
|
3504
|
+
async function runAll(name, context, payload) {
|
|
3505
|
+
const outs = [];
|
|
3506
|
+
const list = getContextPlugins(context);
|
|
3507
|
+
for (const p of list) {
|
|
3508
|
+
const fn = p && typeof p[name] === "function" ? p[name] : null;
|
|
3509
|
+
if (!fn) continue;
|
|
3510
|
+
const out = await fn(context, payload);
|
|
3511
|
+
if (typeof out !== "undefined") outs.push(out);
|
|
3512
|
+
}
|
|
3513
|
+
return outs;
|
|
3514
|
+
}
|
|
3515
|
+
function mergePlugins(localDefs) {
|
|
3516
|
+
const out = [];
|
|
3517
|
+
if (Array.isArray(localDefs)) {
|
|
3518
|
+
for (const d of localDefs) {
|
|
3519
|
+
const inst = normalizePlugin(d);
|
|
3520
|
+
if (!inst || !inst.name) continue;
|
|
3521
|
+
const i = out.findIndex((x) => x && x.name === inst.name);
|
|
3522
|
+
if (i >= 0) out.splice(i, 1);
|
|
3523
|
+
out.push(inst);
|
|
3524
|
+
}
|
|
3525
|
+
}
|
|
3526
|
+
for (const g of __plugins) {
|
|
3527
|
+
if (g && g.name && !out.some((x) => x.name === g.name)) {
|
|
3528
|
+
out.push(g);
|
|
3529
|
+
}
|
|
3530
|
+
}
|
|
3531
|
+
return Object.freeze(out);
|
|
3532
|
+
}
|
|
3533
|
+
function attachSessionPlugins(context, localDefs, force = false) {
|
|
3534
|
+
if (!context || context.plugins && !force) return context;
|
|
3535
|
+
context.plugins = mergePlugins(localDefs);
|
|
3536
|
+
return context;
|
|
3537
|
+
}
|
|
3538
|
+
|
|
3156
3539
|
// src/core/capture.js
|
|
3157
3540
|
function stripRootShadows(originalEl, cloneRoot) {
|
|
3158
3541
|
if (!originalEl || !cloneRoot || !cloneRoot.style) return;
|
|
@@ -3180,43 +3563,52 @@ async function captureDOM(element, options) {
|
|
|
3180
3563
|
if (!element) throw new Error("Element cannot be null or undefined");
|
|
3181
3564
|
applyCachePolicy(options.cache);
|
|
3182
3565
|
const fast = options.fast;
|
|
3183
|
-
const
|
|
3184
|
-
const
|
|
3566
|
+
const outerTransforms = options.outerTransforms !== false;
|
|
3567
|
+
const outerShadows = !!options.outerShadows;
|
|
3568
|
+
let state = { element, options, plugins: options.plugins };
|
|
3185
3569
|
let clone, classCSS, styleCache;
|
|
3186
3570
|
let fontsCSS = "";
|
|
3187
3571
|
let baseCSS = "";
|
|
3188
3572
|
let dataURL;
|
|
3189
3573
|
let svgString;
|
|
3190
3574
|
let rootTransform2D = null;
|
|
3191
|
-
|
|
3575
|
+
await runHook("beforeSnap", state);
|
|
3576
|
+
await runHook("beforeClone", state);
|
|
3577
|
+
const undoClamp = lineClamp(state.element);
|
|
3192
3578
|
try {
|
|
3193
|
-
({ clone, classCSS, styleCache } = await prepareClone(element, options));
|
|
3194
|
-
if (
|
|
3195
|
-
rootTransform2D = normalizeRootTransforms(element, clone);
|
|
3579
|
+
({ clone, classCSS, styleCache } = await prepareClone(state.element, state.options));
|
|
3580
|
+
if (!outerTransforms && clone) {
|
|
3581
|
+
rootTransform2D = normalizeRootTransforms(state.element, clone);
|
|
3196
3582
|
}
|
|
3197
|
-
if (
|
|
3198
|
-
stripRootShadows(element, clone);
|
|
3583
|
+
if (!outerShadows && clone) {
|
|
3584
|
+
stripRootShadows(state.element, clone);
|
|
3199
3585
|
}
|
|
3200
3586
|
} finally {
|
|
3201
3587
|
undoClamp();
|
|
3202
3588
|
}
|
|
3589
|
+
state = { clone, classCSS, styleCache, ...state };
|
|
3590
|
+
await runHook("afterClone", state);
|
|
3591
|
+
try {
|
|
3592
|
+
await ligatureIconToImage(state.clone, state.element);
|
|
3593
|
+
} catch {
|
|
3594
|
+
}
|
|
3203
3595
|
await new Promise((resolve) => {
|
|
3204
3596
|
idle(async () => {
|
|
3205
|
-
await inlineImages(clone, options);
|
|
3597
|
+
await inlineImages(state.clone, state.options);
|
|
3206
3598
|
resolve();
|
|
3207
3599
|
}, { fast });
|
|
3208
3600
|
});
|
|
3209
3601
|
await new Promise((resolve) => {
|
|
3210
3602
|
idle(async () => {
|
|
3211
|
-
await inlineBackgroundImages(element, clone, styleCache, options);
|
|
3603
|
+
await inlineBackgroundImages(state.element, state.clone, state.styleCache, state.options);
|
|
3212
3604
|
resolve();
|
|
3213
3605
|
}, { fast });
|
|
3214
3606
|
});
|
|
3215
3607
|
if (options.embedFonts) {
|
|
3216
3608
|
await new Promise((resolve) => {
|
|
3217
3609
|
idle(async () => {
|
|
3218
|
-
const required = collectUsedFontVariants(element);
|
|
3219
|
-
const usedCodepoints = collectUsedCodepoints(element);
|
|
3610
|
+
const required = collectUsedFontVariants(state.element);
|
|
3611
|
+
const usedCodepoints = collectUsedCodepoints(state.element);
|
|
3220
3612
|
if (isSafari()) {
|
|
3221
3613
|
const families = new Set(
|
|
3222
3614
|
Array.from(required).map((k) => String(k).split("__")[0]).filter(Boolean)
|
|
@@ -3227,14 +3619,14 @@ async function captureDOM(element, options) {
|
|
|
3227
3619
|
required,
|
|
3228
3620
|
usedCodepoints,
|
|
3229
3621
|
preCached: false,
|
|
3230
|
-
exclude: options.excludeFonts,
|
|
3231
|
-
useProxy: options.useProxy
|
|
3622
|
+
exclude: state.options.excludeFonts,
|
|
3623
|
+
useProxy: state.options.useProxy
|
|
3232
3624
|
});
|
|
3233
3625
|
resolve();
|
|
3234
3626
|
}, { fast });
|
|
3235
3627
|
});
|
|
3236
3628
|
}
|
|
3237
|
-
const usedTags = collectUsedTagNames(clone).sort();
|
|
3629
|
+
const usedTags = collectUsedTagNames(state.clone).sort();
|
|
3238
3630
|
const tagKey = usedTags.join(",");
|
|
3239
3631
|
if (cache.baseStyle.has(tagKey)) {
|
|
3240
3632
|
baseCSS = cache.baseStyle.get(tagKey);
|
|
@@ -3247,69 +3639,57 @@ async function captureDOM(element, options) {
|
|
|
3247
3639
|
}, { fast });
|
|
3248
3640
|
});
|
|
3249
3641
|
}
|
|
3642
|
+
state = { fontsCSS, baseCSS, ...state };
|
|
3643
|
+
await runHook("beforeRender", state);
|
|
3250
3644
|
await new Promise((resolve) => {
|
|
3251
3645
|
idle(() => {
|
|
3252
|
-
const csEl = getComputedStyle(element);
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
return { bleed: { top: 0, right: 0, bottom: 0, left: 0 }, has: false };
|
|
3257
|
-
}
|
|
3258
|
-
const tokens = raw.match(/drop-shadow\((?:[^()]|\([^()]*\))*\)/gi) || [];
|
|
3259
|
-
let t = 0, r = 0, b = 0, l = 0;
|
|
3260
|
-
let found = false;
|
|
3261
|
-
for (const tok of tokens) {
|
|
3262
|
-
found = true;
|
|
3263
|
-
const nums = tok.match(/-?\d+(?:\.\d+)?px/gi)?.map((v) => parseFloat(v)) || [];
|
|
3264
|
-
const [ox = 0, oy = 0, blur = 0] = nums;
|
|
3265
|
-
const extX = Math.abs(ox) + blur;
|
|
3266
|
-
const extY = Math.abs(oy) + blur;
|
|
3267
|
-
r = Math.max(r, extX + Math.max(ox, 0));
|
|
3268
|
-
l = Math.max(l, extX + Math.max(-ox, 0));
|
|
3269
|
-
b = Math.max(b, extY + Math.max(oy, 0));
|
|
3270
|
-
t = Math.max(t, extY + Math.max(-oy, 0));
|
|
3271
|
-
}
|
|
3272
|
-
return { bleed: { top: Math.ceil(t), right: Math.ceil(r), bottom: Math.ceil(b), left: Math.ceil(l) }, has: found };
|
|
3273
|
-
}
|
|
3274
|
-
const rect = element.getBoundingClientRect();
|
|
3275
|
-
const w0 = Math.max(1, Math.ceil(element.offsetWidth || parseFloat(csEl.width) || rect.width || 1));
|
|
3276
|
-
const h0 = Math.max(1, Math.ceil(element.offsetHeight || parseFloat(csEl.height) || rect.height || 1));
|
|
3646
|
+
const csEl = getComputedStyle(state.element);
|
|
3647
|
+
const rect = state.element.getBoundingClientRect();
|
|
3648
|
+
const w0 = Math.max(1, limitDecimals(state.element.offsetWidth || parseFloat(csEl.width) || rect.width || 1));
|
|
3649
|
+
const h0 = Math.max(1, limitDecimals(state.element.offsetHeight || parseFloat(csEl.height) || rect.height || 1));
|
|
3277
3650
|
const coerceNum = (v, def = NaN) => {
|
|
3278
3651
|
const n = typeof v === "string" ? parseFloat(v) : v;
|
|
3279
3652
|
return Number.isFinite(n) ? n : def;
|
|
3280
3653
|
};
|
|
3281
|
-
const optW = coerceNum(options.width);
|
|
3282
|
-
const optH = coerceNum(options.height);
|
|
3654
|
+
const optW = coerceNum(state.options.width);
|
|
3655
|
+
const optH = coerceNum(state.options.height);
|
|
3283
3656
|
let w = w0, h = h0;
|
|
3284
3657
|
const hasW = Number.isFinite(optW);
|
|
3285
3658
|
const hasH = Number.isFinite(optH);
|
|
3286
3659
|
const aspect0 = h0 > 0 ? w0 / h0 : 1;
|
|
3287
3660
|
if (hasW && hasH) {
|
|
3288
|
-
w = Math.max(1,
|
|
3289
|
-
h = Math.max(1,
|
|
3661
|
+
w = Math.max(1, limitDecimals(optW));
|
|
3662
|
+
h = Math.max(1, limitDecimals(optH));
|
|
3290
3663
|
} else if (hasW) {
|
|
3291
|
-
w = Math.max(1,
|
|
3292
|
-
h = Math.max(1,
|
|
3664
|
+
w = Math.max(1, limitDecimals(optW));
|
|
3665
|
+
h = Math.max(1, limitDecimals(w / (aspect0 || 1)));
|
|
3293
3666
|
} else if (hasH) {
|
|
3294
|
-
h = Math.max(1,
|
|
3295
|
-
w = Math.max(1,
|
|
3667
|
+
h = Math.max(1, limitDecimals(optH));
|
|
3668
|
+
w = Math.max(1, limitDecimals(h * (aspect0 || 1)));
|
|
3296
3669
|
} else {
|
|
3297
3670
|
w = w0;
|
|
3298
3671
|
h = h0;
|
|
3299
3672
|
}
|
|
3300
3673
|
let minX = 0, minY = 0, maxX = w0, maxY = h0;
|
|
3301
|
-
if (
|
|
3302
|
-
const M2 = {
|
|
3674
|
+
if (!outerTransforms && rootTransform2D && Number.isFinite(rootTransform2D.a)) {
|
|
3675
|
+
const M2 = {
|
|
3676
|
+
a: rootTransform2D.a,
|
|
3677
|
+
b: rootTransform2D.b || 0,
|
|
3678
|
+
c: rootTransform2D.c || 0,
|
|
3679
|
+
d: rootTransform2D.d || 1,
|
|
3680
|
+
e: 0,
|
|
3681
|
+
f: 0
|
|
3682
|
+
};
|
|
3303
3683
|
const bb2 = bboxWithOriginFull(w0, h0, M2, 0, 0);
|
|
3304
|
-
minX = bb2.minX;
|
|
3305
|
-
minY = bb2.minY;
|
|
3306
|
-
maxX = bb2.maxX;
|
|
3307
|
-
maxY = bb2.maxY;
|
|
3684
|
+
minX = limitDecimals(bb2.minX);
|
|
3685
|
+
minY = limitDecimals(bb2.minY);
|
|
3686
|
+
maxX = limitDecimals(bb2.maxX);
|
|
3687
|
+
maxY = limitDecimals(bb2.maxY);
|
|
3308
3688
|
} else {
|
|
3309
|
-
const useTFBBox =
|
|
3689
|
+
const useTFBBox = outerTransforms && hasTFBBox(state.element);
|
|
3310
3690
|
if (useTFBBox) {
|
|
3311
3691
|
const baseTransform2 = csEl.transform && csEl.transform !== "none" ? csEl.transform : "";
|
|
3312
|
-
const ind2 = readIndividualTransforms(element);
|
|
3692
|
+
const ind2 = readIndividualTransforms(state.element);
|
|
3313
3693
|
const TOTAL = readTotalTransformMatrix({
|
|
3314
3694
|
baseTransform: baseTransform2,
|
|
3315
3695
|
rotate: ind2.rotate || "0deg",
|
|
@@ -3319,71 +3699,105 @@ async function captureDOM(element, options) {
|
|
|
3319
3699
|
const { ox: ox2, oy: oy2 } = parseTransformOriginPx(csEl, w0, h0);
|
|
3320
3700
|
const M = TOTAL.is2D ? TOTAL : new DOMMatrix(TOTAL.toString());
|
|
3321
3701
|
const bb = bboxWithOriginFull(w0, h0, M, ox2, oy2);
|
|
3322
|
-
minX = bb.minX;
|
|
3323
|
-
minY = bb.minY;
|
|
3324
|
-
maxX = bb.maxX;
|
|
3325
|
-
maxY = bb.maxY;
|
|
3702
|
+
minX = limitDecimals(bb.minX);
|
|
3703
|
+
minY = limitDecimals(bb.minY);
|
|
3704
|
+
maxX = limitDecimals(bb.maxX);
|
|
3705
|
+
maxY = limitDecimals(bb.maxY);
|
|
3326
3706
|
}
|
|
3327
3707
|
}
|
|
3328
3708
|
const bleedShadow = parseBoxShadow(csEl);
|
|
3329
3709
|
const bleedBlur = parseFilterBlur(csEl);
|
|
3330
3710
|
const bleedOutline = parseOutline(csEl);
|
|
3331
3711
|
const drop = parseFilterDropShadows(csEl);
|
|
3332
|
-
const bleed =
|
|
3333
|
-
top: bleedShadow.top + bleedBlur.top + bleedOutline.top + drop.bleed.top,
|
|
3334
|
-
right: bleedShadow.right + bleedBlur.right + bleedOutline.right + drop.bleed.right,
|
|
3335
|
-
bottom: bleedShadow.bottom + bleedBlur.bottom + bleedOutline.bottom + drop.bleed.bottom,
|
|
3336
|
-
left: bleedShadow.left + bleedBlur.left + bleedOutline.left + drop.bleed.left
|
|
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)
|
|
3337
3717
|
};
|
|
3338
|
-
minX
|
|
3339
|
-
minY
|
|
3340
|
-
maxX
|
|
3341
|
-
maxY
|
|
3342
|
-
const vbW0 = Math.max(1,
|
|
3343
|
-
const vbH0 = Math.max(1,
|
|
3344
|
-
const
|
|
3345
|
-
const
|
|
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));
|
|
3346
3728
|
const svgNS = "http://www.w3.org/2000/svg";
|
|
3347
3729
|
const basePad = isSafari() ? 1 : 0;
|
|
3348
|
-
const extraPad =
|
|
3349
|
-
const pad = basePad + extraPad;
|
|
3730
|
+
const extraPad = !outerTransforms ? 1 : 0;
|
|
3731
|
+
const pad = limitDecimals(basePad + extraPad);
|
|
3350
3732
|
const fo = document.createElementNS(svgNS, "foreignObject");
|
|
3351
|
-
const vbMinX =
|
|
3352
|
-
const vbMinY =
|
|
3353
|
-
fo.setAttribute("x", String(-(vbMinX - pad)));
|
|
3354
|
-
fo.setAttribute("y", String(-(vbMinY - pad)));
|
|
3355
|
-
fo.setAttribute("width", String(
|
|
3356
|
-
fo.setAttribute("height", String(
|
|
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)));
|
|
3357
3739
|
fo.style.overflow = "visible";
|
|
3358
3740
|
const styleTag = document.createElement("style");
|
|
3359
|
-
styleTag.textContent = baseCSS + fontsCSS + "svg{overflow:visible;} foreignObject{overflow:visible;}" + classCSS;
|
|
3741
|
+
styleTag.textContent = state.baseCSS + state.fontsCSS + "svg{overflow:visible;} foreignObject{overflow:visible;}" + state.classCSS;
|
|
3360
3742
|
fo.appendChild(styleTag);
|
|
3361
3743
|
const container = document.createElement("div");
|
|
3362
3744
|
container.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
|
|
3363
|
-
container.style.width = `${w0}px`;
|
|
3364
|
-
container.style.height = `${h0}px`;
|
|
3745
|
+
container.style.width = `${limitDecimals(w0)}px`;
|
|
3746
|
+
container.style.height = `${limitDecimals(h0)}px`;
|
|
3365
3747
|
container.style.overflow = "visible";
|
|
3366
|
-
clone.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
|
|
3367
|
-
container.appendChild(clone);
|
|
3748
|
+
state.clone.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
|
|
3749
|
+
container.appendChild(state.clone);
|
|
3368
3750
|
fo.appendChild(container);
|
|
3369
3751
|
const serializer = new XMLSerializer();
|
|
3370
3752
|
const foString = serializer.serializeToString(fo);
|
|
3371
|
-
const vbW = vbW0 + pad * 2;
|
|
3372
|
-
const vbH = vbH0 + pad * 2;
|
|
3753
|
+
const vbW = limitDecimals(vbW0 + pad * 2);
|
|
3754
|
+
const vbH = limitDecimals(vbH0 + pad * 2);
|
|
3373
3755
|
const wantsSize = hasW || hasH;
|
|
3374
3756
|
options.meta = { w0, h0, vbW, vbH, targetW: w, targetH: h };
|
|
3375
|
-
const svgOutW = isSafari() && wantsSize ? vbW : outW + pad * 2;
|
|
3376
|
-
const svgOutH = isSafari() && wantsSize ? vbH : outH + pad * 2;
|
|
3757
|
+
const svgOutW = isSafari() && wantsSize ? vbW : limitDecimals(outW + pad * 2);
|
|
3758
|
+
const svgOutH = isSafari() && wantsSize ? vbH : limitDecimals(outH + pad * 2);
|
|
3377
3759
|
const svgHeader = `<svg xmlns="${svgNS}" width="${svgOutW}" height="${svgOutH}" viewBox="0 0 ${vbW} ${vbH}">`;
|
|
3378
3760
|
const svgFooter = "</svg>";
|
|
3379
3761
|
svgString = svgHeader + foString + svgFooter;
|
|
3380
3762
|
dataURL = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgString)}`;
|
|
3763
|
+
state = { svgString, dataURL, ...state };
|
|
3381
3764
|
resolve();
|
|
3382
3765
|
}, { fast });
|
|
3383
3766
|
});
|
|
3767
|
+
await runHook("afterRender", state);
|
|
3384
3768
|
const sandbox = document.getElementById("snapdom-sandbox");
|
|
3385
3769
|
if (sandbox && sandbox.style.position === "absolute") sandbox.remove();
|
|
3386
|
-
return dataURL;
|
|
3770
|
+
return state.dataURL;
|
|
3771
|
+
}
|
|
3772
|
+
var limitDecimals = (v, n = 3) => Number.isFinite(v) ? Math.round(v * 10 ** n) / 10 ** n : v;
|
|
3773
|
+
function parseFilterDropShadows(cs) {
|
|
3774
|
+
const raw = `${cs.filter || ""} ${cs.webkitFilter || ""}`.trim();
|
|
3775
|
+
if (!raw || raw === "none") {
|
|
3776
|
+
return { bleed: { top: 0, right: 0, bottom: 0, left: 0 }, has: false };
|
|
3777
|
+
}
|
|
3778
|
+
const tokens = raw.match(/drop-shadow\((?:[^()]|\([^()]*\))*\)/gi) || [];
|
|
3779
|
+
let t = 0, r = 0, b = 0, l = 0;
|
|
3780
|
+
let found = false;
|
|
3781
|
+
for (const tok of tokens) {
|
|
3782
|
+
found = true;
|
|
3783
|
+
const nums = tok.match(/-?\d+(?:\.\d+)?px/gi)?.map((v) => parseFloat(v)) || [];
|
|
3784
|
+
const [ox = 0, oy = 0, blur = 0] = nums;
|
|
3785
|
+
const extX = Math.abs(ox) + blur;
|
|
3786
|
+
const extY = Math.abs(oy) + blur;
|
|
3787
|
+
r = Math.max(r, extX + Math.max(ox, 0));
|
|
3788
|
+
l = Math.max(l, extX + Math.max(-ox, 0));
|
|
3789
|
+
b = Math.max(b, extY + Math.max(oy, 0));
|
|
3790
|
+
t = Math.max(t, extY + Math.max(-oy, 0));
|
|
3791
|
+
}
|
|
3792
|
+
return {
|
|
3793
|
+
bleed: {
|
|
3794
|
+
top: limitDecimals(t),
|
|
3795
|
+
right: limitDecimals(r),
|
|
3796
|
+
bottom: limitDecimals(b),
|
|
3797
|
+
left: limitDecimals(l)
|
|
3798
|
+
},
|
|
3799
|
+
has: found
|
|
3800
|
+
};
|
|
3387
3801
|
}
|
|
3388
3802
|
function normalizeRootTransforms(originalEl, cloneRoot) {
|
|
3389
3803
|
if (!originalEl || !cloneRoot || !cloneRoot.style) return null;
|
|
@@ -3634,7 +4048,8 @@ function normalizeCachePolicy(v) {
|
|
|
3634
4048
|
return "soft";
|
|
3635
4049
|
}
|
|
3636
4050
|
function createContext(options = {}) {
|
|
3637
|
-
|
|
4051
|
+
let resolvedFormat = options.format ?? "png";
|
|
4052
|
+
if (resolvedFormat === "jpg") resolvedFormat = "jpeg";
|
|
3638
4053
|
const cachePolicy = normalizeCachePolicy(options.cache);
|
|
3639
4054
|
return {
|
|
3640
4055
|
// Debug & perf
|
|
@@ -3666,11 +4081,11 @@ function createContext(options = {}) {
|
|
|
3666
4081
|
type: options.type ?? "svg",
|
|
3667
4082
|
quality: options.quality ?? 0.92,
|
|
3668
4083
|
dpr: options.dpr ?? (window.devicePixelRatio || 1),
|
|
3669
|
-
backgroundColor: options.backgroundColor ?? (["
|
|
4084
|
+
backgroundColor: options.backgroundColor ?? (["jpeg", "webp"].includes(resolvedFormat) ? "#ffffff" : null),
|
|
3670
4085
|
filename: options.filename ?? "snapDOM",
|
|
3671
4086
|
// NEW flags (user-friendly)
|
|
3672
|
-
|
|
3673
|
-
|
|
4087
|
+
outerTransforms: options.outerTransforms ?? true,
|
|
4088
|
+
outerShadows: options.outerShadows ?? false
|
|
3674
4089
|
// Plugins (reservado)
|
|
3675
4090
|
// plugins: normalizePlugins(...),
|
|
3676
4091
|
};
|
|
@@ -3797,20 +4212,20 @@ async function toCanvas(url, options) {
|
|
|
3797
4212
|
} else if (hasW) {
|
|
3798
4213
|
const k = optW / Math.max(1, refW);
|
|
3799
4214
|
outW = optW;
|
|
3800
|
-
outH =
|
|
4215
|
+
outH = refH * k;
|
|
3801
4216
|
} else if (hasH) {
|
|
3802
4217
|
const k = optH / Math.max(1, refH);
|
|
3803
4218
|
outH = optH;
|
|
3804
|
-
outW =
|
|
4219
|
+
outW = refW * k;
|
|
3805
4220
|
} else {
|
|
3806
4221
|
outW = natW;
|
|
3807
4222
|
outH = natH;
|
|
3808
4223
|
}
|
|
3809
|
-
outW =
|
|
3810
|
-
outH =
|
|
4224
|
+
outW = outW * scale;
|
|
4225
|
+
outH = outH * scale;
|
|
3811
4226
|
const canvas = document.createElement("canvas");
|
|
3812
|
-
canvas.width =
|
|
3813
|
-
canvas.height =
|
|
4227
|
+
canvas.width = outW * dpr;
|
|
4228
|
+
canvas.height = outH * dpr;
|
|
3814
4229
|
canvas.style.width = `${outW}px`;
|
|
3815
4230
|
canvas.style.height = `${outH}px`;
|
|
3816
4231
|
const ctx = canvas.getContext("2d");
|
|
@@ -3919,16 +4334,22 @@ async function download(url, options) {
|
|
|
3919
4334
|
}
|
|
3920
4335
|
|
|
3921
4336
|
// src/api/snapdom.js
|
|
4337
|
+
snapdom.plugins = (...defs) => {
|
|
4338
|
+
registerPlugins(...defs);
|
|
4339
|
+
return snapdom;
|
|
4340
|
+
};
|
|
3922
4341
|
var INTERNAL_TOKEN = Symbol("snapdom.internal");
|
|
4342
|
+
var INTERNAL_EXPORT_TOKEN = Symbol("snapdom.internal.silent");
|
|
3923
4343
|
var _safariWarmup = false;
|
|
3924
4344
|
async function snapdom(element, userOptions) {
|
|
3925
4345
|
if (!element) throw new Error("Element cannot be null or undefined");
|
|
3926
4346
|
const context = createContext(userOptions);
|
|
4347
|
+
attachSessionPlugins(context, userOptions && userOptions.plugins);
|
|
3927
4348
|
if (isSafari() && (context.embedFonts === true || hasBackgroundOrMask(element))) {
|
|
3928
4349
|
for (let i = 0; i < 3; i++) {
|
|
3929
4350
|
try {
|
|
3930
4351
|
await safariWarmup(element, userOptions);
|
|
3931
|
-
console.log("
|
|
4352
|
+
console.log("safariWarmup:", i);
|
|
3932
4353
|
_safariWarmup = false;
|
|
3933
4354
|
} catch {
|
|
3934
4355
|
}
|
|
@@ -3943,31 +4364,90 @@ async function snapdom(element, userOptions) {
|
|
|
3943
4364
|
}
|
|
3944
4365
|
return snapdom.capture(element, context, INTERNAL_TOKEN);
|
|
3945
4366
|
}
|
|
4367
|
+
snapdom.plugins = (...defs) => {
|
|
4368
|
+
registerPlugins(...defs);
|
|
4369
|
+
return snapdom;
|
|
4370
|
+
};
|
|
3946
4371
|
snapdom.capture = async (el, context, _token) => {
|
|
3947
4372
|
if (_token !== INTERNAL_TOKEN) throw new Error("[snapdom.capture] is internal. Use snapdom(...) instead.");
|
|
3948
4373
|
const url = await captureDOM(el, context);
|
|
3949
|
-
const
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
}
|
|
3957
|
-
|
|
4374
|
+
const coreExports = {
|
|
4375
|
+
img: async (ctx, opts) => toImg(url, { ...ctx, ...opts || {} }),
|
|
4376
|
+
svg: async (ctx, opts) => toImg(url, { ...ctx, ...opts || {} }),
|
|
4377
|
+
canvas: async (ctx, opts) => toCanvas(url, { ...ctx, ...opts || {} }),
|
|
4378
|
+
blob: async (ctx, opts) => toBlob(url, { ...ctx, ...opts || {} }),
|
|
4379
|
+
png: async (ctx, opts) => rasterize(url, { ...ctx, ...opts || {}, format: "png" }),
|
|
4380
|
+
jpeg: async (ctx, opts) => rasterize(url, { ...ctx, ...opts || {}, format: "jpeg" }),
|
|
4381
|
+
webp: async (ctx, opts) => rasterize(url, { ...ctx, ...opts || {}, format: "webp" }),
|
|
4382
|
+
download: async (ctx, opts) => download(url, { ...ctx, ...opts || {} })
|
|
3958
4383
|
};
|
|
3959
|
-
|
|
4384
|
+
const _pluginExports = {
|
|
4385
|
+
svg: (opts) => toImg(url, { ...context, ...opts || {}, [INTERNAL_EXPORT_TOKEN]: true }),
|
|
4386
|
+
canvas: (opts) => toCanvas(url, { ...context, ...opts || {}, [INTERNAL_EXPORT_TOKEN]: true }),
|
|
4387
|
+
png: (opts) => rasterize(url, { ...context, ...opts || {}, format: "png", [INTERNAL_EXPORT_TOKEN]: true }),
|
|
4388
|
+
jpeg: (opts) => rasterize(url, { ...context, ...opts || {}, format: "jpeg", [INTERNAL_EXPORT_TOKEN]: true }),
|
|
4389
|
+
jpg: (opts) => rasterize(url, { ...context, ...opts || {}, format: "jpeg", [INTERNAL_EXPORT_TOKEN]: true }),
|
|
4390
|
+
webp: (opts) => rasterize(url, { ...context, ...opts || {}, format: "webp", [INTERNAL_EXPORT_TOKEN]: true }),
|
|
4391
|
+
blob: (opts) => toBlob(url, { ...context, ...opts || {}, [INTERNAL_EXPORT_TOKEN]: true }),
|
|
4392
|
+
img: (opts) => toImg(url, { ...context, ...opts || {}, [INTERNAL_EXPORT_TOKEN]: true })
|
|
4393
|
+
};
|
|
4394
|
+
const _defineCtx = { ...context, export: { url }, exports: _pluginExports };
|
|
4395
|
+
const providedMaps = await runAll("defineExports", _defineCtx);
|
|
4396
|
+
const provided = Object.assign({}, ...providedMaps.filter((x) => x && typeof x === "object"));
|
|
4397
|
+
const exportsMap = { ...coreExports, ...provided };
|
|
4398
|
+
if (exportsMap.jpeg && !exportsMap.jpg) {
|
|
4399
|
+
exportsMap.jpg = (ctx, opts) => exportsMap.jpeg(ctx, opts);
|
|
4400
|
+
}
|
|
4401
|
+
function normalizeExportOptions(type, opts) {
|
|
4402
|
+
const next = { ...context, ...opts || {} };
|
|
4403
|
+
if (type === "jpeg" || type === "jpg") {
|
|
4404
|
+
const noBg = next.backgroundColor == null || next.backgroundColor === "transparent";
|
|
4405
|
+
if (noBg) next.backgroundColor = "#ffffff";
|
|
4406
|
+
}
|
|
4407
|
+
return next;
|
|
4408
|
+
}
|
|
4409
|
+
let afterSnapFired = false;
|
|
4410
|
+
let _exportQueue = Promise.resolve();
|
|
4411
|
+
async function runExport(type, opts) {
|
|
4412
|
+
const job = async () => {
|
|
4413
|
+
const work = exportsMap[type];
|
|
4414
|
+
if (!work) throw new Error(`[snapdom] Unknown export type: ${type}`);
|
|
4415
|
+
const nextOpts = normalizeExportOptions(type, opts);
|
|
4416
|
+
const ctx = { ...context, export: { type, options: nextOpts, url } };
|
|
4417
|
+
await runHook("beforeExport", ctx);
|
|
4418
|
+
const result2 = await work(ctx, nextOpts);
|
|
4419
|
+
await runHook("afterExport", ctx, result2);
|
|
4420
|
+
if (!afterSnapFired) {
|
|
4421
|
+
afterSnapFired = true;
|
|
4422
|
+
await runHook("afterSnap", context);
|
|
4423
|
+
}
|
|
4424
|
+
return result2;
|
|
4425
|
+
};
|
|
4426
|
+
return _exportQueue = _exportQueue.then(job);
|
|
4427
|
+
}
|
|
4428
|
+
const result = {
|
|
3960
4429
|
url,
|
|
3961
4430
|
toRaw: () => url,
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
4431
|
+
to: (type, opts) => runExport(type, opts),
|
|
4432
|
+
// Métodos “clásicos” que los tests esperan:
|
|
4433
|
+
toImg: (opts) => runExport("img", opts),
|
|
4434
|
+
toSvg: (opts) => runExport("svg", opts),
|
|
4435
|
+
toCanvas: (opts) => runExport("canvas", opts),
|
|
4436
|
+
toBlob: (opts) => runExport("blob", opts),
|
|
4437
|
+
toPng: (opts) => runExport("png", opts),
|
|
4438
|
+
toJpg: (opts) => runExport("jpg", opts),
|
|
4439
|
+
// ← alias requerido por los tests
|
|
4440
|
+
toWebp: (opts) => runExport("webp", opts),
|
|
4441
|
+
download: (opts) => runExport("download", opts)
|
|
4442
|
+
// ← método directo (no toDownload)
|
|
3970
4443
|
};
|
|
4444
|
+
for (const key of Object.keys(exportsMap)) {
|
|
4445
|
+
const helper = "to" + key.charAt(0).toUpperCase() + key.slice(1);
|
|
4446
|
+
if (!result[helper]) {
|
|
4447
|
+
result[helper] = (opts) => runExport(key, opts);
|
|
4448
|
+
}
|
|
4449
|
+
}
|
|
4450
|
+
return result;
|
|
3971
4451
|
};
|
|
3972
4452
|
snapdom.toRaw = (el, options) => snapdom(el, options).then((result) => result.toRaw());
|
|
3973
4453
|
snapdom.toImg = (el, options) => snapdom(el, options).then((result) => result.toImg());
|
|
@@ -3990,30 +4470,47 @@ async function safariWarmup(element, baseOptions) {
|
|
|
3990
4470
|
try {
|
|
3991
4471
|
url = await captureDOM(element, preflight);
|
|
3992
4472
|
} catch {
|
|
3993
|
-
return;
|
|
3994
4473
|
}
|
|
3995
|
-
await new Promise((
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4474
|
+
await new Promise((r) => requestAnimationFrame(() => requestAnimationFrame(r)));
|
|
4475
|
+
if (url) {
|
|
4476
|
+
await new Promise((resolve) => {
|
|
4477
|
+
const img = new Image();
|
|
4478
|
+
try {
|
|
4479
|
+
img.decoding = "sync";
|
|
4480
|
+
img.loading = "eager";
|
|
4481
|
+
} catch {
|
|
4482
|
+
}
|
|
4483
|
+
img.style.cssText = "position:fixed;left:0px;top:0px;width:10px;height:10px;opacity:0.01;pointer-events:none;";
|
|
4484
|
+
img.src = url;
|
|
4485
|
+
document.body.appendChild(img);
|
|
4486
|
+
(async () => {
|
|
4487
|
+
try {
|
|
4488
|
+
if (typeof img.decode === "function") await img.decode();
|
|
4489
|
+
} catch {
|
|
4490
|
+
}
|
|
4491
|
+
const start = performance.now();
|
|
4492
|
+
while (!(img.complete && img.naturalWidth > 0) && performance.now() - start < 900) {
|
|
4493
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
4494
|
+
}
|
|
4495
|
+
await new Promise((r) => requestAnimationFrame(r));
|
|
4496
|
+
try {
|
|
4497
|
+
img.remove();
|
|
4498
|
+
} catch {
|
|
4499
|
+
}
|
|
4500
|
+
resolve();
|
|
4501
|
+
})();
|
|
4502
|
+
});
|
|
4503
|
+
}
|
|
4504
|
+
element.querySelectorAll("canvas").forEach((c) => {
|
|
4505
|
+
try {
|
|
4506
|
+
const ctx = c.getContext("2d", { willReadFrequently: true });
|
|
4507
|
+
if (ctx) {
|
|
4508
|
+
ctx.getImageData(0, 0, 1, 1);
|
|
4509
|
+
}
|
|
4510
|
+
} catch {
|
|
4511
|
+
}
|
|
4016
4512
|
});
|
|
4513
|
+
_safariWarmup = true;
|
|
4017
4514
|
}
|
|
4018
4515
|
function hasBackgroundOrMask(el) {
|
|
4019
4516
|
const walker = document.createTreeWalker(el, NodeFilter.SHOW_ELEMENT);
|
|
@@ -4026,6 +4523,7 @@ function hasBackgroundOrMask(el) {
|
|
|
4026
4523
|
const bg = cs.backgroundImage && cs.backgroundImage !== "none";
|
|
4027
4524
|
const mask = cs.maskImage && cs.maskImage !== "none" || cs.webkitMaskImage && cs.webkitMaskImage !== "none";
|
|
4028
4525
|
if (bg || mask) return true;
|
|
4526
|
+
if (node.tagName === "CANVAS") return true;
|
|
4029
4527
|
}
|
|
4030
4528
|
return false;
|
|
4031
4529
|
}
|
|
@@ -4051,6 +4549,7 @@ async function preCache(root = document, options = {}) {
|
|
|
4051
4549
|
cache.session.styleCache = /* @__PURE__ */ new WeakMap();
|
|
4052
4550
|
}
|
|
4053
4551
|
cache.image = cache.image || /* @__PURE__ */ new Map();
|
|
4552
|
+
cache.background = cache.background || /* @__PURE__ */ new Map();
|
|
4054
4553
|
try {
|
|
4055
4554
|
await inlineBackgroundImages(
|
|
4056
4555
|
root,
|
|
@@ -4063,7 +4562,13 @@ async function preCache(root = document, options = {}) {
|
|
|
4063
4562
|
}
|
|
4064
4563
|
let imgEls = [], allEls = [];
|
|
4065
4564
|
try {
|
|
4066
|
-
if (root
|
|
4565
|
+
if (root && root.nodeType === 1) {
|
|
4566
|
+
const descendants = root.querySelectorAll ? Array.from(root.querySelectorAll("*")) : [];
|
|
4567
|
+
allEls = [root, ...descendants];
|
|
4568
|
+
imgEls = [];
|
|
4569
|
+
if (root.tagName === "IMG" && root.getAttribute("src")) imgEls.push(root);
|
|
4570
|
+
imgEls.push(...Array.from(root.querySelectorAll?.("img[src]") || []));
|
|
4571
|
+
} else if (root?.querySelectorAll) {
|
|
4067
4572
|
imgEls = Array.from(root.querySelectorAll("img[src]"));
|
|
4068
4573
|
allEls = Array.from(root.querySelectorAll("*"));
|
|
4069
4574
|
}
|
|
@@ -4087,17 +4592,18 @@ async function preCache(root = document, options = {}) {
|
|
|
4087
4592
|
for (const el of allEls) {
|
|
4088
4593
|
let bg = "";
|
|
4089
4594
|
try {
|
|
4090
|
-
bg =
|
|
4595
|
+
bg = el?.style?.backgroundImage || "";
|
|
4596
|
+
if (!bg || bg === "none") {
|
|
4597
|
+
bg = getStyle(el).backgroundImage;
|
|
4598
|
+
}
|
|
4091
4599
|
} catch {
|
|
4092
4600
|
}
|
|
4093
4601
|
if (bg && bg !== "none") {
|
|
4094
|
-
const
|
|
4095
|
-
for (const entry of
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
promises.push(p);
|
|
4100
|
-
}
|
|
4602
|
+
const urlEntries = bg.match(/url\((?:[^()"']+|"(?:[^"]*)"|'(?:[^']*)')\)/gi) || [];
|
|
4603
|
+
for (const entry of urlEntries) {
|
|
4604
|
+
const p = Promise.resolve().then(() => inlineSingleBackgroundEntry(entry, { ...options, useProxy })).catch(() => {
|
|
4605
|
+
});
|
|
4606
|
+
promises.push(p);
|
|
4101
4607
|
}
|
|
4102
4608
|
}
|
|
4103
4609
|
}
|