@zumer/snapdom 2.0.0-dev.0 → 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 +15 -16
- package/dist/snapdom.js +141 -13
- package/dist/snapdom.min.js +5 -5
- package/dist/snapdom.min.mjs +7 -7
- package/dist/snapdom.mjs +134 -13
- package/package.json +1 -1
- package/types/snapdom.d.ts +3 -3
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.1
|
|
5
5
|
* Author Juan Martin Muda
|
|
6
6
|
* License MIT
|
|
7
7
|
**/
|
|
@@ -1443,6 +1443,123 @@ function isIconFont2(input) {
|
|
|
1443
1443
|
if (/icon/i.test(text) || /glyph/i.test(text) || /symbols/i.test(text) || /feather/i.test(text) || /fontawesome/i.test(text)) return true;
|
|
1444
1444
|
return false;
|
|
1445
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
|
+
}
|
|
1446
1563
|
|
|
1447
1564
|
// src/modules/fonts.js
|
|
1448
1565
|
async function iconToImage(unicodeChar, fontFamily, fontWeight, fontSize = 32, color = "#000") {
|
|
@@ -3446,8 +3563,8 @@ async function captureDOM(element, options) {
|
|
|
3446
3563
|
if (!element) throw new Error("Element cannot be null or undefined");
|
|
3447
3564
|
applyCachePolicy(options.cache);
|
|
3448
3565
|
const fast = options.fast;
|
|
3449
|
-
const
|
|
3450
|
-
const
|
|
3566
|
+
const outerTransforms = options.outerTransforms !== false;
|
|
3567
|
+
const outerShadows = !!options.outerShadows;
|
|
3451
3568
|
let state = { element, options, plugins: options.plugins };
|
|
3452
3569
|
let clone, classCSS, styleCache;
|
|
3453
3570
|
let fontsCSS = "";
|
|
@@ -3460,17 +3577,21 @@ async function captureDOM(element, options) {
|
|
|
3460
3577
|
const undoClamp = lineClamp(state.element);
|
|
3461
3578
|
try {
|
|
3462
3579
|
({ clone, classCSS, styleCache } = await prepareClone(state.element, state.options));
|
|
3463
|
-
if (
|
|
3464
|
-
rootTransform2D = normalizeRootTransforms(state.element,
|
|
3580
|
+
if (!outerTransforms && clone) {
|
|
3581
|
+
rootTransform2D = normalizeRootTransforms(state.element, clone);
|
|
3465
3582
|
}
|
|
3466
|
-
if (
|
|
3467
|
-
stripRootShadows(state.element,
|
|
3583
|
+
if (!outerShadows && clone) {
|
|
3584
|
+
stripRootShadows(state.element, clone);
|
|
3468
3585
|
}
|
|
3469
3586
|
} finally {
|
|
3470
3587
|
undoClamp();
|
|
3471
3588
|
}
|
|
3472
3589
|
state = { clone, classCSS, styleCache, ...state };
|
|
3473
3590
|
await runHook("afterClone", state);
|
|
3591
|
+
try {
|
|
3592
|
+
await ligatureIconToImage(state.clone, state.element);
|
|
3593
|
+
} catch {
|
|
3594
|
+
}
|
|
3474
3595
|
await new Promise((resolve) => {
|
|
3475
3596
|
idle(async () => {
|
|
3476
3597
|
await inlineImages(state.clone, state.options);
|
|
@@ -3550,7 +3671,7 @@ async function captureDOM(element, options) {
|
|
|
3550
3671
|
h = h0;
|
|
3551
3672
|
}
|
|
3552
3673
|
let minX = 0, minY = 0, maxX = w0, maxY = h0;
|
|
3553
|
-
if (
|
|
3674
|
+
if (!outerTransforms && rootTransform2D && Number.isFinite(rootTransform2D.a)) {
|
|
3554
3675
|
const M2 = {
|
|
3555
3676
|
a: rootTransform2D.a,
|
|
3556
3677
|
b: rootTransform2D.b || 0,
|
|
@@ -3565,7 +3686,7 @@ async function captureDOM(element, options) {
|
|
|
3565
3686
|
maxX = limitDecimals(bb2.maxX);
|
|
3566
3687
|
maxY = limitDecimals(bb2.maxY);
|
|
3567
3688
|
} else {
|
|
3568
|
-
const useTFBBox =
|
|
3689
|
+
const useTFBBox = outerTransforms && hasTFBBox(state.element);
|
|
3569
3690
|
if (useTFBBox) {
|
|
3570
3691
|
const baseTransform2 = csEl.transform && csEl.transform !== "none" ? csEl.transform : "";
|
|
3571
3692
|
const ind2 = readIndividualTransforms(state.element);
|
|
@@ -3588,7 +3709,7 @@ async function captureDOM(element, options) {
|
|
|
3588
3709
|
const bleedBlur = parseFilterBlur(csEl);
|
|
3589
3710
|
const bleedOutline = parseOutline(csEl);
|
|
3590
3711
|
const drop = parseFilterDropShadows(csEl);
|
|
3591
|
-
const bleed =
|
|
3712
|
+
const bleed = !outerShadows ? { top: 0, right: 0, bottom: 0, left: 0 } : {
|
|
3592
3713
|
top: limitDecimals(bleedShadow.top + bleedBlur.top + bleedOutline.top + drop.bleed.top),
|
|
3593
3714
|
right: limitDecimals(bleedShadow.right + bleedBlur.right + bleedOutline.right + drop.bleed.right),
|
|
3594
3715
|
bottom: limitDecimals(bleedShadow.bottom + bleedBlur.bottom + bleedOutline.bottom + drop.bleed.bottom),
|
|
@@ -3606,7 +3727,7 @@ async function captureDOM(element, options) {
|
|
|
3606
3727
|
const outH = Math.max(1, limitDecimals(vbH0 * scaleH));
|
|
3607
3728
|
const svgNS = "http://www.w3.org/2000/svg";
|
|
3608
3729
|
const basePad = isSafari() ? 1 : 0;
|
|
3609
|
-
const extraPad =
|
|
3730
|
+
const extraPad = !outerTransforms ? 1 : 0;
|
|
3610
3731
|
const pad = limitDecimals(basePad + extraPad);
|
|
3611
3732
|
const fo = document.createElementNS(svgNS, "foreignObject");
|
|
3612
3733
|
const vbMinX = limitDecimals(minX);
|
|
@@ -3963,8 +4084,8 @@ function createContext(options = {}) {
|
|
|
3963
4084
|
backgroundColor: options.backgroundColor ?? (["jpeg", "webp"].includes(resolvedFormat) ? "#ffffff" : null),
|
|
3964
4085
|
filename: options.filename ?? "snapDOM",
|
|
3965
4086
|
// NEW flags (user-friendly)
|
|
3966
|
-
|
|
3967
|
-
|
|
4087
|
+
outerTransforms: options.outerTransforms ?? true,
|
|
4088
|
+
outerShadows: options.outerShadows ?? false
|
|
3968
4089
|
// Plugins (reservado)
|
|
3969
4090
|
// plugins: normalizePlugins(...),
|
|
3970
4091
|
};
|
package/package.json
CHANGED
package/types/snapdom.d.ts
CHANGED
|
@@ -78,13 +78,13 @@ export interface SnapdomOptions {
|
|
|
78
78
|
/** How to apply `filter` ("hide" or "remove"). Default "hide". */
|
|
79
79
|
filterMode?: "hide" | "remove";
|
|
80
80
|
|
|
81
|
-
/**
|
|
82
|
-
|
|
81
|
+
/** outerTransforms the root: remove translate/rotate, keep scale/skew. */
|
|
82
|
+
outerTransforms?: boolean;
|
|
83
83
|
/**
|
|
84
84
|
* Do not expand root bbox for shadows/blur/outline; also strip shadows/outline
|
|
85
85
|
* from the cloned root to get a tight capture box.
|
|
86
86
|
*/
|
|
87
|
-
|
|
87
|
+
outerShadows?: boolean;
|
|
88
88
|
|
|
89
89
|
/** Inline non-icon fonts actually used within the subtree. */
|
|
90
90
|
embedFonts?: boolean;
|