@zumer/snapdom 1.9.10-dev.0 → 1.9.10-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 +32 -3
- package/dist/snapdom.js +608 -200
- package/dist/snapdom.min.js +4 -4
- package/dist/snapdom.min.mjs +5 -5
- package/dist/snapdom.mjs +471 -73
- package/package.json +1 -1
- package/types/snapdom.d.ts +15 -1
package/dist/snapdom.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
/*
|
|
3
3
|
* snapdom
|
|
4
|
-
* v.1.9.10-dev.
|
|
4
|
+
* v.1.9.10-dev.2
|
|
5
5
|
* Author Juan Martin Muda
|
|
6
6
|
* License MIT
|
|
7
7
|
**/
|
|
@@ -42,10 +42,13 @@ function applyCachePolicy(policy = "soft") {
|
|
|
42
42
|
cache.session.styleMap = /* @__PURE__ */ new Map();
|
|
43
43
|
cache.session.nodeMap = /* @__PURE__ */ new Map();
|
|
44
44
|
cache.session.styleCache = /* @__PURE__ */ new WeakMap();
|
|
45
|
-
cache.
|
|
46
|
-
cache.
|
|
47
|
-
cache.
|
|
48
|
-
cache.
|
|
45
|
+
cache.computedStyle = /* @__PURE__ */ new WeakMap();
|
|
46
|
+
cache.baseStyle = /* @__PURE__ */ new Map();
|
|
47
|
+
cache.defaultStyle = /* @__PURE__ */ new Map();
|
|
48
|
+
cache.image = /* @__PURE__ */ new Map();
|
|
49
|
+
cache.background = /* @__PURE__ */ new Map();
|
|
50
|
+
cache.resource = /* @__PURE__ */ new Map();
|
|
51
|
+
cache.font = /* @__PURE__ */ new Set();
|
|
49
52
|
return;
|
|
50
53
|
}
|
|
51
54
|
default: {
|
|
@@ -297,7 +300,6 @@ var NO_DEFAULTS_TAGS = /* @__PURE__ */ new Set([
|
|
|
297
300
|
"script",
|
|
298
301
|
"template",
|
|
299
302
|
// SVG whole namespace (safe for LeaderLine/presentation attrs)
|
|
300
|
-
"svg",
|
|
301
303
|
"g",
|
|
302
304
|
"defs",
|
|
303
305
|
"use",
|
|
@@ -638,6 +640,11 @@ async function inlineAllStyles(source, clone, sessionOrCtx, opts) {
|
|
|
638
640
|
const ctx = _resolveCtx(sessionOrCtx, opts);
|
|
639
641
|
const resetMode = ctx.options && ctx.options.cache || "auto";
|
|
640
642
|
if (resetMode !== "disabled") setupInvalidationOnce(document.documentElement);
|
|
643
|
+
if (resetMode === "disabled" && !ctx.session.__bumpedForDisabled) {
|
|
644
|
+
bumpEpoch();
|
|
645
|
+
snapshotKeyCache.clear();
|
|
646
|
+
ctx.session.__bumpedForDisabled = true;
|
|
647
|
+
}
|
|
641
648
|
if (NO_DEFAULTS_TAGS.has(source.tagName?.toLowerCase())) {
|
|
642
649
|
const author = source.getAttribute?.("style");
|
|
643
650
|
if (author) clone.setAttribute("style", author);
|
|
@@ -659,6 +666,31 @@ async function inlineAllStyles(source, clone, sessionOrCtx, opts) {
|
|
|
659
666
|
}
|
|
660
667
|
|
|
661
668
|
// src/core/clone.js
|
|
669
|
+
function idleCallback(childList, callback, fast) {
|
|
670
|
+
return Promise.all(childList.map((child) => {
|
|
671
|
+
return new Promise((resolve) => {
|
|
672
|
+
function deal() {
|
|
673
|
+
idle(
|
|
674
|
+
(deadline) => {
|
|
675
|
+
if (deadline instanceof IdleDeadline) {
|
|
676
|
+
if (deadline.timeRemaining()) {
|
|
677
|
+
callback(child, resolve);
|
|
678
|
+
} else {
|
|
679
|
+
deal();
|
|
680
|
+
}
|
|
681
|
+
} else {
|
|
682
|
+
callback(child, resolve);
|
|
683
|
+
}
|
|
684
|
+
},
|
|
685
|
+
{
|
|
686
|
+
fast
|
|
687
|
+
}
|
|
688
|
+
);
|
|
689
|
+
}
|
|
690
|
+
deal();
|
|
691
|
+
});
|
|
692
|
+
}));
|
|
693
|
+
}
|
|
662
694
|
function addNotSlottedRightmost(sel) {
|
|
663
695
|
sel = sel.trim();
|
|
664
696
|
if (!sel) return sel;
|
|
@@ -780,10 +812,84 @@ function markSlottedSubtree(root) {
|
|
|
780
812
|
root.querySelectorAll("*").forEach((el) => el.setAttribute("data-sd-slotted", ""));
|
|
781
813
|
}
|
|
782
814
|
}
|
|
783
|
-
function
|
|
815
|
+
async function getAccessibleIframeDocument(iframe, attempts = 3) {
|
|
816
|
+
const probe = () => {
|
|
817
|
+
try {
|
|
818
|
+
return iframe.contentDocument || iframe.contentWindow?.document || null;
|
|
819
|
+
} catch {
|
|
820
|
+
return null;
|
|
821
|
+
}
|
|
822
|
+
};
|
|
823
|
+
let doc = probe();
|
|
824
|
+
let i = 0;
|
|
825
|
+
while (i < attempts && (!doc || !doc.body && !doc.documentElement)) {
|
|
826
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
827
|
+
doc = probe();
|
|
828
|
+
i++;
|
|
829
|
+
}
|
|
830
|
+
return doc && (doc.body || doc.documentElement) ? doc : null;
|
|
831
|
+
}
|
|
832
|
+
function measureContentBox(el) {
|
|
833
|
+
const rect = el.getBoundingClientRect();
|
|
834
|
+
let bl = 0, br = 0, bt = 0, bb = 0;
|
|
835
|
+
try {
|
|
836
|
+
const cs = getComputedStyle(el);
|
|
837
|
+
bl = parseFloat(cs.borderLeftWidth) || 0;
|
|
838
|
+
br = parseFloat(cs.borderRightWidth) || 0;
|
|
839
|
+
bt = parseFloat(cs.borderTopWidth) || 0;
|
|
840
|
+
bb = parseFloat(cs.borderBottomWidth) || 0;
|
|
841
|
+
} catch {
|
|
842
|
+
}
|
|
843
|
+
const contentWidth = Math.max(0, Math.round(rect.width - (bl + br)));
|
|
844
|
+
const contentHeight = Math.max(0, Math.round(rect.height - (bt + bb)));
|
|
845
|
+
return { contentWidth, contentHeight, rect };
|
|
846
|
+
}
|
|
847
|
+
function pinIframeViewport(doc, w, h) {
|
|
848
|
+
const style = doc.createElement("style");
|
|
849
|
+
style.setAttribute("data-sd-iframe-pin", "");
|
|
850
|
+
style.textContent = `html, body {margin: 0 !important;padding: 0 !important;width: ${w}px !important;height: ${h}px !important;min-width: ${w}px !important;min-height: ${h}px !important;box-sizing: border-box !important;overflow: hidden !important;background-clip: border-box !important;}`;
|
|
851
|
+
(doc.head || doc.documentElement).appendChild(style);
|
|
852
|
+
return () => {
|
|
853
|
+
try {
|
|
854
|
+
style.remove();
|
|
855
|
+
} catch {
|
|
856
|
+
}
|
|
857
|
+
};
|
|
858
|
+
}
|
|
859
|
+
async function rasterizeIframe(iframe, sessionCache, options) {
|
|
860
|
+
const doc = await getAccessibleIframeDocument(iframe, 3);
|
|
861
|
+
if (!doc) throw new Error("iframe document not accessible/ready");
|
|
862
|
+
const { contentWidth, contentHeight, rect } = measureContentBox(iframe);
|
|
863
|
+
const snap = options?.snap;
|
|
864
|
+
if (!snap || typeof snap.toPng !== "function") {
|
|
865
|
+
throw new Error("snapdom.toPng not available in iframe or window");
|
|
866
|
+
}
|
|
867
|
+
const nested = { ...options, scale: 1 };
|
|
868
|
+
const unpin = pinIframeViewport(doc, contentWidth, contentHeight);
|
|
869
|
+
let imgEl;
|
|
870
|
+
try {
|
|
871
|
+
imgEl = await snap.toPng(doc.documentElement, nested);
|
|
872
|
+
} finally {
|
|
873
|
+
unpin();
|
|
874
|
+
}
|
|
875
|
+
imgEl.style.display = "block";
|
|
876
|
+
imgEl.style.width = `${contentWidth}px`;
|
|
877
|
+
imgEl.style.height = `${contentHeight}px`;
|
|
878
|
+
const wrapper = document.createElement("div");
|
|
879
|
+
sessionCache.nodeMap.set(wrapper, iframe);
|
|
880
|
+
inlineAllStyles(iframe, wrapper, sessionCache, options);
|
|
881
|
+
wrapper.style.overflow = "hidden";
|
|
882
|
+
wrapper.style.display = "block";
|
|
883
|
+
if (!wrapper.style.width) wrapper.style.width = `${Math.round(rect.width)}px`;
|
|
884
|
+
if (!wrapper.style.height) wrapper.style.height = `${Math.round(rect.height)}px`;
|
|
885
|
+
wrapper.appendChild(imgEl);
|
|
886
|
+
return wrapper;
|
|
887
|
+
}
|
|
888
|
+
async function deepClone(node, sessionCache, options) {
|
|
784
889
|
if (!node) throw new Error("Invalid node");
|
|
785
890
|
const clonedAssignedNodes = /* @__PURE__ */ new Set();
|
|
786
891
|
let pendingSelectValue = null;
|
|
892
|
+
let pendingTextAreaValue = null;
|
|
787
893
|
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
788
894
|
const tag = (node.localName || node.tagName || "").toLowerCase();
|
|
789
895
|
if (node.id === "snapdom-sandbox" || node.hasAttribute("data-snapdom-sandbox")) {
|
|
@@ -832,9 +938,32 @@ function deepClone(node, sessionCache, options) {
|
|
|
832
938
|
}
|
|
833
939
|
}
|
|
834
940
|
if (node.tagName === "IFRAME") {
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
941
|
+
let sameOrigin = false;
|
|
942
|
+
try {
|
|
943
|
+
sameOrigin = !!(node.contentDocument || node.contentWindow?.document);
|
|
944
|
+
} catch {
|
|
945
|
+
sameOrigin = false;
|
|
946
|
+
}
|
|
947
|
+
if (sameOrigin) {
|
|
948
|
+
try {
|
|
949
|
+
const wrapper = await rasterizeIframe(node, sessionCache, options);
|
|
950
|
+
return wrapper;
|
|
951
|
+
} catch (err) {
|
|
952
|
+
console.warn("[SnapDOM] iframe rasterization failed, fallback:", err);
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
if (options.placeholders) {
|
|
956
|
+
const fallback = document.createElement("div");
|
|
957
|
+
fallback.style.cssText = `width:${node.offsetWidth}px;height:${node.offsetHeight}px;background-image:repeating-linear-gradient(45deg,#ddd,#ddd 5px,#f9f9f9 5px,#f9f9f9 10px);display:flex;align-items:center;justify-content:center;font-size:12px;color:#555;border:1px solid #aaa;`;
|
|
958
|
+
inlineAllStyles(node, fallback, sessionCache, options);
|
|
959
|
+
return fallback;
|
|
960
|
+
} else {
|
|
961
|
+
const rect = node.getBoundingClientRect();
|
|
962
|
+
const spacer = document.createElement("div");
|
|
963
|
+
spacer.style.cssText = `display:inline-block;width:${rect.width}px;height:${rect.height}px;visibility:hidden;`;
|
|
964
|
+
inlineAllStyles(node, spacer, sessionCache, options);
|
|
965
|
+
return spacer;
|
|
966
|
+
}
|
|
838
967
|
}
|
|
839
968
|
if (node.getAttribute("data-capture") === "placeholder") {
|
|
840
969
|
const clone2 = node.cloneNode(false);
|
|
@@ -862,18 +991,34 @@ function deepClone(node, sessionCache, options) {
|
|
|
862
991
|
sessionCache.nodeMap.set(clone, node);
|
|
863
992
|
if (node.tagName === "IMG") {
|
|
864
993
|
freezeImgSrcset(node, clone);
|
|
994
|
+
try {
|
|
995
|
+
const rect = node.getBoundingClientRect();
|
|
996
|
+
let w = Math.round(rect.width || 0);
|
|
997
|
+
let h = Math.round(rect.height || 0);
|
|
998
|
+
if (!w || !h) {
|
|
999
|
+
const computed = window.getComputedStyle(node);
|
|
1000
|
+
const cssW = parseFloat(computed.width) || 0;
|
|
1001
|
+
const cssH = parseFloat(computed.height) || 0;
|
|
1002
|
+
const attrW = parseInt(node.getAttribute("width") || "", 10) || 0;
|
|
1003
|
+
const attrH = parseInt(node.getAttribute("height") || "", 10) || 0;
|
|
1004
|
+
const propW = node.width || node.naturalWidth || 0;
|
|
1005
|
+
const propH = node.height || node.naturalHeight || 0;
|
|
1006
|
+
w = Math.round(w || cssW || attrW || propW || 0);
|
|
1007
|
+
h = Math.round(h || cssH || attrH || propH || 0);
|
|
1008
|
+
}
|
|
1009
|
+
if (w) clone.dataset.snapdomWidth = String(w);
|
|
1010
|
+
if (h) clone.dataset.snapdomHeight = String(h);
|
|
1011
|
+
} catch {
|
|
1012
|
+
}
|
|
865
1013
|
}
|
|
866
1014
|
} catch (err) {
|
|
867
1015
|
console.error("[Snapdom] Failed to clone node:", node, err);
|
|
868
1016
|
throw err;
|
|
869
1017
|
}
|
|
870
1018
|
if (node instanceof HTMLTextAreaElement) {
|
|
871
|
-
clone.textContent = node.value;
|
|
872
|
-
clone.value = node.value;
|
|
873
1019
|
const rect = node.getBoundingClientRect();
|
|
874
1020
|
clone.style.width = `${rect.width}px`;
|
|
875
1021
|
clone.style.height = `${rect.height}px`;
|
|
876
|
-
return clone;
|
|
877
1022
|
}
|
|
878
1023
|
if (node instanceof HTMLInputElement) {
|
|
879
1024
|
clone.value = node.value;
|
|
@@ -887,8 +1032,22 @@ function deepClone(node, sessionCache, options) {
|
|
|
887
1032
|
if (node instanceof HTMLSelectElement) {
|
|
888
1033
|
pendingSelectValue = node.value;
|
|
889
1034
|
}
|
|
1035
|
+
if (node instanceof HTMLTextAreaElement) {
|
|
1036
|
+
pendingTextAreaValue = node.value;
|
|
1037
|
+
}
|
|
890
1038
|
inlineAllStyles(node, clone, sessionCache, options);
|
|
891
1039
|
if (node.shadowRoot) {
|
|
1040
|
+
let callback2 = function(child, resolve) {
|
|
1041
|
+
if (child.nodeType === Node.ELEMENT_NODE && child.tagName === "STYLE") {
|
|
1042
|
+
return resolve(null);
|
|
1043
|
+
} else {
|
|
1044
|
+
deepClone(child, sessionCache, options).then((clonedChild) => {
|
|
1045
|
+
resolve(clonedChild || null);
|
|
1046
|
+
}).catch((e) => {
|
|
1047
|
+
resolve(null);
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
1050
|
+
};
|
|
892
1051
|
try {
|
|
893
1052
|
const slots = node.shadowRoot.querySelectorAll("slot");
|
|
894
1053
|
for (const s of slots) {
|
|
@@ -914,31 +1073,38 @@ function deepClone(node, sessionCache, options) {
|
|
|
914
1073
|
const seed = buildSeedCustomPropsRule(node, neededVars, scopeSelector);
|
|
915
1074
|
injectScopedStyle(clone, seed + rewritten, scopeId);
|
|
916
1075
|
const shadowFrag = document.createDocumentFragment();
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
const clonedChild = deepClone(child, sessionCache, options);
|
|
920
|
-
if (clonedChild) shadowFrag.appendChild(clonedChild);
|
|
921
|
-
}
|
|
1076
|
+
const cloneList2 = await idleCallback(Array.from(node.shadowRoot.childNodes), callback2, options.fast);
|
|
1077
|
+
shadowFrag.append(...cloneList2.filter((clonedChild) => !!clonedChild));
|
|
922
1078
|
clone.appendChild(shadowFrag);
|
|
923
1079
|
}
|
|
924
1080
|
if (node.tagName === "SLOT") {
|
|
1081
|
+
let callback2 = function(child, resolve) {
|
|
1082
|
+
deepClone(child, sessionCache, options).then((clonedChild) => {
|
|
1083
|
+
if (clonedChild) {
|
|
1084
|
+
markSlottedSubtree(clonedChild);
|
|
1085
|
+
}
|
|
1086
|
+
resolve(clonedChild || null);
|
|
1087
|
+
}).catch((e) => {
|
|
1088
|
+
resolve(null);
|
|
1089
|
+
});
|
|
1090
|
+
};
|
|
925
1091
|
const assigned = node.assignedNodes?.({ flatten: true }) || [];
|
|
926
1092
|
const nodesToClone = assigned.length > 0 ? assigned : Array.from(node.childNodes);
|
|
927
1093
|
const fragment = document.createDocumentFragment();
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
if (clonedChild) {
|
|
931
|
-
markSlottedSubtree(clonedChild);
|
|
932
|
-
fragment.appendChild(clonedChild);
|
|
933
|
-
}
|
|
934
|
-
}
|
|
1094
|
+
const cloneList2 = await idleCallback(Array.from(nodesToClone), callback2, options.fast);
|
|
1095
|
+
fragment.append(...cloneList2.filter((clonedChild) => !!clonedChild));
|
|
935
1096
|
return fragment;
|
|
936
1097
|
}
|
|
937
|
-
|
|
938
|
-
if (clonedAssignedNodes.has(child))
|
|
939
|
-
|
|
940
|
-
|
|
1098
|
+
function callback(child, resolve) {
|
|
1099
|
+
if (clonedAssignedNodes.has(child)) return resolve(null);
|
|
1100
|
+
deepClone(child, sessionCache, options).then((clonedChild) => {
|
|
1101
|
+
resolve(clonedChild || null);
|
|
1102
|
+
}).catch((e) => {
|
|
1103
|
+
resolve(null);
|
|
1104
|
+
});
|
|
941
1105
|
}
|
|
1106
|
+
const cloneList = await idleCallback(Array.from(node.childNodes), callback, options.fast);
|
|
1107
|
+
clone.append(...cloneList.filter((clonedChild) => !!clonedChild));
|
|
942
1108
|
if (pendingSelectValue !== null && clone instanceof HTMLSelectElement) {
|
|
943
1109
|
clone.value = pendingSelectValue;
|
|
944
1110
|
for (const opt of clone.options) {
|
|
@@ -949,6 +1115,9 @@ function deepClone(node, sessionCache, options) {
|
|
|
949
1115
|
}
|
|
950
1116
|
}
|
|
951
1117
|
}
|
|
1118
|
+
if (pendingTextAreaValue !== null && clone instanceof HTMLTextAreaElement) {
|
|
1119
|
+
clone.textContent = pendingTextAreaValue;
|
|
1120
|
+
}
|
|
952
1121
|
return clone;
|
|
953
1122
|
}
|
|
954
1123
|
|
|
@@ -1819,7 +1988,7 @@ async function prepareClone(element, options = {}) {
|
|
|
1819
1988
|
console.warn("inlineExternal defs or symbol failed:", e);
|
|
1820
1989
|
}
|
|
1821
1990
|
try {
|
|
1822
|
-
clone = deepClone(element, sessionCache, options, element);
|
|
1991
|
+
clone = await deepClone(element, sessionCache, options, element);
|
|
1823
1992
|
} catch (e) {
|
|
1824
1993
|
console.warn("deepClone failed:", e);
|
|
1825
1994
|
throw e;
|
|
@@ -2057,22 +2226,52 @@ async function inlineImages(clone, options = {}) {
|
|
|
2057
2226
|
if (!img.height) img.height = img.naturalHeight || 100;
|
|
2058
2227
|
return;
|
|
2059
2228
|
}
|
|
2229
|
+
const { defaultImageUrl } = options || {};
|
|
2230
|
+
if (defaultImageUrl) {
|
|
2231
|
+
try {
|
|
2232
|
+
const dsW = parseInt(img.dataset?.snapdomWidth || "", 10) || 0;
|
|
2233
|
+
const dsH = parseInt(img.dataset?.snapdomHeight || "", 10) || 0;
|
|
2234
|
+
const attrW = parseInt(img.getAttribute("width") || "", 10) || 0;
|
|
2235
|
+
const attrH = parseInt(img.getAttribute("height") || "", 10) || 0;
|
|
2236
|
+
const styleW = parseFloat(img.style?.width || "") || 0;
|
|
2237
|
+
const styleH = parseFloat(img.style?.height || "") || 0;
|
|
2238
|
+
const width = dsW || styleW || attrW || img.width || void 0;
|
|
2239
|
+
const height = dsH || styleH || attrH || img.height || void 0;
|
|
2240
|
+
const fallbackUrl = typeof defaultImageUrl === "function" ? await defaultImageUrl({ width, height, src, element: img }) : defaultImageUrl;
|
|
2241
|
+
if (fallbackUrl) {
|
|
2242
|
+
const fallbackData = await snapFetch(fallbackUrl, { as: "dataURL", useProxy: options.useProxy });
|
|
2243
|
+
img.src = fallbackData.data;
|
|
2244
|
+
if (!img.width && width) img.width = width;
|
|
2245
|
+
if (!img.height && height) img.height = height;
|
|
2246
|
+
if (!img.width) img.width = img.naturalWidth || 100;
|
|
2247
|
+
if (!img.height) img.height = img.naturalHeight || 100;
|
|
2248
|
+
return;
|
|
2249
|
+
}
|
|
2250
|
+
} catch {
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2060
2253
|
const w = img.width || img.naturalWidth || 100;
|
|
2061
2254
|
const h = img.height || img.naturalHeight || 100;
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2255
|
+
if (options.placeholders !== false) {
|
|
2256
|
+
const fallback = document.createElement("div");
|
|
2257
|
+
fallback.style.cssText = [
|
|
2258
|
+
`width:${w}px`,
|
|
2259
|
+
`height:${h}px`,
|
|
2260
|
+
"background:#ccc",
|
|
2261
|
+
"display:inline-block",
|
|
2262
|
+
"text-align:center",
|
|
2263
|
+
`line-height:${h}px`,
|
|
2264
|
+
"color:#666",
|
|
2265
|
+
"font-size:12px",
|
|
2266
|
+
"overflow:hidden"
|
|
2267
|
+
].join(";");
|
|
2268
|
+
fallback.textContent = "img";
|
|
2269
|
+
img.replaceWith(fallback);
|
|
2270
|
+
} else {
|
|
2271
|
+
const spacer = document.createElement("div");
|
|
2272
|
+
spacer.style.cssText = `display:inline-block;width:${w}px;height:${h}px;visibility:hidden;`;
|
|
2273
|
+
img.replaceWith(spacer);
|
|
2274
|
+
}
|
|
2076
2275
|
};
|
|
2077
2276
|
for (let i = 0; i < imgs.length; i += 4) {
|
|
2078
2277
|
const group = imgs.slice(i, i + 4).map(processImg);
|
|
@@ -2164,7 +2363,13 @@ async function captureDOM(element, options) {
|
|
|
2164
2363
|
);
|
|
2165
2364
|
await ensureFontsReady(families, 2);
|
|
2166
2365
|
}
|
|
2167
|
-
fontsCSS = await embedCustomFonts({
|
|
2366
|
+
fontsCSS = await embedCustomFonts({
|
|
2367
|
+
required,
|
|
2368
|
+
usedCodepoints,
|
|
2369
|
+
preCached: false,
|
|
2370
|
+
exclude: options.excludeFonts,
|
|
2371
|
+
useProxy: options.useProxy
|
|
2372
|
+
});
|
|
2168
2373
|
resolve();
|
|
2169
2374
|
}, { fast });
|
|
2170
2375
|
});
|
|
@@ -2184,12 +2389,13 @@ async function captureDOM(element, options) {
|
|
|
2184
2389
|
}
|
|
2185
2390
|
await new Promise((resolve) => {
|
|
2186
2391
|
idle(() => {
|
|
2187
|
-
const rect = element
|
|
2392
|
+
const rect = getNaturalBorderBoxSize(element);
|
|
2188
2393
|
let w = rect.width;
|
|
2189
2394
|
let h = rect.height;
|
|
2190
2395
|
const hasW = Number.isFinite(options.width);
|
|
2191
2396
|
const hasH = Number.isFinite(options.height);
|
|
2192
|
-
const
|
|
2397
|
+
const scaleOpt = options.scale ?? 1;
|
|
2398
|
+
const hasScale = typeof scaleOpt === "number" && scaleOpt !== 1;
|
|
2193
2399
|
if (!hasScale) {
|
|
2194
2400
|
const aspect = rect.width / rect.height;
|
|
2195
2401
|
if (hasW && hasH) {
|
|
@@ -2203,30 +2409,79 @@ async function captureDOM(element, options) {
|
|
|
2203
2409
|
w = h * aspect;
|
|
2204
2410
|
}
|
|
2205
2411
|
}
|
|
2206
|
-
w = Math.ceil(w);
|
|
2207
|
-
h = Math.ceil(h);
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
const
|
|
2214
|
-
const
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2412
|
+
w = Math.max(1, Math.ceil(w));
|
|
2413
|
+
h = Math.max(1, Math.ceil(h));
|
|
2414
|
+
const csEl = getComputedStyle(element);
|
|
2415
|
+
const baseTransform = csEl.transform && csEl.transform !== "none" ? csEl.transform : "";
|
|
2416
|
+
const ind = readIndividualTransforms(element);
|
|
2417
|
+
let outW, outH, tx, ty, cancelTranslateX = 0, cancelTranslateY = 0;
|
|
2418
|
+
if (!hasBBoxAffectingTransform(element)) {
|
|
2419
|
+
const r = element.getBoundingClientRect();
|
|
2420
|
+
const vp = inflateRect(r, { top: 0, right: 0, bottom: 0, left: 0 });
|
|
2421
|
+
outW = Math.max(1, vp.width);
|
|
2422
|
+
outH = Math.max(1, vp.height);
|
|
2423
|
+
tx = -(vp.x - Math.floor(r.left));
|
|
2424
|
+
ty = -(vp.y - Math.floor(r.top));
|
|
2425
|
+
} else {
|
|
2426
|
+
const TOTAL = readTotalTransformMatrix({
|
|
2427
|
+
baseTransform,
|
|
2428
|
+
rotate: ind.rotate || "0deg",
|
|
2429
|
+
scale: ind.scale,
|
|
2430
|
+
translate: ind.translate
|
|
2431
|
+
});
|
|
2432
|
+
const e = TOTAL.e ?? TOTAL.m41 ?? 0;
|
|
2433
|
+
const f = TOTAL.f ?? TOTAL.m42 ?? 0;
|
|
2434
|
+
cancelTranslateX = -e;
|
|
2435
|
+
cancelTranslateY = -f;
|
|
2436
|
+
const M2D = TOTAL.is2D ? matrix2DNoTranslate(TOTAL) : matrix2DNoTranslate(new DOMMatrix(TOTAL.toString()));
|
|
2437
|
+
const bb = bboxFromMatrix(w, h, M2D);
|
|
2438
|
+
outW = Math.max(1, Math.ceil(bb.width));
|
|
2439
|
+
outH = Math.max(1, Math.ceil(bb.height));
|
|
2440
|
+
tx = -bb.minX;
|
|
2441
|
+
ty = -bb.minY;
|
|
2218
2442
|
}
|
|
2219
2443
|
const svgNS = "http://www.w3.org/2000/svg";
|
|
2220
2444
|
const fo = document.createElementNS(svgNS, "foreignObject");
|
|
2221
2445
|
fo.setAttribute("width", "100%");
|
|
2222
2446
|
fo.setAttribute("height", "100%");
|
|
2447
|
+
fo.setAttribute("x", String(tx));
|
|
2448
|
+
fo.setAttribute("y", String(ty));
|
|
2449
|
+
fo.style.overflow = "visible";
|
|
2223
2450
|
const styleTag = document.createElement("style");
|
|
2224
|
-
styleTag.textContent = baseCSS + fontsCSS + "svg{overflow:visible;}" + classCSS;
|
|
2451
|
+
styleTag.textContent = baseCSS + fontsCSS + "svg{overflow:visible;} foreignObject{overflow:visible;}" + classCSS;
|
|
2225
2452
|
fo.appendChild(styleTag);
|
|
2226
|
-
|
|
2453
|
+
const container = document.createElement("div");
|
|
2454
|
+
container.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
|
|
2455
|
+
container.style.width = `${w}px`;
|
|
2456
|
+
container.style.height = `${h}px`;
|
|
2457
|
+
container.style.overflow = "visible";
|
|
2458
|
+
container.style.transformOrigin = "0 0";
|
|
2459
|
+
const cancels = [];
|
|
2460
|
+
if (cancelTranslateX || cancelTranslateY) {
|
|
2461
|
+
cancels.push(`translate(${cancelTranslateX}px, ${cancelTranslateY}px)`);
|
|
2462
|
+
}
|
|
2463
|
+
if (!hasScale && (hasW || hasH)) {
|
|
2464
|
+
const sxWH = rect.width ? w / rect.width : 1;
|
|
2465
|
+
const syWH = rect.height ? h / rect.height : 1;
|
|
2466
|
+
cancels.push(`scale(${sxWH}, ${syWH})`);
|
|
2467
|
+
} else if (hasScale) {
|
|
2468
|
+
cancels.push(`scale(${scaleOpt})`);
|
|
2469
|
+
}
|
|
2470
|
+
if (cancels.length) container.style.transform = cancels.join(" ");
|
|
2471
|
+
clone.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
|
|
2472
|
+
clone.style.transformOrigin = "0 0";
|
|
2473
|
+
clone.style.position = "static";
|
|
2474
|
+
clone.style.left = "0";
|
|
2475
|
+
clone.style.top = "0";
|
|
2476
|
+
if (baseTransform) clone.style.transform = baseTransform;
|
|
2477
|
+
clone.style.rotate = ind.rotate || "0deg";
|
|
2478
|
+
clone.style.scale = ind.scale || "1";
|
|
2479
|
+
clone.style.translate = ind.translate || "0 0";
|
|
2480
|
+
container.appendChild(clone);
|
|
2481
|
+
fo.appendChild(container);
|
|
2227
2482
|
const serializer = new XMLSerializer();
|
|
2228
2483
|
const foString = serializer.serializeToString(fo);
|
|
2229
|
-
const svgHeader = `<svg xmlns="${svgNS}" width="${
|
|
2484
|
+
const svgHeader = `<svg xmlns="${svgNS}" width="${outW}" height="${outH}" viewBox="0 0 ${outW} ${outH}">`;
|
|
2230
2485
|
const svgFooter = "</svg>";
|
|
2231
2486
|
svgString = svgHeader + foString + svgFooter;
|
|
2232
2487
|
dataURL = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgString)}`;
|
|
@@ -2237,6 +2492,128 @@ async function captureDOM(element, options) {
|
|
|
2237
2492
|
if (sandbox && sandbox.style.position === "absolute") sandbox.remove();
|
|
2238
2493
|
return dataURL;
|
|
2239
2494
|
}
|
|
2495
|
+
function getNaturalBorderBoxSize(el) {
|
|
2496
|
+
const cs = getComputedStyle(el);
|
|
2497
|
+
const px = (v) => parseFloat(v) || 0;
|
|
2498
|
+
let w = px(cs.width);
|
|
2499
|
+
let h = px(cs.height);
|
|
2500
|
+
if (cs.boxSizing === "content-box") {
|
|
2501
|
+
w += px(cs.paddingLeft) + px(cs.paddingRight) + px(cs.borderLeftWidth) + px(cs.borderRightWidth);
|
|
2502
|
+
h += px(cs.paddingTop) + px(cs.paddingBottom) + px(cs.borderTopWidth) + px(cs.borderBottomWidth);
|
|
2503
|
+
}
|
|
2504
|
+
return { width: w, height: h };
|
|
2505
|
+
}
|
|
2506
|
+
function matrixFromComputed(el) {
|
|
2507
|
+
const tr = getComputedStyle(el).transform;
|
|
2508
|
+
if (!tr || tr === "none") return new DOMMatrix();
|
|
2509
|
+
try {
|
|
2510
|
+
return new DOMMatrix(tr);
|
|
2511
|
+
} catch {
|
|
2512
|
+
return new WebKitCSSMatrix(tr);
|
|
2513
|
+
}
|
|
2514
|
+
}
|
|
2515
|
+
function matrix2DNoTranslate(M) {
|
|
2516
|
+
return new DOMMatrix([M.a, M.b, M.c, M.d, 0, 0]);
|
|
2517
|
+
}
|
|
2518
|
+
function bboxFromMatrix(w, h, M2D) {
|
|
2519
|
+
const pts = [
|
|
2520
|
+
{ x: 0, y: 0 },
|
|
2521
|
+
{ x: w, y: 0 },
|
|
2522
|
+
{ x: 0, y: h },
|
|
2523
|
+
{ x: w, y: h }
|
|
2524
|
+
];
|
|
2525
|
+
const t = pts.map((p) => ({
|
|
2526
|
+
x: M2D.a * p.x + M2D.c * p.y,
|
|
2527
|
+
y: M2D.b * p.x + M2D.d * p.y
|
|
2528
|
+
}));
|
|
2529
|
+
const minX = Math.min(...t.map((p) => p.x));
|
|
2530
|
+
const maxX = Math.max(...t.map((p) => p.x));
|
|
2531
|
+
const minY = Math.min(...t.map((p) => p.y));
|
|
2532
|
+
const maxY = Math.max(...t.map((p) => p.y));
|
|
2533
|
+
return { minX, minY, width: maxX - minX, height: maxY - minY };
|
|
2534
|
+
}
|
|
2535
|
+
function readIndividualTransforms(el) {
|
|
2536
|
+
const out = { rotate: "0deg", scale: null, translate: null };
|
|
2537
|
+
const map = el.computedStyleMap?.();
|
|
2538
|
+
if (map) {
|
|
2539
|
+
const rot = map.get("rotate");
|
|
2540
|
+
if (rot) {
|
|
2541
|
+
const ang = rot.angle ?? rot;
|
|
2542
|
+
if (ang && "unit" in ang) {
|
|
2543
|
+
out.rotate = ang.unit === "rad" ? ang.value * 180 / Math.PI + "deg" : ang.value + ang.unit;
|
|
2544
|
+
} else {
|
|
2545
|
+
out.rotate = String(rot);
|
|
2546
|
+
}
|
|
2547
|
+
}
|
|
2548
|
+
const sc = map.get("scale");
|
|
2549
|
+
if (sc && sc.length) {
|
|
2550
|
+
const sx = sc[0]?.value ?? 1;
|
|
2551
|
+
const sy = sc[1]?.value ?? sx;
|
|
2552
|
+
out.scale = `${sx} ${sy}`;
|
|
2553
|
+
}
|
|
2554
|
+
const tr = map.get("translate");
|
|
2555
|
+
if (tr && tr.length) {
|
|
2556
|
+
const tx = tr[0]?.value ?? 0;
|
|
2557
|
+
const ty = tr[1]?.value ?? 0;
|
|
2558
|
+
const ux = tr[0]?.unit ?? "px";
|
|
2559
|
+
const uy = tr[1]?.unit ?? "px";
|
|
2560
|
+
out.translate = `${tx}${ux} ${ty}${uy}`;
|
|
2561
|
+
}
|
|
2562
|
+
return out;
|
|
2563
|
+
}
|
|
2564
|
+
const cs = getComputedStyle(el);
|
|
2565
|
+
out.rotate = cs.rotate && cs.rotate !== "none" ? cs.rotate : "0deg";
|
|
2566
|
+
out.scale = cs.scale && cs.scale !== "none" ? cs.scale : null;
|
|
2567
|
+
out.translate = cs.translate && cs.translate !== "none" ? cs.translate : null;
|
|
2568
|
+
return out;
|
|
2569
|
+
}
|
|
2570
|
+
function hasBBoxAffectingTransform(el) {
|
|
2571
|
+
const t = getComputedStyle(el).transform || "none";
|
|
2572
|
+
if (t === "none" || t === "matrix(1, 0, 0, 1, 0, 0)") return false;
|
|
2573
|
+
if (/^matrix\(\s*1\s*,\s*0\s*,\s*0\s*,\s*1\s*,/i.test(t)) return false;
|
|
2574
|
+
return true;
|
|
2575
|
+
}
|
|
2576
|
+
function inflateRect(r, p) {
|
|
2577
|
+
const x = Math.floor(r.left - p.left);
|
|
2578
|
+
const y = Math.floor(r.top - p.top);
|
|
2579
|
+
const w = Math.ceil(r.width + p.left + p.right);
|
|
2580
|
+
const h = Math.ceil(r.height + p.top + p.bottom);
|
|
2581
|
+
return { x, y, width: w, height: h };
|
|
2582
|
+
}
|
|
2583
|
+
var __measureHost = null;
|
|
2584
|
+
function getMeasureHost() {
|
|
2585
|
+
if (__measureHost) return __measureHost;
|
|
2586
|
+
const n = document.createElement("div");
|
|
2587
|
+
n.id = "snapdom-measure-slot";
|
|
2588
|
+
n.setAttribute("aria-hidden", "true");
|
|
2589
|
+
Object.assign(n.style, {
|
|
2590
|
+
position: "absolute",
|
|
2591
|
+
left: "-99999px",
|
|
2592
|
+
top: "0px",
|
|
2593
|
+
width: "0px",
|
|
2594
|
+
height: "0px",
|
|
2595
|
+
overflow: "hidden",
|
|
2596
|
+
opacity: "0",
|
|
2597
|
+
pointerEvents: "none",
|
|
2598
|
+
contain: "size layout style"
|
|
2599
|
+
});
|
|
2600
|
+
document.documentElement.appendChild(n);
|
|
2601
|
+
__measureHost = n;
|
|
2602
|
+
return n;
|
|
2603
|
+
}
|
|
2604
|
+
function readTotalTransformMatrix(t) {
|
|
2605
|
+
const host = getMeasureHost();
|
|
2606
|
+
const tmp = document.createElement("div");
|
|
2607
|
+
tmp.style.transformOrigin = "0 0";
|
|
2608
|
+
if (t.baseTransform) tmp.style.transform = t.baseTransform;
|
|
2609
|
+
if (t.rotate) tmp.style.rotate = t.rotate;
|
|
2610
|
+
if (t.scale) tmp.style.scale = t.scale;
|
|
2611
|
+
if (t.translate) tmp.style.translate = t.translate;
|
|
2612
|
+
host.appendChild(tmp);
|
|
2613
|
+
const M = matrixFromComputed(tmp);
|
|
2614
|
+
host.removeChild(tmp);
|
|
2615
|
+
return M;
|
|
2616
|
+
}
|
|
2240
2617
|
|
|
2241
2618
|
// src/core/context.js
|
|
2242
2619
|
function normalizeCachePolicy(v) {
|
|
@@ -2262,11 +2639,14 @@ function createContext(options = {}) {
|
|
|
2262
2639
|
// DOM filters
|
|
2263
2640
|
exclude: options.exclude ?? [],
|
|
2264
2641
|
filter: options.filter ?? null,
|
|
2642
|
+
placeholders: options.placeholders !== false,
|
|
2643
|
+
// default true
|
|
2265
2644
|
// Fuentes
|
|
2266
2645
|
embedFonts: options.embedFonts ?? false,
|
|
2267
2646
|
iconFonts: Array.isArray(options.iconFonts) ? options.iconFonts : options.iconFonts ? [options.iconFonts] : [],
|
|
2268
2647
|
localFonts: Array.isArray(options.localFonts) ? options.localFonts : [],
|
|
2269
2648
|
excludeFonts: options.excludeFonts ?? void 0,
|
|
2649
|
+
defaultImageUrl: options.defaultImageUrl ?? void 0,
|
|
2270
2650
|
/** Cache policy (disabled|full|auto|soft). Default: "soft" */
|
|
2271
2651
|
/** @type {CachePolicy} */
|
|
2272
2652
|
cache: cachePolicy,
|
|
@@ -2300,31 +2680,41 @@ async function toImg(url, options) {
|
|
|
2300
2680
|
|
|
2301
2681
|
// src/exporters/toCanvas.js
|
|
2302
2682
|
async function toCanvas(url, options) {
|
|
2683
|
+
const { scale, dpr } = options;
|
|
2684
|
+
const safari = isSafari();
|
|
2303
2685
|
const img = new Image();
|
|
2304
2686
|
img.crossOrigin = "anonymous";
|
|
2305
2687
|
img.loading = "eager";
|
|
2306
2688
|
img.decoding = "sync";
|
|
2307
2689
|
img.src = url;
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2690
|
+
let tempSlot = null;
|
|
2691
|
+
if (safari) {
|
|
2692
|
+
tempSlot = document.createElement("div");
|
|
2693
|
+
tempSlot.setAttribute("aria-hidden", "true");
|
|
2694
|
+
tempSlot.style.cssText = "position:absolute;left:-99999px;top:0;width:0;height:0;overflow:hidden;opacity:0;pointer-events:none;contain:size layout style;";
|
|
2695
|
+
document.body.appendChild(tempSlot);
|
|
2696
|
+
tempSlot.appendChild(img);
|
|
2313
2697
|
}
|
|
2314
2698
|
await img.decode();
|
|
2315
|
-
if (
|
|
2316
|
-
|
|
2317
|
-
|
|
2699
|
+
if (safari) {
|
|
2700
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
2701
|
+
}
|
|
2702
|
+
const width = img.naturalWidth * scale;
|
|
2703
|
+
const height = img.naturalHeight * scale;
|
|
2318
2704
|
const canvas = document.createElement("canvas");
|
|
2319
|
-
const dpr = options.dpr;
|
|
2320
2705
|
canvas.width = Math.ceil(width * dpr);
|
|
2321
2706
|
canvas.height = Math.ceil(height * dpr);
|
|
2322
2707
|
canvas.style.width = `${width}px`;
|
|
2323
2708
|
canvas.style.height = `${height}px`;
|
|
2324
2709
|
const ctx = canvas.getContext("2d");
|
|
2325
|
-
ctx.scale(dpr, dpr);
|
|
2710
|
+
if (dpr !== 1) ctx.scale(dpr, dpr);
|
|
2326
2711
|
ctx.drawImage(img, 0, 0, width, height);
|
|
2327
|
-
if (
|
|
2712
|
+
if (tempSlot && tempSlot.parentNode) {
|
|
2713
|
+
try {
|
|
2714
|
+
tempSlot.parentNode.removeChild(tempSlot);
|
|
2715
|
+
} catch {
|
|
2716
|
+
}
|
|
2717
|
+
}
|
|
2328
2718
|
return canvas;
|
|
2329
2719
|
}
|
|
2330
2720
|
|
|
@@ -2384,6 +2774,14 @@ async function snapdom(element, userOptions) {
|
|
|
2384
2774
|
if (!element) throw new Error("Element cannot be null or undefined");
|
|
2385
2775
|
const context = createContext(userOptions);
|
|
2386
2776
|
if (context.iconFonts && context.iconFonts.length > 0) extendIconFonts(context.iconFonts);
|
|
2777
|
+
if (!context.snap) {
|
|
2778
|
+
context.snap = {
|
|
2779
|
+
/** Delegates to this module’s PNG path (tu build devuelve <img>). */
|
|
2780
|
+
toPng: (el, opts) => snapdom.toPng(el, opts),
|
|
2781
|
+
/** Útil si en algún flujo necesitás vector o <img> directo. */
|
|
2782
|
+
toImg: (el, opts) => snapdom.toImg(el, opts)
|
|
2783
|
+
};
|
|
2784
|
+
}
|
|
2387
2785
|
return snapdom.capture(element, context, INTERNAL_TOKEN);
|
|
2388
2786
|
}
|
|
2389
2787
|
snapdom.capture = async (el, context, _token) => {
|