@zumer/snapdom 1.9.12-dev.4 → 1.9.14
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 +58 -27
- package/dist/snapdom.js +621 -337
- package/dist/snapdom.min.js +8 -5
- package/dist/snapdom.min.mjs +8 -5
- package/dist/snapdom.mjs +609 -332
- package/package.json +7 -2
- package/types/snapdom.d.ts +18 -0
package/dist/snapdom.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
/*
|
|
3
3
|
* snapdom
|
|
4
|
-
* v.1.9.
|
|
4
|
+
* v.1.9.14
|
|
5
5
|
* Author Juan Martin Muda
|
|
6
6
|
* License MIT
|
|
7
7
|
**/
|
|
@@ -807,6 +807,64 @@ function stripHeightForWrappers(el, cs, snap) {
|
|
|
807
807
|
delete snap["block-size"];
|
|
808
808
|
}
|
|
809
809
|
|
|
810
|
+
// src/modules/CSSVar.js
|
|
811
|
+
function resolveCSSVars(sourceEl, cloneEl) {
|
|
812
|
+
if (!(sourceEl instanceof Element) || !(cloneEl instanceof Element)) return;
|
|
813
|
+
const styleAttr = sourceEl.getAttribute?.("style");
|
|
814
|
+
let hasVar = !!(styleAttr && styleAttr.includes("var("));
|
|
815
|
+
if (!hasVar && sourceEl.attributes?.length) {
|
|
816
|
+
const attrs = sourceEl.attributes;
|
|
817
|
+
for (let i = 0; i < attrs.length; i++) {
|
|
818
|
+
const a = attrs[i];
|
|
819
|
+
if (a && typeof a.value === "string" && a.value.includes("var(")) {
|
|
820
|
+
hasVar = true;
|
|
821
|
+
break;
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
if (!hasVar) return;
|
|
826
|
+
let cs;
|
|
827
|
+
try {
|
|
828
|
+
cs = getComputedStyle(sourceEl);
|
|
829
|
+
} catch {
|
|
830
|
+
return;
|
|
831
|
+
}
|
|
832
|
+
const author = sourceEl.style;
|
|
833
|
+
if (author && author.length) {
|
|
834
|
+
for (let i = 0; i < author.length; i++) {
|
|
835
|
+
const prop = author[i];
|
|
836
|
+
const val = author.getPropertyValue(prop);
|
|
837
|
+
if (!val || !val.includes("var(")) continue;
|
|
838
|
+
const resolved = cs.getPropertyValue(prop);
|
|
839
|
+
if (resolved) {
|
|
840
|
+
try {
|
|
841
|
+
cloneEl.style.setProperty(prop, resolved.trim(), author.getPropertyPriority(prop));
|
|
842
|
+
} catch {
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
if (sourceEl.attributes?.length) {
|
|
848
|
+
const attrs = sourceEl.attributes;
|
|
849
|
+
for (let i = 0; i < attrs.length; i++) {
|
|
850
|
+
const a = attrs[i];
|
|
851
|
+
if (!a || typeof a.value !== "string" || !a.value.includes("var(")) continue;
|
|
852
|
+
const propName = a.name;
|
|
853
|
+
let resolved = "";
|
|
854
|
+
try {
|
|
855
|
+
resolved = cs.getPropertyValue(propName);
|
|
856
|
+
} catch {
|
|
857
|
+
}
|
|
858
|
+
if (resolved) {
|
|
859
|
+
try {
|
|
860
|
+
cloneEl.style.setProperty(propName, resolved.trim());
|
|
861
|
+
} catch {
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
|
|
810
868
|
// src/core/clone.js
|
|
811
869
|
function idleCallback(childList, callback, fast) {
|
|
812
870
|
return Promise.all(childList.map((child) => {
|
|
@@ -828,7 +886,7 @@ function idleCallback(childList, callback, fast) {
|
|
|
828
886
|
function addNotSlottedRightmost(sel) {
|
|
829
887
|
sel = sel.trim();
|
|
830
888
|
if (!sel) return sel;
|
|
831
|
-
if (
|
|
889
|
+
if (/:not\(\s*\[data-sd-slotted\]\s*\)\s*$/.test(sel)) return sel;
|
|
832
890
|
return `${sel}:not([data-sd-slotted])`;
|
|
833
891
|
}
|
|
834
892
|
function wrapWithScope(selectorList, scopeSelector, excludeSlotted = true) {
|
|
@@ -1117,7 +1175,7 @@ async function deepClone(node, sessionCache, options) {
|
|
|
1117
1175
|
inlineAllStyles(node, clone2, sessionCache, options);
|
|
1118
1176
|
const placeholder = document.createElement("div");
|
|
1119
1177
|
placeholder.textContent = node.getAttribute("data-placeholder-text") || "";
|
|
1120
|
-
placeholder.style.cssText =
|
|
1178
|
+
placeholder.style.cssText = "color:#666;font-size:12px;text-align:center;line-height:1.4;padding:0.5em;box-sizing:border-box;";
|
|
1121
1179
|
clone2.appendChild(placeholder);
|
|
1122
1180
|
return clone2;
|
|
1123
1181
|
}
|
|
@@ -1134,6 +1192,7 @@ async function deepClone(node, sessionCache, options) {
|
|
|
1134
1192
|
let clone;
|
|
1135
1193
|
try {
|
|
1136
1194
|
clone = node.cloneNode(false);
|
|
1195
|
+
resolveCSSVars(node, clone);
|
|
1137
1196
|
sessionCache.nodeMap.set(clone, node);
|
|
1138
1197
|
if (node.tagName === "IMG") {
|
|
1139
1198
|
freezeImgSrcset(node, clone);
|
|
@@ -1189,7 +1248,7 @@ async function deepClone(node, sessionCache, options) {
|
|
|
1189
1248
|
} else {
|
|
1190
1249
|
deepClone(child, sessionCache, options).then((clonedChild) => {
|
|
1191
1250
|
resolve(clonedChild || null);
|
|
1192
|
-
}).catch((
|
|
1251
|
+
}).catch(() => {
|
|
1193
1252
|
resolve(null);
|
|
1194
1253
|
});
|
|
1195
1254
|
}
|
|
@@ -1230,7 +1289,7 @@ async function deepClone(node, sessionCache, options) {
|
|
|
1230
1289
|
markSlottedSubtree(clonedChild);
|
|
1231
1290
|
}
|
|
1232
1291
|
resolve(clonedChild || null);
|
|
1233
|
-
}).catch((
|
|
1292
|
+
}).catch(() => {
|
|
1234
1293
|
resolve(null);
|
|
1235
1294
|
});
|
|
1236
1295
|
};
|
|
@@ -1245,7 +1304,7 @@ async function deepClone(node, sessionCache, options) {
|
|
|
1245
1304
|
if (clonedAssignedNodes.has(child)) return resolve(null);
|
|
1246
1305
|
deepClone(child, sessionCache, options).then((clonedChild) => {
|
|
1247
1306
|
resolve(clonedChild || null);
|
|
1248
|
-
}).catch((
|
|
1307
|
+
}).catch(() => {
|
|
1249
1308
|
resolve(null);
|
|
1250
1309
|
});
|
|
1251
1310
|
}
|
|
@@ -1308,7 +1367,10 @@ function isIconFont2(input) {
|
|
|
1308
1367
|
async function iconToImage(unicodeChar, fontFamily, fontWeight, fontSize = 32, color = "#000") {
|
|
1309
1368
|
fontFamily = fontFamily.replace(/^['"]+|['"]+$/g, "");
|
|
1310
1369
|
const dpr = window.devicePixelRatio || 1;
|
|
1311
|
-
|
|
1370
|
+
try {
|
|
1371
|
+
await document.fonts.ready;
|
|
1372
|
+
} catch {
|
|
1373
|
+
}
|
|
1312
1374
|
const span = document.createElement("span");
|
|
1313
1375
|
span.textContent = unicodeChar;
|
|
1314
1376
|
span.style.position = "absolute";
|
|
@@ -1326,8 +1388,8 @@ async function iconToImage(unicodeChar, fontFamily, fontWeight, fontSize = 32, c
|
|
|
1326
1388
|
const height = Math.ceil(rect.height);
|
|
1327
1389
|
document.body.removeChild(span);
|
|
1328
1390
|
const canvas = document.createElement("canvas");
|
|
1329
|
-
canvas.width = width * dpr;
|
|
1330
|
-
canvas.height = height * dpr;
|
|
1391
|
+
canvas.width = Math.max(1, width * dpr);
|
|
1392
|
+
canvas.height = Math.max(1, height * dpr);
|
|
1331
1393
|
const ctx = canvas.getContext("2d");
|
|
1332
1394
|
ctx.scale(dpr, dpr);
|
|
1333
1395
|
ctx.font = fontWeight ? `${fontWeight} ${fontSize}px "${fontFamily}"` : `${fontSize}px "${fontFamily}"`;
|
|
@@ -1445,6 +1507,303 @@ function familiesFromRequired(required) {
|
|
|
1445
1507
|
}
|
|
1446
1508
|
return out;
|
|
1447
1509
|
}
|
|
1510
|
+
function rewriteRelativeUrls(cssText, baseHref) {
|
|
1511
|
+
if (!cssText) return cssText;
|
|
1512
|
+
return cssText.replace(
|
|
1513
|
+
/url\(\s*(['"]?)([^)'"]+)\1\s*\)/g,
|
|
1514
|
+
(m, q, u) => {
|
|
1515
|
+
const src = (u || "").trim();
|
|
1516
|
+
if (!src || /^data:|^blob:|^https?:|^file:|^about:/i.test(src)) return m;
|
|
1517
|
+
let abs = src;
|
|
1518
|
+
try {
|
|
1519
|
+
abs = new URL(src, baseHref || location.href).href;
|
|
1520
|
+
} catch {
|
|
1521
|
+
}
|
|
1522
|
+
return `url("${abs}")`;
|
|
1523
|
+
}
|
|
1524
|
+
);
|
|
1525
|
+
}
|
|
1526
|
+
var IMPORT_ANY_RE = /@import\s+(?:url\(\s*(['"]?)([^)"']+)\1\s*\)|(['"])([^"']+)\3)([^;]*);/g;
|
|
1527
|
+
var MAX_IMPORT_DEPTH = 4;
|
|
1528
|
+
async function inlineImportsAndRewrite(cssText, ownerHref, useProxy) {
|
|
1529
|
+
if (!cssText) return cssText;
|
|
1530
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1531
|
+
function normalizeUrl(u, base) {
|
|
1532
|
+
try {
|
|
1533
|
+
return new URL(u, base || location.href).href;
|
|
1534
|
+
} catch {
|
|
1535
|
+
return u;
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
async function resolveOnce(text, baseHref, depth = 0) {
|
|
1539
|
+
if (depth > MAX_IMPORT_DEPTH) {
|
|
1540
|
+
console.warn(`[snapDOM] @import depth exceeded (${MAX_IMPORT_DEPTH}) at ${baseHref}`);
|
|
1541
|
+
return text;
|
|
1542
|
+
}
|
|
1543
|
+
let out = "";
|
|
1544
|
+
let last = 0;
|
|
1545
|
+
let m;
|
|
1546
|
+
while (m = IMPORT_ANY_RE.exec(text)) {
|
|
1547
|
+
out += text.slice(last, m.index);
|
|
1548
|
+
last = IMPORT_ANY_RE.lastIndex;
|
|
1549
|
+
const rawUrl = (m[2] || m[4] || "").trim();
|
|
1550
|
+
const absUrl = normalizeUrl(rawUrl, baseHref);
|
|
1551
|
+
if (visited.has(absUrl)) {
|
|
1552
|
+
console.warn(`[snapDOM] Skipping circular @import: ${absUrl}`);
|
|
1553
|
+
continue;
|
|
1554
|
+
}
|
|
1555
|
+
visited.add(absUrl);
|
|
1556
|
+
let imported = "";
|
|
1557
|
+
try {
|
|
1558
|
+
const r = await snapFetch(absUrl, { as: "text", useProxy, silent: true });
|
|
1559
|
+
if (r.ok && typeof r.data === "string") imported = r.data;
|
|
1560
|
+
} catch {
|
|
1561
|
+
}
|
|
1562
|
+
if (imported) {
|
|
1563
|
+
imported = rewriteRelativeUrls(imported, absUrl);
|
|
1564
|
+
imported = await resolveOnce(imported, absUrl, depth + 1);
|
|
1565
|
+
out += `
|
|
1566
|
+
/* inlined: ${absUrl} */
|
|
1567
|
+
${imported}
|
|
1568
|
+
`;
|
|
1569
|
+
} else {
|
|
1570
|
+
out += m[0];
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
out += text.slice(last);
|
|
1574
|
+
return out;
|
|
1575
|
+
}
|
|
1576
|
+
let rewritten = rewriteRelativeUrls(cssText, ownerHref || location.href);
|
|
1577
|
+
rewritten = await resolveOnce(rewritten, ownerHref || location.href, 0);
|
|
1578
|
+
return rewritten;
|
|
1579
|
+
}
|
|
1580
|
+
var URL_RE = /url\((["']?)([^"')]+)\1\)/g;
|
|
1581
|
+
var FACE_RE = /@font-face[^{}]*\{[^}]*\}/g;
|
|
1582
|
+
function parseUnicodeRange(ur) {
|
|
1583
|
+
if (!ur) return [];
|
|
1584
|
+
const ranges = [];
|
|
1585
|
+
const parts = ur.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1586
|
+
for (const p of parts) {
|
|
1587
|
+
const m = p.match(/^U\+([0-9A-Fa-f?]+)(?:-([0-9A-Fa-f?]+))?$/);
|
|
1588
|
+
if (!m) continue;
|
|
1589
|
+
const a = m[1], b = m[2];
|
|
1590
|
+
const expand = (hex) => {
|
|
1591
|
+
if (!hex.includes("?")) return parseInt(hex, 16);
|
|
1592
|
+
const min = parseInt(hex.replace(/\?/g, "0"), 16);
|
|
1593
|
+
const max = parseInt(hex.replace(/\?/g, "F"), 16);
|
|
1594
|
+
return [min, max];
|
|
1595
|
+
};
|
|
1596
|
+
if (b) {
|
|
1597
|
+
const A = expand(a), B = expand(b);
|
|
1598
|
+
const min = Array.isArray(A) ? A[0] : A;
|
|
1599
|
+
const max = Array.isArray(B) ? B[1] : B;
|
|
1600
|
+
ranges.push([Math.min(min, max), Math.max(min, max)]);
|
|
1601
|
+
} else {
|
|
1602
|
+
const X = expand(a);
|
|
1603
|
+
if (Array.isArray(X)) ranges.push([X[0], X[1]]);
|
|
1604
|
+
else ranges.push([X, X]);
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
return ranges;
|
|
1608
|
+
}
|
|
1609
|
+
function unicodeIntersects(used, ranges) {
|
|
1610
|
+
if (!ranges.length) return true;
|
|
1611
|
+
if (!used || used.size === 0) return true;
|
|
1612
|
+
for (const cp of used) {
|
|
1613
|
+
for (const [a, b] of ranges) if (cp >= a && cp <= b) return true;
|
|
1614
|
+
}
|
|
1615
|
+
return false;
|
|
1616
|
+
}
|
|
1617
|
+
function extractSrcUrls(srcValue, baseHref) {
|
|
1618
|
+
const urls = [];
|
|
1619
|
+
if (!srcValue) return urls;
|
|
1620
|
+
for (const m of srcValue.matchAll(URL_RE)) {
|
|
1621
|
+
let u = (m[2] || "").trim();
|
|
1622
|
+
if (!u || u.startsWith("data:")) continue;
|
|
1623
|
+
if (!/^https?:/i.test(u)) {
|
|
1624
|
+
try {
|
|
1625
|
+
u = new URL(u, baseHref || location.href).href;
|
|
1626
|
+
} catch {
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
urls.push(u);
|
|
1630
|
+
}
|
|
1631
|
+
return urls;
|
|
1632
|
+
}
|
|
1633
|
+
async function inlineUrlsInCssBlock(cssBlock, baseHref, useProxy = "") {
|
|
1634
|
+
let out = cssBlock;
|
|
1635
|
+
for (const m of cssBlock.matchAll(URL_RE)) {
|
|
1636
|
+
const raw = extractURL(m[0]);
|
|
1637
|
+
if (!raw) continue;
|
|
1638
|
+
let abs = raw;
|
|
1639
|
+
if (!abs.startsWith("http") && !abs.startsWith("data:")) {
|
|
1640
|
+
try {
|
|
1641
|
+
abs = new URL(abs, baseHref || location.href).href;
|
|
1642
|
+
} catch {
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
if (isIconFont2(abs)) continue;
|
|
1646
|
+
if (cache.resource?.has(abs)) {
|
|
1647
|
+
cache.font?.add(abs);
|
|
1648
|
+
out = out.replace(m[0], `url(${cache.resource.get(abs)})`);
|
|
1649
|
+
continue;
|
|
1650
|
+
}
|
|
1651
|
+
if (cache.font?.has(abs)) continue;
|
|
1652
|
+
try {
|
|
1653
|
+
const r = await snapFetch(abs, { as: "dataURL", useProxy, silent: true });
|
|
1654
|
+
if (r.ok && typeof r.data === "string") {
|
|
1655
|
+
const b64 = r.data;
|
|
1656
|
+
cache.resource?.set(abs, b64);
|
|
1657
|
+
cache.font?.add(abs);
|
|
1658
|
+
out = out.replace(m[0], `url(${b64})`);
|
|
1659
|
+
}
|
|
1660
|
+
} catch {
|
|
1661
|
+
console.warn("[snapDOM] Failed to fetch font resource:", abs);
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
return out;
|
|
1665
|
+
}
|
|
1666
|
+
function subsetFromRanges(ranges) {
|
|
1667
|
+
if (!ranges.length) return null;
|
|
1668
|
+
const hit = (a, b) => ranges.some(([x, y]) => !(y < a || x > b));
|
|
1669
|
+
const latin = hit(0, 255) || hit(305, 305);
|
|
1670
|
+
const latinExt = hit(256, 591) || hit(7680, 7935);
|
|
1671
|
+
const greek = hit(880, 1023);
|
|
1672
|
+
const cyr = hit(1024, 1279);
|
|
1673
|
+
const viet = hit(7840, 7929) || hit(258, 259) || hit(416, 417) || hit(431, 432);
|
|
1674
|
+
if (viet) return "vietnamese";
|
|
1675
|
+
if (cyr) return "cyrillic";
|
|
1676
|
+
if (greek) return "greek";
|
|
1677
|
+
if (latinExt) return "latin-ext";
|
|
1678
|
+
if (latin) return "latin";
|
|
1679
|
+
return null;
|
|
1680
|
+
}
|
|
1681
|
+
function buildSimpleExcluder(ex = {}) {
|
|
1682
|
+
const famSet = new Set((ex.families || []).map((s) => String(s).toLowerCase()));
|
|
1683
|
+
const domSet = new Set((ex.domains || []).map((s) => String(s).toLowerCase()));
|
|
1684
|
+
const subSet = new Set((ex.subsets || []).map((s) => String(s).toLowerCase()));
|
|
1685
|
+
return (meta, parsedRanges) => {
|
|
1686
|
+
if (famSet.size && famSet.has(meta.family.toLowerCase())) return true;
|
|
1687
|
+
if (domSet.size) {
|
|
1688
|
+
for (const u of meta.srcUrls) {
|
|
1689
|
+
try {
|
|
1690
|
+
if (domSet.has(new URL(u).host.toLowerCase())) return true;
|
|
1691
|
+
} catch {
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
if (subSet.size) {
|
|
1696
|
+
const label = subsetFromRanges(parsedRanges);
|
|
1697
|
+
if (label && subSet.has(label)) return true;
|
|
1698
|
+
}
|
|
1699
|
+
return false;
|
|
1700
|
+
};
|
|
1701
|
+
}
|
|
1702
|
+
function dedupeFontFaces(cssText) {
|
|
1703
|
+
if (!cssText) return cssText;
|
|
1704
|
+
const FACE_RE_G = /@font-face[^{}]*\{[^}]*\}/gi;
|
|
1705
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1706
|
+
const out = [];
|
|
1707
|
+
for (const block of cssText.match(FACE_RE_G) || []) {
|
|
1708
|
+
const familyRaw = block.match(/font-family:\s*([^;]+);/i)?.[1] || "";
|
|
1709
|
+
const family = pickPrimaryFamily(familyRaw);
|
|
1710
|
+
const weightSpec = (block.match(/font-weight:\s*([^;]+);/i)?.[1] || "400").trim();
|
|
1711
|
+
const styleSpec = (block.match(/font-style:\s*([^;]+);/i)?.[1] || "normal").trim();
|
|
1712
|
+
const stretchSpec = (block.match(/font-stretch:\s*([^;]+);/i)?.[1] || "100%").trim();
|
|
1713
|
+
const urange = (block.match(/unicode-range:\s*([^;]+);/i)?.[1] || "").trim();
|
|
1714
|
+
const srcRaw = (block.match(/src\s*:\s*([^;]+);/i)?.[1] || "").trim();
|
|
1715
|
+
const urls = extractSrcUrls(srcRaw, location.href);
|
|
1716
|
+
const srcPart = urls.length ? urls.map((u) => String(u).toLowerCase()).sort().join("|") : srcRaw.toLowerCase();
|
|
1717
|
+
const key = [
|
|
1718
|
+
String(family || "").toLowerCase(),
|
|
1719
|
+
weightSpec,
|
|
1720
|
+
styleSpec,
|
|
1721
|
+
stretchSpec,
|
|
1722
|
+
urange.toLowerCase(),
|
|
1723
|
+
srcPart
|
|
1724
|
+
].join("|");
|
|
1725
|
+
if (!seen.has(key)) {
|
|
1726
|
+
seen.add(key);
|
|
1727
|
+
out.push(block);
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
if (out.length === 0) return cssText;
|
|
1731
|
+
let i = 0;
|
|
1732
|
+
return cssText.replace(FACE_RE_G, () => out[i++] || "");
|
|
1733
|
+
}
|
|
1734
|
+
function buildFontsCacheKey(required, exclude, localFonts, useProxy) {
|
|
1735
|
+
const req = Array.from(required || []).sort().join("|");
|
|
1736
|
+
const ex = exclude ? JSON.stringify({
|
|
1737
|
+
families: (exclude.families || []).map((s) => String(s).toLowerCase()).sort(),
|
|
1738
|
+
domains: (exclude.domains || []).map((s) => String(s).toLowerCase()).sort(),
|
|
1739
|
+
subsets: (exclude.subsets || []).map((s) => String(s).toLowerCase()).sort()
|
|
1740
|
+
}) : "";
|
|
1741
|
+
const lf = (localFonts || []).map((f) => `${(f.family || "").toLowerCase()}::${f.weight || "normal"}::${f.style || "normal"}::${f.src || ""}`).sort().join("|");
|
|
1742
|
+
const px = useProxy || "";
|
|
1743
|
+
return `fonts-embed-css::req=${req}::ex=${ex}::lf=${lf}::px=${px}`;
|
|
1744
|
+
}
|
|
1745
|
+
async function collectFacesFromSheet(sheet, baseHref, emitFace, ctx) {
|
|
1746
|
+
let rules;
|
|
1747
|
+
try {
|
|
1748
|
+
rules = sheet.cssRules || [];
|
|
1749
|
+
} catch {
|
|
1750
|
+
return;
|
|
1751
|
+
}
|
|
1752
|
+
const normalizeUrl = (u, base) => {
|
|
1753
|
+
try {
|
|
1754
|
+
return new URL(u, base || location.href).href;
|
|
1755
|
+
} catch {
|
|
1756
|
+
return u;
|
|
1757
|
+
}
|
|
1758
|
+
};
|
|
1759
|
+
for (const rule of rules) {
|
|
1760
|
+
if (rule.type === CSSRule.IMPORT_RULE && rule.styleSheet) {
|
|
1761
|
+
const childHref = rule.href ? normalizeUrl(rule.href, baseHref) : baseHref;
|
|
1762
|
+
if (ctx.depth >= MAX_IMPORT_DEPTH) {
|
|
1763
|
+
console.warn(`[snapDOM] CSSOM import depth exceeded (${MAX_IMPORT_DEPTH}) at ${childHref}`);
|
|
1764
|
+
continue;
|
|
1765
|
+
}
|
|
1766
|
+
if (childHref && ctx.visitedSheets.has(childHref)) {
|
|
1767
|
+
console.warn(`[snapDOM] Skipping circular CSSOM import: ${childHref}`);
|
|
1768
|
+
continue;
|
|
1769
|
+
}
|
|
1770
|
+
if (childHref) ctx.visitedSheets.add(childHref);
|
|
1771
|
+
const nextCtx = { ...ctx, depth: (ctx.depth || 0) + 1 };
|
|
1772
|
+
await collectFacesFromSheet(rule.styleSheet, childHref, emitFace, nextCtx);
|
|
1773
|
+
continue;
|
|
1774
|
+
}
|
|
1775
|
+
if (rule.type === CSSRule.FONT_FACE_RULE) {
|
|
1776
|
+
const famRaw = (rule.style.getPropertyValue("font-family") || "").trim();
|
|
1777
|
+
const family = pickPrimaryFamily(famRaw);
|
|
1778
|
+
if (!family || isIconFont2(family)) continue;
|
|
1779
|
+
const weightSpec = (rule.style.getPropertyValue("font-weight") || "400").trim();
|
|
1780
|
+
const styleSpec = (rule.style.getPropertyValue("font-style") || "normal").trim();
|
|
1781
|
+
const stretchSpec = (rule.style.getPropertyValue("font-stretch") || "100%").trim();
|
|
1782
|
+
const srcRaw = (rule.style.getPropertyValue("src") || "").trim();
|
|
1783
|
+
const urange = (rule.style.getPropertyValue("unicode-range") || "").trim();
|
|
1784
|
+
if (!ctx.faceMatchesRequired(family, styleSpec, weightSpec, stretchSpec)) continue;
|
|
1785
|
+
const ranges = parseUnicodeRange(urange);
|
|
1786
|
+
if (!unicodeIntersects(ctx.usedCodepoints, ranges)) continue;
|
|
1787
|
+
const meta = {
|
|
1788
|
+
family,
|
|
1789
|
+
weightSpec,
|
|
1790
|
+
styleSpec,
|
|
1791
|
+
stretchSpec,
|
|
1792
|
+
unicodeRange: urange,
|
|
1793
|
+
srcRaw,
|
|
1794
|
+
srcUrls: extractSrcUrls(srcRaw, baseHref || location.href),
|
|
1795
|
+
href: baseHref || location.href
|
|
1796
|
+
};
|
|
1797
|
+
if (ctx.simpleExcluder && ctx.simpleExcluder(meta, ranges)) continue;
|
|
1798
|
+
if (/url\(/i.test(srcRaw)) {
|
|
1799
|
+
const inlinedSrc = await inlineUrlsInCssBlock(srcRaw, baseHref || location.href, ctx.useProxy);
|
|
1800
|
+
await emitFace(`@font-face{font-family:${family};src:${inlinedSrc};font-style:${styleSpec};font-weight:${weightSpec};font-stretch:${stretchSpec};${urange ? `unicode-range:${urange};` : ""}}`);
|
|
1801
|
+
} else {
|
|
1802
|
+
await emitFace(`@font-face{font-family:${family};src:${srcRaw};font-style:${styleSpec};font-weight:${weightSpec};font-stretch:${stretchSpec};${urange ? `unicode-range:${urange};` : ""}}`);
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1448
1807
|
async function embedCustomFonts({
|
|
1449
1808
|
required,
|
|
1450
1809
|
usedCodepoints,
|
|
@@ -1462,9 +1821,6 @@ async function embedCustomFonts({
|
|
|
1462
1821
|
arr.push({ w: parseInt(w, 10), s, st: parseInt(st, 10) });
|
|
1463
1822
|
requiredIndex.set(fam, arr);
|
|
1464
1823
|
}
|
|
1465
|
-
const IMPORT_RE = /@import\s+url\(["']?([^"')]+)["']?\)/g;
|
|
1466
|
-
const URL_RE = /url\((["']?)([^"')]+)\1\)/g;
|
|
1467
|
-
const FACE_RE = /@font-face[^{}]*\{[^}]*\}/g;
|
|
1468
1824
|
function faceMatchesRequired(fam, styleSpec, weightSpec, stretchSpec) {
|
|
1469
1825
|
if (!requiredIndex.has(fam)) return false;
|
|
1470
1826
|
const need = requiredIndex.get(fam);
|
|
@@ -1490,188 +1846,23 @@ async function embedCustomFonts({
|
|
|
1490
1846
|
const sOk = styleOK(normStyle(r.s));
|
|
1491
1847
|
const tOk = r.st >= ts.min && r.st <= ts.max;
|
|
1492
1848
|
const nearWeight = Math.abs(faceSingleW - r.w) <= 300;
|
|
1493
|
-
if (nearWeight && sOk && tOk)
|
|
1494
|
-
return true;
|
|
1495
|
-
}
|
|
1849
|
+
if (nearWeight && sOk && tOk) return true;
|
|
1496
1850
|
}
|
|
1497
1851
|
}
|
|
1498
1852
|
return false;
|
|
1499
1853
|
}
|
|
1500
|
-
function parseUnicodeRange(ur) {
|
|
1501
|
-
if (!ur) return [];
|
|
1502
|
-
const ranges = [];
|
|
1503
|
-
const parts = ur.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1504
|
-
for (const p of parts) {
|
|
1505
|
-
const m = p.match(/^U\+([0-9A-Fa-f?]+)(?:-([0-9A-Fa-f?]+))?$/);
|
|
1506
|
-
if (!m) continue;
|
|
1507
|
-
const a = m[1], b = m[2];
|
|
1508
|
-
const expand = (hex) => {
|
|
1509
|
-
if (!hex.includes("?")) return parseInt(hex, 16);
|
|
1510
|
-
const min = parseInt(hex.replace(/\?/g, "0"), 16);
|
|
1511
|
-
const max = parseInt(hex.replace(/\?/g, "F"), 16);
|
|
1512
|
-
return [min, max];
|
|
1513
|
-
};
|
|
1514
|
-
if (b) {
|
|
1515
|
-
const A = expand(a), B = expand(b);
|
|
1516
|
-
const min = Array.isArray(A) ? A[0] : A;
|
|
1517
|
-
const max = Array.isArray(B) ? B[1] : B;
|
|
1518
|
-
ranges.push([Math.min(min, max), Math.max(min, max)]);
|
|
1519
|
-
} else {
|
|
1520
|
-
const X = expand(a);
|
|
1521
|
-
if (Array.isArray(X)) ranges.push([X[0], X[1]]);
|
|
1522
|
-
else ranges.push([X, X]);
|
|
1523
|
-
}
|
|
1524
|
-
}
|
|
1525
|
-
return ranges;
|
|
1526
|
-
}
|
|
1527
|
-
function unicodeIntersects(used, ranges) {
|
|
1528
|
-
if (!ranges.length) return true;
|
|
1529
|
-
if (!used || used.size === 0) return true;
|
|
1530
|
-
for (const cp of used) {
|
|
1531
|
-
for (const [a, b] of ranges) if (cp >= a && cp <= b) return true;
|
|
1532
|
-
}
|
|
1533
|
-
return false;
|
|
1534
|
-
}
|
|
1535
|
-
function extractSrcUrls(srcValue, baseHref) {
|
|
1536
|
-
const urls = [];
|
|
1537
|
-
if (!srcValue) return urls;
|
|
1538
|
-
for (const m of srcValue.matchAll(URL_RE)) {
|
|
1539
|
-
let u = (m[2] || "").trim();
|
|
1540
|
-
if (!u || u.startsWith("data:")) continue;
|
|
1541
|
-
if (!/^https?:/i.test(u)) {
|
|
1542
|
-
try {
|
|
1543
|
-
u = new URL(u, baseHref || location.href).href;
|
|
1544
|
-
} catch {
|
|
1545
|
-
}
|
|
1546
|
-
}
|
|
1547
|
-
urls.push(u);
|
|
1548
|
-
}
|
|
1549
|
-
return urls;
|
|
1550
|
-
}
|
|
1551
|
-
async function inlineUrlsInCssBlock(cssBlock, baseHref) {
|
|
1552
|
-
let out = cssBlock;
|
|
1553
|
-
for (const m of cssBlock.matchAll(URL_RE)) {
|
|
1554
|
-
const raw = extractURL(m[0]);
|
|
1555
|
-
if (!raw) continue;
|
|
1556
|
-
let abs = raw;
|
|
1557
|
-
if (!abs.startsWith("http") && !abs.startsWith("data:")) {
|
|
1558
|
-
try {
|
|
1559
|
-
abs = new URL(abs, baseHref || location.href).href;
|
|
1560
|
-
} catch {
|
|
1561
|
-
}
|
|
1562
|
-
}
|
|
1563
|
-
if (isIconFont2(abs)) continue;
|
|
1564
|
-
if (cache.resource?.has(abs)) {
|
|
1565
|
-
cache.font?.add(abs);
|
|
1566
|
-
out = out.replace(m[0], `url(${cache.resource.get(abs)})`);
|
|
1567
|
-
continue;
|
|
1568
|
-
}
|
|
1569
|
-
if (cache.font?.has(abs)) continue;
|
|
1570
|
-
try {
|
|
1571
|
-
const r = await snapFetch(abs, { as: "dataURL", useProxy, silent: true });
|
|
1572
|
-
if (r.ok && typeof r.data === "string") {
|
|
1573
|
-
const b64 = r.data;
|
|
1574
|
-
cache.resource?.set(abs, b64);
|
|
1575
|
-
cache.font?.add(abs);
|
|
1576
|
-
out = out.replace(m[0], `url(${b64})`);
|
|
1577
|
-
}
|
|
1578
|
-
} catch {
|
|
1579
|
-
console.warn("[snapDOM] Failed to fetch font resource:", abs);
|
|
1580
|
-
}
|
|
1581
|
-
}
|
|
1582
|
-
return out;
|
|
1583
|
-
}
|
|
1584
|
-
function subsetFromRanges(ranges) {
|
|
1585
|
-
if (!ranges.length) return null;
|
|
1586
|
-
const hit = (a, b) => ranges.some(([x, y]) => !(y < a || x > b));
|
|
1587
|
-
const latin = hit(0, 255) || hit(305, 305);
|
|
1588
|
-
const latinExt = hit(256, 591) || hit(7680, 7935);
|
|
1589
|
-
const greek = hit(880, 1023);
|
|
1590
|
-
const cyr = hit(1024, 1279);
|
|
1591
|
-
const viet = hit(7840, 7929) || hit(258, 259) || hit(416, 417) || hit(431, 432);
|
|
1592
|
-
if (viet) return "vietnamese";
|
|
1593
|
-
if (cyr) return "cyrillic";
|
|
1594
|
-
if (greek) return "greek";
|
|
1595
|
-
if (latinExt) return "latin-ext";
|
|
1596
|
-
if (latin) return "latin";
|
|
1597
|
-
return null;
|
|
1598
|
-
}
|
|
1599
|
-
function buildSimpleExcluder(ex = {}) {
|
|
1600
|
-
const famSet = new Set((ex.families || []).map((s) => String(s).toLowerCase()));
|
|
1601
|
-
const domSet = new Set((ex.domains || []).map((s) => String(s).toLowerCase()));
|
|
1602
|
-
const subSet = new Set((ex.subsets || []).map((s) => String(s).toLowerCase()));
|
|
1603
|
-
return (meta, parsedRanges) => {
|
|
1604
|
-
if (famSet.size && famSet.has(meta.family.toLowerCase())) return true;
|
|
1605
|
-
if (domSet.size) {
|
|
1606
|
-
for (const u of meta.srcUrls) {
|
|
1607
|
-
try {
|
|
1608
|
-
if (domSet.has(new URL(u).host.toLowerCase())) return true;
|
|
1609
|
-
} catch {
|
|
1610
|
-
}
|
|
1611
|
-
}
|
|
1612
|
-
}
|
|
1613
|
-
if (subSet.size) {
|
|
1614
|
-
const label = subsetFromRanges(parsedRanges);
|
|
1615
|
-
if (label && subSet.has(label)) return true;
|
|
1616
|
-
}
|
|
1617
|
-
return false;
|
|
1618
|
-
};
|
|
1619
|
-
}
|
|
1620
1854
|
const simpleExcluder = buildSimpleExcluder(exclude);
|
|
1621
|
-
|
|
1622
|
-
if (!cssText) return cssText;
|
|
1623
|
-
const FACE_RE2 = /@font-face[^{}]*\{[^}]*\}/gi;
|
|
1624
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1625
|
-
const out = [];
|
|
1626
|
-
for (const block of cssText.match(FACE_RE2) || []) {
|
|
1627
|
-
const familyRaw = block.match(/font-family:\s*([^;]+);/i)?.[1] || "";
|
|
1628
|
-
const family = pickPrimaryFamily(familyRaw);
|
|
1629
|
-
const weightSpec = (block.match(/font-weight:\s*([^;]+);/i)?.[1] || "400").trim();
|
|
1630
|
-
const styleSpec = (block.match(/font-style:\s*([^;]+);/i)?.[1] || "normal").trim();
|
|
1631
|
-
const stretchSpec = (block.match(/font-stretch:\s*([^;]+);/i)?.[1] || "100%").trim();
|
|
1632
|
-
const urange = (block.match(/unicode-range:\s*([^;]+);/i)?.[1] || "").trim();
|
|
1633
|
-
const srcRaw = (block.match(/src\s*:\s*([^;]+);/i)?.[1] || "").trim();
|
|
1634
|
-
const urls = extractSrcUrls(srcRaw, location.href);
|
|
1635
|
-
const srcPart = urls.length ? urls.map((u) => String(u).toLowerCase()).sort().join("|") : srcRaw.toLowerCase();
|
|
1636
|
-
const key = [
|
|
1637
|
-
String(family || "").toLowerCase(),
|
|
1638
|
-
weightSpec,
|
|
1639
|
-
styleSpec,
|
|
1640
|
-
stretchSpec,
|
|
1641
|
-
urange.toLowerCase(),
|
|
1642
|
-
srcPart
|
|
1643
|
-
].join("|");
|
|
1644
|
-
if (!seen.has(key)) {
|
|
1645
|
-
seen.add(key);
|
|
1646
|
-
out.push(block);
|
|
1647
|
-
}
|
|
1648
|
-
}
|
|
1649
|
-
if (out.length === 0) return cssText;
|
|
1650
|
-
let i = 0;
|
|
1651
|
-
return cssText.replace(FACE_RE2, () => out[i++] || "");
|
|
1652
|
-
}
|
|
1653
|
-
function buildFontsCacheKey() {
|
|
1654
|
-
const req = Array.from(required || []).sort().join("|");
|
|
1655
|
-
const ex = exclude ? JSON.stringify({
|
|
1656
|
-
families: (exclude.families || []).map((s) => String(s).toLowerCase()).sort(),
|
|
1657
|
-
domains: (exclude.domains || []).map((s) => String(s).toLowerCase()).sort(),
|
|
1658
|
-
subsets: (exclude.subsets || []).map((s) => String(s).toLowerCase()).sort()
|
|
1659
|
-
}) : "";
|
|
1660
|
-
const lf = (localFonts || []).map((f) => `${(f.family || "").toLowerCase()}::${f.weight || "normal"}::${f.style || "normal"}::${f.src || ""}`).sort().join("|");
|
|
1661
|
-
const px = useProxy || "";
|
|
1662
|
-
return `fonts-embed-css::req=${req}::ex=${ex}::lf=${lf}::px=${px}`;
|
|
1663
|
-
}
|
|
1664
|
-
const cacheKey = buildFontsCacheKey();
|
|
1855
|
+
const cacheKey = buildFontsCacheKey(required, exclude, localFonts, useProxy);
|
|
1665
1856
|
if (cache.resource?.has(cacheKey)) {
|
|
1666
|
-
|
|
1667
|
-
return css;
|
|
1857
|
+
return cache.resource.get(cacheKey);
|
|
1668
1858
|
}
|
|
1669
1859
|
const requiredFamilies = familiesFromRequired(required);
|
|
1670
1860
|
const importUrls = [];
|
|
1861
|
+
const IMPORT_ANY_RE_LOCAL = IMPORT_ANY_RE;
|
|
1671
1862
|
for (const styleTag of document.querySelectorAll("style")) {
|
|
1672
1863
|
const cssText = styleTag.textContent || "";
|
|
1673
|
-
for (const m of cssText.matchAll(
|
|
1674
|
-
const u = m[
|
|
1864
|
+
for (const m of cssText.matchAll(IMPORT_ANY_RE_LOCAL)) {
|
|
1865
|
+
const u = (m[2] || m[4] || "").trim();
|
|
1675
1866
|
if (!u || isIconFont2(u)) continue;
|
|
1676
1867
|
const hasLink = !!document.querySelector(`link[rel="stylesheet"][href="${u}"]`);
|
|
1677
1868
|
if (!hasLink) importUrls.push(u);
|
|
@@ -1718,65 +1909,53 @@ async function embedCustomFonts({
|
|
|
1718
1909
|
cssText = res.data;
|
|
1719
1910
|
if (isIconFont2(link.href)) continue;
|
|
1720
1911
|
}
|
|
1912
|
+
cssText = await inlineImportsAndRewrite(cssText, link.href, useProxy);
|
|
1721
1913
|
let facesOut = "";
|
|
1722
1914
|
for (const face of cssText.match(FACE_RE) || []) {
|
|
1723
1915
|
const famRaw = (face.match(/font-family:\s*([^;]+);/i)?.[1] || "").trim();
|
|
1724
1916
|
const family = pickPrimaryFamily(famRaw);
|
|
1725
|
-
if (!family || isIconFont2(family))
|
|
1726
|
-
continue;
|
|
1727
|
-
}
|
|
1917
|
+
if (!family || isIconFont2(family)) continue;
|
|
1728
1918
|
const weightSpec = (face.match(/font-weight:\s*([^;]+);/i)?.[1] || "400").trim();
|
|
1729
1919
|
const styleSpec = (face.match(/font-style:\s*([^;]+);/i)?.[1] || "normal").trim();
|
|
1730
1920
|
const stretchSpec = (face.match(/font-stretch:\s*([^;]+);/i)?.[1] || "100%").trim();
|
|
1731
1921
|
const urange = (face.match(/unicode-range:\s*([^;]+);/i)?.[1] || "").trim();
|
|
1732
1922
|
const srcRaw = (face.match(/src\s*:\s*([^;]+);/i)?.[1] || "").trim();
|
|
1733
1923
|
const srcUrls = extractSrcUrls(srcRaw, link.href);
|
|
1734
|
-
if (!faceMatchesRequired(family, styleSpec, weightSpec, stretchSpec))
|
|
1735
|
-
continue;
|
|
1736
|
-
}
|
|
1924
|
+
if (!faceMatchesRequired(family, styleSpec, weightSpec, stretchSpec)) continue;
|
|
1737
1925
|
const ranges = parseUnicodeRange(urange);
|
|
1738
|
-
if (!unicodeIntersects(usedCodepoints, ranges))
|
|
1739
|
-
continue;
|
|
1740
|
-
}
|
|
1926
|
+
if (!unicodeIntersects(usedCodepoints, ranges)) continue;
|
|
1741
1927
|
const meta = { family, weightSpec, styleSpec, stretchSpec, unicodeRange: urange, srcRaw, srcUrls, href: link.href };
|
|
1742
|
-
if (exclude && simpleExcluder(meta, ranges))
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
const newFace = /url\(/i.test(srcRaw) ? await inlineUrlsInCssBlock(face, link.href) : face;
|
|
1746
|
-
facesOut += newFace + "";
|
|
1928
|
+
if (exclude && simpleExcluder(meta, ranges)) continue;
|
|
1929
|
+
const newFace = /url\(/i.test(srcRaw) ? await inlineUrlsInCssBlock(face, link.href, useProxy) : face;
|
|
1930
|
+
facesOut += newFace;
|
|
1747
1931
|
}
|
|
1748
|
-
if (facesOut.trim()) finalCSS += facesOut
|
|
1749
|
-
} catch
|
|
1750
|
-
console.warn("[
|
|
1932
|
+
if (facesOut.trim()) finalCSS += facesOut;
|
|
1933
|
+
} catch {
|
|
1934
|
+
console.warn("[snapDOM] Failed to process stylesheet:", link.href);
|
|
1751
1935
|
}
|
|
1752
1936
|
}
|
|
1937
|
+
const ctx = {
|
|
1938
|
+
requiredIndex,
|
|
1939
|
+
usedCodepoints,
|
|
1940
|
+
faceMatchesRequired,
|
|
1941
|
+
simpleExcluder: exclude ? buildSimpleExcluder(exclude) : null,
|
|
1942
|
+
useProxy,
|
|
1943
|
+
visitedSheets: /* @__PURE__ */ new Set(),
|
|
1944
|
+
depth: 0
|
|
1945
|
+
};
|
|
1753
1946
|
for (const sheet of document.styleSheets) {
|
|
1947
|
+
if (sheet.href && linkNodes.some((l) => l.href === sheet.href)) continue;
|
|
1754
1948
|
try {
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
const srcRaw = (rule.style.getPropertyValue("src") || "").trim();
|
|
1766
|
-
const urange = (rule.style.getPropertyValue("unicode-range") || "").trim();
|
|
1767
|
-
const srcUrls = extractSrcUrls(srcRaw, sheet.href || location.href);
|
|
1768
|
-
if (!faceMatchesRequired(family, styleSpec, weightSpec, stretchSpec)) continue;
|
|
1769
|
-
const ranges = parseUnicodeRange(urange);
|
|
1770
|
-
if (!unicodeIntersects(usedCodepoints, ranges)) continue;
|
|
1771
|
-
const meta = { family, weightSpec, styleSpec, stretchSpec, unicodeRange: urange, srcRaw, srcUrls, href: sheet.href || location.href };
|
|
1772
|
-
if (exclude && simpleExcluder(meta, ranges)) continue;
|
|
1773
|
-
if (/url\(/i.test(srcRaw)) {
|
|
1774
|
-
const inlinedSrc = await inlineUrlsInCssBlock(srcRaw, sheet.href || location.href);
|
|
1775
|
-
finalCSS += `@font-face{font-family:${family};src:${inlinedSrc};font-style:${styleSpec};font-weight:${weightSpec};font-stretch:${stretchSpec};${urange ? `unicode-range:${urange};` : ""}}`;
|
|
1776
|
-
} else {
|
|
1777
|
-
finalCSS += `@font-face{font-family:${family};src:${srcRaw};font-style:${styleSpec};font-weight:${weightSpec};font-stretch:${stretchSpec};${urange ? `unicode-range:${urange};` : ""}}`;
|
|
1778
|
-
}
|
|
1779
|
-
}
|
|
1949
|
+
const rootHref = sheet.href || location.href;
|
|
1950
|
+
if (rootHref) ctx.visitedSheets.add(rootHref);
|
|
1951
|
+
await collectFacesFromSheet(
|
|
1952
|
+
sheet,
|
|
1953
|
+
rootHref,
|
|
1954
|
+
async (faceCss) => {
|
|
1955
|
+
finalCSS += faceCss;
|
|
1956
|
+
},
|
|
1957
|
+
ctx
|
|
1958
|
+
);
|
|
1780
1959
|
} catch {
|
|
1781
1960
|
}
|
|
1782
1961
|
}
|
|
@@ -1805,7 +1984,7 @@ async function embedCustomFonts({
|
|
|
1805
1984
|
continue;
|
|
1806
1985
|
}
|
|
1807
1986
|
} catch {
|
|
1808
|
-
console.warn("[
|
|
1987
|
+
console.warn("[snapDOM] Failed to fetch dynamic font src:", f._snapdomSrc);
|
|
1809
1988
|
continue;
|
|
1810
1989
|
}
|
|
1811
1990
|
}
|
|
@@ -1840,7 +2019,7 @@ async function embedCustomFonts({
|
|
|
1840
2019
|
continue;
|
|
1841
2020
|
}
|
|
1842
2021
|
} catch {
|
|
1843
|
-
console.warn("[
|
|
2022
|
+
console.warn("[snapDOM] Failed to fetch localFonts src:", src);
|
|
1844
2023
|
continue;
|
|
1845
2024
|
}
|
|
1846
2025
|
}
|
|
@@ -1929,7 +2108,7 @@ async function ensureFontsReady(families, warmupRepetitions = 2) {
|
|
|
1929
2108
|
if (fams.length === 0) return;
|
|
1930
2109
|
const warmupOnce = () => {
|
|
1931
2110
|
const container = document.createElement("div");
|
|
1932
|
-
container.style.cssText =
|
|
2111
|
+
container.style.cssText = "position:absolute!important;left:-9999px!important;top:0!important;opacity:0!important;pointer-events:none!important;contain:layout size style;";
|
|
1933
2112
|
for (const fam of fams) {
|
|
1934
2113
|
const span = document.createElement("span");
|
|
1935
2114
|
span.textContent = "AaBbGg1234\xC1\xC9\xCD\xD3\xDA\xE7\xF1\u2014\u221E";
|
|
@@ -2280,9 +2459,6 @@ async function inlinePseudoElements(source, clone, sessionCache, options) {
|
|
|
2280
2459
|
const fontSize = parseInt(style.fontSize) || 32;
|
|
2281
2460
|
const fontWeight = parseInt(style.fontWeight) || false;
|
|
2282
2461
|
const color = style.color || "#000";
|
|
2283
|
-
const display = style.display;
|
|
2284
|
-
const width = parseFloat(style.width);
|
|
2285
|
-
const height = parseFloat(style.height);
|
|
2286
2462
|
const borderStyle = style.borderStyle;
|
|
2287
2463
|
const borderWidth = parseFloat(style.borderWidth);
|
|
2288
2464
|
const transform = style.transform;
|
|
@@ -2290,14 +2466,13 @@ async function inlinePseudoElements(source, clone, sessionCache, options) {
|
|
|
2290
2466
|
const hasExplicitContent = rawContent !== "none" && cleanContent !== "";
|
|
2291
2467
|
const hasBg = bg && bg !== "none";
|
|
2292
2468
|
const hasBgColor = bgColor && bgColor !== "transparent" && bgColor !== "rgba(0, 0, 0, 0)";
|
|
2293
|
-
const hasBox2 = display !== "inline" && (width > 0 || height > 0);
|
|
2294
2469
|
const hasBorder = borderStyle && borderStyle !== "none" && borderWidth > 0;
|
|
2295
2470
|
const hasTransform = transform && transform !== "none";
|
|
2296
2471
|
const shouldRender = hasExplicitContent || hasBg || hasBgColor || hasBorder || hasTransform;
|
|
2297
2472
|
if (!shouldRender) {
|
|
2298
2473
|
if (incs && incs.length && source.parentElement) {
|
|
2299
2474
|
const map = __siblingCounters.get(source.parentElement) || /* @__PURE__ */ new Map();
|
|
2300
|
-
for (const { name
|
|
2475
|
+
for (const { name } of incs) {
|
|
2301
2476
|
if (!name) continue;
|
|
2302
2477
|
const baseWithSibs = withSiblingOverrides(source, counterCtx);
|
|
2303
2478
|
const derived = deriveCounterCtxForPseudo(source, getComputedStyle(source, pseudo), baseWithSibs);
|
|
@@ -2979,18 +3154,49 @@ function isPlainTextContainer(el) {
|
|
|
2979
3154
|
}
|
|
2980
3155
|
|
|
2981
3156
|
// src/core/capture.js
|
|
3157
|
+
function stripRootShadows(originalEl, cloneRoot) {
|
|
3158
|
+
if (!originalEl || !cloneRoot || !cloneRoot.style) return;
|
|
3159
|
+
const cs = getComputedStyle(originalEl);
|
|
3160
|
+
try {
|
|
3161
|
+
cloneRoot.style.boxShadow = "none";
|
|
3162
|
+
} catch {
|
|
3163
|
+
}
|
|
3164
|
+
try {
|
|
3165
|
+
cloneRoot.style.textShadow = "none";
|
|
3166
|
+
} catch {
|
|
3167
|
+
}
|
|
3168
|
+
try {
|
|
3169
|
+
cloneRoot.style.outline = "none";
|
|
3170
|
+
} catch {
|
|
3171
|
+
}
|
|
3172
|
+
const f = cs.filter || "";
|
|
3173
|
+
const cleaned = f.replace(/\bblur\([^()]*\)\s*/gi, "").replace(/\bdrop-shadow\([^()]*\)\s*/gi, "").trim().replace(/\s+/g, " ");
|
|
3174
|
+
try {
|
|
3175
|
+
cloneRoot.style.filter = cleaned.length ? cleaned : "none";
|
|
3176
|
+
} catch {
|
|
3177
|
+
}
|
|
3178
|
+
}
|
|
2982
3179
|
async function captureDOM(element, options) {
|
|
2983
3180
|
if (!element) throw new Error("Element cannot be null or undefined");
|
|
2984
3181
|
applyCachePolicy(options.cache);
|
|
2985
3182
|
const fast = options.fast;
|
|
3183
|
+
const straighten = !!options.straighten;
|
|
3184
|
+
const noShadows = !!options.noShadows;
|
|
2986
3185
|
let clone, classCSS, styleCache;
|
|
2987
3186
|
let fontsCSS = "";
|
|
2988
3187
|
let baseCSS = "";
|
|
2989
3188
|
let dataURL;
|
|
2990
3189
|
let svgString;
|
|
3190
|
+
let rootTransform2D = null;
|
|
2991
3191
|
const undoClamp = lineClamp(element);
|
|
2992
3192
|
try {
|
|
2993
3193
|
({ clone, classCSS, styleCache } = await prepareClone(element, options));
|
|
3194
|
+
if (straighten && clone) {
|
|
3195
|
+
rootTransform2D = normalizeRootTransforms(element, clone);
|
|
3196
|
+
}
|
|
3197
|
+
if (noShadows && clone) {
|
|
3198
|
+
stripRootShadows(element, clone);
|
|
3199
|
+
}
|
|
2994
3200
|
} finally {
|
|
2995
3201
|
undoClamp();
|
|
2996
3202
|
}
|
|
@@ -3015,7 +3221,7 @@ async function captureDOM(element, options) {
|
|
|
3015
3221
|
const families = new Set(
|
|
3016
3222
|
Array.from(required).map((k) => String(k).split("__")[0]).filter(Boolean)
|
|
3017
3223
|
);
|
|
3018
|
-
await ensureFontsReady(families,
|
|
3224
|
+
await ensureFontsReady(families, 1);
|
|
3019
3225
|
}
|
|
3020
3226
|
fontsCSS = await embedCustomFonts({
|
|
3021
3227
|
required,
|
|
@@ -3063,60 +3269,7 @@ async function captureDOM(element, options) {
|
|
|
3063
3269
|
b = Math.max(b, extY + Math.max(oy, 0));
|
|
3064
3270
|
t = Math.max(t, extY + Math.max(-oy, 0));
|
|
3065
3271
|
}
|
|
3066
|
-
return {
|
|
3067
|
-
bleed: { top: Math.ceil(t), right: Math.ceil(r), bottom: Math.ceil(b), left: Math.ceil(l) },
|
|
3068
|
-
has: found
|
|
3069
|
-
};
|
|
3070
|
-
}
|
|
3071
|
-
function parseBoxShadow(cs) {
|
|
3072
|
-
const v = cs.boxShadow || "";
|
|
3073
|
-
if (!v || v === "none") return { top: 0, right: 0, bottom: 0, left: 0 };
|
|
3074
|
-
const parts = v.split(/\),(?=(?:[^()]*\([^()]*\))*[^()]*$)/).map((s) => s.trim());
|
|
3075
|
-
let t = 0, r = 0, b2 = 0, l = 0;
|
|
3076
|
-
for (const part of parts) {
|
|
3077
|
-
const nums = part.match(/-?\d+(\.\d+)?px/g)?.map((n) => parseFloat(n)) || [];
|
|
3078
|
-
if (nums.length < 2) continue;
|
|
3079
|
-
const [ox2, oy2, blur = 0, spread = 0] = nums;
|
|
3080
|
-
const extX = Math.abs(ox2) + blur + spread;
|
|
3081
|
-
const extY = Math.abs(oy2) + blur + spread;
|
|
3082
|
-
r = Math.max(r, extX + Math.max(ox2, 0));
|
|
3083
|
-
l = Math.max(l, extX + Math.max(-ox2, 0));
|
|
3084
|
-
b2 = Math.max(b2, extY + Math.max(oy2, 0));
|
|
3085
|
-
t = Math.max(t, extY + Math.max(-oy2, 0));
|
|
3086
|
-
}
|
|
3087
|
-
return { top: Math.ceil(t), right: Math.ceil(r), bottom: Math.ceil(b2), left: Math.ceil(l) };
|
|
3088
|
-
}
|
|
3089
|
-
function parseFilterBlur(cs) {
|
|
3090
|
-
const m = (cs.filter || "").match(/blur\(\s*([0-9.]+)px\s*\)/);
|
|
3091
|
-
const b2 = m ? Math.ceil(parseFloat(m[1]) || 0) : 0;
|
|
3092
|
-
return { top: b2, right: b2, bottom: b2, left: b2 };
|
|
3093
|
-
}
|
|
3094
|
-
function parseOutline(cs) {
|
|
3095
|
-
if ((cs.outlineStyle || "none") === "none") return { top: 0, right: 0, bottom: 0, left: 0 };
|
|
3096
|
-
const w2 = Math.ceil(parseFloat(cs.outlineWidth || "0") || 0);
|
|
3097
|
-
return { top: w2, right: w2, bottom: w2, left: w2 };
|
|
3098
|
-
}
|
|
3099
|
-
function bboxWithOriginFull(w2, h2, M, ox2, oy2) {
|
|
3100
|
-
const a2 = M.a, b2 = M.b, c2 = M.c, d2 = M.d, e2 = M.e || 0, f2 = M.f || 0;
|
|
3101
|
-
function pt(x, y) {
|
|
3102
|
-
let X = x - ox2, Y = y - oy2;
|
|
3103
|
-
let X2 = a2 * X + c2 * Y, Y2 = b2 * X + d2 * Y;
|
|
3104
|
-
X2 += ox2 + e2;
|
|
3105
|
-
Y2 += oy2 + f2;
|
|
3106
|
-
return [X2, Y2];
|
|
3107
|
-
}
|
|
3108
|
-
const P = [pt(0, 0), pt(w2, 0), pt(0, h2), pt(w2, h2)];
|
|
3109
|
-
let minX2 = Infinity, minY2 = Infinity, maxX2 = -Infinity, maxY2 = -Infinity;
|
|
3110
|
-
for (const [X, Y] of P) {
|
|
3111
|
-
if (X < minX2) minX2 = X;
|
|
3112
|
-
if (Y < minY2) minY2 = Y;
|
|
3113
|
-
if (X > maxX2) maxX2 = X;
|
|
3114
|
-
if (Y > maxY2) maxY2 = Y;
|
|
3115
|
-
}
|
|
3116
|
-
return { minX: minX2, minY: minY2, maxX: maxX2, maxY: maxY2, width: maxX2 - minX2, height: maxY2 - minY2 };
|
|
3117
|
-
}
|
|
3118
|
-
function hasTFBBox(el) {
|
|
3119
|
-
return hasBBoxAffectingTransform(el);
|
|
3272
|
+
return { bleed: { top: Math.ceil(t), right: Math.ceil(r), bottom: Math.ceil(b), left: Math.ceil(l) }, has: found };
|
|
3120
3273
|
}
|
|
3121
3274
|
const rect = element.getBoundingClientRect();
|
|
3122
3275
|
const w0 = Math.max(1, Math.ceil(element.offsetWidth || parseFloat(csEl.width) || rect.width || 1));
|
|
@@ -3145,28 +3298,38 @@ async function captureDOM(element, options) {
|
|
|
3145
3298
|
h = h0;
|
|
3146
3299
|
}
|
|
3147
3300
|
let minX = 0, minY = 0, maxX = w0, maxY = h0;
|
|
3148
|
-
if (
|
|
3149
|
-
const
|
|
3150
|
-
const
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3301
|
+
if (straighten && rootTransform2D && Number.isFinite(rootTransform2D.a)) {
|
|
3302
|
+
const M2 = { a: rootTransform2D.a, b: rootTransform2D.b || 0, c: rootTransform2D.c || 0, d: rootTransform2D.d || 1, e: 0, f: 0 };
|
|
3303
|
+
const bb2 = bboxWithOriginFull(w0, h0, M2, 0, 0);
|
|
3304
|
+
minX = bb2.minX;
|
|
3305
|
+
minY = bb2.minY;
|
|
3306
|
+
maxX = bb2.maxX;
|
|
3307
|
+
maxY = bb2.maxY;
|
|
3308
|
+
} else {
|
|
3309
|
+
const useTFBBox = !straighten && hasTFBBox(element);
|
|
3310
|
+
if (useTFBBox) {
|
|
3311
|
+
const baseTransform2 = csEl.transform && csEl.transform !== "none" ? csEl.transform : "";
|
|
3312
|
+
const ind2 = readIndividualTransforms(element);
|
|
3313
|
+
const TOTAL = readTotalTransformMatrix({
|
|
3314
|
+
baseTransform: baseTransform2,
|
|
3315
|
+
rotate: ind2.rotate || "0deg",
|
|
3316
|
+
scale: ind2.scale,
|
|
3317
|
+
translate: ind2.translate
|
|
3318
|
+
});
|
|
3319
|
+
const { ox: ox2, oy: oy2 } = parseTransformOriginPx(csEl, w0, h0);
|
|
3320
|
+
const M = TOTAL.is2D ? TOTAL : new DOMMatrix(TOTAL.toString());
|
|
3321
|
+
const bb = bboxWithOriginFull(w0, h0, M, ox2, oy2);
|
|
3322
|
+
minX = bb.minX;
|
|
3323
|
+
minY = bb.minY;
|
|
3324
|
+
maxX = bb.maxX;
|
|
3325
|
+
maxY = bb.maxY;
|
|
3326
|
+
}
|
|
3164
3327
|
}
|
|
3165
3328
|
const bleedShadow = parseBoxShadow(csEl);
|
|
3166
3329
|
const bleedBlur = parseFilterBlur(csEl);
|
|
3167
3330
|
const bleedOutline = parseOutline(csEl);
|
|
3168
3331
|
const drop = parseFilterDropShadows(csEl);
|
|
3169
|
-
const bleed = {
|
|
3332
|
+
const bleed = noShadows ? { top: 0, right: 0, bottom: 0, left: 0 } : {
|
|
3170
3333
|
top: bleedShadow.top + bleedBlur.top + bleedOutline.top + drop.bleed.top,
|
|
3171
3334
|
right: bleedShadow.right + bleedBlur.right + bleedOutline.right + drop.bleed.right,
|
|
3172
3335
|
bottom: bleedShadow.bottom + bleedBlur.bottom + bleedOutline.bottom + drop.bleed.bottom,
|
|
@@ -3181,7 +3344,9 @@ async function captureDOM(element, options) {
|
|
|
3181
3344
|
const outW = Math.max(1, Math.round(vbW0 * (hasW || hasH ? w / w0 : 1)));
|
|
3182
3345
|
const outH = Math.max(1, Math.round(vbH0 * (hasH || hasW ? h / h0 : 1)));
|
|
3183
3346
|
const svgNS = "http://www.w3.org/2000/svg";
|
|
3184
|
-
const
|
|
3347
|
+
const basePad = isSafari() ? 1 : 0;
|
|
3348
|
+
const extraPad = straighten ? 1 : 0;
|
|
3349
|
+
const pad = basePad + extraPad;
|
|
3185
3350
|
const fo = document.createElementNS(svgNS, "foreignObject");
|
|
3186
3351
|
const vbMinX = Math.floor(minX);
|
|
3187
3352
|
const vbMinY = Math.floor(minY);
|
|
@@ -3206,20 +3371,7 @@ async function captureDOM(element, options) {
|
|
|
3206
3371
|
const vbW = vbW0 + pad * 2;
|
|
3207
3372
|
const vbH = vbH0 + pad * 2;
|
|
3208
3373
|
const wantsSize = hasW || hasH;
|
|
3209
|
-
options.meta = {
|
|
3210
|
-
w0,
|
|
3211
|
-
// ancho natural del elemento
|
|
3212
|
-
h0,
|
|
3213
|
-
// alto natural
|
|
3214
|
-
vbW,
|
|
3215
|
-
// ancho del viewBox
|
|
3216
|
-
vbH,
|
|
3217
|
-
// alto del viewBox
|
|
3218
|
-
targetW: w,
|
|
3219
|
-
// ancho deseado según options.width
|
|
3220
|
-
targetH: h
|
|
3221
|
-
// alto deseado según options.height
|
|
3222
|
-
};
|
|
3374
|
+
options.meta = { w0, h0, vbW, vbH, targetW: w, targetH: h };
|
|
3223
3375
|
const svgOutW = isSafari() && wantsSize ? vbW : outW + pad * 2;
|
|
3224
3376
|
const svgOutH = isSafari() && wantsSize ? vbH : outH + pad * 2;
|
|
3225
3377
|
const svgHeader = `<svg xmlns="${svgNS}" width="${svgOutW}" height="${svgOutH}" viewBox="0 0 ${vbW} ${vbH}">`;
|
|
@@ -3233,6 +3385,115 @@ async function captureDOM(element, options) {
|
|
|
3233
3385
|
if (sandbox && sandbox.style.position === "absolute") sandbox.remove();
|
|
3234
3386
|
return dataURL;
|
|
3235
3387
|
}
|
|
3388
|
+
function normalizeRootTransforms(originalEl, cloneRoot) {
|
|
3389
|
+
if (!originalEl || !cloneRoot || !cloneRoot.style) return null;
|
|
3390
|
+
const cs = getComputedStyle(originalEl);
|
|
3391
|
+
try {
|
|
3392
|
+
cloneRoot.style.transformOrigin = "0 0";
|
|
3393
|
+
} catch {
|
|
3394
|
+
}
|
|
3395
|
+
try {
|
|
3396
|
+
if ("translate" in cloneRoot.style) cloneRoot.style.translate = "none";
|
|
3397
|
+
if ("rotate" in cloneRoot.style) cloneRoot.style.rotate = "none";
|
|
3398
|
+
} catch {
|
|
3399
|
+
}
|
|
3400
|
+
const tr = cs.transform || "none";
|
|
3401
|
+
if (!tr || tr === "none") {
|
|
3402
|
+
try {
|
|
3403
|
+
const M = matrixFromComputed(originalEl);
|
|
3404
|
+
if (M.a === 1 && M.b === 0 && M.c === 0 && M.d === 1) {
|
|
3405
|
+
cloneRoot.style.transform = "none";
|
|
3406
|
+
return { a: 1, b: 0, c: 0, d: 1 };
|
|
3407
|
+
}
|
|
3408
|
+
} catch {
|
|
3409
|
+
}
|
|
3410
|
+
}
|
|
3411
|
+
const m2d = tr.match(/^matrix\(\s*([^)]+)\)$/i);
|
|
3412
|
+
if (m2d) {
|
|
3413
|
+
const nums = m2d[1].split(",").map((v) => parseFloat(v.trim()));
|
|
3414
|
+
if (nums.length === 6 && nums.every(Number.isFinite)) {
|
|
3415
|
+
const [a, b, c, d] = nums;
|
|
3416
|
+
const scaleX = Math.sqrt(a * a + b * b) || 0;
|
|
3417
|
+
let a1 = 0, b1 = 0, shear = 0, c2 = 0, d2 = 0, scaleY = 0;
|
|
3418
|
+
if (scaleX > 0) {
|
|
3419
|
+
a1 = a / scaleX;
|
|
3420
|
+
b1 = b / scaleX;
|
|
3421
|
+
shear = a1 * c + b1 * d;
|
|
3422
|
+
c2 = c - a1 * shear;
|
|
3423
|
+
d2 = d - b1 * shear;
|
|
3424
|
+
scaleY = Math.sqrt(c2 * c2 + d2 * d2) || 0;
|
|
3425
|
+
if (scaleY > 0) shear = shear / scaleY;
|
|
3426
|
+
else shear = 0;
|
|
3427
|
+
}
|
|
3428
|
+
const aP = scaleX;
|
|
3429
|
+
const bP = 0;
|
|
3430
|
+
const cP = shear * scaleY;
|
|
3431
|
+
const dP = scaleY;
|
|
3432
|
+
try {
|
|
3433
|
+
cloneRoot.style.transform = `matrix(${aP}, ${bP}, ${cP}, ${dP}, 0, 0)`;
|
|
3434
|
+
} catch {
|
|
3435
|
+
}
|
|
3436
|
+
return { a: aP, b: bP, c: cP, d: dP };
|
|
3437
|
+
}
|
|
3438
|
+
}
|
|
3439
|
+
try {
|
|
3440
|
+
const legacy = String(tr).trim();
|
|
3441
|
+
cloneRoot.style.transform = legacy + " translate(0px, 0px) rotate(0deg)";
|
|
3442
|
+
return null;
|
|
3443
|
+
} catch {
|
|
3444
|
+
return null;
|
|
3445
|
+
}
|
|
3446
|
+
}
|
|
3447
|
+
function parseBoxShadow(cs) {
|
|
3448
|
+
const v = cs.boxShadow || "";
|
|
3449
|
+
if (!v || v === "none") return { top: 0, right: 0, bottom: 0, left: 0 };
|
|
3450
|
+
const parts = v.split(/\),(?=(?:[^()]*\([^()]*\))*[^()]*$)/).map((s) => s.trim());
|
|
3451
|
+
let t = 0, r = 0, b2 = 0, l = 0;
|
|
3452
|
+
for (const part of parts) {
|
|
3453
|
+
const nums = part.match(/-?\d+(\.\d+)?px/g)?.map((n) => parseFloat(n)) || [];
|
|
3454
|
+
if (nums.length < 2) continue;
|
|
3455
|
+
const [ox2, oy2, blur = 0, spread = 0] = nums;
|
|
3456
|
+
const extX = Math.abs(ox2) + blur + spread;
|
|
3457
|
+
const extY = Math.abs(oy2) + blur + spread;
|
|
3458
|
+
r = Math.max(r, extX + Math.max(ox2, 0));
|
|
3459
|
+
l = Math.max(l, extX + Math.max(-ox2, 0));
|
|
3460
|
+
b2 = Math.max(b2, extY + Math.max(oy2, 0));
|
|
3461
|
+
t = Math.max(t, extY + Math.max(-oy2, 0));
|
|
3462
|
+
}
|
|
3463
|
+
return { top: Math.ceil(t), right: Math.ceil(r), bottom: Math.ceil(b2), left: Math.ceil(l) };
|
|
3464
|
+
}
|
|
3465
|
+
function parseFilterBlur(cs) {
|
|
3466
|
+
const m = (cs.filter || "").match(/blur\(\s*([0-9.]+)px\s*\)/);
|
|
3467
|
+
const b2 = m ? Math.ceil(parseFloat(m[1]) || 0) : 0;
|
|
3468
|
+
return { top: b2, right: b2, bottom: b2, left: b2 };
|
|
3469
|
+
}
|
|
3470
|
+
function parseOutline(cs) {
|
|
3471
|
+
if ((cs.outlineStyle || "none") === "none") return { top: 0, right: 0, bottom: 0, left: 0 };
|
|
3472
|
+
const w2 = Math.ceil(parseFloat(cs.outlineWidth || "0") || 0);
|
|
3473
|
+
return { top: w2, right: w2, bottom: w2, left: w2 };
|
|
3474
|
+
}
|
|
3475
|
+
function bboxWithOriginFull(w2, h2, M, ox2, oy2) {
|
|
3476
|
+
const a2 = M.a, b2 = M.b, c2 = M.c, d2 = M.d, e2 = M.e || 0, f2 = M.f || 0;
|
|
3477
|
+
function pt(x, y) {
|
|
3478
|
+
let X = x - ox2, Y = y - oy2;
|
|
3479
|
+
let X2 = a2 * X + c2 * Y, Y2 = b2 * X + d2 * Y;
|
|
3480
|
+
X2 += ox2 + e2;
|
|
3481
|
+
Y2 += oy2 + f2;
|
|
3482
|
+
return [X2, Y2];
|
|
3483
|
+
}
|
|
3484
|
+
const P = [pt(0, 0), pt(w2, 0), pt(0, h2), pt(w2, h2)];
|
|
3485
|
+
let minX2 = Infinity, minY2 = Infinity, maxX2 = -Infinity, maxY2 = -Infinity;
|
|
3486
|
+
for (const [X, Y] of P) {
|
|
3487
|
+
if (X < minX2) minX2 = X;
|
|
3488
|
+
if (Y < minY2) minY2 = Y;
|
|
3489
|
+
if (X > maxX2) maxX2 = X;
|
|
3490
|
+
if (Y > maxY2) maxY2 = Y;
|
|
3491
|
+
}
|
|
3492
|
+
return { minX: minX2, minY: minY2, maxX: maxX2, maxY: maxY2, width: maxX2 - minX2, height: maxY2 - minY2 };
|
|
3493
|
+
}
|
|
3494
|
+
function hasTFBBox(el) {
|
|
3495
|
+
return hasBBoxAffectingTransform(el);
|
|
3496
|
+
}
|
|
3236
3497
|
function matrixFromComputed(el) {
|
|
3237
3498
|
const tr = getComputedStyle(el).transform;
|
|
3238
3499
|
if (!tr || tr === "none") return new DOMMatrix();
|
|
@@ -3385,20 +3646,20 @@ function createContext(options = {}) {
|
|
|
3385
3646
|
excludeMode: options.excludeMode ?? "hide",
|
|
3386
3647
|
filter: options.filter ?? null,
|
|
3387
3648
|
filterMode: options.filterMode ?? "hide",
|
|
3649
|
+
// Placeholders
|
|
3388
3650
|
placeholders: options.placeholders !== false,
|
|
3389
3651
|
// default true
|
|
3390
|
-
//
|
|
3652
|
+
// Fonts
|
|
3391
3653
|
embedFonts: options.embedFonts ?? false,
|
|
3392
3654
|
iconFonts: Array.isArray(options.iconFonts) ? options.iconFonts : options.iconFonts ? [options.iconFonts] : [],
|
|
3393
3655
|
localFonts: Array.isArray(options.localFonts) ? options.localFonts : [],
|
|
3394
3656
|
excludeFonts: options.excludeFonts ?? void 0,
|
|
3395
3657
|
fallbackURL: options.fallbackURL ?? void 0,
|
|
3396
|
-
/** Cache policy (disabled|full|auto|soft). Default: "soft" */
|
|
3397
3658
|
/** @type {CachePolicy} */
|
|
3398
3659
|
cache: cachePolicy,
|
|
3399
|
-
//
|
|
3660
|
+
// Network
|
|
3400
3661
|
useProxy: typeof options.useProxy === "string" ? options.useProxy : "",
|
|
3401
|
-
//
|
|
3662
|
+
// Output
|
|
3402
3663
|
width: options.width ?? null,
|
|
3403
3664
|
height: options.height ?? null,
|
|
3404
3665
|
format: resolvedFormat,
|
|
@@ -3406,8 +3667,11 @@ function createContext(options = {}) {
|
|
|
3406
3667
|
quality: options.quality ?? 0.92,
|
|
3407
3668
|
dpr: options.dpr ?? (window.devicePixelRatio || 1),
|
|
3408
3669
|
backgroundColor: options.backgroundColor ?? (["jpg", "jpeg", "webp"].includes(resolvedFormat) ? "#ffffff" : null),
|
|
3409
|
-
filename: options.filename ?? "snapDOM"
|
|
3410
|
-
//
|
|
3670
|
+
filename: options.filename ?? "snapDOM",
|
|
3671
|
+
// NEW flags (user-friendly)
|
|
3672
|
+
straighten: options.straighten ?? false,
|
|
3673
|
+
noShadows: options.noShadows ?? false
|
|
3674
|
+
// Plugins (reservado)
|
|
3411
3675
|
// plugins: normalizePlugins(...),
|
|
3412
3676
|
};
|
|
3413
3677
|
}
|
|
@@ -3597,9 +3861,20 @@ async function toImg(url, options) {
|
|
|
3597
3861
|
const k = height / Math.max(1, refH);
|
|
3598
3862
|
img.style.height = `${height}px`;
|
|
3599
3863
|
img.style.width = `${Math.round(refW * k)}px`;
|
|
3600
|
-
} else
|
|
3601
|
-
|
|
3602
|
-
|
|
3864
|
+
} else {
|
|
3865
|
+
const cssW = Math.round(img.naturalWidth * scale);
|
|
3866
|
+
const cssH = Math.round(img.naturalHeight * scale);
|
|
3867
|
+
img.style.width = `${cssW}px`;
|
|
3868
|
+
img.style.height = `${cssH}px`;
|
|
3869
|
+
if (typeof url === "string" && url.startsWith("data:image/svg+xml")) {
|
|
3870
|
+
try {
|
|
3871
|
+
const decoded = decodeURIComponent(url.split(",")[1]);
|
|
3872
|
+
const patched = decoded.replace(/width="[^"]*"/, `width="${cssW}"`).replace(/height="[^"]*"/, `height="${cssH}"`);
|
|
3873
|
+
url = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(patched)}`;
|
|
3874
|
+
img.src = url;
|
|
3875
|
+
} catch {
|
|
3876
|
+
}
|
|
3877
|
+
}
|
|
3603
3878
|
}
|
|
3604
3879
|
return img;
|
|
3605
3880
|
}
|
|
@@ -3655,7 +3930,7 @@ async function snapdom(element, userOptions) {
|
|
|
3655
3930
|
await safariWarmup(element, userOptions);
|
|
3656
3931
|
console.log("Iteraci\xF3n n\xFAmero:", i);
|
|
3657
3932
|
_safariWarmup = false;
|
|
3658
|
-
} catch
|
|
3933
|
+
} catch {
|
|
3659
3934
|
}
|
|
3660
3935
|
}
|
|
3661
3936
|
}
|
|
@@ -3663,7 +3938,7 @@ async function snapdom(element, userOptions) {
|
|
|
3663
3938
|
if (!context.snap) {
|
|
3664
3939
|
context.snap = {
|
|
3665
3940
|
toPng: (el, opts) => snapdom.toPng(el, opts),
|
|
3666
|
-
|
|
3941
|
+
toSvg: (el, opts) => snapdom.toSvg(el, opts)
|
|
3667
3942
|
};
|
|
3668
3943
|
}
|
|
3669
3944
|
return snapdom.capture(element, context, INTERNAL_TOKEN);
|
|
@@ -3685,6 +3960,7 @@ snapdom.capture = async (el, context, _token) => {
|
|
|
3685
3960
|
url,
|
|
3686
3961
|
toRaw: () => url,
|
|
3687
3962
|
toImg: (opts) => toImg(url, ensureContext(opts)),
|
|
3963
|
+
toSvg: (opts) => toImg(url, ensureContext(opts)),
|
|
3688
3964
|
toCanvas: (opts) => toCanvas(url, ensureContext(opts)),
|
|
3689
3965
|
toBlob: (opts) => toBlob(url, ensureContext(opts)),
|
|
3690
3966
|
toPng: withFormat("png"),
|
|
@@ -3695,6 +3971,7 @@ snapdom.capture = async (el, context, _token) => {
|
|
|
3695
3971
|
};
|
|
3696
3972
|
snapdom.toRaw = (el, options) => snapdom(el, options).then((result) => result.toRaw());
|
|
3697
3973
|
snapdom.toImg = (el, options) => snapdom(el, options).then((result) => result.toImg());
|
|
3974
|
+
snapdom.toSvg = (el, options) => snapdom(el, options).then((result) => result.toSvg());
|
|
3698
3975
|
snapdom.toCanvas = (el, options) => snapdom(el, options).then((result) => result.toCanvas());
|
|
3699
3976
|
snapdom.toBlob = (el, options) => snapdom(el, options).then((result) => result.toBlob());
|
|
3700
3977
|
snapdom.toPng = (el, options) => snapdom(el, { ...options, format: "png" }).then((result) => result.toPng());
|
|
@@ -3833,7 +4110,7 @@ async function preCache(root = document, options = {}) {
|
|
|
3833
4110
|
const families = new Set(
|
|
3834
4111
|
Array.from(required).map((k) => String(k).split("__")[0]).filter(Boolean)
|
|
3835
4112
|
);
|
|
3836
|
-
await ensureFontsReady(families,
|
|
4113
|
+
await ensureFontsReady(families, 3);
|
|
3837
4114
|
}
|
|
3838
4115
|
await embedCustomFonts({
|
|
3839
4116
|
required,
|