@zumer/snapdom 1.9.7 → 1.9.8
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 +52 -6
- package/dist/snapdom.js +185 -102
- package/dist/snapdom.min.js +3 -3
- package/dist/snapdom.min.mjs +3 -3
- package/dist/snapdom.mjs +175 -100
- package/package.json +1 -1
- package/types/snapdom.d.ts +1 -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.8
|
|
5
5
|
* Author Juan Martin Muda
|
|
6
6
|
* License MIT
|
|
7
7
|
**/
|
|
@@ -81,7 +81,7 @@ function getDefaultStyleForTag(tagName) {
|
|
|
81
81
|
if (cache.defaultStyle.has(tagName)) {
|
|
82
82
|
return cache.defaultStyle.get(tagName);
|
|
83
83
|
}
|
|
84
|
-
const skipTags = /* @__PURE__ */ new Set(["script", "style", "meta", "link", "noscript", "template"]);
|
|
84
|
+
const skipTags = /* @__PURE__ */ new Set(["script", "style", "meta", "link", "noscript", "template", "defs", "symbol", "title", "metadata", "desc"]);
|
|
85
85
|
if (skipTags.has(tagName)) {
|
|
86
86
|
const empty = {};
|
|
87
87
|
cache.defaultStyle.set(tagName, empty);
|
|
@@ -111,10 +111,14 @@ function getDefaultStyleForTag(tagName) {
|
|
|
111
111
|
cache.defaultStyle.set(tagName, defaults);
|
|
112
112
|
return defaults;
|
|
113
113
|
}
|
|
114
|
+
var IGNORED_PROPS = /* @__PURE__ */ new Set([
|
|
115
|
+
"-webkit-locale"
|
|
116
|
+
]);
|
|
114
117
|
function getStyleKey(snapshot, tagName, compress = false) {
|
|
115
118
|
const entries = [];
|
|
116
119
|
const defaultStyles = getDefaultStyleForTag(tagName);
|
|
117
120
|
for (let [prop, value] of Object.entries(snapshot)) {
|
|
121
|
+
if (IGNORED_PROPS.has(prop)) continue;
|
|
118
122
|
if (!compress) {
|
|
119
123
|
if (value) {
|
|
120
124
|
entries.push(`${prop}:${value}`);
|
|
@@ -164,6 +168,7 @@ function generateCSSClasses() {
|
|
|
164
168
|
const classMap = /* @__PURE__ */ new Map();
|
|
165
169
|
let counter = 1;
|
|
166
170
|
for (const key of keySet) {
|
|
171
|
+
if (!key.trim()) continue;
|
|
167
172
|
classMap.set(key, `c${counter++}`);
|
|
168
173
|
}
|
|
169
174
|
return classMap;
|
|
@@ -395,6 +400,7 @@ function splitBackgroundImage(bg) {
|
|
|
395
400
|
// src/modules/styles.js
|
|
396
401
|
function snapshotComputedStyleFull(style) {
|
|
397
402
|
const result = {};
|
|
403
|
+
const computedVisibility = style.getPropertyValue("visibility");
|
|
398
404
|
for (let i = 0; i < style.length; i++) {
|
|
399
405
|
const prop = style[i];
|
|
400
406
|
let val = style.getPropertyValue(prop);
|
|
@@ -403,6 +409,9 @@ function snapshotComputedStyleFull(style) {
|
|
|
403
409
|
}
|
|
404
410
|
result[prop] = val;
|
|
405
411
|
}
|
|
412
|
+
if (computedVisibility === "hidden") {
|
|
413
|
+
result.opacity = "0";
|
|
414
|
+
}
|
|
406
415
|
return result;
|
|
407
416
|
}
|
|
408
417
|
function inlineAllStyles(source, clone, compress) {
|
|
@@ -622,25 +631,38 @@ function isIconFont(input) {
|
|
|
622
631
|
async function iconToImage(unicodeChar, fontFamily, fontWeight, fontSize = 32, color = "#000") {
|
|
623
632
|
fontFamily = fontFamily.replace(/^['"]+|['"]+$/g, "");
|
|
624
633
|
const dpr = window.devicePixelRatio || 1;
|
|
625
|
-
|
|
626
|
-
const
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
634
|
+
await document.fonts.ready;
|
|
635
|
+
const span = document.createElement("span");
|
|
636
|
+
span.textContent = unicodeChar;
|
|
637
|
+
span.style.position = "absolute";
|
|
638
|
+
span.style.visibility = "hidden";
|
|
639
|
+
span.style.fontFamily = `"${fontFamily}"`;
|
|
640
|
+
span.style.fontWeight = fontWeight || "normal";
|
|
641
|
+
span.style.fontSize = `${fontSize}px`;
|
|
642
|
+
span.style.lineHeight = "1";
|
|
643
|
+
span.style.whiteSpace = "nowrap";
|
|
644
|
+
span.style.padding = "0";
|
|
645
|
+
span.style.margin = "0";
|
|
646
|
+
document.body.appendChild(span);
|
|
647
|
+
const rect = span.getBoundingClientRect();
|
|
648
|
+
const width = Math.ceil(rect.width);
|
|
649
|
+
const height = Math.ceil(rect.height);
|
|
650
|
+
document.body.removeChild(span);
|
|
633
651
|
const canvas = document.createElement("canvas");
|
|
634
|
-
canvas.width =
|
|
635
|
-
canvas.height =
|
|
652
|
+
canvas.width = width * dpr;
|
|
653
|
+
canvas.height = height * dpr;
|
|
636
654
|
const ctx = canvas.getContext("2d");
|
|
637
655
|
ctx.scale(dpr, dpr);
|
|
638
|
-
ctx.font =
|
|
656
|
+
ctx.font = fontWeight ? `${fontWeight} ${fontSize}px "${fontFamily}"` : `${fontSize}px "${fontFamily}"`;
|
|
639
657
|
ctx.textAlign = "left";
|
|
640
|
-
ctx.textBaseline = "
|
|
658
|
+
ctx.textBaseline = "top";
|
|
641
659
|
ctx.fillStyle = color;
|
|
642
|
-
ctx.fillText(unicodeChar, 0,
|
|
643
|
-
return
|
|
660
|
+
ctx.fillText(unicodeChar, 0, 0);
|
|
661
|
+
return {
|
|
662
|
+
dataUrl: canvas.toDataURL(),
|
|
663
|
+
width,
|
|
664
|
+
height
|
|
665
|
+
};
|
|
644
666
|
}
|
|
645
667
|
function isStylesheetLoaded(href) {
|
|
646
668
|
return Array.from(document.styleSheets).some((sheet) => sheet.href === href);
|
|
@@ -825,18 +847,20 @@ async function embedCustomFonts({ preCached = false } = {}) {
|
|
|
825
847
|
}
|
|
826
848
|
|
|
827
849
|
// src/modules/pseudo.js
|
|
828
|
-
async function inlinePseudoElements(source, clone,
|
|
850
|
+
async function inlinePseudoElements(source, clone, options) {
|
|
829
851
|
if (!(source instanceof Element) || !(clone instanceof Element)) return;
|
|
830
852
|
for (const pseudo of ["::before", "::after", "::first-letter"]) {
|
|
831
853
|
try {
|
|
832
854
|
const style = getStyle(source, pseudo);
|
|
833
855
|
if (!style || typeof style[Symbol.iterator] !== "function") continue;
|
|
856
|
+
const isEmptyPseudo = style.content === "none" && style.backgroundImage === "none" && style.backgroundColor === "transparent" && (style.borderStyle === "none" || parseFloat(style.borderWidth) === 0) && (!style.transform || style.transform === "none") && style.display === "inline";
|
|
857
|
+
if (isEmptyPseudo) continue;
|
|
834
858
|
if (pseudo === "::first-letter") {
|
|
835
859
|
const normal = getComputedStyle(source);
|
|
836
860
|
const isMeaningful = style.color !== normal.color || style.fontSize !== normal.fontSize || style.fontWeight !== normal.fontWeight;
|
|
837
861
|
if (!isMeaningful) continue;
|
|
838
862
|
const textNode = Array.from(clone.childNodes).find(
|
|
839
|
-
(n) => n.nodeType === Node.TEXT_NODE && n.textContent
|
|
863
|
+
(n) => n.nodeType === Node.TEXT_NODE && n.textContent?.trim().length > 0
|
|
840
864
|
);
|
|
841
865
|
if (!textNode) continue;
|
|
842
866
|
const text = textNode.textContent;
|
|
@@ -848,57 +872,55 @@ async function inlinePseudoElements(source, clone, compress, embedFonts = false,
|
|
|
848
872
|
span.textContent = first;
|
|
849
873
|
span.dataset.snapdomPseudo = "::first-letter";
|
|
850
874
|
const snapshot2 = snapshotComputedStyle(style);
|
|
851
|
-
const key2 = getStyleKey(snapshot2, "span",
|
|
875
|
+
const key2 = getStyleKey(snapshot2, "span", options);
|
|
852
876
|
cache.preStyleMap.set(span, key2);
|
|
853
877
|
const restNode = document.createTextNode(rest);
|
|
854
878
|
clone.replaceChild(restNode, textNode);
|
|
855
879
|
clone.insertBefore(span, restNode);
|
|
856
880
|
continue;
|
|
857
881
|
}
|
|
858
|
-
const content = style.
|
|
859
|
-
const
|
|
860
|
-
const
|
|
861
|
-
const
|
|
862
|
-
const
|
|
863
|
-
const
|
|
864
|
-
const
|
|
865
|
-
const
|
|
866
|
-
const
|
|
867
|
-
const
|
|
868
|
-
const
|
|
869
|
-
const
|
|
882
|
+
const content = style.content;
|
|
883
|
+
const cleanContent = /counter\s*\(|counters\s*\(/.test(content) ? "- " : parseContent(content);
|
|
884
|
+
const bg = style.backgroundImage;
|
|
885
|
+
const bgColor = style.backgroundColor;
|
|
886
|
+
const fontFamily = style.fontFamily;
|
|
887
|
+
const fontSize = parseInt(style.fontSize) || 32;
|
|
888
|
+
const fontWeight = parseInt(style.fontWeight) || false;
|
|
889
|
+
const color = style.color || "#000";
|
|
890
|
+
const display = style.display;
|
|
891
|
+
const width = parseFloat(style.width);
|
|
892
|
+
const height = parseFloat(style.height);
|
|
893
|
+
const borderStyle = style.borderStyle;
|
|
894
|
+
const borderWidth = parseFloat(style.borderWidth);
|
|
895
|
+
const transform = style.transform;
|
|
870
896
|
const isIconFont2 = isIconFont(fontFamily);
|
|
871
|
-
|
|
872
|
-
if (/counter\s*\(|counters\s*\(/.test(content)) {
|
|
873
|
-
cleanContent = "- ";
|
|
874
|
-
} else {
|
|
875
|
-
cleanContent = parseContent(content);
|
|
876
|
-
}
|
|
877
|
-
const hasContent = content !== "none";
|
|
878
|
-
const hasExplicitContent = hasContent && cleanContent !== "";
|
|
897
|
+
const hasExplicitContent = content !== "none" && cleanContent !== "";
|
|
879
898
|
const hasBg = bg && bg !== "none";
|
|
880
899
|
const hasBgColor = bgColor && bgColor !== "transparent" && bgColor !== "rgba(0, 0, 0, 0)";
|
|
881
900
|
const hasBox = display !== "inline" && (width > 0 || height > 0);
|
|
882
|
-
const hasBorder = borderStyle && borderStyle !== "none";
|
|
901
|
+
const hasBorder = borderStyle && borderStyle !== "none" && borderWidth > 0;
|
|
883
902
|
const hasTransform = transform && transform !== "none";
|
|
884
903
|
const shouldRender = hasExplicitContent || hasBg || hasBgColor || hasBox || hasBorder || hasTransform;
|
|
885
904
|
if (!shouldRender) continue;
|
|
886
905
|
const pseudoEl = document.createElement("span");
|
|
887
906
|
pseudoEl.dataset.snapdomPseudo = pseudo;
|
|
907
|
+
pseudoEl.style.verticalAlign = "middle";
|
|
888
908
|
const snapshot = snapshotComputedStyle(style);
|
|
889
|
-
const key = getStyleKey(snapshot, "span",
|
|
909
|
+
const key = getStyleKey(snapshot, "span", options);
|
|
890
910
|
cache.preStyleMap.set(pseudoEl, key);
|
|
891
911
|
if (isIconFont2 && cleanContent.length === 1) {
|
|
912
|
+
const { dataUrl, width: width2, height: height2 } = await iconToImage(cleanContent, fontFamily, fontWeight, fontSize, color);
|
|
892
913
|
const imgEl = document.createElement("img");
|
|
893
|
-
imgEl.src =
|
|
894
|
-
imgEl.style = `width:${fontSize}px;
|
|
914
|
+
imgEl.src = dataUrl;
|
|
915
|
+
imgEl.style = `height:${fontSize}px;width:${width2 / height2 * fontSize}px;object-fit:contain;`;
|
|
895
916
|
pseudoEl.appendChild(imgEl);
|
|
917
|
+
clone.dataset.snapdomHasIcon = "true";
|
|
896
918
|
} else if (cleanContent.startsWith("url(")) {
|
|
897
919
|
const rawUrl = extractURL(cleanContent);
|
|
898
|
-
if (rawUrl
|
|
920
|
+
if (rawUrl?.trim()) {
|
|
899
921
|
try {
|
|
900
922
|
const imgEl = document.createElement("img");
|
|
901
|
-
const dataUrl = await fetchImage(safeEncodeURI(rawUrl,
|
|
923
|
+
const dataUrl = await fetchImage(safeEncodeURI(rawUrl), options);
|
|
902
924
|
imgEl.src = dataUrl;
|
|
903
925
|
imgEl.style = `width:${fontSize}px;height:auto;object-fit:contain;`;
|
|
904
926
|
pseudoEl.appendChild(imgEl);
|
|
@@ -912,19 +934,21 @@ async function inlinePseudoElements(source, clone, compress, embedFonts = false,
|
|
|
912
934
|
if (hasBg) {
|
|
913
935
|
try {
|
|
914
936
|
const bgSplits = splitBackgroundImage(bg);
|
|
915
|
-
const newBgParts = await Promise.all(
|
|
916
|
-
bgSplits.map((entry) => inlineSingleBackgroundEntry(entry))
|
|
917
|
-
);
|
|
937
|
+
const newBgParts = await Promise.all(bgSplits.map(inlineSingleBackgroundEntry));
|
|
918
938
|
pseudoEl.style.backgroundImage = newBgParts.join(", ");
|
|
919
939
|
} catch (e) {
|
|
920
940
|
console.warn(`[snapdom] Failed to inline background-image for ${pseudo}`, e);
|
|
921
941
|
}
|
|
922
942
|
}
|
|
923
943
|
if (hasBgColor) pseudoEl.style.backgroundColor = bgColor;
|
|
924
|
-
const hasContent2 = pseudoEl.childNodes.length > 0 || pseudoEl.textContent
|
|
944
|
+
const hasContent2 = pseudoEl.childNodes.length > 0 || pseudoEl.textContent?.trim() !== "";
|
|
925
945
|
const hasVisibleBox = hasContent2 || hasBg || hasBgColor || hasBox || hasBorder || hasTransform;
|
|
926
946
|
if (!hasVisibleBox) continue;
|
|
927
|
-
pseudo === "::before"
|
|
947
|
+
if (pseudo === "::before") {
|
|
948
|
+
clone.insertBefore(pseudoEl, clone.firstChild);
|
|
949
|
+
} else {
|
|
950
|
+
clone.appendChild(pseudoEl);
|
|
951
|
+
}
|
|
928
952
|
} catch (e) {
|
|
929
953
|
console.warn(`[snapdom] Failed to capture ${pseudo} for`, source, e);
|
|
930
954
|
}
|
|
@@ -932,43 +956,57 @@ async function inlinePseudoElements(source, clone, compress, embedFonts = false,
|
|
|
932
956
|
const sChildren = Array.from(source.children);
|
|
933
957
|
const cChildren = Array.from(clone.children).filter((child) => !child.dataset.snapdomPseudo);
|
|
934
958
|
for (let i = 0; i < Math.min(sChildren.length, cChildren.length); i++) {
|
|
935
|
-
await inlinePseudoElements(
|
|
936
|
-
sChildren[i],
|
|
937
|
-
cChildren[i],
|
|
938
|
-
compress,
|
|
939
|
-
embedFonts,
|
|
940
|
-
useProxy
|
|
941
|
-
);
|
|
959
|
+
await inlinePseudoElements(sChildren[i], cChildren[i], options);
|
|
942
960
|
}
|
|
943
961
|
}
|
|
944
962
|
|
|
945
963
|
// src/modules/svgDefs.js
|
|
946
|
-
function
|
|
947
|
-
if (!
|
|
948
|
-
const
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
964
|
+
function inlineExternalDefsAndSymbols(rootElement) {
|
|
965
|
+
if (!rootElement) return;
|
|
966
|
+
const usedIds = /* @__PURE__ */ new Set();
|
|
967
|
+
rootElement.querySelectorAll("use").forEach((use) => {
|
|
968
|
+
const href = use.getAttribute("xlink:href") || use.getAttribute("href");
|
|
969
|
+
if (href && href.startsWith("#")) {
|
|
970
|
+
usedIds.add(href.slice(1));
|
|
971
|
+
}
|
|
972
|
+
});
|
|
973
|
+
if (!usedIds.size) return;
|
|
974
|
+
const allGlobal = Array.from(document.querySelectorAll("svg > symbol, svg > defs"));
|
|
975
|
+
const globalSymbols = allGlobal.filter((el) => el.tagName.toLowerCase() === "symbol");
|
|
976
|
+
const globalDefs = allGlobal.filter((el) => el.tagName.toLowerCase() === "defs");
|
|
977
|
+
let container = rootElement.querySelector("svg.inline-defs-container");
|
|
978
|
+
if (!container) {
|
|
979
|
+
container = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
980
|
+
container.setAttribute("aria-hidden", "true");
|
|
981
|
+
container.setAttribute("style", "position: absolute; width: 0; height: 0; overflow: hidden;");
|
|
982
|
+
container.classList.add("inline-defs-container");
|
|
983
|
+
rootElement.insertBefore(container, rootElement.firstChild);
|
|
984
|
+
}
|
|
985
|
+
const existingIds = /* @__PURE__ */ new Set();
|
|
986
|
+
rootElement.querySelectorAll("symbol[id], defs > *[id]").forEach((el) => {
|
|
987
|
+
existingIds.add(el.id);
|
|
988
|
+
});
|
|
989
|
+
usedIds.forEach((id) => {
|
|
990
|
+
if (existingIds.has(id)) return;
|
|
991
|
+
const symbol = globalSymbols.find((sym) => sym.id === id);
|
|
992
|
+
if (symbol) {
|
|
993
|
+
container.appendChild(symbol.cloneNode(true));
|
|
994
|
+
existingIds.add(id);
|
|
995
|
+
return;
|
|
996
|
+
}
|
|
997
|
+
for (const defs of globalDefs) {
|
|
998
|
+
const defEl = defs.querySelector(`#${CSS.escape(id)}`);
|
|
999
|
+
if (defEl) {
|
|
1000
|
+
let defsContainer = container.querySelector("defs");
|
|
1001
|
+
if (!defsContainer) {
|
|
1002
|
+
defsContainer = document.createElementNS("http://www.w3.org/2000/svg", "defs");
|
|
1003
|
+
container.appendChild(defsContainer);
|
|
966
1004
|
}
|
|
1005
|
+
defsContainer.appendChild(defEl.cloneNode(true));
|
|
1006
|
+
existingIds.add(id);
|
|
1007
|
+
break;
|
|
967
1008
|
}
|
|
968
1009
|
}
|
|
969
|
-
if (defs.childNodes.length) {
|
|
970
|
-
svg.insertBefore(defs, svg.firstChild);
|
|
971
|
-
}
|
|
972
1010
|
});
|
|
973
1011
|
}
|
|
974
1012
|
|
|
@@ -976,6 +1014,12 @@ function inlineExternalDef(root) {
|
|
|
976
1014
|
async function prepareClone(element, compress = false, embedFonts = false, options = {}) {
|
|
977
1015
|
let clone;
|
|
978
1016
|
let classCSS = "";
|
|
1017
|
+
stabilizeLayout(element);
|
|
1018
|
+
try {
|
|
1019
|
+
inlineExternalDefsAndSymbols(element);
|
|
1020
|
+
} catch (e) {
|
|
1021
|
+
console.warn("inlineExternal defs or symbol failed:", e);
|
|
1022
|
+
}
|
|
979
1023
|
try {
|
|
980
1024
|
clone = deepClone(element, compress, options, element);
|
|
981
1025
|
} catch (e) {
|
|
@@ -987,21 +1031,24 @@ async function prepareClone(element, compress = false, embedFonts = false, optio
|
|
|
987
1031
|
} catch (e) {
|
|
988
1032
|
console.warn("inlinePseudoElements failed:", e);
|
|
989
1033
|
}
|
|
990
|
-
try {
|
|
991
|
-
inlineExternalDef(clone);
|
|
992
|
-
} catch (e) {
|
|
993
|
-
console.warn("inlineExternalDef failed:", e);
|
|
994
|
-
}
|
|
995
1034
|
if (compress) {
|
|
996
1035
|
const keyToClass = generateCSSClasses();
|
|
997
1036
|
classCSS = Array.from(keyToClass.entries()).map(([key, className]) => `.${className}{${key}}`).join("");
|
|
998
1037
|
for (const [node, key] of cache.preStyleMap.entries()) {
|
|
999
1038
|
if (node.tagName === "STYLE") continue;
|
|
1039
|
+
if (node.getRootNode && node.getRootNode() instanceof ShadowRoot) {
|
|
1040
|
+
node.setAttribute("style", key.replace(/;/g, "; "));
|
|
1041
|
+
continue;
|
|
1042
|
+
}
|
|
1000
1043
|
const className = keyToClass.get(key);
|
|
1001
1044
|
if (className) node.classList.add(className);
|
|
1002
1045
|
const bgImage = node.style?.backgroundImage;
|
|
1003
|
-
node.
|
|
1046
|
+
const hasIcon = node.dataset?.snapdomHasIcon;
|
|
1004
1047
|
if (bgImage && bgImage !== "none") node.style.backgroundImage = bgImage;
|
|
1048
|
+
if (hasIcon) {
|
|
1049
|
+
node.style.verticalAlign = "middle";
|
|
1050
|
+
node.style.display = "inline";
|
|
1051
|
+
}
|
|
1005
1052
|
}
|
|
1006
1053
|
} else {
|
|
1007
1054
|
for (const [node, key] of cache.preStyleMap.entries()) {
|
|
@@ -1051,6 +1098,18 @@ async function prepareClone(element, compress = false, embedFonts = false, optio
|
|
|
1051
1098
|
}
|
|
1052
1099
|
return { clone, classCSS };
|
|
1053
1100
|
}
|
|
1101
|
+
function stabilizeLayout(element) {
|
|
1102
|
+
const style = getComputedStyle(element);
|
|
1103
|
+
const outlineStyle = style.outlineStyle;
|
|
1104
|
+
const outlineWidth = style.outlineWidth;
|
|
1105
|
+
const borderStyle = style.borderStyle;
|
|
1106
|
+
const borderWidth = style.borderWidth;
|
|
1107
|
+
const outlineVisible = outlineStyle !== "none" && parseFloat(outlineWidth) > 0;
|
|
1108
|
+
const borderAbsent = borderStyle === "none" || parseFloat(borderWidth) === 0;
|
|
1109
|
+
if (outlineVisible && borderAbsent) {
|
|
1110
|
+
element.style.border = `${outlineWidth} solid transparent`;
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1054
1113
|
|
|
1055
1114
|
// src/modules/images.js
|
|
1056
1115
|
async function inlineImages(clone, options = {}) {
|
|
@@ -1086,13 +1145,27 @@ async function inlineBackgroundImages(source, clone, options = {}) {
|
|
|
1086
1145
|
"mask-source",
|
|
1087
1146
|
"mask-box-image-source",
|
|
1088
1147
|
"mask-border-source",
|
|
1089
|
-
"-webkit-mask-box-image-source"
|
|
1148
|
+
"-webkit-mask-box-image-source",
|
|
1149
|
+
"border-image",
|
|
1150
|
+
"border-image-source",
|
|
1151
|
+
"border-image-slice",
|
|
1152
|
+
"border-image-width",
|
|
1153
|
+
"border-image-outset",
|
|
1154
|
+
"border-image-repeat"
|
|
1090
1155
|
];
|
|
1091
1156
|
while (queue.length) {
|
|
1092
1157
|
const [srcNode, cloneNode] = queue.shift();
|
|
1093
1158
|
const style = cache.preStyle.get(srcNode) || getStyle(srcNode);
|
|
1094
1159
|
if (!cache.preStyle.has(srcNode)) cache.preStyle.set(srcNode, style);
|
|
1160
|
+
const hasBorderImage = (() => {
|
|
1161
|
+
const bi = style.getPropertyValue("border-image");
|
|
1162
|
+
const bis = style.getPropertyValue("border-image-source");
|
|
1163
|
+
return bi && bi !== "none" || bis && bis !== "none";
|
|
1164
|
+
})();
|
|
1095
1165
|
for (const prop of imageProps) {
|
|
1166
|
+
if (["border-image-slice", "border-image-width", "border-image-outset", "border-image-repeat"].includes(prop) && !hasBorderImage) {
|
|
1167
|
+
continue;
|
|
1168
|
+
}
|
|
1096
1169
|
const val = style.getPropertyValue(prop);
|
|
1097
1170
|
if (!val || val === "none") continue;
|
|
1098
1171
|
const splits = splitBackgroundImage(val);
|
|
@@ -1218,12 +1291,14 @@ async function captureDOM(element, options = {}) {
|
|
|
1218
1291
|
}
|
|
1219
1292
|
|
|
1220
1293
|
// src/api/snapdom.js
|
|
1221
|
-
async function toImg(url, {
|
|
1294
|
+
async function toImg(url, { scale = 1 } = {}) {
|
|
1222
1295
|
const img = new Image();
|
|
1223
1296
|
img.src = url;
|
|
1224
1297
|
await img.decode();
|
|
1225
|
-
|
|
1226
|
-
|
|
1298
|
+
if (scale !== 1) {
|
|
1299
|
+
img.style.width = `${img.naturalWidth * scale}px`;
|
|
1300
|
+
img.style.height = `${img.naturalHeight * scale}px`;
|
|
1301
|
+
}
|
|
1227
1302
|
return img;
|
|
1228
1303
|
}
|
|
1229
1304
|
async function toCanvas(url, { dpr = 1, scale = 1 } = {}) {
|
|
@@ -1246,8 +1321,8 @@ async function toCanvas(url, { dpr = 1, scale = 1 } = {}) {
|
|
|
1246
1321
|
if (appended) img.remove();
|
|
1247
1322
|
throw new Error("Image failed to load or has no dimensions");
|
|
1248
1323
|
}
|
|
1249
|
-
const width = img.
|
|
1250
|
-
const height = img.
|
|
1324
|
+
const width = img.naturalWidth * scale;
|
|
1325
|
+
const height = img.naturalHeight * scale;
|
|
1251
1326
|
const canvas = document.createElement("canvas");
|
|
1252
1327
|
canvas.width = Math.ceil(width * dpr);
|
|
1253
1328
|
canvas.height = Math.ceil(height * dpr);
|
|
@@ -1339,19 +1414,19 @@ async function snapdom(element, options = {}) {
|
|
|
1339
1414
|
}
|
|
1340
1415
|
snapdom.capture = async (el, options = {}) => {
|
|
1341
1416
|
const url = await captureDOM(el, options);
|
|
1342
|
-
const dpr = window.devicePixelRatio || 1;
|
|
1417
|
+
const dpr = options.dpr ?? (window.devicePixelRatio || 1);
|
|
1343
1418
|
const scale = options.scale || 1;
|
|
1344
1419
|
return {
|
|
1345
1420
|
url,
|
|
1346
1421
|
options,
|
|
1347
1422
|
toRaw: () => url,
|
|
1348
|
-
toImg: () => toImg(url, { dpr, scale }),
|
|
1349
|
-
toCanvas: () => toCanvas(url, { dpr, scale }),
|
|
1350
|
-
toBlob: (
|
|
1351
|
-
toPng: (
|
|
1352
|
-
toJpg: (
|
|
1353
|
-
toWebp: (
|
|
1354
|
-
download: ({ format = "png", filename = "
|
|
1423
|
+
toImg: (opts = {}) => toImg(url, { dpr, scale, ...opts }),
|
|
1424
|
+
toCanvas: (opts = {}) => toCanvas(url, { dpr, scale, ...opts }),
|
|
1425
|
+
toBlob: (opts = {}) => toBlob(url, { dpr, scale, ...opts }),
|
|
1426
|
+
toPng: (opts = {}) => toRasterImg(url, { dpr, scale, ...opts }, "png"),
|
|
1427
|
+
toJpg: (opts = {}) => toRasterImg(url, { dpr, scale, ...opts }, "jpeg"),
|
|
1428
|
+
toWebp: (opts = {}) => toRasterImg(url, { dpr, scale, ...opts }, "webp"),
|
|
1429
|
+
download: ({ format = "png", filename = "snapDOM", backgroundColor, ...opts } = {}) => download(url, { dpr, scale, format, filename, backgroundColor, ...opts })
|
|
1355
1430
|
};
|
|
1356
1431
|
};
|
|
1357
1432
|
snapdom.toRaw = async (el, options) => (await snapdom.capture(el, options)).toRaw();
|
package/package.json
CHANGED