@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/dist/snapdom.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
 
2
2
  /*
3
3
  * snapdom
4
- * v.2.0.0-dev.0
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 straighten = !!options.straighten;
3450
- const noShadows = !!options.noShadows;
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 (straighten && clone) {
3464
- rootTransform2D = normalizeRootTransforms(state.element, state.clone);
3580
+ if (!outerTransforms && clone) {
3581
+ rootTransform2D = normalizeRootTransforms(state.element, clone);
3465
3582
  }
3466
- if (noShadows && clone) {
3467
- stripRootShadows(state.element, state.clone);
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 (straighten && rootTransform2D && Number.isFinite(rootTransform2D.a)) {
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 = !straighten && hasTFBBox(state.element);
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 = noShadows ? { top: 0, right: 0, bottom: 0, left: 0 } : {
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 = straighten ? 1 : 0;
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
- straighten: options.straighten ?? false,
3967
- noShadows: options.noShadows ?? false
4087
+ outerTransforms: options.outerTransforms ?? true,
4088
+ outerShadows: options.outerShadows ?? false
3968
4089
  // Plugins (reservado)
3969
4090
  // plugins: normalizePlugins(...),
3970
4091
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zumer/snapdom",
3
- "version": "2.0.0-dev.0",
3
+ "version": "2.0.0-dev.1",
4
4
  "description": "snapDOM captures HTML elements to images with exceptional speed and accuracy.",
5
5
  "main": "dist/snapdom.js",
6
6
  "module": "dist/snapdom.mjs",
@@ -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
- /** Straighten the root: remove translate/rotate, keep scale/skew. */
82
- straighten?: boolean;
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
- noShadows?: boolean;
87
+ outerShadows?: boolean;
88
88
 
89
89
  /** Inline non-icon fonts actually used within the subtree. */
90
90
  embedFonts?: boolean;