@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/README.md
CHANGED
|
@@ -72,8 +72,8 @@ It captures any HTML element as a scalable SVG image, preserving styles, fonts,
|
|
|
72
72
|
- [iconFonts](#iconfonts)
|
|
73
73
|
- [excludeFonts](#excludefonts)
|
|
74
74
|
- [Filtering nodes: `exclude` vs `filter`](#filtering-nodes-exclude-vs-filter)
|
|
75
|
-
- [
|
|
76
|
-
- [
|
|
75
|
+
- [outerTransforms](#outerTransforms)
|
|
76
|
+
- [outerShadows](#no-shadows)
|
|
77
77
|
- [Cache control](#cache-control)
|
|
78
78
|
- [preCache](#precache--optional-helper)
|
|
79
79
|
- [Plugins (BETA)](#plugins-beta)
|
|
@@ -228,8 +228,8 @@ All capture methods accept an `options` object:
|
|
|
228
228
|
| `cache` | string | `"soft"` | `disabled` \| `soft` \| `auto` \| `full` |
|
|
229
229
|
| `placeholders` | boolean | `true` | Show placeholders for images/CORS iframes |
|
|
230
230
|
| `fallbackURL` | string \| function | - | Fallback image for `<img>` load failure |
|
|
231
|
-
| `
|
|
232
|
-
| `
|
|
231
|
+
| `outerTransforms` | boolean | `true` | When `false` removes `translate/rotate` but preserves `scale/skew`, producing a flat, reusable capture |
|
|
232
|
+
| `outerShadows` | boolean | `false` | Do not expand the root’s bounding box for shadows/blur/outline, and strip those visual effects from the cloned root |
|
|
233
233
|
|
|
234
234
|
### Fallback image on `<img>` load failure
|
|
235
235
|
|
|
@@ -354,26 +354,25 @@ await snapdom.toPng(el, {
|
|
|
354
354
|
});
|
|
355
355
|
```
|
|
356
356
|
|
|
357
|
-
###
|
|
357
|
+
### outerTransforms
|
|
358
358
|
|
|
359
|
-
When capturing rotated or translated elements, you may want
|
|
359
|
+
When capturing rotated or translated elements, you may want use **outerTransforms: false** option if you want to eliminate those external transforms. So, the output is **flat, upright, and ready** to use elsewhere.
|
|
360
360
|
|
|
361
|
-
- **`
|
|
362
|
-
|
|
363
|
-
|
|
361
|
+
- **`outerTransforms: true (default)`**
|
|
362
|
+
**Keeps the original `transforms` and `rotate`**.
|
|
363
|
+
|
|
364
364
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
- **`noShadows: true`**
|
|
365
|
+
### outerShadows
|
|
366
|
+
- **`outerShadows: false (default)`**
|
|
368
367
|
Prevents expanding the bounding box for shadows, blur, or outline on the root, and also strips `box-shadow`, `text-shadow`, `filter: blur()/drop-shadow()`, and `outline` from the cloned root.
|
|
369
368
|
|
|
370
|
-
> 💡 **Tip:** Using both (`
|
|
369
|
+
> 💡 **Tip:** Using both (`outerTransforms: false` + `outerShadows: false`) produces a strict, minimal bounding box with no visual bleed.
|
|
371
370
|
|
|
372
371
|
**Example**
|
|
373
372
|
|
|
374
373
|
```js
|
|
375
|
-
//
|
|
376
|
-
await snapdom.toSvg(el, {
|
|
374
|
+
// outerTransforms and remove shadow bleed
|
|
375
|
+
await snapdom.toSvg(el, { outerTransforms: true, outerShadows: true });
|
|
377
376
|
```
|
|
378
377
|
|
|
379
378
|
## Cache control
|
|
@@ -474,7 +473,7 @@ const out = await snapdom(element, {
|
|
|
474
473
|
Every hook receives a single `context` object that contains normalized capture state:
|
|
475
474
|
|
|
476
475
|
* **Input & options:**
|
|
477
|
-
`element`, `debug`, `fast`, `scale`, `dpr`, `width`, `height`, `backgroundColor`, `quality`, `useProxy`, `cache`, `
|
|
476
|
+
`element`, `debug`, `fast`, `scale`, `dpr`, `width`, `height`, `backgroundColor`, `quality`, `useProxy`, `cache`, `outerTransforms`, `outerShadows`, `embedFonts`, `localFonts`, `iconFonts`, `excludeFonts`, `exclude`, `excludeMode`, `filter`, `filterMode`, `fallbackURL`.
|
|
478
477
|
|
|
479
478
|
* **Intermediate values (depending on stage):**
|
|
480
479
|
`clone`, `classCSS`, `styleCache`, `fontsCSS`, `baseCSS`, `svgString`, `dataURL`.
|
package/dist/snapdom.js
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
|
**/
|
|
@@ -1504,6 +1504,130 @@
|
|
|
1504
1504
|
if (/icon/i.test(text) || /glyph/i.test(text) || /symbols/i.test(text) || /feather/i.test(text) || /fontawesome/i.test(text)) return true;
|
|
1505
1505
|
return false;
|
|
1506
1506
|
}
|
|
1507
|
+
function isMaterialFamily(family = "") {
|
|
1508
|
+
const s = String(family).toLowerCase();
|
|
1509
|
+
return /\bmaterial\s*icons\b/.test(s) || /\bmaterial\s*symbols\b/.test(s);
|
|
1510
|
+
}
|
|
1511
|
+
function ensureMaterialFontsReady(family = "Material Icons", px = 24) {
|
|
1512
|
+
return __async(this, null, function* () {
|
|
1513
|
+
try {
|
|
1514
|
+
yield Promise.all([
|
|
1515
|
+
document.fonts.load(`400 ${px}px "${family.replace(/["']/g, "")}"`),
|
|
1516
|
+
document.fonts.ready
|
|
1517
|
+
]);
|
|
1518
|
+
} catch (e) {
|
|
1519
|
+
}
|
|
1520
|
+
});
|
|
1521
|
+
}
|
|
1522
|
+
function resolvePaintColor(cs) {
|
|
1523
|
+
var _a, _b;
|
|
1524
|
+
let fill = ((_a = cs.getPropertyValue("-webkit-text-fill-color")) == null ? void 0 : _a.trim()) || "";
|
|
1525
|
+
const isTransparent = /rgba?\(\s*0\s*,\s*0\s*,\s*0\s*,\s*0\s*\)/i.test(fill);
|
|
1526
|
+
if (fill && !isTransparent && fill.toLowerCase() !== "currentcolor") {
|
|
1527
|
+
return fill;
|
|
1528
|
+
}
|
|
1529
|
+
const c = (_b = cs.color) == null ? void 0 : _b.trim();
|
|
1530
|
+
return c && c !== "inherit" ? c : "#000";
|
|
1531
|
+
}
|
|
1532
|
+
function materialIconToImage(_0) {
|
|
1533
|
+
return __async(this, arguments, function* (ligatureText, {
|
|
1534
|
+
family = "Material Icons",
|
|
1535
|
+
weight = "normal",
|
|
1536
|
+
fontSize = 32,
|
|
1537
|
+
color = "#000",
|
|
1538
|
+
variation = ""
|
|
1539
|
+
} = {}) {
|
|
1540
|
+
const fam = String(family || "").replace(/^['"]+|['"]+$/g, "");
|
|
1541
|
+
const dpr = window.devicePixelRatio || 1;
|
|
1542
|
+
yield ensureMaterialFontsReady(fam, fontSize);
|
|
1543
|
+
const span = document.createElement("span");
|
|
1544
|
+
span.textContent = ligatureText;
|
|
1545
|
+
span.style.position = "absolute";
|
|
1546
|
+
span.style.visibility = "hidden";
|
|
1547
|
+
span.style.left = "-99999px";
|
|
1548
|
+
span.style.whiteSpace = "nowrap";
|
|
1549
|
+
span.style.fontFamily = `"${fam}"`;
|
|
1550
|
+
span.style.fontWeight = String(weight || "normal");
|
|
1551
|
+
span.style.fontSize = `${fontSize}px`;
|
|
1552
|
+
span.style.lineHeight = "1";
|
|
1553
|
+
span.style.margin = "0";
|
|
1554
|
+
span.style.padding = "0";
|
|
1555
|
+
span.style.fontFeatureSettings = "'liga' 1";
|
|
1556
|
+
span.style.fontVariantLigatures = "normal";
|
|
1557
|
+
if (variation) span.style.fontVariationSettings = variation;
|
|
1558
|
+
span.style.color = color;
|
|
1559
|
+
document.body.appendChild(span);
|
|
1560
|
+
const rect = span.getBoundingClientRect();
|
|
1561
|
+
const width = Math.max(1, Math.ceil(rect.width));
|
|
1562
|
+
const height = Math.max(1, Math.ceil(rect.height));
|
|
1563
|
+
document.body.removeChild(span);
|
|
1564
|
+
const canvas = document.createElement("canvas");
|
|
1565
|
+
canvas.width = width * dpr;
|
|
1566
|
+
canvas.height = height * dpr;
|
|
1567
|
+
const ctx = canvas.getContext("2d");
|
|
1568
|
+
ctx.scale(dpr, dpr);
|
|
1569
|
+
ctx.font = `${weight ? `${weight} ` : ""}${fontSize}px "${fam}"`;
|
|
1570
|
+
ctx.textAlign = "left";
|
|
1571
|
+
ctx.textBaseline = "top";
|
|
1572
|
+
ctx.fillStyle = color;
|
|
1573
|
+
try {
|
|
1574
|
+
ctx.fontKerning = "normal";
|
|
1575
|
+
} catch (e) {
|
|
1576
|
+
}
|
|
1577
|
+
ctx.fillText(ligatureText, 0, 0);
|
|
1578
|
+
return {
|
|
1579
|
+
dataUrl: canvas.toDataURL(),
|
|
1580
|
+
width,
|
|
1581
|
+
height
|
|
1582
|
+
};
|
|
1583
|
+
});
|
|
1584
|
+
}
|
|
1585
|
+
function ligatureIconToImage(cloneRoot, sourceRoot) {
|
|
1586
|
+
return __async(this, null, function* () {
|
|
1587
|
+
if (!(cloneRoot instanceof Element)) return 0;
|
|
1588
|
+
const selector = '.material-icons, [class*="material-symbols"]';
|
|
1589
|
+
const cloneNodes = Array.from(
|
|
1590
|
+
cloneRoot.querySelectorAll(selector)
|
|
1591
|
+
).filter((n) => n && n.textContent && n.textContent.trim());
|
|
1592
|
+
if (cloneNodes.length === 0) return 0;
|
|
1593
|
+
const sourceNodes = sourceRoot instanceof Element ? Array.from(sourceRoot.querySelectorAll(selector)).filter((n) => n && n.textContent && n.textContent.trim()) : [];
|
|
1594
|
+
let replaced = 0;
|
|
1595
|
+
for (let i = 0; i < cloneNodes.length; i++) {
|
|
1596
|
+
const el = cloneNodes[i];
|
|
1597
|
+
const src = sourceNodes[i] || null;
|
|
1598
|
+
try {
|
|
1599
|
+
const csSrc = src ? getComputedStyle(src) : getComputedStyle(el);
|
|
1600
|
+
const family = csSrc.fontFamily || "Material Icons";
|
|
1601
|
+
if (!isMaterialFamily(family)) continue;
|
|
1602
|
+
const text = (src || el).textContent.trim();
|
|
1603
|
+
if (!text) continue;
|
|
1604
|
+
const size = parseInt(csSrc.fontSize, 10) || 24;
|
|
1605
|
+
const weight = csSrc.fontWeight && csSrc.fontWeight !== "normal" ? csSrc.fontWeight : "normal";
|
|
1606
|
+
const color = resolvePaintColor(csSrc);
|
|
1607
|
+
const variation = csSrc.fontVariationSettings && csSrc.fontVariationSettings !== "normal" ? csSrc.fontVariationSettings : "";
|
|
1608
|
+
const { dataUrl, width, height } = yield materialIconToImage(text, {
|
|
1609
|
+
family,
|
|
1610
|
+
weight,
|
|
1611
|
+
fontSize: size,
|
|
1612
|
+
color,
|
|
1613
|
+
variation
|
|
1614
|
+
});
|
|
1615
|
+
el.textContent = "";
|
|
1616
|
+
const img = el.ownerDocument.createElement("img");
|
|
1617
|
+
img.src = dataUrl;
|
|
1618
|
+
img.alt = text;
|
|
1619
|
+
img.style.height = `${size}px`;
|
|
1620
|
+
img.style.width = `${Math.max(1, Math.round(width / height * size))}px`;
|
|
1621
|
+
img.style.objectFit = "contain";
|
|
1622
|
+
img.style.verticalAlign = getComputedStyle(el).verticalAlign || "baseline";
|
|
1623
|
+
el.appendChild(img);
|
|
1624
|
+
replaced++;
|
|
1625
|
+
} catch (e) {
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
return replaced;
|
|
1629
|
+
});
|
|
1630
|
+
}
|
|
1507
1631
|
|
|
1508
1632
|
// src/modules/fonts.js
|
|
1509
1633
|
function iconToImage(unicodeChar, fontFamily, fontWeight, fontSize = 32, color = "#000") {
|
|
@@ -3563,8 +3687,8 @@ ${imported}
|
|
|
3563
3687
|
if (!element) throw new Error("Element cannot be null or undefined");
|
|
3564
3688
|
applyCachePolicy(options.cache);
|
|
3565
3689
|
const fast = options.fast;
|
|
3566
|
-
const
|
|
3567
|
-
const
|
|
3690
|
+
const outerTransforms = options.outerTransforms !== false;
|
|
3691
|
+
const outerShadows = !!options.outerShadows;
|
|
3568
3692
|
let state = { element, options, plugins: options.plugins };
|
|
3569
3693
|
let clone, classCSS, styleCache;
|
|
3570
3694
|
let fontsCSS = "";
|
|
@@ -3577,17 +3701,21 @@ ${imported}
|
|
|
3577
3701
|
const undoClamp = lineClamp(state.element);
|
|
3578
3702
|
try {
|
|
3579
3703
|
({ clone, classCSS, styleCache } = yield prepareClone(state.element, state.options));
|
|
3580
|
-
if (
|
|
3581
|
-
rootTransform2D = normalizeRootTransforms(state.element,
|
|
3704
|
+
if (!outerTransforms && clone) {
|
|
3705
|
+
rootTransform2D = normalizeRootTransforms(state.element, clone);
|
|
3582
3706
|
}
|
|
3583
|
-
if (
|
|
3584
|
-
stripRootShadows(state.element,
|
|
3707
|
+
if (!outerShadows && clone) {
|
|
3708
|
+
stripRootShadows(state.element, clone);
|
|
3585
3709
|
}
|
|
3586
3710
|
} finally {
|
|
3587
3711
|
undoClamp();
|
|
3588
3712
|
}
|
|
3589
3713
|
state = __spreadValues({ clone, classCSS, styleCache }, state);
|
|
3590
3714
|
yield runHook("afterClone", state);
|
|
3715
|
+
try {
|
|
3716
|
+
yield ligatureIconToImage(state.clone, state.element);
|
|
3717
|
+
} catch (e) {
|
|
3718
|
+
}
|
|
3591
3719
|
yield new Promise((resolve) => {
|
|
3592
3720
|
idle(() => __async(null, null, function* () {
|
|
3593
3721
|
yield inlineImages(state.clone, state.options);
|
|
@@ -3667,7 +3795,7 @@ ${imported}
|
|
|
3667
3795
|
h = h0;
|
|
3668
3796
|
}
|
|
3669
3797
|
let minX = 0, minY = 0, maxX = w0, maxY = h0;
|
|
3670
|
-
if (
|
|
3798
|
+
if (!outerTransforms && rootTransform2D && Number.isFinite(rootTransform2D.a)) {
|
|
3671
3799
|
const M2 = {
|
|
3672
3800
|
a: rootTransform2D.a,
|
|
3673
3801
|
b: rootTransform2D.b || 0,
|
|
@@ -3682,7 +3810,7 @@ ${imported}
|
|
|
3682
3810
|
maxX = limitDecimals(bb2.maxX);
|
|
3683
3811
|
maxY = limitDecimals(bb2.maxY);
|
|
3684
3812
|
} else {
|
|
3685
|
-
const useTFBBox =
|
|
3813
|
+
const useTFBBox = outerTransforms && hasTFBBox(state.element);
|
|
3686
3814
|
if (useTFBBox) {
|
|
3687
3815
|
const baseTransform2 = csEl.transform && csEl.transform !== "none" ? csEl.transform : "";
|
|
3688
3816
|
const ind2 = readIndividualTransforms(state.element);
|
|
@@ -3705,7 +3833,7 @@ ${imported}
|
|
|
3705
3833
|
const bleedBlur = parseFilterBlur(csEl);
|
|
3706
3834
|
const bleedOutline = parseOutline(csEl);
|
|
3707
3835
|
const drop = parseFilterDropShadows(csEl);
|
|
3708
|
-
const bleed =
|
|
3836
|
+
const bleed = !outerShadows ? { top: 0, right: 0, bottom: 0, left: 0 } : {
|
|
3709
3837
|
top: limitDecimals(bleedShadow.top + bleedBlur.top + bleedOutline.top + drop.bleed.top),
|
|
3710
3838
|
right: limitDecimals(bleedShadow.right + bleedBlur.right + bleedOutline.right + drop.bleed.right),
|
|
3711
3839
|
bottom: limitDecimals(bleedShadow.bottom + bleedBlur.bottom + bleedOutline.bottom + drop.bleed.bottom),
|
|
@@ -3723,7 +3851,7 @@ ${imported}
|
|
|
3723
3851
|
const outH = Math.max(1, limitDecimals(vbH0 * scaleH));
|
|
3724
3852
|
const svgNS = "http://www.w3.org/2000/svg";
|
|
3725
3853
|
const basePad = isSafari() ? 1 : 0;
|
|
3726
|
-
const extraPad =
|
|
3854
|
+
const extraPad = !outerTransforms ? 1 : 0;
|
|
3727
3855
|
const pad = limitDecimals(basePad + extraPad);
|
|
3728
3856
|
const fo = document.createElementNS(svgNS, "foreignObject");
|
|
3729
3857
|
const vbMinX = limitDecimals(minX);
|
|
@@ -4085,8 +4213,8 @@ ${imported}
|
|
|
4085
4213
|
backgroundColor: (_q = options.backgroundColor) != null ? _q : ["jpeg", "webp"].includes(resolvedFormat) ? "#ffffff" : null,
|
|
4086
4214
|
filename: (_r = options.filename) != null ? _r : "snapDOM",
|
|
4087
4215
|
// NEW flags (user-friendly)
|
|
4088
|
-
|
|
4089
|
-
|
|
4216
|
+
outerTransforms: (_s = options.outerTransforms) != null ? _s : true,
|
|
4217
|
+
outerShadows: (_t = options.outerShadows) != null ? _t : false
|
|
4090
4218
|
// Plugins (reservado)
|
|
4091
4219
|
// plugins: normalizePlugins(...),
|
|
4092
4220
|
};
|