@zumer/snapdom 2.0.0-dev.1 → 2.0.0-dev.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -2
- package/dist/snapdom.js +289 -60
- package/dist/snapdom.min.js +7 -7
- package/dist/snapdom.min.mjs +6 -6
- package/dist/snapdom.mjs +283 -60
- package/package.json +1 -1
package/dist/snapdom.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
/*
|
|
3
3
|
* snapdom
|
|
4
|
-
* v.2.0.0-dev.
|
|
4
|
+
* v.2.0.0-dev.2
|
|
5
5
|
* Author Juan Martin Muda
|
|
6
6
|
* License MIT
|
|
7
7
|
**/
|
|
@@ -328,19 +328,6 @@ async function snapFetch(url, options = {}) {
|
|
|
328
328
|
}
|
|
329
329
|
|
|
330
330
|
// src/utils/image.js
|
|
331
|
-
function createBackground(baseCanvas, backgroundColor) {
|
|
332
|
-
if (!backgroundColor || !baseCanvas.width || !baseCanvas.height) {
|
|
333
|
-
return baseCanvas;
|
|
334
|
-
}
|
|
335
|
-
const temp = document.createElement("canvas");
|
|
336
|
-
temp.width = baseCanvas.width;
|
|
337
|
-
temp.height = baseCanvas.height;
|
|
338
|
-
const ctx = temp.getContext("2d");
|
|
339
|
-
ctx.fillStyle = backgroundColor;
|
|
340
|
-
ctx.fillRect(0, 0, temp.width, temp.height);
|
|
341
|
-
ctx.drawImage(baseCanvas, 0, 0);
|
|
342
|
-
return temp;
|
|
343
|
-
}
|
|
344
331
|
async function inlineSingleBackgroundEntry(entry, options = {}) {
|
|
345
332
|
const isGradient = /^((repeating-)?(linear|radial|conic)-gradient)\(/i.test(entry);
|
|
346
333
|
if (isGradient || entry.trim() === "none") {
|
|
@@ -616,11 +603,20 @@ function idle(fn, { fast = false } = {}) {
|
|
|
616
603
|
}
|
|
617
604
|
}
|
|
618
605
|
function isSafari() {
|
|
619
|
-
|
|
620
|
-
const
|
|
621
|
-
const
|
|
622
|
-
const
|
|
623
|
-
|
|
606
|
+
if (typeof navigator === "undefined") return false;
|
|
607
|
+
const ua = navigator.userAgent || "";
|
|
608
|
+
const uaLower = ua.toLowerCase();
|
|
609
|
+
const isSafariUA = uaLower.includes("safari") && !uaLower.includes("chrome") && !uaLower.includes("crios") && // Chrome on iOS
|
|
610
|
+
!uaLower.includes("fxios") && // Firefox on iOS
|
|
611
|
+
!uaLower.includes("android");
|
|
612
|
+
const isWebKit = /applewebkit/i.test(ua);
|
|
613
|
+
const isMobile = /mobile/i.test(ua);
|
|
614
|
+
const missingSafariToken = !/safari/i.test(ua);
|
|
615
|
+
const isUIWebView = isWebKit && isMobile && missingSafariToken;
|
|
616
|
+
const isWeChatUA = /(micromessenger|wxwork|wecom|windowswechat|macwechat)/i.test(ua);
|
|
617
|
+
const isBaiduUA = /(baiduboxapp|baidubrowser|baidusearch|baiduboxlite)/i.test(uaLower);
|
|
618
|
+
const isIOSWebKit = /ipad|iphone|ipod/.test(uaLower) && isWebKit;
|
|
619
|
+
return isSafariUA || isUIWebView || isWeChatUA || isBaiduUA || isIOSWebKit;
|
|
624
620
|
}
|
|
625
621
|
|
|
626
622
|
// src/modules/styles.js
|
|
@@ -665,6 +661,22 @@ function snapshotComputedStyleFull(style, options = {}) {
|
|
|
665
661
|
}
|
|
666
662
|
out[prop] = val;
|
|
667
663
|
}
|
|
664
|
+
const EXTRA_TEXT_DECORATION_PROPS = [
|
|
665
|
+
"text-decoration-line",
|
|
666
|
+
"text-decoration-color",
|
|
667
|
+
"text-decoration-style",
|
|
668
|
+
"text-decoration-thickness",
|
|
669
|
+
"text-underline-offset",
|
|
670
|
+
"text-decoration-skip-ink"
|
|
671
|
+
];
|
|
672
|
+
for (const prop of EXTRA_TEXT_DECORATION_PROPS) {
|
|
673
|
+
if (out[prop]) continue;
|
|
674
|
+
try {
|
|
675
|
+
const v = style.getPropertyValue(prop);
|
|
676
|
+
if (v) out[prop] = v;
|
|
677
|
+
} catch {
|
|
678
|
+
}
|
|
679
|
+
}
|
|
668
680
|
if (options.embedFonts) {
|
|
669
681
|
const EXTRA_FONT_PROPS = [
|
|
670
682
|
"font-feature-settings",
|
|
@@ -1421,17 +1433,19 @@ var defaultIconFonts = [
|
|
|
1421
1433
|
/layui/i,
|
|
1422
1434
|
/lucide/i
|
|
1423
1435
|
];
|
|
1436
|
+
var ICON_FONT_URLS = Object.assign({
|
|
1437
|
+
materialIconsFilled: "https://fonts.gstatic.com/s/materialicons/v48/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.woff2",
|
|
1438
|
+
materialIconsOutlined: "https://fonts.gstatic.com/s/materialiconsoutlined/v110/gok-H7zzDkdnRel8-DQ6KAXJ69wP1tGnf4ZGhUcel5euIg.woff2",
|
|
1439
|
+
materialIconsRound: "https://fonts.gstatic.com/s/materialiconsround/v109/LDItaoyNOAY6Uewc665JcIzCKsKc_M9flwmPq_HTTw.woff2",
|
|
1440
|
+
materialIconsSharp: "https://fonts.gstatic.com/s/materialiconssharp/v110/oPWQ_lt5nv4pWNJpghLP75WiFR4kLh3kvmvRImcycg.woff2"
|
|
1441
|
+
}, typeof window !== "undefined" && window.__SNAPDOM_ICON_FONTS__ || {});
|
|
1424
1442
|
var userIconFonts = [];
|
|
1425
1443
|
function extendIconFonts(fonts) {
|
|
1426
1444
|
const list = Array.isArray(fonts) ? fonts : [fonts];
|
|
1427
1445
|
for (const f of list) {
|
|
1428
|
-
if (f instanceof RegExp)
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
userIconFonts.push(new RegExp(f, "i"));
|
|
1432
|
-
} else {
|
|
1433
|
-
console.warn("[snapdom] Ignored invalid iconFont value:", f);
|
|
1434
|
-
}
|
|
1446
|
+
if (f instanceof RegExp) userIconFonts.push(f);
|
|
1447
|
+
else if (typeof f === "string") userIconFonts.push(new RegExp(f, "i"));
|
|
1448
|
+
else console.warn("[snapdom] Ignored invalid iconFont value:", f);
|
|
1435
1449
|
}
|
|
1436
1450
|
}
|
|
1437
1451
|
function isIconFont2(input) {
|
|
@@ -1447,10 +1461,62 @@ function isMaterialFamily(family = "") {
|
|
|
1447
1461
|
const s = String(family).toLowerCase();
|
|
1448
1462
|
return /\bmaterial\s*icons\b/.test(s) || /\bmaterial\s*symbols\b/.test(s);
|
|
1449
1463
|
}
|
|
1464
|
+
var loadedCanvasFamilies = /* @__PURE__ */ new Map();
|
|
1465
|
+
function parseAxes(variation = "") {
|
|
1466
|
+
const out = /* @__PURE__ */ Object.create(null);
|
|
1467
|
+
const v = String(variation || "");
|
|
1468
|
+
const rx = /['"]?\s*([A-Za-z]{3,4})\s*['"]?\s*([+-]?\d+(?:\.\d+)?)\s*/g;
|
|
1469
|
+
let m;
|
|
1470
|
+
while (m = rx.exec(v)) out[m[1].toUpperCase()] = Number(m[2]);
|
|
1471
|
+
return out;
|
|
1472
|
+
}
|
|
1473
|
+
async function ensureLigatureCanvasFont(cssFamily, className, axes) {
|
|
1474
|
+
const fam = String(cssFamily || "");
|
|
1475
|
+
const lowerFam = fam.toLowerCase();
|
|
1476
|
+
const cls = String(className || "").toLowerCase();
|
|
1477
|
+
if (/\bmaterial\s*icons\b/.test(lowerFam) && !/\bsymbols\b/.test(lowerFam)) {
|
|
1478
|
+
return { familyForMeasure: fam, familyForCanvas: fam };
|
|
1479
|
+
}
|
|
1480
|
+
const isSymbols = /\bmaterial\s*symbols\b/.test(lowerFam);
|
|
1481
|
+
if (!isSymbols) {
|
|
1482
|
+
return { familyForMeasure: fam, familyForCanvas: fam };
|
|
1483
|
+
}
|
|
1484
|
+
const FILL = axes && (axes.FILL ?? axes.fill);
|
|
1485
|
+
let style = "outlined";
|
|
1486
|
+
if (/\brounded\b/.test(cls) || /\bround\b/.test(cls)) style = "rounded";
|
|
1487
|
+
else if (/\bsharp\b/.test(cls)) style = "sharp";
|
|
1488
|
+
else if (/\boutlined\b/.test(cls)) style = "outlined";
|
|
1489
|
+
const filled = FILL === 1;
|
|
1490
|
+
let pick = null;
|
|
1491
|
+
if (filled) {
|
|
1492
|
+
if (style === "outlined" && ICON_FONT_URLS.materialIconsFilled) {
|
|
1493
|
+
pick = { url: ICON_FONT_URLS.materialIconsFilled, alias: "snapdom-mi-filled" };
|
|
1494
|
+
} else if (style === "rounded" && ICON_FONT_URLS.materialIconsRound) {
|
|
1495
|
+
pick = { url: ICON_FONT_URLS.materialIconsRound, alias: "snapdom-mi-round" };
|
|
1496
|
+
} else if (style === "sharp" && ICON_FONT_URLS.materialIconsSharp) {
|
|
1497
|
+
pick = { url: ICON_FONT_URLS.materialIconsSharp, alias: "snapdom-mi-sharp" };
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
if (!pick) {
|
|
1501
|
+
return { familyForMeasure: fam, familyForCanvas: fam };
|
|
1502
|
+
}
|
|
1503
|
+
if (!loadedCanvasFamilies.has(pick.alias)) {
|
|
1504
|
+
try {
|
|
1505
|
+
const ff = new FontFace(pick.alias, `url(${pick.url})`, { style: "normal", weight: "400" });
|
|
1506
|
+
document.fonts.add(ff);
|
|
1507
|
+
await ff.load();
|
|
1508
|
+
loadedCanvasFamilies.set(pick.alias, true);
|
|
1509
|
+
} catch {
|
|
1510
|
+
return { familyForMeasure: fam, familyForCanvas: fam };
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
const quoted = `"${pick.alias}"`;
|
|
1514
|
+
return { familyForMeasure: quoted, familyForCanvas: quoted };
|
|
1515
|
+
}
|
|
1450
1516
|
async function ensureMaterialFontsReady(family = "Material Icons", px = 24) {
|
|
1451
1517
|
try {
|
|
1452
1518
|
await Promise.all([
|
|
1453
|
-
document.fonts.load(`400 ${px}px "${family.replace(/["']/g, "")}"`),
|
|
1519
|
+
document.fonts.load(`400 ${px}px "${String(family).replace(/["']/g, "")}"`),
|
|
1454
1520
|
document.fonts.ready
|
|
1455
1521
|
]);
|
|
1456
1522
|
} catch {
|
|
@@ -1458,10 +1524,8 @@ async function ensureMaterialFontsReady(family = "Material Icons", px = 24) {
|
|
|
1458
1524
|
}
|
|
1459
1525
|
function resolvePaintColor(cs) {
|
|
1460
1526
|
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
|
-
}
|
|
1527
|
+
const isTransparent = /^transparent$/i.test(fill) || /rgba?\(\s*0\s*,\s*0\s*,\s*0\s*,\s*0\s*\)/i.test(fill);
|
|
1528
|
+
if (fill && !isTransparent && fill.toLowerCase() !== "currentcolor") return fill;
|
|
1465
1529
|
const c = cs.color?.trim();
|
|
1466
1530
|
return c && c !== "inherit" ? c : "#000";
|
|
1467
1531
|
}
|
|
@@ -1470,18 +1534,21 @@ async function materialIconToImage(ligatureText, {
|
|
|
1470
1534
|
weight = "normal",
|
|
1471
1535
|
fontSize = 32,
|
|
1472
1536
|
color = "#000",
|
|
1473
|
-
variation = ""
|
|
1537
|
+
variation = "",
|
|
1538
|
+
className = ""
|
|
1474
1539
|
} = {}) {
|
|
1475
1540
|
const fam = String(family || "").replace(/^['"]+|['"]+$/g, "");
|
|
1476
1541
|
const dpr = window.devicePixelRatio || 1;
|
|
1477
|
-
|
|
1542
|
+
const axes = parseAxes(variation);
|
|
1543
|
+
const { familyForMeasure, familyForCanvas } = await ensureLigatureCanvasFont(fam, className, axes);
|
|
1544
|
+
await ensureMaterialFontsReady(familyForCanvas.replace(/^["']+|["']+$/g, ""), fontSize);
|
|
1478
1545
|
const span = document.createElement("span");
|
|
1479
1546
|
span.textContent = ligatureText;
|
|
1480
1547
|
span.style.position = "absolute";
|
|
1481
1548
|
span.style.visibility = "hidden";
|
|
1482
1549
|
span.style.left = "-99999px";
|
|
1483
1550
|
span.style.whiteSpace = "nowrap";
|
|
1484
|
-
span.style.fontFamily =
|
|
1551
|
+
span.style.fontFamily = familyForMeasure;
|
|
1485
1552
|
span.style.fontWeight = String(weight || "normal");
|
|
1486
1553
|
span.style.fontSize = `${fontSize}px`;
|
|
1487
1554
|
span.style.lineHeight = "1";
|
|
@@ -1489,7 +1556,6 @@ async function materialIconToImage(ligatureText, {
|
|
|
1489
1556
|
span.style.padding = "0";
|
|
1490
1557
|
span.style.fontFeatureSettings = "'liga' 1";
|
|
1491
1558
|
span.style.fontVariantLigatures = "normal";
|
|
1492
|
-
if (variation) span.style.fontVariationSettings = variation;
|
|
1493
1559
|
span.style.color = color;
|
|
1494
1560
|
document.body.appendChild(span);
|
|
1495
1561
|
const rect = span.getBoundingClientRect();
|
|
@@ -1501,7 +1567,7 @@ async function materialIconToImage(ligatureText, {
|
|
|
1501
1567
|
canvas.height = height * dpr;
|
|
1502
1568
|
const ctx = canvas.getContext("2d");
|
|
1503
1569
|
ctx.scale(dpr, dpr);
|
|
1504
|
-
ctx.font = `${weight ? `${weight} ` : ""}${fontSize}px
|
|
1570
|
+
ctx.font = `${weight ? `${weight} ` : ""}${fontSize}px ${familyForCanvas}`;
|
|
1505
1571
|
ctx.textAlign = "left";
|
|
1506
1572
|
ctx.textBaseline = "top";
|
|
1507
1573
|
ctx.fillStyle = color;
|
|
@@ -1529,21 +1595,23 @@ async function ligatureIconToImage(cloneRoot, sourceRoot) {
|
|
|
1529
1595
|
const el = cloneNodes[i];
|
|
1530
1596
|
const src = sourceNodes[i] || null;
|
|
1531
1597
|
try {
|
|
1532
|
-
const
|
|
1533
|
-
const family =
|
|
1598
|
+
const cs = src ? getComputedStyle(src) : getComputedStyle(el);
|
|
1599
|
+
const family = cs.fontFamily || "Material Icons";
|
|
1534
1600
|
if (!isMaterialFamily(family)) continue;
|
|
1535
1601
|
const text = (src || el).textContent.trim();
|
|
1536
1602
|
if (!text) continue;
|
|
1537
|
-
const size = parseInt(
|
|
1538
|
-
const weight =
|
|
1539
|
-
const color = resolvePaintColor(
|
|
1540
|
-
const variation =
|
|
1603
|
+
const size = parseInt(cs.fontSize, 10) || 24;
|
|
1604
|
+
const weight = cs.fontWeight && cs.fontWeight !== "normal" ? cs.fontWeight : "normal";
|
|
1605
|
+
const color = resolvePaintColor(cs);
|
|
1606
|
+
const variation = cs.fontVariationSettings && cs.fontVariationSettings !== "normal" ? cs.fontVariationSettings : "";
|
|
1607
|
+
const className = (src || el).className || "";
|
|
1541
1608
|
const { dataUrl, width, height } = await materialIconToImage(text, {
|
|
1542
1609
|
family,
|
|
1543
1610
|
weight,
|
|
1544
1611
|
fontSize: size,
|
|
1545
1612
|
color,
|
|
1546
|
-
variation
|
|
1613
|
+
variation,
|
|
1614
|
+
className
|
|
1547
1615
|
});
|
|
1548
1616
|
el.textContent = "";
|
|
1549
1617
|
const img = el.ownerDocument.createElement("img");
|
|
@@ -1909,7 +1977,7 @@ function dedupeFontFaces(cssText) {
|
|
|
1909
1977
|
const styleSpec = (block.match(/font-style:\s*([^;]+);/i)?.[1] || "normal").trim();
|
|
1910
1978
|
const stretchSpec = (block.match(/font-stretch:\s*([^;]+);/i)?.[1] || "100%").trim();
|
|
1911
1979
|
const urange = (block.match(/unicode-range:\s*([^;]+);/i)?.[1] || "").trim();
|
|
1912
|
-
const srcRaw = (block.match(/src\s*:\s*([^;]+)
|
|
1980
|
+
const srcRaw = (block.match(/src\s*:\s*([^;}]+)[;}]/i)?.[1] || "").trim();
|
|
1913
1981
|
const urls = extractSrcUrls(srcRaw, location.href);
|
|
1914
1982
|
const srcPart = urls.length ? urls.map((u) => String(u).toLowerCase()).sort().join("|") : srcRaw.toLowerCase();
|
|
1915
1983
|
const key = [
|
|
@@ -2117,7 +2185,7 @@ async function embedCustomFonts({
|
|
|
2117
2185
|
const styleSpec = (face.match(/font-style:\s*([^;]+);/i)?.[1] || "normal").trim();
|
|
2118
2186
|
const stretchSpec = (face.match(/font-stretch:\s*([^;]+);/i)?.[1] || "100%").trim();
|
|
2119
2187
|
const urange = (face.match(/unicode-range:\s*([^;]+);/i)?.[1] || "").trim();
|
|
2120
|
-
const srcRaw = (face.match(/src\s*:\s*([^;]+)
|
|
2188
|
+
const srcRaw = (face.match(/src\s*:\s*([^;}]+)[;}]/i)?.[1] || "").trim();
|
|
2121
2189
|
const srcUrls = extractSrcUrls(srcRaw, link.href);
|
|
2122
2190
|
if (!faceMatchesRequired(family, styleSpec, weightSpec, stretchSpec)) continue;
|
|
2123
2191
|
const ranges = parseUnicodeRange(urange);
|
|
@@ -3559,6 +3627,50 @@ function stripRootShadows(originalEl, cloneRoot) {
|
|
|
3559
3627
|
} catch {
|
|
3560
3628
|
}
|
|
3561
3629
|
}
|
|
3630
|
+
function removeAllComments(root) {
|
|
3631
|
+
const it = document.createTreeWalker(root, NodeFilter.SHOW_COMMENT);
|
|
3632
|
+
const toRemove = [];
|
|
3633
|
+
while (it.nextNode()) toRemove.push(it.currentNode);
|
|
3634
|
+
for (const n of toRemove) n.remove();
|
|
3635
|
+
}
|
|
3636
|
+
function sanitizeAttributesForXHTML(root, opts = {}) {
|
|
3637
|
+
const { stripFrameworkDirectives = true } = opts;
|
|
3638
|
+
const ALLOWED_PREFIXES = /* @__PURE__ */ new Set(["xml", "xlink"]);
|
|
3639
|
+
const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
|
|
3640
|
+
while (walker.nextNode()) {
|
|
3641
|
+
const el = walker.currentNode;
|
|
3642
|
+
for (const attr of Array.from(el.attributes)) {
|
|
3643
|
+
const name = attr.name;
|
|
3644
|
+
if (name.includes("@")) {
|
|
3645
|
+
el.removeAttribute(name);
|
|
3646
|
+
continue;
|
|
3647
|
+
}
|
|
3648
|
+
if (name.includes(":")) {
|
|
3649
|
+
const prefix = name.split(":", 1)[0];
|
|
3650
|
+
if (!ALLOWED_PREFIXES.has(prefix)) {
|
|
3651
|
+
el.removeAttribute(name);
|
|
3652
|
+
continue;
|
|
3653
|
+
}
|
|
3654
|
+
}
|
|
3655
|
+
if (!stripFrameworkDirectives) continue;
|
|
3656
|
+
if (name.startsWith("x-") || // Alpine
|
|
3657
|
+
name.startsWith("v-") || // Vue
|
|
3658
|
+
name.startsWith(":") || // Vue/Alpine shorthand
|
|
3659
|
+
name.startsWith("on:") || // Svelte
|
|
3660
|
+
name.startsWith("bind:") || // Svelte
|
|
3661
|
+
name.startsWith("let:") || // Svelte
|
|
3662
|
+
name.startsWith("class:")) {
|
|
3663
|
+
el.removeAttribute(name);
|
|
3664
|
+
continue;
|
|
3665
|
+
}
|
|
3666
|
+
}
|
|
3667
|
+
}
|
|
3668
|
+
}
|
|
3669
|
+
function sanitizeCloneForXHTML(root, opts = {}) {
|
|
3670
|
+
if (!root) return;
|
|
3671
|
+
sanitizeAttributesForXHTML(root, opts);
|
|
3672
|
+
removeAllComments(root);
|
|
3673
|
+
}
|
|
3562
3674
|
async function captureDOM(element, options) {
|
|
3563
3675
|
if (!element) throw new Error("Element cannot be null or undefined");
|
|
3564
3676
|
applyCachePolicy(options.cache);
|
|
@@ -3588,6 +3700,14 @@ async function captureDOM(element, options) {
|
|
|
3588
3700
|
}
|
|
3589
3701
|
state = { clone, classCSS, styleCache, ...state };
|
|
3590
3702
|
await runHook("afterClone", state);
|
|
3703
|
+
sanitizeCloneForXHTML(state.clone);
|
|
3704
|
+
if (state.options?.excludeMode === "remove") {
|
|
3705
|
+
try {
|
|
3706
|
+
shrinkAutoSizeBoxes(state.element, state.clone, state.styleCache);
|
|
3707
|
+
} catch (e) {
|
|
3708
|
+
console.warn("[snapdom] shrink pass failed:", e);
|
|
3709
|
+
}
|
|
3710
|
+
}
|
|
3591
3711
|
try {
|
|
3592
3712
|
await ligatureIconToImage(state.clone, state.element);
|
|
3593
3713
|
} catch {
|
|
@@ -3645,8 +3765,15 @@ async function captureDOM(element, options) {
|
|
|
3645
3765
|
idle(() => {
|
|
3646
3766
|
const csEl = getComputedStyle(state.element);
|
|
3647
3767
|
const rect = state.element.getBoundingClientRect();
|
|
3648
|
-
|
|
3649
|
-
|
|
3768
|
+
let w0 = Math.max(1, limitDecimals(state.element.offsetWidth || parseFloat(csEl.width) || rect.width || 1));
|
|
3769
|
+
let h0 = Math.max(1, limitDecimals(state.element.offsetHeight || parseFloat(csEl.height) || rect.height || 1));
|
|
3770
|
+
if (state.options?.excludeMode === "remove") {
|
|
3771
|
+
const hEst = estimateKeptHeight(state.element, state.options);
|
|
3772
|
+
const EPS = 1;
|
|
3773
|
+
if (Number.isFinite(hEst) && hEst > 0) {
|
|
3774
|
+
h0 = Math.max(1, Math.min(h0, limitDecimals(hEst + EPS)));
|
|
3775
|
+
}
|
|
3776
|
+
}
|
|
3650
3777
|
const coerceNum = (v, def = NaN) => {
|
|
3651
3778
|
const n = typeof v === "string" ? parseFloat(v) : v;
|
|
3652
3779
|
return Number.isFinite(n) ? n : def;
|
|
@@ -3769,6 +3896,57 @@ async function captureDOM(element, options) {
|
|
|
3769
3896
|
if (sandbox && sandbox.style.position === "absolute") sandbox.remove();
|
|
3770
3897
|
return state.dataURL;
|
|
3771
3898
|
}
|
|
3899
|
+
function authorHasExplicitSize(el) {
|
|
3900
|
+
try {
|
|
3901
|
+
const s = el.getAttribute?.("style") || "";
|
|
3902
|
+
return /\b(height|width|block-size|inline-size)\s*:/.test(s);
|
|
3903
|
+
} catch {
|
|
3904
|
+
return false;
|
|
3905
|
+
}
|
|
3906
|
+
}
|
|
3907
|
+
function isReplacedElement(el) {
|
|
3908
|
+
return el instanceof HTMLImageElement || el instanceof HTMLCanvasElement || el instanceof HTMLVideoElement || el instanceof HTMLIFrameElement || el instanceof SVGElement || el instanceof HTMLObjectElement || el instanceof HTMLEmbedElement;
|
|
3909
|
+
}
|
|
3910
|
+
function shouldShrinkBox(srcEl, cs) {
|
|
3911
|
+
if (!(srcEl instanceof Element)) return false;
|
|
3912
|
+
if (authorHasExplicitSize(srcEl)) return false;
|
|
3913
|
+
if (isReplacedElement(srcEl)) return false;
|
|
3914
|
+
const pos = cs.position;
|
|
3915
|
+
if (pos === "absolute" || pos === "fixed" || pos === "sticky") return false;
|
|
3916
|
+
const disp = cs.display || "";
|
|
3917
|
+
if (disp.includes("flex") || disp.includes("grid") || disp.startsWith("table")) return false;
|
|
3918
|
+
if (cs.transform && cs.transform !== "none") return false;
|
|
3919
|
+
return true;
|
|
3920
|
+
}
|
|
3921
|
+
function shrinkAutoSizeBoxes(sourceRoot, cloneRoot, styleCache = /* @__PURE__ */ new Map()) {
|
|
3922
|
+
function walk(src, cln) {
|
|
3923
|
+
if (!(src instanceof Element) || !(cln instanceof Element)) return;
|
|
3924
|
+
const lostKids = src.childElementCount > cln.childElementCount;
|
|
3925
|
+
const cs = styleCache.get(src) || getComputedStyle(src);
|
|
3926
|
+
if (!styleCache.has(src)) styleCache.set(src, cs);
|
|
3927
|
+
if (lostKids && shouldShrinkBox(src, cs)) {
|
|
3928
|
+
if (!cln.style.height) cln.style.height = "auto";
|
|
3929
|
+
if (!cln.style.width) cln.style.width = "auto";
|
|
3930
|
+
cln.style.removeProperty("block-size");
|
|
3931
|
+
cln.style.removeProperty("inline-size");
|
|
3932
|
+
if (!cln.style.minHeight) cln.style.minHeight = "0";
|
|
3933
|
+
if (!cln.style.minWidth) cln.style.minWidth = "0";
|
|
3934
|
+
if (!cln.style.maxHeight) cln.style.maxHeight = "none";
|
|
3935
|
+
if (!cln.style.maxWidth) cln.style.maxWidth = "none";
|
|
3936
|
+
const oy = cs.overflowY || cs.overflowBlock || "visible";
|
|
3937
|
+
const ox = cs.overflowX || cs.overflowInline || "visible";
|
|
3938
|
+
if (oy !== "visible" || ox !== "visible") {
|
|
3939
|
+
cln.style.overflow = "visible";
|
|
3940
|
+
}
|
|
3941
|
+
}
|
|
3942
|
+
const sKids = Array.from(src.children);
|
|
3943
|
+
const cKids = Array.from(cln.children);
|
|
3944
|
+
for (let i = 0; i < Math.min(sKids.length, cKids.length); i++) {
|
|
3945
|
+
walk(sKids[i], cKids[i]);
|
|
3946
|
+
}
|
|
3947
|
+
}
|
|
3948
|
+
walk(sourceRoot, cloneRoot);
|
|
3949
|
+
}
|
|
3772
3950
|
var limitDecimals = (v, n = 3) => Number.isFinite(v) ? Math.round(v * 10 ** n) / 10 ** n : v;
|
|
3773
3951
|
function parseFilterDropShadows(cs) {
|
|
3774
3952
|
const raw = `${cs.filter || ""} ${cs.webkitFilter || ""}`.trim();
|
|
@@ -4035,6 +4213,52 @@ function readTotalTransformMatrix(t) {
|
|
|
4035
4213
|
host.removeChild(tmp);
|
|
4036
4214
|
return M;
|
|
4037
4215
|
}
|
|
4216
|
+
function isInNormalFlow(el) {
|
|
4217
|
+
const cs = getComputedStyle(el);
|
|
4218
|
+
if (cs.display === "none") return false;
|
|
4219
|
+
if (cs.position === "absolute" || cs.position === "fixed" || cs.position === "sticky") return false;
|
|
4220
|
+
if ((cs.cssFloat || cs.float || "none") !== "none") return false;
|
|
4221
|
+
if (cs.transform && cs.transform !== "none") return false;
|
|
4222
|
+
return true;
|
|
4223
|
+
}
|
|
4224
|
+
function willBeExcluded(el, options) {
|
|
4225
|
+
if (!(el instanceof Element)) return false;
|
|
4226
|
+
if (el.getAttribute("data-capture") === "exclude" && options?.excludeMode === "remove") return true;
|
|
4227
|
+
if (Array.isArray(options?.exclude)) {
|
|
4228
|
+
for (const sel of options.exclude) {
|
|
4229
|
+
try {
|
|
4230
|
+
if (el.matches(sel)) return options.excludeMode === "remove";
|
|
4231
|
+
} catch {
|
|
4232
|
+
}
|
|
4233
|
+
}
|
|
4234
|
+
}
|
|
4235
|
+
return false;
|
|
4236
|
+
}
|
|
4237
|
+
function estimateKeptHeight(container, options) {
|
|
4238
|
+
const csC = getComputedStyle(container);
|
|
4239
|
+
const rC = container.getBoundingClientRect();
|
|
4240
|
+
let minTop = Infinity;
|
|
4241
|
+
let maxBottom = -Infinity;
|
|
4242
|
+
let found = false;
|
|
4243
|
+
const kids = Array.from(container.children);
|
|
4244
|
+
for (const k of kids) {
|
|
4245
|
+
if (willBeExcluded(k, options)) continue;
|
|
4246
|
+
if (!isInNormalFlow(k)) continue;
|
|
4247
|
+
const rk = k.getBoundingClientRect();
|
|
4248
|
+
const top = rk.top - rC.top;
|
|
4249
|
+
const bottom = rk.bottom - rC.top;
|
|
4250
|
+
if (bottom <= top) continue;
|
|
4251
|
+
if (top < minTop) minTop = top;
|
|
4252
|
+
if (bottom > maxBottom) maxBottom = bottom;
|
|
4253
|
+
found = true;
|
|
4254
|
+
}
|
|
4255
|
+
const contentSpan = found ? Math.max(0, maxBottom - minTop) : 0;
|
|
4256
|
+
const bt = parseFloat(csC.borderTopWidth) || 0;
|
|
4257
|
+
const bb = parseFloat(csC.borderBottomWidth) || 0;
|
|
4258
|
+
const pt = parseFloat(csC.paddingTop) || 0;
|
|
4259
|
+
const pb = parseFloat(csC.paddingBottom) || 0;
|
|
4260
|
+
return bt + bb + pt + pb + contentSpan;
|
|
4261
|
+
}
|
|
4038
4262
|
|
|
4039
4263
|
// src/core/context.js
|
|
4040
4264
|
function normalizeCachePolicy(v) {
|
|
@@ -4191,7 +4415,7 @@ function maybeConvertBoxShadowForSafari(url) {
|
|
|
4191
4415
|
}
|
|
4192
4416
|
}
|
|
4193
4417
|
async function toCanvas(url, options) {
|
|
4194
|
-
let { width: optW, height: optH, scale = 1, dpr = 1, meta = {} } = options;
|
|
4418
|
+
let { width: optW, height: optH, scale = 1, dpr = 1, meta = {}, backgroundColor } = options;
|
|
4195
4419
|
url = maybeConvertBoxShadowForSafari(url);
|
|
4196
4420
|
const img = new Image();
|
|
4197
4421
|
img.loading = "eager";
|
|
@@ -4230,6 +4454,12 @@ async function toCanvas(url, options) {
|
|
|
4230
4454
|
canvas.style.height = `${outH}px`;
|
|
4231
4455
|
const ctx = canvas.getContext("2d");
|
|
4232
4456
|
if (dpr !== 1) ctx.scale(dpr, dpr);
|
|
4457
|
+
if (backgroundColor) {
|
|
4458
|
+
ctx.save();
|
|
4459
|
+
ctx.fillStyle = backgroundColor;
|
|
4460
|
+
ctx.fillRect(0, 0, outW, outH);
|
|
4461
|
+
ctx.restore();
|
|
4462
|
+
}
|
|
4233
4463
|
ctx.drawImage(img, 0, 0, outW, outH);
|
|
4234
4464
|
return canvas;
|
|
4235
4465
|
}
|
|
@@ -4237,12 +4467,11 @@ async function toCanvas(url, options) {
|
|
|
4237
4467
|
// src/modules/rasterize.js
|
|
4238
4468
|
async function rasterize(url, options) {
|
|
4239
4469
|
const canvas = await toCanvas(url, options);
|
|
4240
|
-
const finalCanvas = options.backgroundColor ? createBackground(canvas, options.backgroundColor) : canvas;
|
|
4241
4470
|
const img = new Image();
|
|
4242
|
-
img.src =
|
|
4471
|
+
img.src = canvas.toDataURL(`image/${options.format}`, options.quality);
|
|
4243
4472
|
await img.decode();
|
|
4244
|
-
img.style.width = `${
|
|
4245
|
-
img.style.height = `${
|
|
4473
|
+
img.style.width = `${canvas.width / options.dpr}px`;
|
|
4474
|
+
img.style.height = `${canvas.height / options.dpr}px`;
|
|
4246
4475
|
return img;
|
|
4247
4476
|
}
|
|
4248
4477
|
|
|
@@ -4302,13 +4531,8 @@ async function toBlob(url, options) {
|
|
|
4302
4531
|
return new Blob([svgText], { type: "image/svg+xml" });
|
|
4303
4532
|
}
|
|
4304
4533
|
const canvas = await toCanvas(url, options);
|
|
4305
|
-
const finalCanvas = options.backgroundColor ? createBackground(canvas, options.backgroundColor) : canvas;
|
|
4306
4534
|
return new Promise(
|
|
4307
|
-
(resolve) =>
|
|
4308
|
-
(blob) => resolve(blob),
|
|
4309
|
-
`image/${type}`,
|
|
4310
|
-
options.quality
|
|
4311
|
-
)
|
|
4535
|
+
(resolve) => canvas.toBlob((blob) => resolve(blob), `image/${type}`, options.quality)
|
|
4312
4536
|
);
|
|
4313
4537
|
}
|
|
4314
4538
|
|
|
@@ -4326,9 +4550,8 @@ async function download(url, options) {
|
|
|
4326
4550
|
return;
|
|
4327
4551
|
}
|
|
4328
4552
|
const canvas = await toCanvas(url, options);
|
|
4329
|
-
const finalCanvas = options.backgroundColor ? createBackground(canvas, options.backgroundColor) : canvas;
|
|
4330
4553
|
const a = document.createElement("a");
|
|
4331
|
-
a.href =
|
|
4554
|
+
a.href = canvas.toDataURL(`image/${options.format}`, options.quality);
|
|
4332
4555
|
a.download = options.filename;
|
|
4333
4556
|
a.click();
|
|
4334
4557
|
}
|
package/package.json
CHANGED