image-exporter 1.0.7 → 1.0.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.
@@ -1,809 +1,1578 @@
1
+ var _a;
2
+ function handleFileNames(imageOptions, filenames) {
3
+ let proposedFilename = imageOptions.label;
4
+ if (imageOptions.includeScaleInLabel) proposedFilename += `_@${imageOptions.scale}x`;
5
+ const extension = `.${imageOptions.format}`;
6
+ proposedFilename += extension;
7
+ if (!filenames.includes(proposedFilename)) {
8
+ filenames.push(proposedFilename);
9
+ return proposedFilename;
10
+ }
11
+ const numberPattern = /-(\d+)$/;
12
+ const match = proposedFilename.match(numberPattern);
13
+ if (match) {
14
+ const baseFilename = proposedFilename.replace(numberPattern, "");
15
+ let counter = parseInt(match[1], 10);
16
+ while (filenames.includes(`${baseFilename}-${counter}${extension}`)) {
17
+ counter++;
18
+ }
19
+ const newFilename = `${baseFilename}-${counter}${extension}`;
20
+ filenames.push(newFilename);
21
+ return newFilename;
22
+ } else {
23
+ const baseFilename = proposedFilename.replace(extension, "");
24
+ let counter = 2;
25
+ while (filenames.includes(`${baseFilename}-${counter}${extension}`)) {
26
+ counter++;
27
+ }
28
+ const newFilename = `${baseFilename}-${counter}${extension}`;
29
+ filenames.push(newFilename);
30
+ return newFilename;
31
+ }
32
+ }
33
+ function changeJpegDpi(uint8Array, dpi) {
34
+ uint8Array[13] = 1;
35
+ uint8Array[14] = dpi >> 8;
36
+ uint8Array[15] = dpi & 255;
37
+ uint8Array[16] = dpi >> 8;
38
+ uint8Array[17] = dpi & 255;
39
+ return uint8Array;
40
+ }
41
+ const _P = "p".charCodeAt(0);
42
+ const _H = "H".charCodeAt(0);
43
+ const _Y = "Y".charCodeAt(0);
44
+ const _S = "s".charCodeAt(0);
45
+ let pngDataTable;
46
+ function createPngDataTable() {
47
+ const crcTable = new Int32Array(256);
48
+ for (let n = 0; n < 256; n++) {
49
+ let c = n;
50
+ for (let k = 0; k < 8; k++) {
51
+ c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
52
+ }
53
+ crcTable[n] = c;
54
+ }
55
+ return crcTable;
56
+ }
57
+ function calcCrc(uint8Array) {
58
+ let c = -1;
59
+ if (!pngDataTable)
60
+ pngDataTable = createPngDataTable();
61
+ for (let n = 0; n < uint8Array.length; n++) {
62
+ c = pngDataTable[(c ^ uint8Array[n]) & 255] ^ c >>> 8;
63
+ }
64
+ return c ^ -1;
65
+ }
66
+ function searchStartOfPhys(uint8Array) {
67
+ const length = uint8Array.length - 1;
68
+ for (let i = length; i >= 4; i--) {
69
+ if (uint8Array[i - 4] === 9 && uint8Array[i - 3] === _P && uint8Array[i - 2] === _H && uint8Array[i - 1] === _Y && uint8Array[i] === _S) {
70
+ return i - 3;
71
+ }
72
+ }
73
+ return 0;
74
+ }
75
+ function changePngDpi(uint8Array, dpi, overwritepHYs = false) {
76
+ const physChunk = new Uint8Array(13);
77
+ dpi *= 39.3701;
78
+ physChunk[0] = _P;
79
+ physChunk[1] = _H;
80
+ physChunk[2] = _Y;
81
+ physChunk[3] = _S;
82
+ physChunk[4] = dpi >>> 24;
83
+ physChunk[5] = dpi >>> 16;
84
+ physChunk[6] = dpi >>> 8;
85
+ physChunk[7] = dpi & 255;
86
+ physChunk[8] = physChunk[4];
87
+ physChunk[9] = physChunk[5];
88
+ physChunk[10] = physChunk[6];
89
+ physChunk[11] = physChunk[7];
90
+ physChunk[12] = 1;
91
+ const crc = calcCrc(physChunk);
92
+ const crcChunk = new Uint8Array(4);
93
+ crcChunk[0] = crc >>> 24;
94
+ crcChunk[1] = crc >>> 16;
95
+ crcChunk[2] = crc >>> 8;
96
+ crcChunk[3] = crc & 255;
97
+ if (overwritepHYs) {
98
+ const startingIndex = searchStartOfPhys(uint8Array);
99
+ uint8Array.set(physChunk, startingIndex);
100
+ uint8Array.set(crcChunk, startingIndex + 13);
101
+ return uint8Array;
102
+ } else {
103
+ const chunkLength = new Uint8Array(4);
104
+ chunkLength[0] = 0;
105
+ chunkLength[1] = 0;
106
+ chunkLength[2] = 0;
107
+ chunkLength[3] = 9;
108
+ const finalHeader = new Uint8Array(54);
109
+ finalHeader.set(uint8Array, 0);
110
+ finalHeader.set(chunkLength, 33);
111
+ finalHeader.set(physChunk, 37);
112
+ finalHeader.set(crcChunk, 50);
113
+ return finalHeader;
114
+ }
115
+ }
116
+ const b64PhysSignature1 = "AAlwSFlz";
117
+ const b64PhysSignature2 = "AAAJcEhZ";
118
+ const b64PhysSignature3 = "AAAACXBI";
119
+ function detectPhysChunkFromDataUrl(dataUrl) {
120
+ let b64index = dataUrl.indexOf(b64PhysSignature1);
121
+ if (b64index === -1) {
122
+ b64index = dataUrl.indexOf(b64PhysSignature2);
123
+ }
124
+ if (b64index === -1) {
125
+ b64index = dataUrl.indexOf(b64PhysSignature3);
126
+ }
127
+ return b64index;
128
+ }
129
+ const PREFIX = "[modern-screenshot]";
130
+ const IN_BROWSER = typeof window !== "undefined";
131
+ const SUPPORT_WEB_WORKER = IN_BROWSER && "Worker" in window;
132
+ const SUPPORT_ATOB = IN_BROWSER && "atob" in window;
133
+ const SUPPORT_BTOA = IN_BROWSER && "btoa" in window;
134
+ const USER_AGENT = IN_BROWSER ? (_a = window.navigator) == null ? void 0 : _a.userAgent : "";
135
+ const IN_CHROME = USER_AGENT.includes("Chrome");
136
+ const IN_SAFARI = USER_AGENT.includes("AppleWebKit") && !IN_CHROME;
137
+ const IN_FIREFOX = USER_AGENT.includes("Firefox");
138
+ const isContext = (value) => value && "__CONTEXT__" in value;
139
+ const isCssFontFaceRule = (rule) => rule.constructor.name === "CSSFontFaceRule";
140
+ const isCSSImportRule = (rule) => rule.constructor.name === "CSSImportRule";
141
+ const isElementNode = (node) => node.nodeType === 1;
142
+ const isSVGElementNode = (node) => typeof node.className === "object";
143
+ const isSVGImageElementNode = (node) => node.tagName === "image";
144
+ const isSVGUseElementNode = (node) => node.tagName === "use";
145
+ const isHTMLElementNode = (node) => isElementNode(node) && typeof node.style !== "undefined" && !isSVGElementNode(node);
146
+ const isCommentNode = (node) => node.nodeType === 8;
147
+ const isTextNode = (node) => node.nodeType === 3;
148
+ const isImageElement = (node) => node.tagName === "IMG";
149
+ const isVideoElement = (node) => node.tagName === "VIDEO";
150
+ const isCanvasElement = (node) => node.tagName === "CANVAS";
151
+ const isTextareaElement = (node) => node.tagName === "TEXTAREA";
152
+ const isInputElement = (node) => node.tagName === "INPUT";
153
+ const isStyleElement = (node) => node.tagName === "STYLE";
154
+ const isScriptElement = (node) => node.tagName === "SCRIPT";
155
+ const isSelectElement = (node) => node.tagName === "SELECT";
156
+ const isSlotElement = (node) => node.tagName === "SLOT";
157
+ const isIFrameElement = (node) => node.tagName === "IFRAME";
158
+ const consoleWarn = (...args) => console.warn(PREFIX, ...args);
159
+ function supportWebp(ownerDocument) {
160
+ var _a2;
161
+ const canvas = (_a2 = ownerDocument == null ? void 0 : ownerDocument.createElement) == null ? void 0 : _a2.call(ownerDocument, "canvas");
162
+ if (canvas) {
163
+ canvas.height = canvas.width = 1;
164
+ }
165
+ return Boolean(canvas) && "toDataURL" in canvas && Boolean(canvas.toDataURL("image/webp").includes("image/webp"));
166
+ }
167
+ const isDataUrl = (url) => url.startsWith("data:");
1
168
  function resolveUrl(url, baseUrl) {
2
- if (url.match(/^[a-z]+:\/\//i)) {
169
+ if (url.match(/^[a-z]+:\/\//i))
3
170
  return url;
4
- }
5
- if (url.match(/^\/\//)) {
171
+ if (IN_BROWSER && url.match(/^\/\//))
6
172
  return window.location.protocol + url;
7
- }
8
- if (url.match(/^[a-z]+:/i)) {
173
+ if (url.match(/^[a-z]+:/i))
9
174
  return url;
10
- }
11
- const doc = document.implementation.createHTMLDocument();
175
+ if (!IN_BROWSER)
176
+ return url;
177
+ const doc = getDocument().implementation.createHTMLDocument();
12
178
  const base = doc.createElement("base");
13
179
  const a = doc.createElement("a");
14
180
  doc.head.appendChild(base);
15
181
  doc.body.appendChild(a);
16
- if (baseUrl) {
182
+ if (baseUrl)
17
183
  base.href = baseUrl;
18
- }
19
184
  a.href = url;
20
185
  return a.href;
21
186
  }
22
- const uuid = /* @__PURE__ */ (() => {
23
- let counter = 0;
24
- const random = () => (
25
- // eslint-disable-next-line no-bitwise
26
- `0000${(Math.random() * 36 ** 4 << 0).toString(36)}`.slice(-4)
27
- );
28
- return () => {
29
- counter += 1;
30
- return `u${random()}${counter}`;
31
- };
32
- })();
33
- function toArray(arrayLike) {
34
- const arr = [];
35
- for (let i = 0, l = arrayLike.length; i < l; i++) {
36
- arr.push(arrayLike[i]);
37
- }
38
- return arr;
39
- }
40
- let styleProps = null;
41
- function getStyleProperties(options = {}) {
42
- if (styleProps) {
43
- return styleProps;
44
- }
45
- if (options.includeStyleProperties) {
46
- styleProps = options.includeStyleProperties;
47
- return styleProps;
48
- }
49
- styleProps = toArray(window.getComputedStyle(document.documentElement));
50
- return styleProps;
51
- }
52
- function px(node, styleProperty) {
53
- const win = node.ownerDocument.defaultView || window;
54
- const val = win.getComputedStyle(node).getPropertyValue(styleProperty);
55
- return val ? parseFloat(val.replace("px", "")) : 0;
56
- }
57
- function getNodeWidth(node) {
58
- const leftBorder = px(node, "border-left-width");
59
- const rightBorder = px(node, "border-right-width");
60
- return node.clientWidth + leftBorder + rightBorder;
61
- }
62
- function getNodeHeight(node) {
63
- const topBorder = px(node, "border-top-width");
64
- const bottomBorder = px(node, "border-bottom-width");
65
- return node.clientHeight + topBorder + bottomBorder;
66
- }
67
- function getImageSize(targetNode, options = {}) {
68
- const width = options.width || getNodeWidth(targetNode);
69
- const height = options.height || getNodeHeight(targetNode);
70
- return { width, height };
187
+ function getDocument(target) {
188
+ return (target && isElementNode(target) ? target == null ? void 0 : target.ownerDocument : target) ?? window.document;
71
189
  }
72
- function getPixelRatio() {
73
- let ratio;
74
- let FINAL_PROCESS;
75
- try {
76
- FINAL_PROCESS = process;
77
- } catch (e) {
78
- }
79
- const val = FINAL_PROCESS && FINAL_PROCESS.env ? FINAL_PROCESS.env.devicePixelRatio : null;
80
- if (val) {
81
- ratio = parseInt(val, 10);
82
- if (Number.isNaN(ratio)) {
83
- ratio = 1;
84
- }
85
- }
86
- return ratio || window.devicePixelRatio || 1;
190
+ const XMLNS = "http://www.w3.org/2000/svg";
191
+ function createSvg(width, height, ownerDocument) {
192
+ const svg = getDocument(ownerDocument).createElementNS(XMLNS, "svg");
193
+ svg.setAttributeNS(null, "width", width.toString());
194
+ svg.setAttributeNS(null, "height", height.toString());
195
+ svg.setAttributeNS(null, "viewBox", `0 0 ${width} ${height}`);
196
+ return svg;
87
197
  }
88
- const canvasDimensionLimit = 16384;
89
- function checkCanvasDimensions(canvas) {
90
- if (canvas.width > canvasDimensionLimit || canvas.height > canvasDimensionLimit) {
91
- if (canvas.width > canvasDimensionLimit && canvas.height > canvasDimensionLimit) {
92
- if (canvas.width > canvas.height) {
93
- canvas.height *= canvasDimensionLimit / canvas.width;
94
- canvas.width = canvasDimensionLimit;
95
- } else {
96
- canvas.width *= canvasDimensionLimit / canvas.height;
97
- canvas.height = canvasDimensionLimit;
98
- }
99
- } else if (canvas.width > canvasDimensionLimit) {
100
- canvas.height *= canvasDimensionLimit / canvas.width;
101
- canvas.width = canvasDimensionLimit;
102
- } else {
103
- canvas.width *= canvasDimensionLimit / canvas.height;
104
- canvas.height = canvasDimensionLimit;
105
- }
198
+ function svgToDataUrl(svg, removeControlCharacter) {
199
+ let xhtml = new XMLSerializer().serializeToString(svg);
200
+ if (removeControlCharacter) {
201
+ xhtml = xhtml.replace(/[\u0000-\u0008\v\f\u000E-\u001F\uD800-\uDFFF\uFFFE\uFFFF]/gu, "");
106
202
  }
203
+ return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(xhtml)}`;
107
204
  }
108
- function createImage(url) {
205
+ function readBlob(blob, type) {
109
206
  return new Promise((resolve, reject) => {
110
- const img = new Image();
111
- img.onload = () => {
112
- img.decode().then(() => {
113
- requestAnimationFrame(() => resolve(img));
114
- });
115
- };
116
- img.onerror = reject;
117
- img.crossOrigin = "anonymous";
118
- img.decoding = "async";
119
- img.src = url;
207
+ const reader = new FileReader();
208
+ reader.onload = () => resolve(reader.result);
209
+ reader.onerror = () => reject(reader.error);
210
+ reader.onabort = () => reject(new Error(`Failed read blob to ${type}`));
211
+ {
212
+ reader.readAsDataURL(blob);
213
+ }
120
214
  });
121
215
  }
122
- async function svgToDataURL(svg) {
123
- return Promise.resolve().then(() => new XMLSerializer().serializeToString(svg)).then(encodeURIComponent).then((html) => `data:image/svg+xml;charset=utf-8,${html}`);
124
- }
125
- async function nodeToDataURL(node, width, height) {
126
- const xmlns = "http://www.w3.org/2000/svg";
127
- const svg = document.createElementNS(xmlns, "svg");
128
- const foreignObject = document.createElementNS(xmlns, "foreignObject");
129
- svg.setAttribute("width", `${width}`);
130
- svg.setAttribute("height", `${height}`);
131
- svg.setAttribute("viewBox", `0 0 ${width} ${height}`);
132
- foreignObject.setAttribute("width", "100%");
133
- foreignObject.setAttribute("height", "100%");
134
- foreignObject.setAttribute("x", "0");
135
- foreignObject.setAttribute("y", "0");
136
- foreignObject.setAttribute("externalResourcesRequired", "true");
137
- svg.appendChild(foreignObject);
138
- foreignObject.appendChild(node);
139
- return svgToDataURL(svg);
216
+ const blobToDataUrl = (blob) => readBlob(blob, "dataUrl");
217
+ function createImage(url, ownerDocument) {
218
+ const img = getDocument(ownerDocument).createElement("img");
219
+ img.decoding = "sync";
220
+ img.loading = "eager";
221
+ img.src = url;
222
+ return img;
140
223
  }
141
- const isInstanceOfElement = (node, instance) => {
142
- if (node instanceof instance)
143
- return true;
144
- const nodePrototype = Object.getPrototypeOf(node);
145
- if (nodePrototype === null)
146
- return false;
147
- return nodePrototype.constructor.name === instance.name || isInstanceOfElement(nodePrototype, instance);
148
- };
149
- function formatCSSText(style) {
150
- const content = style.getPropertyValue("content");
151
- return `${style.cssText} content: '${content.replace(/'|"/g, "")}';`;
224
+ function loadMedia(media, options) {
225
+ return new Promise((resolve) => {
226
+ const { timeout, ownerDocument, onError: userOnError, onWarn } = options ?? {};
227
+ const node = typeof media === "string" ? createImage(media, getDocument(ownerDocument)) : media;
228
+ let timer = null;
229
+ let removeEventListeners = null;
230
+ function onResolve() {
231
+ resolve(node);
232
+ timer && clearTimeout(timer);
233
+ removeEventListeners == null ? void 0 : removeEventListeners();
234
+ }
235
+ if (timeout) {
236
+ timer = setTimeout(onResolve, timeout);
237
+ }
238
+ if (isVideoElement(node)) {
239
+ const currentSrc = node.currentSrc || node.src;
240
+ if (!currentSrc) {
241
+ if (node.poster) {
242
+ return loadMedia(node.poster, options).then(resolve);
243
+ }
244
+ return onResolve();
245
+ }
246
+ if (node.readyState >= 2) {
247
+ return onResolve();
248
+ }
249
+ const onLoadeddata = onResolve;
250
+ const onError = (error) => {
251
+ onWarn == null ? void 0 : onWarn(
252
+ "Failed video load",
253
+ currentSrc,
254
+ error
255
+ );
256
+ userOnError == null ? void 0 : userOnError(error);
257
+ onResolve();
258
+ };
259
+ removeEventListeners = () => {
260
+ node.removeEventListener("loadeddata", onLoadeddata);
261
+ node.removeEventListener("error", onError);
262
+ };
263
+ node.addEventListener("loadeddata", onLoadeddata, { once: true });
264
+ node.addEventListener("error", onError, { once: true });
265
+ } else {
266
+ const currentSrc = isSVGImageElementNode(node) ? node.href.baseVal : node.currentSrc || node.src;
267
+ if (!currentSrc) {
268
+ return onResolve();
269
+ }
270
+ const onLoad = async () => {
271
+ if (isImageElement(node) && "decode" in node) {
272
+ try {
273
+ await node.decode();
274
+ } catch (error) {
275
+ onWarn == null ? void 0 : onWarn(
276
+ "Failed to decode image, trying to render anyway",
277
+ node.dataset.originalSrc || currentSrc,
278
+ error
279
+ );
280
+ }
281
+ }
282
+ onResolve();
283
+ };
284
+ const onError = (error) => {
285
+ onWarn == null ? void 0 : onWarn(
286
+ "Failed image load",
287
+ node.dataset.originalSrc || currentSrc,
288
+ error
289
+ );
290
+ onResolve();
291
+ };
292
+ if (isImageElement(node) && node.complete) {
293
+ return onLoad();
294
+ }
295
+ removeEventListeners = () => {
296
+ node.removeEventListener("load", onLoad);
297
+ node.removeEventListener("error", onError);
298
+ };
299
+ node.addEventListener("load", onLoad, { once: true });
300
+ node.addEventListener("error", onError, { once: true });
301
+ }
302
+ });
152
303
  }
153
- function formatCSSProperties(style, options) {
154
- return getStyleProperties(options).map((name) => {
155
- const value = style.getPropertyValue(name);
156
- const priority = style.getPropertyPriority(name);
157
- return `${name}: ${value}${priority ? " !important" : ""};`;
158
- }).join(" ");
159
- }
160
- function getPseudoElementStyle(className, pseudo, style, options) {
161
- const selector = `.${className}:${pseudo}`;
162
- const cssText = style.cssText ? formatCSSText(style) : formatCSSProperties(style, options);
163
- return document.createTextNode(`${selector}{${cssText}}`);
164
- }
165
- function clonePseudoElement(nativeNode, clonedNode, pseudo, options) {
166
- const style = window.getComputedStyle(nativeNode, pseudo);
167
- const content = style.getPropertyValue("content");
168
- if (content === "" || content === "none") {
169
- return;
170
- }
171
- const className = uuid();
172
- try {
173
- clonedNode.className = `${clonedNode.className} ${className}`;
174
- } catch (err) {
175
- return;
304
+ async function waitUntilLoad(node, options) {
305
+ if (isHTMLElementNode(node)) {
306
+ if (isImageElement(node) || isVideoElement(node)) {
307
+ await loadMedia(node, options);
308
+ } else {
309
+ await Promise.all(
310
+ ["img", "video"].flatMap((selectors) => {
311
+ return Array.from(node.querySelectorAll(selectors)).map((el) => loadMedia(el, options));
312
+ })
313
+ );
314
+ }
176
315
  }
177
- const styleElement = document.createElement("style");
178
- styleElement.appendChild(getPseudoElementStyle(className, pseudo, style, options));
179
- clonedNode.appendChild(styleElement);
180
- }
181
- function clonePseudoElements(nativeNode, clonedNode, options) {
182
- clonePseudoElement(nativeNode, clonedNode, ":before", options);
183
- clonePseudoElement(nativeNode, clonedNode, ":after", options);
184
- }
185
- const WOFF = "application/font-woff";
186
- const JPEG = "image/jpeg";
187
- const mimes = {
188
- woff: WOFF,
189
- woff2: WOFF,
190
- ttf: "application/font-truetype",
191
- eot: "application/vnd.ms-fontobject",
192
- png: "image/png",
193
- jpg: JPEG,
194
- jpeg: JPEG,
195
- gif: "image/gif",
196
- tiff: "image/tiff",
197
- svg: "image/svg+xml",
198
- webp: "image/webp"
199
- };
200
- function getExtension(url) {
201
- const match = /\.([^./]*?)$/g.exec(url);
202
- return match ? match[1] : "";
203
316
  }
204
- function getMimeType(url) {
205
- const extension = getExtension(url).toLowerCase();
206
- return mimes[extension] || "";
317
+ const uuid = /* @__PURE__ */ function uuid2() {
318
+ let counter = 0;
319
+ const random = () => `0000${(Math.random() * 36 ** 4 << 0).toString(36)}`.slice(-4);
320
+ return () => {
321
+ counter += 1;
322
+ return `u${random()}${counter}`;
323
+ };
324
+ }();
325
+ function splitFontFamily(fontFamily) {
326
+ return fontFamily == null ? void 0 : fontFamily.split(",").map((val) => val.trim().replace(/"|'/g, "").toLowerCase()).filter(Boolean);
207
327
  }
208
- function getContentFromDataUrl(dataURL) {
209
- return dataURL.split(/,/)[1];
328
+ let uid = 0;
329
+ function createLogger(debug) {
330
+ const prefix = `${PREFIX}[#${uid}]`;
331
+ uid++;
332
+ return {
333
+ // eslint-disable-next-line no-console
334
+ time: (label) => debug && console.time(`${prefix} ${label}`),
335
+ // eslint-disable-next-line no-console
336
+ timeEnd: (label) => debug && console.timeEnd(`${prefix} ${label}`),
337
+ warn: (...args) => debug && consoleWarn(...args)
338
+ };
210
339
  }
211
- function isDataUrl(url) {
212
- return url.search(/^(data:)/) !== -1;
340
+ function getDefaultRequestInit(bypassingCache) {
341
+ return {
342
+ cache: bypassingCache ? "no-cache" : "force-cache"
343
+ };
213
344
  }
214
- function makeDataUrl(content, mimeType) {
215
- return `data:${mimeType};base64,${content}`;
345
+ async function orCreateContext(node, options) {
346
+ return isContext(node) ? node : createContext(node, { ...options, autoDestruct: true });
216
347
  }
217
- async function fetchAsDataURL(url, init, process2) {
218
- const res = await fetch(url, init);
219
- if (res.status === 404) {
220
- throw new Error(`Resource "${res.url}" not found`);
221
- }
222
- const blob = await res.blob();
223
- return new Promise((resolve, reject) => {
224
- const reader = new FileReader();
225
- reader.onerror = reject;
226
- reader.onloadend = () => {
348
+ async function createContext(node, options) {
349
+ var _a2, _b;
350
+ const { scale = 1, workerUrl, workerNumber = 1 } = options || {};
351
+ const debug = Boolean(options == null ? void 0 : options.debug);
352
+ const features = (options == null ? void 0 : options.features) ?? true;
353
+ const ownerDocument = node.ownerDocument ?? (IN_BROWSER ? window.document : void 0);
354
+ const ownerWindow = ((_a2 = node.ownerDocument) == null ? void 0 : _a2.defaultView) ?? (IN_BROWSER ? window : void 0);
355
+ const requests = /* @__PURE__ */ new Map();
356
+ const context = {
357
+ // Options
358
+ width: 0,
359
+ height: 0,
360
+ quality: 1,
361
+ type: "image/png",
362
+ scale,
363
+ backgroundColor: null,
364
+ style: null,
365
+ filter: null,
366
+ maximumCanvasSize: 0,
367
+ timeout: 3e4,
368
+ progress: null,
369
+ debug,
370
+ fetch: {
371
+ requestInit: getDefaultRequestInit((_b = options == null ? void 0 : options.fetch) == null ? void 0 : _b.bypassingCache),
372
+ placeholderImage: "data:image/png;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7",
373
+ bypassingCache: false,
374
+ ...options == null ? void 0 : options.fetch
375
+ },
376
+ fetchFn: null,
377
+ font: {},
378
+ drawImageInterval: 100,
379
+ workerUrl: null,
380
+ workerNumber,
381
+ onCloneNode: null,
382
+ onEmbedNode: null,
383
+ onCreateForeignObjectSvg: null,
384
+ includeStyleProperties: null,
385
+ autoDestruct: false,
386
+ ...options,
387
+ // InternalContext
388
+ __CONTEXT__: true,
389
+ log: createLogger(debug),
390
+ node,
391
+ ownerDocument,
392
+ ownerWindow,
393
+ dpi: scale === 1 ? null : 96 * scale,
394
+ svgStyleElement: createStyleElement(ownerDocument),
395
+ svgDefsElement: ownerDocument == null ? void 0 : ownerDocument.createElementNS(XMLNS, "defs"),
396
+ svgStyles: /* @__PURE__ */ new Map(),
397
+ defaultComputedStyles: /* @__PURE__ */ new Map(),
398
+ workers: [
399
+ ...Array.from({
400
+ length: SUPPORT_WEB_WORKER && workerUrl && workerNumber ? workerNumber : 0
401
+ })
402
+ ].map(() => {
227
403
  try {
228
- resolve(process2({ res, result: reader.result }));
404
+ const worker = new Worker(workerUrl);
405
+ worker.onmessage = async (event) => {
406
+ var _a3, _b2, _c, _d;
407
+ const { url, result } = event.data;
408
+ if (result) {
409
+ (_b2 = (_a3 = requests.get(url)) == null ? void 0 : _a3.resolve) == null ? void 0 : _b2.call(_a3, result);
410
+ } else {
411
+ (_d = (_c = requests.get(url)) == null ? void 0 : _c.reject) == null ? void 0 : _d.call(_c, new Error(`Error receiving message from worker: ${url}`));
412
+ }
413
+ };
414
+ worker.onmessageerror = (event) => {
415
+ var _a3, _b2;
416
+ const { url } = event.data;
417
+ (_b2 = (_a3 = requests.get(url)) == null ? void 0 : _a3.reject) == null ? void 0 : _b2.call(_a3, new Error(`Error receiving message from worker: ${url}`));
418
+ };
419
+ return worker;
229
420
  } catch (error) {
230
- reject(error);
421
+ context.log.warn("Failed to new Worker", error);
422
+ return null;
231
423
  }
232
- };
233
- reader.readAsDataURL(blob);
234
- });
424
+ }).filter(Boolean),
425
+ fontFamilies: /* @__PURE__ */ new Map(),
426
+ fontCssTexts: /* @__PURE__ */ new Map(),
427
+ acceptOfImage: `${[
428
+ supportWebp(ownerDocument) && "image/webp",
429
+ "image/svg+xml",
430
+ "image/*",
431
+ "*/*"
432
+ ].filter(Boolean).join(",")};q=0.8`,
433
+ requests,
434
+ drawImageCount: 0,
435
+ tasks: [],
436
+ features,
437
+ isEnable: (key) => {
438
+ if (key === "restoreScrollPosition") {
439
+ return typeof features === "boolean" ? false : features[key] ?? false;
440
+ }
441
+ if (typeof features === "boolean") {
442
+ return features;
443
+ }
444
+ return features[key] ?? true;
445
+ }
446
+ };
447
+ context.log.time("wait until load");
448
+ await waitUntilLoad(node, { timeout: context.timeout, onWarn: context.log.warn });
449
+ context.log.timeEnd("wait until load");
450
+ const { width, height } = resolveBoundingBox(node, context);
451
+ context.width = width;
452
+ context.height = height;
453
+ return context;
235
454
  }
236
- const cache = {};
237
- function getCacheKey(url, contentType, includeQueryParams) {
238
- let key = url.replace(/\?.*/, "");
239
- if (includeQueryParams) {
240
- key = url;
455
+ function createStyleElement(ownerDocument) {
456
+ if (!ownerDocument)
457
+ return void 0;
458
+ const style = ownerDocument.createElement("style");
459
+ const cssText = style.ownerDocument.createTextNode(`
460
+ .______background-clip--text {
461
+ background-clip: text;
462
+ -webkit-background-clip: text;
463
+ }
464
+ `);
465
+ style.appendChild(cssText);
466
+ return style;
467
+ }
468
+ function resolveBoundingBox(node, context) {
469
+ let { width, height } = context;
470
+ if (isElementNode(node) && (!width || !height)) {
471
+ const box = node.getBoundingClientRect();
472
+ width = width || box.width || Number(node.getAttribute("width")) || 0;
473
+ height = height || box.height || Number(node.getAttribute("height")) || 0;
241
474
  }
242
- if (/ttf|otf|eot|woff2?/i.test(key)) {
243
- key = key.replace(/.*\//, "");
475
+ return { width, height };
476
+ }
477
+ async function imageToCanvas(image, context) {
478
+ const {
479
+ log: log2,
480
+ timeout,
481
+ drawImageCount,
482
+ drawImageInterval
483
+ } = context;
484
+ log2.time("image to canvas");
485
+ const loaded = await loadMedia(image, { timeout, onWarn: context.log.warn });
486
+ const { canvas, context2d } = createCanvas(image.ownerDocument, context);
487
+ const drawImage = () => {
488
+ try {
489
+ context2d == null ? void 0 : context2d.drawImage(loaded, 0, 0, canvas.width, canvas.height);
490
+ } catch (error) {
491
+ context.log.warn("Failed to drawImage", error);
492
+ }
493
+ };
494
+ drawImage();
495
+ if (context.isEnable("fixSvgXmlDecode")) {
496
+ for (let i = 0; i < drawImageCount; i++) {
497
+ await new Promise((resolve) => {
498
+ setTimeout(() => {
499
+ drawImage();
500
+ resolve();
501
+ }, i + drawImageInterval);
502
+ });
503
+ }
244
504
  }
245
- return contentType ? `[${contentType}]${key}` : key;
505
+ context.drawImageCount = 0;
506
+ log2.timeEnd("image to canvas");
507
+ return canvas;
246
508
  }
247
- async function resourceToDataURL(resourceUrl, contentType, options) {
248
- const cacheKey = getCacheKey(resourceUrl, contentType, options.includeQueryParams);
249
- if (cache[cacheKey] != null) {
250
- return cache[cacheKey];
509
+ function createCanvas(ownerDocument, context) {
510
+ const { width, height, scale, backgroundColor, maximumCanvasSize: max } = context;
511
+ const canvas = ownerDocument.createElement("canvas");
512
+ canvas.width = Math.floor(width * scale);
513
+ canvas.height = Math.floor(height * scale);
514
+ canvas.style.width = `${width}px`;
515
+ canvas.style.height = `${height}px`;
516
+ if (max) {
517
+ if (canvas.width > max || canvas.height > max) {
518
+ if (canvas.width > max && canvas.height > max) {
519
+ if (canvas.width > canvas.height) {
520
+ canvas.height *= max / canvas.width;
521
+ canvas.width = max;
522
+ } else {
523
+ canvas.width *= max / canvas.height;
524
+ canvas.height = max;
525
+ }
526
+ } else if (canvas.width > max) {
527
+ canvas.height *= max / canvas.width;
528
+ canvas.width = max;
529
+ } else {
530
+ canvas.width *= max / canvas.height;
531
+ canvas.height = max;
532
+ }
533
+ }
251
534
  }
252
- if (options.cacheBust) {
253
- resourceUrl += (/\?/.test(resourceUrl) ? "&" : "?") + (/* @__PURE__ */ new Date()).getTime();
535
+ const context2d = canvas.getContext("2d");
536
+ if (context2d && backgroundColor) {
537
+ context2d.fillStyle = backgroundColor;
538
+ context2d.fillRect(0, 0, canvas.width, canvas.height);
254
539
  }
255
- let dataURL;
256
- try {
257
- const content = await fetchAsDataURL(resourceUrl, options.fetchRequestInit, ({ res, result }) => {
258
- if (!contentType) {
259
- contentType = res.headers.get("Content-Type") || "";
540
+ return { canvas, context2d };
541
+ }
542
+ function cloneCanvas(canvas, context) {
543
+ if (canvas.ownerDocument) {
544
+ try {
545
+ const dataURL = canvas.toDataURL();
546
+ if (dataURL !== "data:,") {
547
+ return createImage(dataURL, canvas.ownerDocument);
260
548
  }
261
- return getContentFromDataUrl(result);
262
- });
263
- dataURL = makeDataUrl(content, contentType);
549
+ } catch (error) {
550
+ context.log.warn("Failed to clone canvas", error);
551
+ }
552
+ }
553
+ const cloned = canvas.cloneNode(false);
554
+ const ctx = canvas.getContext("2d");
555
+ const clonedCtx = cloned.getContext("2d");
556
+ try {
557
+ if (ctx && clonedCtx) {
558
+ clonedCtx.putImageData(
559
+ ctx.getImageData(0, 0, canvas.width, canvas.height),
560
+ 0,
561
+ 0
562
+ );
563
+ }
564
+ return cloned;
264
565
  } catch (error) {
265
- dataURL = options.imagePlaceholder || "";
266
- let msg = `Failed to fetch resource: ${resourceUrl}`;
267
- if (error) {
268
- msg = typeof error === "string" ? error : error.message;
269
- }
270
- if (msg) {
271
- console.warn(msg);
272
- }
273
- }
274
- cache[cacheKey] = dataURL;
275
- return dataURL;
276
- }
277
- async function cloneCanvasElement(canvas) {
278
- const dataURL = canvas.toDataURL();
279
- if (dataURL === "data:,") {
280
- return canvas.cloneNode(false);
281
- }
282
- return createImage(dataURL);
283
- }
284
- async function cloneVideoElement(video, options) {
285
- if (video.currentSrc) {
286
- const canvas = document.createElement("canvas");
287
- const ctx = canvas.getContext("2d");
288
- canvas.width = video.clientWidth;
289
- canvas.height = video.clientHeight;
290
- ctx === null || ctx === void 0 ? void 0 : ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
291
- const dataURL2 = canvas.toDataURL();
292
- return createImage(dataURL2);
293
- }
294
- const poster = video.poster;
295
- const contentType = getMimeType(poster);
296
- const dataURL = await resourceToDataURL(poster, contentType, options);
297
- return createImage(dataURL);
298
- }
299
- async function cloneIFrameElement(iframe, options) {
300
- var _a;
566
+ context.log.warn("Failed to clone canvas", error);
567
+ }
568
+ return cloned;
569
+ }
570
+ function cloneIframe(iframe, context) {
571
+ var _a2;
301
572
  try {
302
- if ((_a = iframe === null || iframe === void 0 ? void 0 : iframe.contentDocument) === null || _a === void 0 ? void 0 : _a.body) {
303
- return await cloneNode(iframe.contentDocument.body, options, true);
573
+ if ((_a2 = iframe == null ? void 0 : iframe.contentDocument) == null ? void 0 : _a2.body) {
574
+ return cloneNode(iframe.contentDocument.body, context);
304
575
  }
305
- } catch (_b) {
576
+ } catch (error) {
577
+ context.log.warn("Failed to clone iframe", error);
306
578
  }
307
579
  return iframe.cloneNode(false);
308
580
  }
309
- async function cloneSingleNode(node, options) {
310
- if (isInstanceOfElement(node, HTMLCanvasElement)) {
311
- return cloneCanvasElement(node);
581
+ function cloneImage(image) {
582
+ const cloned = image.cloneNode(false);
583
+ if (image.currentSrc && image.currentSrc !== image.src) {
584
+ cloned.src = image.currentSrc;
585
+ cloned.srcset = "";
586
+ }
587
+ if (cloned.loading === "lazy") {
588
+ cloned.loading = "eager";
589
+ }
590
+ return cloned;
591
+ }
592
+ async function cloneVideo(video, context) {
593
+ if (video.ownerDocument && !video.currentSrc && video.poster) {
594
+ return createImage(video.poster, video.ownerDocument);
312
595
  }
313
- if (isInstanceOfElement(node, HTMLVideoElement)) {
314
- return cloneVideoElement(node, options);
596
+ const cloned = video.cloneNode(false);
597
+ cloned.crossOrigin = "anonymous";
598
+ if (video.currentSrc && video.currentSrc !== video.src) {
599
+ cloned.src = video.currentSrc;
315
600
  }
316
- if (isInstanceOfElement(node, HTMLIFrameElement)) {
317
- return cloneIFrameElement(node, options);
601
+ const ownerDocument = cloned.ownerDocument;
602
+ if (ownerDocument) {
603
+ let canPlay = true;
604
+ await loadMedia(cloned, { onError: () => canPlay = false, onWarn: context.log.warn });
605
+ if (!canPlay) {
606
+ if (video.poster) {
607
+ return createImage(video.poster, video.ownerDocument);
608
+ }
609
+ return cloned;
610
+ }
611
+ cloned.currentTime = video.currentTime;
612
+ await new Promise((resolve) => {
613
+ cloned.addEventListener("seeked", resolve, { once: true });
614
+ });
615
+ const canvas = ownerDocument.createElement("canvas");
616
+ canvas.width = video.offsetWidth;
617
+ canvas.height = video.offsetHeight;
618
+ try {
619
+ const ctx = canvas.getContext("2d");
620
+ if (ctx)
621
+ ctx.drawImage(cloned, 0, 0, canvas.width, canvas.height);
622
+ } catch (error) {
623
+ context.log.warn("Failed to clone video", error);
624
+ if (video.poster) {
625
+ return createImage(video.poster, video.ownerDocument);
626
+ }
627
+ return cloned;
628
+ }
629
+ return cloneCanvas(canvas, context);
318
630
  }
319
- return node.cloneNode(isSVGElement(node));
631
+ return cloned;
320
632
  }
321
- const isSlotElement = (node) => node.tagName != null && node.tagName.toUpperCase() === "SLOT";
322
- const isSVGElement = (node) => node.tagName != null && node.tagName.toUpperCase() === "SVG";
323
- async function cloneChildren(nativeNode, clonedNode, options) {
324
- var _a, _b;
325
- if (isSVGElement(clonedNode)) {
326
- return clonedNode;
633
+ function cloneElement(node, context) {
634
+ if (isCanvasElement(node)) {
635
+ return cloneCanvas(node, context);
327
636
  }
328
- let children = [];
329
- if (isSlotElement(nativeNode) && nativeNode.assignedNodes) {
330
- children = toArray(nativeNode.assignedNodes());
331
- } else if (isInstanceOfElement(nativeNode, HTMLIFrameElement) && ((_a = nativeNode.contentDocument) === null || _a === void 0 ? void 0 : _a.body)) {
332
- children = toArray(nativeNode.contentDocument.body.childNodes);
333
- } else {
334
- children = toArray(((_b = nativeNode.shadowRoot) !== null && _b !== void 0 ? _b : nativeNode).childNodes);
637
+ if (isIFrameElement(node)) {
638
+ return cloneIframe(node, context);
639
+ }
640
+ if (isImageElement(node)) {
641
+ return cloneImage(node);
335
642
  }
336
- if (children.length === 0 || isInstanceOfElement(nativeNode, HTMLVideoElement)) {
337
- return clonedNode;
643
+ if (isVideoElement(node)) {
644
+ return cloneVideo(node, context);
338
645
  }
339
- await children.reduce((deferred, child) => deferred.then(() => cloneNode(child, options)).then((clonedChild) => {
340
- if (clonedChild) {
341
- clonedNode.appendChild(clonedChild);
646
+ return node.cloneNode(false);
647
+ }
648
+ function getSandBox(context) {
649
+ var _a2;
650
+ let sandbox = context.sandbox;
651
+ if (!sandbox) {
652
+ const { ownerDocument } = context;
653
+ try {
654
+ if (ownerDocument) {
655
+ sandbox = ownerDocument.createElement("iframe");
656
+ sandbox.id = `__SANDBOX__-${uuid()}`;
657
+ sandbox.width = "0";
658
+ sandbox.height = "0";
659
+ sandbox.style.visibility = "hidden";
660
+ sandbox.style.position = "fixed";
661
+ ownerDocument.body.appendChild(sandbox);
662
+ (_a2 = sandbox.contentWindow) == null ? void 0 : _a2.document.write('<!DOCTYPE html><meta charset="UTF-8"><title></title><body>');
663
+ context.sandbox = sandbox;
664
+ }
665
+ } catch (error) {
666
+ context.log.warn("Failed to getSandBox", error);
342
667
  }
343
- }), Promise.resolve());
344
- return clonedNode;
668
+ }
669
+ return sandbox;
345
670
  }
346
- function cloneCSSStyle(nativeNode, clonedNode, options) {
347
- const targetStyle = clonedNode.style;
348
- if (!targetStyle) {
349
- return;
671
+ const ignoredStyles = [
672
+ "width",
673
+ "height",
674
+ "-webkit-text-fill-color"
675
+ ];
676
+ const includedAttributes = [
677
+ "stroke",
678
+ "fill"
679
+ ];
680
+ function getDefaultStyle(node, pseudoElement, context) {
681
+ const { defaultComputedStyles } = context;
682
+ const nodeName = node.nodeName.toLowerCase();
683
+ const isSvgNode = isSVGElementNode(node) && nodeName !== "svg";
684
+ const attributes = isSvgNode ? includedAttributes.map((name) => [name, node.getAttribute(name)]).filter(([, value]) => value !== null) : [];
685
+ const key = [
686
+ isSvgNode && "svg",
687
+ nodeName,
688
+ attributes.map((name, value) => `${name}=${value}`).join(","),
689
+ pseudoElement
690
+ ].filter(Boolean).join(":");
691
+ if (defaultComputedStyles.has(key))
692
+ return defaultComputedStyles.get(key);
693
+ const sandbox = getSandBox(context);
694
+ const sandboxWindow = sandbox == null ? void 0 : sandbox.contentWindow;
695
+ if (!sandboxWindow)
696
+ return /* @__PURE__ */ new Map();
697
+ const sandboxDocument = sandboxWindow == null ? void 0 : sandboxWindow.document;
698
+ let root;
699
+ let el;
700
+ if (isSvgNode) {
701
+ root = sandboxDocument.createElementNS(XMLNS, "svg");
702
+ el = root.ownerDocument.createElementNS(root.namespaceURI, nodeName);
703
+ attributes.forEach(([name, value]) => {
704
+ el.setAttributeNS(null, name, value);
705
+ });
706
+ root.appendChild(el);
707
+ } else {
708
+ root = el = sandboxDocument.createElement(nodeName);
709
+ }
710
+ el.textContent = " ";
711
+ sandboxDocument.body.appendChild(root);
712
+ const computedStyle = sandboxWindow.getComputedStyle(el, pseudoElement);
713
+ const styles = /* @__PURE__ */ new Map();
714
+ for (let len = computedStyle.length, i = 0; i < len; i++) {
715
+ const name = computedStyle.item(i);
716
+ if (ignoredStyles.includes(name))
717
+ continue;
718
+ styles.set(name, computedStyle.getPropertyValue(name));
350
719
  }
351
- const sourceStyle = window.getComputedStyle(nativeNode);
352
- if (sourceStyle.cssText) {
353
- targetStyle.cssText = sourceStyle.cssText;
354
- targetStyle.transformOrigin = sourceStyle.transformOrigin;
720
+ sandboxDocument.body.removeChild(root);
721
+ defaultComputedStyles.set(key, styles);
722
+ return styles;
723
+ }
724
+ function getDiffStyle(style, defaultStyle, includeStyleProperties) {
725
+ var _a2;
726
+ const diffStyle = /* @__PURE__ */ new Map();
727
+ const prefixs = [];
728
+ const prefixTree = /* @__PURE__ */ new Map();
729
+ if (includeStyleProperties) {
730
+ for (const name of includeStyleProperties) {
731
+ applyTo(name);
732
+ }
355
733
  } else {
356
- getStyleProperties(options).forEach((name) => {
357
- let value = sourceStyle.getPropertyValue(name);
358
- if (name === "font-size" && value.endsWith("px")) {
359
- const reducedFont = Math.floor(parseFloat(value.substring(0, value.length - 2))) - 0.1;
360
- value = `${reducedFont}px`;
361
- }
362
- if (isInstanceOfElement(nativeNode, HTMLIFrameElement) && name === "display" && value === "inline") {
363
- value = "block";
364
- }
365
- if (name === "d" && clonedNode.getAttribute("d")) {
366
- value = `path(${clonedNode.getAttribute("d")})`;
734
+ for (let len = style.length, i = 0; i < len; i++) {
735
+ const name = style.item(i);
736
+ applyTo(name);
737
+ }
738
+ }
739
+ for (let len = prefixs.length, i = 0; i < len; i++) {
740
+ (_a2 = prefixTree.get(prefixs[i])) == null ? void 0 : _a2.forEach((value, name) => diffStyle.set(name, value));
741
+ }
742
+ function applyTo(name) {
743
+ const value = style.getPropertyValue(name);
744
+ const priority = style.getPropertyPriority(name);
745
+ const subIndex = name.lastIndexOf("-");
746
+ const prefix = subIndex > -1 ? name.substring(0, subIndex) : void 0;
747
+ if (prefix) {
748
+ let map = prefixTree.get(prefix);
749
+ if (!map) {
750
+ map = /* @__PURE__ */ new Map();
751
+ prefixTree.set(prefix, map);
367
752
  }
368
- targetStyle.setProperty(name, value, sourceStyle.getPropertyPriority(name));
369
- });
753
+ map.set(name, [value, priority]);
754
+ }
755
+ if (defaultStyle.get(name) === value && !priority)
756
+ return;
757
+ if (prefix) {
758
+ prefixs.push(prefix);
759
+ } else {
760
+ diffStyle.set(name, [value, priority]);
761
+ }
370
762
  }
763
+ return diffStyle;
371
764
  }
372
- function cloneInputValue(nativeNode, clonedNode) {
373
- if (isInstanceOfElement(nativeNode, HTMLTextAreaElement)) {
374
- clonedNode.innerHTML = nativeNode.value;
765
+ function copyCssStyles(node, cloned, isRoot, context) {
766
+ var _a2, _b, _c, _d;
767
+ const { ownerWindow, includeStyleProperties, currentParentNodeStyle } = context;
768
+ const clonedStyle = cloned.style;
769
+ const computedStyle = ownerWindow.getComputedStyle(node);
770
+ const defaultStyle = getDefaultStyle(node, null, context);
771
+ currentParentNodeStyle == null ? void 0 : currentParentNodeStyle.forEach((_, key) => {
772
+ defaultStyle.delete(key);
773
+ });
774
+ const style = getDiffStyle(computedStyle, defaultStyle, includeStyleProperties);
775
+ style.delete("transition-property");
776
+ style.delete("all");
777
+ style.delete("d");
778
+ style.delete("content");
779
+ if (isRoot) {
780
+ style.delete("margin-top");
781
+ style.delete("margin-right");
782
+ style.delete("margin-bottom");
783
+ style.delete("margin-left");
784
+ style.delete("margin-block-start");
785
+ style.delete("margin-block-end");
786
+ style.delete("margin-inline-start");
787
+ style.delete("margin-inline-end");
788
+ style.set("box-sizing", ["border-box", ""]);
375
789
  }
376
- if (isInstanceOfElement(nativeNode, HTMLInputElement)) {
377
- clonedNode.setAttribute("value", nativeNode.value);
790
+ if (((_a2 = style.get("background-clip")) == null ? void 0 : _a2[0]) === "text") {
791
+ cloned.classList.add("______background-clip--text");
378
792
  }
379
- }
380
- function cloneSelectValue(nativeNode, clonedNode) {
381
- if (isInstanceOfElement(nativeNode, HTMLSelectElement)) {
382
- const clonedSelect = clonedNode;
383
- const selectedOption = Array.from(clonedSelect.children).find((child) => nativeNode.value === child.getAttribute("value"));
384
- if (selectedOption) {
385
- selectedOption.setAttribute("selected", "");
793
+ if (IN_CHROME) {
794
+ if (!style.has("font-kerning"))
795
+ style.set("font-kerning", ["normal", ""]);
796
+ if ((((_b = style.get("overflow-x")) == null ? void 0 : _b[0]) === "hidden" || ((_c = style.get("overflow-y")) == null ? void 0 : _c[0]) === "hidden") && ((_d = style.get("text-overflow")) == null ? void 0 : _d[0]) === "ellipsis" && node.scrollWidth === node.clientWidth) {
797
+ style.set("text-overflow", ["clip", ""]);
386
798
  }
387
799
  }
388
- }
389
- function decorate(nativeNode, clonedNode, options) {
390
- if (isInstanceOfElement(clonedNode, Element)) {
391
- cloneCSSStyle(nativeNode, clonedNode, options);
392
- clonePseudoElements(nativeNode, clonedNode, options);
393
- cloneInputValue(nativeNode, clonedNode);
394
- cloneSelectValue(nativeNode, clonedNode);
800
+ for (let len = clonedStyle.length, i = 0; i < len; i++) {
801
+ clonedStyle.removeProperty(clonedStyle.item(i));
395
802
  }
396
- return clonedNode;
803
+ style.forEach(([value, priority], name) => {
804
+ clonedStyle.setProperty(name, value, priority);
805
+ });
806
+ return style;
397
807
  }
398
- async function ensureSVGSymbols(clone, options) {
399
- const uses = clone.querySelectorAll ? clone.querySelectorAll("use") : [];
400
- if (uses.length === 0) {
401
- return clone;
808
+ function copyInputValue(node, cloned) {
809
+ if (isTextareaElement(node) || isInputElement(node) || isSelectElement(node)) {
810
+ cloned.setAttribute("value", node.value);
402
811
  }
403
- const processedDefs = {};
404
- for (let i = 0; i < uses.length; i++) {
405
- const use = uses[i];
406
- const id = use.getAttribute("xlink:href");
407
- if (id) {
408
- const exist = clone.querySelector(id);
409
- const definition = document.querySelector(id);
410
- if (!exist && definition && !processedDefs[id]) {
411
- processedDefs[id] = await cloneNode(definition, options, true);
412
- }
812
+ }
813
+ const pseudoClasses = [
814
+ ":before",
815
+ ":after"
816
+ // ':placeholder', TODO
817
+ ];
818
+ const scrollbarPseudoClasses = [
819
+ ":-webkit-scrollbar",
820
+ ":-webkit-scrollbar-button",
821
+ // ':-webkit-scrollbar:horizontal', TODO
822
+ ":-webkit-scrollbar-thumb",
823
+ ":-webkit-scrollbar-track",
824
+ ":-webkit-scrollbar-track-piece",
825
+ // ':-webkit-scrollbar:vertical', TODO
826
+ ":-webkit-scrollbar-corner",
827
+ ":-webkit-resizer"
828
+ ];
829
+ function copyPseudoClass(node, cloned, copyScrollbar, context, addWordToFontFamilies) {
830
+ const { ownerWindow, svgStyleElement, svgStyles, currentNodeStyle } = context;
831
+ if (!svgStyleElement || !ownerWindow)
832
+ return;
833
+ function copyBy(pseudoClass) {
834
+ var _a2;
835
+ const computedStyle = ownerWindow.getComputedStyle(node, pseudoClass);
836
+ let content = computedStyle.getPropertyValue("content");
837
+ if (!content || content === "none")
838
+ return;
839
+ addWordToFontFamilies == null ? void 0 : addWordToFontFamilies(content);
840
+ content = content.replace(/(')|(")|(counter\(.+\))/g, "");
841
+ const klasses = [uuid()];
842
+ const defaultStyle = getDefaultStyle(node, pseudoClass, context);
843
+ currentNodeStyle == null ? void 0 : currentNodeStyle.forEach((_, key) => {
844
+ defaultStyle.delete(key);
845
+ });
846
+ const style = getDiffStyle(computedStyle, defaultStyle, context.includeStyleProperties);
847
+ style.delete("content");
848
+ style.delete("-webkit-locale");
849
+ if (((_a2 = style.get("background-clip")) == null ? void 0 : _a2[0]) === "text") {
850
+ cloned.classList.add("______background-clip--text");
413
851
  }
414
- }
415
- const nodes = Object.values(processedDefs);
416
- if (nodes.length) {
417
- const ns = "http://www.w3.org/1999/xhtml";
418
- const svg = document.createElementNS(ns, "svg");
419
- svg.setAttribute("xmlns", ns);
420
- svg.style.position = "absolute";
421
- svg.style.width = "0";
422
- svg.style.height = "0";
423
- svg.style.overflow = "hidden";
424
- svg.style.display = "none";
425
- const defs = document.createElementNS(ns, "defs");
426
- svg.appendChild(defs);
427
- for (let i = 0; i < nodes.length; i++) {
428
- defs.appendChild(nodes[i]);
852
+ const cloneStyle = [
853
+ `content: '${content}';`
854
+ ];
855
+ style.forEach(([value, priority], name) => {
856
+ cloneStyle.push(`${name}: ${value}${priority ? " !important" : ""};`);
857
+ });
858
+ if (cloneStyle.length === 1)
859
+ return;
860
+ try {
861
+ cloned.className = [cloned.className, ...klasses].join(" ");
862
+ } catch (err) {
863
+ context.log.warn("Failed to copyPseudoClass", err);
864
+ return;
865
+ }
866
+ const cssText = cloneStyle.join("\n ");
867
+ let allClasses = svgStyles.get(cssText);
868
+ if (!allClasses) {
869
+ allClasses = [];
870
+ svgStyles.set(cssText, allClasses);
429
871
  }
430
- clone.appendChild(svg);
872
+ allClasses.push(`.${klasses[0]}:${pseudoClass}`);
431
873
  }
432
- return clone;
874
+ pseudoClasses.forEach(copyBy);
875
+ if (copyScrollbar)
876
+ scrollbarPseudoClasses.forEach(copyBy);
433
877
  }
434
- async function cloneNode(node, options, isRoot) {
435
- if (!isRoot && options.filter && !options.filter(node)) {
436
- return null;
878
+ const excludeParentNodes = /* @__PURE__ */ new Set([
879
+ "symbol"
880
+ // test/fixtures/svg.symbol.html
881
+ ]);
882
+ async function appendChildNode(node, cloned, child, context, addWordToFontFamilies) {
883
+ if (isElementNode(child) && (isStyleElement(child) || isScriptElement(child)))
884
+ return;
885
+ if (context.filter && !context.filter(child))
886
+ return;
887
+ if (excludeParentNodes.has(cloned.nodeName) || excludeParentNodes.has(child.nodeName)) {
888
+ context.currentParentNodeStyle = void 0;
889
+ } else {
890
+ context.currentParentNodeStyle = context.currentNodeStyle;
437
891
  }
438
- return Promise.resolve(node).then((clonedNode) => cloneSingleNode(clonedNode, options)).then((clonedNode) => cloneChildren(node, clonedNode, options)).then((clonedNode) => decorate(node, clonedNode, options)).then((clonedNode) => ensureSVGSymbols(clonedNode, options));
892
+ const childCloned = await cloneNode(child, context, false, addWordToFontFamilies);
893
+ if (context.isEnable("restoreScrollPosition")) {
894
+ restoreScrollPosition(node, childCloned);
895
+ }
896
+ cloned.appendChild(childCloned);
439
897
  }
440
- const URL_REGEX = /url\((['"]?)([^'"]+?)\1\)/g;
441
- const URL_WITH_FORMAT_REGEX = /url\([^)]+\)\s*format\((["']?)([^"']+)\1\)/g;
442
- const FONT_SRC_REGEX = /src:\s*(?:url\([^)]+\)\s*format\([^)]+\)[,;]\s*)+/g;
443
- function toRegex(url) {
444
- const escaped = url.replace(/([.*+?^${}()|\[\]\/\\])/g, "\\$1");
445
- return new RegExp(`(url\\(['"]?)(${escaped})(['"]?\\))`, "g");
898
+ async function cloneChildNodes(node, cloned, context, addWordToFontFamilies) {
899
+ var _a2;
900
+ const firstChild = (isElementNode(node) ? (_a2 = node.shadowRoot) == null ? void 0 : _a2.firstChild : void 0) ?? node.firstChild;
901
+ for (let child = firstChild; child; child = child.nextSibling) {
902
+ if (isCommentNode(child))
903
+ continue;
904
+ if (isElementNode(child) && isSlotElement(child) && typeof child.assignedNodes === "function") {
905
+ const nodes = child.assignedNodes();
906
+ for (let i = 0; i < nodes.length; i++) {
907
+ await appendChildNode(node, cloned, nodes[i], context, addWordToFontFamilies);
908
+ }
909
+ } else {
910
+ await appendChildNode(node, cloned, child, context, addWordToFontFamilies);
911
+ }
912
+ }
446
913
  }
447
- function parseURLs(cssText) {
448
- const urls = [];
449
- cssText.replace(URL_REGEX, (raw, quotation, url) => {
450
- urls.push(url);
451
- return raw;
452
- });
453
- return urls.filter((url) => !isDataUrl(url));
914
+ function restoreScrollPosition(node, chlidCloned) {
915
+ if (!isHTMLElementNode(node) || !isHTMLElementNode(chlidCloned))
916
+ return;
917
+ const { scrollTop, scrollLeft } = node;
918
+ if (!scrollTop && !scrollLeft) {
919
+ return;
920
+ }
921
+ const { transform } = chlidCloned.style;
922
+ const matrix = new DOMMatrix(transform);
923
+ const { a, b, c, d } = matrix;
924
+ matrix.a = 1;
925
+ matrix.b = 0;
926
+ matrix.c = 0;
927
+ matrix.d = 1;
928
+ matrix.translateSelf(-scrollLeft, -scrollTop);
929
+ matrix.a = a;
930
+ matrix.b = b;
931
+ matrix.c = c;
932
+ matrix.d = d;
933
+ chlidCloned.style.transform = matrix.toString();
454
934
  }
455
- async function embed(cssText, resourceURL, baseURL, options, getContentFromUrl) {
456
- try {
457
- const resolvedURL = baseURL ? resolveUrl(resourceURL, baseURL) : resourceURL;
458
- const contentType = getMimeType(resourceURL);
459
- let dataURL;
460
- if (getContentFromUrl) ;
461
- else {
462
- dataURL = await resourceToDataURL(resolvedURL, contentType, options);
463
- }
464
- return cssText.replace(toRegex(resourceURL), `$1${dataURL}$3`);
465
- } catch (error) {
935
+ function applyCssStyleWithOptions(cloned, context) {
936
+ const { backgroundColor, width, height, style: styles } = context;
937
+ const clonedStyle = cloned.style;
938
+ if (backgroundColor)
939
+ clonedStyle.setProperty("background-color", backgroundColor, "important");
940
+ if (width)
941
+ clonedStyle.setProperty("width", `${width}px`, "important");
942
+ if (height)
943
+ clonedStyle.setProperty("height", `${height}px`, "important");
944
+ if (styles) {
945
+ for (const name in styles) clonedStyle[name] = styles[name];
466
946
  }
467
- return cssText;
468
947
  }
469
- function filterPreferredFontFormat(str, { preferredFontFormat }) {
470
- return !preferredFontFormat ? str : str.replace(FONT_SRC_REGEX, (match) => {
471
- while (true) {
472
- const [src, , format] = URL_WITH_FORMAT_REGEX.exec(match) || [];
473
- if (!format) {
474
- return "";
948
+ const NORMAL_ATTRIBUTE_RE = /^[\w-:]+$/;
949
+ async function cloneNode(node, context, isRoot = false, addWordToFontFamilies) {
950
+ var _a2, _b, _c, _d;
951
+ const { ownerDocument, ownerWindow, fontFamilies } = context;
952
+ if (ownerDocument && isTextNode(node)) {
953
+ if (addWordToFontFamilies && /\S/.test(node.data)) {
954
+ addWordToFontFamilies(node.data);
955
+ }
956
+ return ownerDocument.createTextNode(node.data);
957
+ }
958
+ if (ownerDocument && ownerWindow && isElementNode(node) && (isHTMLElementNode(node) || isSVGElementNode(node))) {
959
+ const cloned2 = await cloneElement(node, context);
960
+ if (context.isEnable("removeAbnormalAttributes")) {
961
+ const names = cloned2.getAttributeNames();
962
+ for (let len = names.length, i = 0; i < len; i++) {
963
+ const name = names[i];
964
+ if (!NORMAL_ATTRIBUTE_RE.test(name)) {
965
+ cloned2.removeAttribute(name);
966
+ }
475
967
  }
476
- if (format === preferredFontFormat) {
477
- return `src: ${src};`;
968
+ }
969
+ const style = context.currentNodeStyle = copyCssStyles(node, cloned2, isRoot, context);
970
+ if (isRoot)
971
+ applyCssStyleWithOptions(cloned2, context);
972
+ let copyScrollbar = false;
973
+ if (context.isEnable("copyScrollbar")) {
974
+ const overflow = [
975
+ (_a2 = style.get("overflow-x")) == null ? void 0 : _a2[0],
976
+ (_b = style.get("overflow-y")) == null ? void 0 : _b[0]
977
+ ];
978
+ copyScrollbar = overflow.includes("scroll") || (overflow.includes("auto") || overflow.includes("overlay")) && (node.scrollHeight > node.clientHeight || node.scrollWidth > node.clientWidth);
979
+ }
980
+ const textTransform = (_c = style.get("text-transform")) == null ? void 0 : _c[0];
981
+ const families = splitFontFamily((_d = style.get("font-family")) == null ? void 0 : _d[0]);
982
+ const addWordToFontFamilies2 = families ? (word) => {
983
+ if (textTransform === "uppercase") {
984
+ word = word.toUpperCase();
985
+ } else if (textTransform === "lowercase") {
986
+ word = word.toLowerCase();
987
+ } else if (textTransform === "capitalize") {
988
+ word = word[0].toUpperCase() + word.substring(1);
478
989
  }
990
+ families.forEach((family) => {
991
+ let fontFamily = fontFamilies.get(family);
992
+ if (!fontFamily) {
993
+ fontFamilies.set(family, fontFamily = /* @__PURE__ */ new Set());
994
+ }
995
+ word.split("").forEach((text) => fontFamily.add(text));
996
+ });
997
+ } : void 0;
998
+ copyPseudoClass(
999
+ node,
1000
+ cloned2,
1001
+ copyScrollbar,
1002
+ context,
1003
+ addWordToFontFamilies2
1004
+ );
1005
+ copyInputValue(node, cloned2);
1006
+ if (!isVideoElement(node)) {
1007
+ await cloneChildNodes(
1008
+ node,
1009
+ cloned2,
1010
+ context,
1011
+ addWordToFontFamilies2
1012
+ );
479
1013
  }
480
- });
481
- }
482
- function shouldEmbed(url) {
483
- return url.search(URL_REGEX) !== -1;
484
- }
485
- async function embedResources(cssText, baseUrl, options) {
486
- if (!shouldEmbed(cssText)) {
487
- return cssText;
1014
+ return cloned2;
488
1015
  }
489
- const filteredCSSText = filterPreferredFontFormat(cssText, options);
490
- const urls = parseURLs(filteredCSSText);
491
- return urls.reduce((deferred, url) => deferred.then((css) => embed(css, url, baseUrl, options)), Promise.resolve(filteredCSSText));
1016
+ const cloned = node.cloneNode(false);
1017
+ await cloneChildNodes(node, cloned, context);
1018
+ return cloned;
492
1019
  }
493
- async function embedProp(propName, node, options) {
494
- var _a;
495
- const propValue = (_a = node.style) === null || _a === void 0 ? void 0 : _a.getPropertyValue(propName);
496
- if (propValue) {
497
- const cssString = await embedResources(propValue, null, options);
498
- node.style.setProperty(propName, cssString, node.style.getPropertyPriority(propName));
499
- return true;
1020
+ function destroyContext(context) {
1021
+ context.ownerDocument = void 0;
1022
+ context.ownerWindow = void 0;
1023
+ context.svgStyleElement = void 0;
1024
+ context.svgDefsElement = void 0;
1025
+ context.svgStyles.clear();
1026
+ context.defaultComputedStyles.clear();
1027
+ if (context.sandbox) {
1028
+ try {
1029
+ context.sandbox.remove();
1030
+ } catch (err) {
1031
+ context.log.warn("Failed to destroyContext", err);
1032
+ }
1033
+ context.sandbox = void 0;
500
1034
  }
501
- return false;
1035
+ context.workers = [];
1036
+ context.fontFamilies.clear();
1037
+ context.fontCssTexts.clear();
1038
+ context.requests.clear();
1039
+ context.tasks = [];
502
1040
  }
503
- async function embedBackground(clonedNode, options) {
504
- await embedProp("background", clonedNode, options) || await embedProp("background-image", clonedNode, options);
505
- await embedProp("mask", clonedNode, options) || await embedProp("-webkit-mask", clonedNode, options) || await embedProp("mask-image", clonedNode, options) || await embedProp("-webkit-mask-image", clonedNode, options);
1041
+ function baseFetch(options) {
1042
+ const { url, timeout, responseType, ...requestInit } = options;
1043
+ const controller = new AbortController();
1044
+ const timer = timeout ? setTimeout(() => controller.abort(), timeout) : void 0;
1045
+ return fetch(url, { signal: controller.signal, ...requestInit }).then((response) => {
1046
+ if (!response.ok) {
1047
+ throw new Error("Failed fetch, not 2xx response", { cause: response });
1048
+ }
1049
+ switch (responseType) {
1050
+ case "arrayBuffer":
1051
+ return response.arrayBuffer();
1052
+ case "dataUrl":
1053
+ return response.blob().then(blobToDataUrl);
1054
+ case "text":
1055
+ default:
1056
+ return response.text();
1057
+ }
1058
+ }).finally(() => clearTimeout(timer));
506
1059
  }
507
- async function embedImageNode(clonedNode, options) {
508
- const isImageElement = isInstanceOfElement(clonedNode, HTMLImageElement);
509
- if (!(isImageElement && !isDataUrl(clonedNode.src)) && !(isInstanceOfElement(clonedNode, SVGImageElement) && !isDataUrl(clonedNode.href.baseVal))) {
510
- return;
1060
+ function contextFetch(context, options) {
1061
+ const { url: rawUrl, requestType = "text", responseType = "text", imageDom } = options;
1062
+ let url = rawUrl;
1063
+ const {
1064
+ timeout,
1065
+ acceptOfImage,
1066
+ requests,
1067
+ fetchFn,
1068
+ fetch: {
1069
+ requestInit,
1070
+ bypassingCache,
1071
+ placeholderImage
1072
+ },
1073
+ font,
1074
+ workers,
1075
+ fontFamilies
1076
+ } = context;
1077
+ if (requestType === "image" && (IN_SAFARI || IN_FIREFOX)) {
1078
+ context.drawImageCount++;
511
1079
  }
512
- const url = isImageElement ? clonedNode.src : clonedNode.href.baseVal;
513
- const dataURL = await resourceToDataURL(url, getMimeType(url), options);
514
- await new Promise((resolve, reject) => {
515
- clonedNode.onload = resolve;
516
- clonedNode.onerror = options.onImageErrorHandler ? (...attributes) => {
517
- try {
518
- resolve(options.onImageErrorHandler(...attributes));
519
- } catch (error) {
520
- reject(error);
1080
+ let request = requests.get(rawUrl);
1081
+ if (!request) {
1082
+ if (bypassingCache) {
1083
+ if (bypassingCache instanceof RegExp && bypassingCache.test(url)) {
1084
+ url += (/\?/.test(url) ? "&" : "?") + (/* @__PURE__ */ new Date()).getTime();
521
1085
  }
522
- } : reject;
523
- const image = clonedNode;
524
- if (image.decode) {
525
- image.decode = resolve;
526
1086
  }
527
- if (image.loading === "lazy") {
528
- image.loading = "eager";
1087
+ const canFontMinify = requestType.startsWith("font") && font && font.minify;
1088
+ const fontTexts = /* @__PURE__ */ new Set();
1089
+ if (canFontMinify) {
1090
+ const families = requestType.split(";")[1].split(",");
1091
+ families.forEach((family) => {
1092
+ if (!fontFamilies.has(family))
1093
+ return;
1094
+ fontFamilies.get(family).forEach((text) => fontTexts.add(text));
1095
+ });
529
1096
  }
530
- if (isImageElement) {
531
- clonedNode.srcset = "";
532
- clonedNode.src = dataURL;
533
- } else {
534
- clonedNode.href.baseVal = dataURL;
1097
+ const needFontMinify = canFontMinify && fontTexts.size;
1098
+ const baseFetchOptions = {
1099
+ url,
1100
+ timeout,
1101
+ responseType: needFontMinify ? "arrayBuffer" : responseType,
1102
+ headers: requestType === "image" ? { accept: acceptOfImage } : void 0,
1103
+ ...requestInit
1104
+ };
1105
+ request = {
1106
+ type: requestType,
1107
+ resolve: void 0,
1108
+ reject: void 0,
1109
+ response: null
1110
+ };
1111
+ request.response = (async () => {
1112
+ if (fetchFn && requestType === "image") {
1113
+ const result = await fetchFn(rawUrl);
1114
+ if (result)
1115
+ return result;
1116
+ }
1117
+ if (!IN_SAFARI && rawUrl.startsWith("http") && workers.length) {
1118
+ return new Promise((resolve, reject) => {
1119
+ const worker = workers[requests.size & workers.length - 1];
1120
+ worker.postMessage({ rawUrl, ...baseFetchOptions });
1121
+ request.resolve = resolve;
1122
+ request.reject = reject;
1123
+ });
1124
+ }
1125
+ return baseFetch(baseFetchOptions);
1126
+ })().catch((error) => {
1127
+ requests.delete(rawUrl);
1128
+ if (requestType === "image" && placeholderImage) {
1129
+ context.log.warn("Failed to fetch image base64, trying to use placeholder image", url);
1130
+ return typeof placeholderImage === "string" ? placeholderImage : placeholderImage(imageDom);
1131
+ }
1132
+ throw error;
1133
+ });
1134
+ requests.set(rawUrl, request);
1135
+ }
1136
+ return request.response;
1137
+ }
1138
+ async function replaceCssUrlToDataUrl(cssText, baseUrl, context, isImage) {
1139
+ if (!hasCssUrl(cssText))
1140
+ return cssText;
1141
+ for (const [rawUrl, url] of parseCssUrls(cssText, baseUrl)) {
1142
+ try {
1143
+ const dataUrl = await contextFetch(
1144
+ context,
1145
+ {
1146
+ url,
1147
+ requestType: isImage ? "image" : "text",
1148
+ responseType: "dataUrl"
1149
+ }
1150
+ );
1151
+ cssText = cssText.replace(toRE(rawUrl), `$1${dataUrl}$3`);
1152
+ } catch (error) {
1153
+ context.log.warn("Failed to fetch css data url", rawUrl, error);
535
1154
  }
1155
+ }
1156
+ return cssText;
1157
+ }
1158
+ function hasCssUrl(cssText) {
1159
+ return /url\((['"]?)([^'"]+?)\1\)/.test(cssText);
1160
+ }
1161
+ const URL_RE = /url\((['"]?)([^'"]+?)\1\)/g;
1162
+ function parseCssUrls(cssText, baseUrl) {
1163
+ const result = [];
1164
+ cssText.replace(URL_RE, (raw, quotation, url) => {
1165
+ result.push([url, resolveUrl(url, baseUrl)]);
1166
+ return raw;
536
1167
  });
1168
+ return result.filter(([url]) => !isDataUrl(url));
1169
+ }
1170
+ function toRE(url) {
1171
+ const escaped = url.replace(/([.*+?^${}()|\[\]\/\\])/g, "\\$1");
1172
+ return new RegExp(`(url\\(['"]?)(${escaped})(['"]?\\))`, "g");
537
1173
  }
538
- async function embedChildren(clonedNode, options) {
539
- const children = toArray(clonedNode.childNodes);
540
- const deferreds = children.map((child) => embedImages(child, options));
541
- await Promise.all(deferreds).then(() => clonedNode);
1174
+ const properties = [
1175
+ "background-image",
1176
+ "border-image-source",
1177
+ "-webkit-border-image",
1178
+ "-webkit-mask-image",
1179
+ "list-style-image"
1180
+ ];
1181
+ function embedCssStyleImage(style, context) {
1182
+ return properties.map((property) => {
1183
+ const value = style.getPropertyValue(property);
1184
+ if (!value || value === "none") {
1185
+ return null;
1186
+ }
1187
+ if (IN_SAFARI || IN_FIREFOX) {
1188
+ context.drawImageCount++;
1189
+ }
1190
+ return replaceCssUrlToDataUrl(value, null, context, true).then((newValue) => {
1191
+ if (!newValue || value === newValue)
1192
+ return;
1193
+ style.setProperty(
1194
+ property,
1195
+ newValue,
1196
+ style.getPropertyPriority(property)
1197
+ );
1198
+ });
1199
+ }).filter(Boolean);
542
1200
  }
543
- async function embedImages(clonedNode, options) {
544
- if (isInstanceOfElement(clonedNode, Element)) {
545
- await embedBackground(clonedNode, options);
546
- await embedImageNode(clonedNode, options);
547
- await embedChildren(clonedNode, options);
1201
+ function embedImageElement(cloned, context) {
1202
+ if (isImageElement(cloned)) {
1203
+ const originalSrc = cloned.currentSrc || cloned.src;
1204
+ if (!isDataUrl(originalSrc)) {
1205
+ return [
1206
+ contextFetch(context, {
1207
+ url: originalSrc,
1208
+ imageDom: cloned,
1209
+ requestType: "image",
1210
+ responseType: "dataUrl"
1211
+ }).then((url) => {
1212
+ if (!url)
1213
+ return;
1214
+ cloned.srcset = "";
1215
+ cloned.dataset.originalSrc = originalSrc;
1216
+ cloned.src = url || "";
1217
+ })
1218
+ ];
1219
+ }
1220
+ if (IN_SAFARI || IN_FIREFOX) {
1221
+ context.drawImageCount++;
1222
+ }
1223
+ } else if (isSVGElementNode(cloned) && !isDataUrl(cloned.href.baseVal)) {
1224
+ const originalSrc = cloned.href.baseVal;
1225
+ return [
1226
+ contextFetch(context, {
1227
+ url: originalSrc,
1228
+ imageDom: cloned,
1229
+ requestType: "image",
1230
+ responseType: "dataUrl"
1231
+ }).then((url) => {
1232
+ if (!url)
1233
+ return;
1234
+ cloned.dataset.originalSrc = originalSrc;
1235
+ cloned.href.baseVal = url || "";
1236
+ })
1237
+ ];
548
1238
  }
1239
+ return [];
549
1240
  }
550
- function applyStyle(node, options) {
551
- const { style } = node;
552
- if (options.backgroundColor) {
553
- style.backgroundColor = options.backgroundColor;
1241
+ function embedSvgUse(cloned, context) {
1242
+ const { ownerDocument, svgDefsElement } = context;
1243
+ const href = cloned.getAttribute("href") ?? cloned.getAttribute("xlink:href");
1244
+ if (!href)
1245
+ return [];
1246
+ const [svgUrl, id] = href.split("#");
1247
+ if (id) {
1248
+ const query = `#${id}`;
1249
+ const definition = ownerDocument == null ? void 0 : ownerDocument.querySelector(`svg ${query}`);
1250
+ if (svgUrl) {
1251
+ cloned.setAttribute("href", query);
1252
+ }
1253
+ if (svgDefsElement == null ? void 0 : svgDefsElement.querySelector(query))
1254
+ return [];
1255
+ if (definition) {
1256
+ svgDefsElement == null ? void 0 : svgDefsElement.appendChild(definition.cloneNode(true));
1257
+ return [];
1258
+ } else if (svgUrl) {
1259
+ return [
1260
+ contextFetch(context, {
1261
+ url: svgUrl,
1262
+ responseType: "text"
1263
+ }).then((svgData) => {
1264
+ svgDefsElement == null ? void 0 : svgDefsElement.insertAdjacentHTML("beforeend", svgData);
1265
+ })
1266
+ ];
1267
+ }
554
1268
  }
555
- if (options.width) {
556
- style.width = `${options.width}px`;
1269
+ return [];
1270
+ }
1271
+ function embedNode(cloned, context) {
1272
+ const { tasks } = context;
1273
+ if (isElementNode(cloned)) {
1274
+ if (isImageElement(cloned) || isSVGImageElementNode(cloned)) {
1275
+ tasks.push(...embedImageElement(cloned, context));
1276
+ }
1277
+ if (isSVGUseElementNode(cloned)) {
1278
+ tasks.push(...embedSvgUse(cloned, context));
1279
+ }
557
1280
  }
558
- if (options.height) {
559
- style.height = `${options.height}px`;
1281
+ if (isHTMLElementNode(cloned)) {
1282
+ tasks.push(...embedCssStyleImage(cloned.style, context));
560
1283
  }
561
- const manual = options.style;
562
- if (manual != null) {
563
- Object.keys(manual).forEach((key) => {
564
- style[key] = manual[key];
565
- });
1284
+ cloned.childNodes.forEach((child) => {
1285
+ embedNode(child, context);
1286
+ });
1287
+ }
1288
+ async function embedWebFont(clone, context) {
1289
+ const {
1290
+ ownerDocument,
1291
+ svgStyleElement,
1292
+ fontFamilies,
1293
+ fontCssTexts,
1294
+ tasks,
1295
+ font
1296
+ } = context;
1297
+ if (!ownerDocument || !svgStyleElement || !fontFamilies.size) {
1298
+ return;
566
1299
  }
567
- return node;
568
- }
569
- const cssFetchCache = {};
570
- async function fetchCSS(url) {
571
- let cache2 = cssFetchCache[url];
572
- if (cache2 != null) {
573
- return cache2;
574
- }
575
- const res = await fetch(url);
576
- const cssText = await res.text();
577
- cache2 = { url, cssText };
578
- cssFetchCache[url] = cache2;
579
- return cache2;
580
- }
581
- async function embedFonts(data, options) {
582
- let cssText = data.cssText;
583
- const regexUrl = /url\(["']?([^"')]+)["']?\)/g;
584
- const fontLocs = cssText.match(/url\([^)]+\)/g) || [];
585
- const loadFonts = fontLocs.map(async (loc) => {
586
- let url = loc.replace(regexUrl, "$1");
587
- if (!url.startsWith("https://")) {
588
- url = new URL(url, data.url).href;
589
- }
590
- return fetchAsDataURL(url, options.fetchRequestInit, ({ result }) => {
591
- cssText = cssText.replace(loc, `url(${result})`);
592
- return [loc, result];
1300
+ if (font && font.cssText) {
1301
+ const cssText = filterPreferredFormat(font.cssText, context);
1302
+ svgStyleElement.appendChild(ownerDocument.createTextNode(`${cssText}
1303
+ `));
1304
+ } else {
1305
+ const styleSheets = Array.from(ownerDocument.styleSheets).filter((styleSheet) => {
1306
+ try {
1307
+ return "cssRules" in styleSheet && Boolean(styleSheet.cssRules.length);
1308
+ } catch (error) {
1309
+ context.log.warn(`Error while reading CSS rules from ${styleSheet.href}`, error);
1310
+ return false;
1311
+ }
593
1312
  });
594
- });
595
- return Promise.all(loadFonts).then(() => cssText);
1313
+ await Promise.all(
1314
+ styleSheets.flatMap((styleSheet) => {
1315
+ return Array.from(styleSheet.cssRules).map(async (cssRule, index) => {
1316
+ if (isCSSImportRule(cssRule)) {
1317
+ let importIndex = index + 1;
1318
+ const baseUrl = cssRule.href;
1319
+ let cssText = "";
1320
+ try {
1321
+ cssText = await contextFetch(context, {
1322
+ url: baseUrl,
1323
+ requestType: "text",
1324
+ responseType: "text"
1325
+ });
1326
+ } catch (error) {
1327
+ context.log.warn(`Error fetch remote css import from ${baseUrl}`, error);
1328
+ }
1329
+ const replacedCssText = cssText.replace(
1330
+ URL_RE,
1331
+ (raw, quotation, url) => raw.replace(url, resolveUrl(url, baseUrl))
1332
+ );
1333
+ for (const rule of parseCss(replacedCssText)) {
1334
+ try {
1335
+ styleSheet.insertRule(
1336
+ rule,
1337
+ rule.startsWith("@import") ? importIndex += 1 : styleSheet.cssRules.length
1338
+ );
1339
+ } catch (error) {
1340
+ context.log.warn("Error inserting rule from remote css import", { rule, error });
1341
+ }
1342
+ }
1343
+ }
1344
+ });
1345
+ })
1346
+ );
1347
+ const cssRules = styleSheets.flatMap((styleSheet) => Array.from(styleSheet.cssRules));
1348
+ cssRules.filter((cssRule) => {
1349
+ var _a2;
1350
+ return isCssFontFaceRule(cssRule) && hasCssUrl(cssRule.style.getPropertyValue("src")) && ((_a2 = splitFontFamily(cssRule.style.getPropertyValue("font-family"))) == null ? void 0 : _a2.some((val) => fontFamilies.has(val)));
1351
+ }).forEach((value) => {
1352
+ const rule = value;
1353
+ const cssText = fontCssTexts.get(rule.cssText);
1354
+ if (cssText) {
1355
+ svgStyleElement.appendChild(ownerDocument.createTextNode(`${cssText}
1356
+ `));
1357
+ } else {
1358
+ tasks.push(
1359
+ replaceCssUrlToDataUrl(
1360
+ rule.cssText,
1361
+ rule.parentStyleSheet ? rule.parentStyleSheet.href : null,
1362
+ context
1363
+ ).then((cssText2) => {
1364
+ cssText2 = filterPreferredFormat(cssText2, context);
1365
+ fontCssTexts.set(rule.cssText, cssText2);
1366
+ svgStyleElement.appendChild(ownerDocument.createTextNode(`${cssText2}
1367
+ `));
1368
+ })
1369
+ );
1370
+ }
1371
+ });
1372
+ }
596
1373
  }
597
- function parseCSS(source) {
598
- if (source == null) {
1374
+ const COMMENTS_RE = /(\/\*[\s\S]*?\*\/)/g;
1375
+ const KEYFRAMES_RE = /((@.*?keyframes [\s\S]*?){([\s\S]*?}\s*?)})/gi;
1376
+ function parseCss(source) {
1377
+ if (source == null)
599
1378
  return [];
600
- }
601
1379
  const result = [];
602
- const commentsRegex = /(\/\*[\s\S]*?\*\/)/gi;
603
- let cssText = source.replace(commentsRegex, "");
604
- const keyframesRegex = new RegExp("((@.*?keyframes [\\s\\S]*?){([\\s\\S]*?}\\s*?)})", "gi");
1380
+ let cssText = source.replace(COMMENTS_RE, "");
605
1381
  while (true) {
606
- const matches = keyframesRegex.exec(cssText);
607
- if (matches === null) {
1382
+ const matches = KEYFRAMES_RE.exec(cssText);
1383
+ if (!matches)
608
1384
  break;
609
- }
610
1385
  result.push(matches[0]);
611
1386
  }
612
- cssText = cssText.replace(keyframesRegex, "");
613
- const importRegex = /@import[\s\S]*?url\([^)]*\)[\s\S]*?;/gi;
614
- const combinedCSSRegex = "((\\s*?(?:\\/\\*[\\s\\S]*?\\*\\/)?\\s*?@media[\\s\\S]*?){([\\s\\S]*?)}\\s*?})|(([\\s\\S]*?){([\\s\\S]*?)})";
615
- const unifiedRegex = new RegExp(combinedCSSRegex, "gi");
1387
+ cssText = cssText.replace(KEYFRAMES_RE, "");
1388
+ const IMPORT_RE = /@import[\s\S]*?url\([^)]*\)[\s\S]*?;/gi;
1389
+ const UNIFIED_RE = new RegExp(
1390
+ // eslint-disable-next-line
1391
+ "((\\s*?(?:\\/\\*[\\s\\S]*?\\*\\/)?\\s*?@media[\\s\\S]*?){([\\s\\S]*?)}\\s*?})|(([\\s\\S]*?){([\\s\\S]*?)})",
1392
+ "gi"
1393
+ );
616
1394
  while (true) {
617
- let matches = importRegex.exec(cssText);
618
- if (matches === null) {
619
- matches = unifiedRegex.exec(cssText);
620
- if (matches === null) {
1395
+ let matches = IMPORT_RE.exec(cssText);
1396
+ if (!matches) {
1397
+ matches = UNIFIED_RE.exec(cssText);
1398
+ if (!matches) {
621
1399
  break;
622
1400
  } else {
623
- importRegex.lastIndex = unifiedRegex.lastIndex;
1401
+ IMPORT_RE.lastIndex = UNIFIED_RE.lastIndex;
624
1402
  }
625
1403
  } else {
626
- unifiedRegex.lastIndex = importRegex.lastIndex;
1404
+ UNIFIED_RE.lastIndex = IMPORT_RE.lastIndex;
627
1405
  }
628
1406
  result.push(matches[0]);
629
1407
  }
630
1408
  return result;
631
1409
  }
632
- async function getCSSRules(styleSheets, options) {
633
- const ret = [];
634
- const deferreds = [];
635
- styleSheets.forEach((sheet) => {
636
- if ("cssRules" in sheet) {
1410
+ const URL_WITH_FORMAT_RE = /url\([^)]+\)\s*format\((["']?)([^"']+)\1\)/g;
1411
+ const FONT_SRC_RE = /src:\s*(?:url\([^)]+\)\s*format\([^)]+\)[,;]\s*)+/g;
1412
+ function filterPreferredFormat(str, context) {
1413
+ const { font } = context;
1414
+ const preferredFormat = font ? font == null ? void 0 : font.preferredFormat : void 0;
1415
+ return preferredFormat ? str.replace(FONT_SRC_RE, (match) => {
1416
+ while (true) {
1417
+ const [src, , format] = URL_WITH_FORMAT_RE.exec(match) || [];
1418
+ if (!format)
1419
+ return "";
1420
+ if (format === preferredFormat)
1421
+ return `src: ${src};`;
1422
+ }
1423
+ }) : str;
1424
+ }
1425
+ async function domToForeignObjectSvg(node, options) {
1426
+ const context = await orCreateContext(node, options);
1427
+ if (isElementNode(context.node) && isSVGElementNode(context.node))
1428
+ return context.node;
1429
+ const {
1430
+ ownerDocument,
1431
+ log: log2,
1432
+ tasks,
1433
+ svgStyleElement,
1434
+ svgDefsElement,
1435
+ svgStyles,
1436
+ font,
1437
+ progress,
1438
+ autoDestruct,
1439
+ onCloneNode,
1440
+ onEmbedNode,
1441
+ onCreateForeignObjectSvg
1442
+ } = context;
1443
+ log2.time("clone node");
1444
+ const clone = await cloneNode(context.node, context, true);
1445
+ if (svgStyleElement && ownerDocument) {
1446
+ let allCssText = "";
1447
+ svgStyles.forEach((klasses, cssText) => {
1448
+ allCssText += `${klasses.join(",\n")} {
1449
+ ${cssText}
1450
+ }
1451
+ `;
1452
+ });
1453
+ svgStyleElement.appendChild(ownerDocument.createTextNode(allCssText));
1454
+ }
1455
+ log2.timeEnd("clone node");
1456
+ await (onCloneNode == null ? void 0 : onCloneNode(clone));
1457
+ if (font !== false && isElementNode(clone)) {
1458
+ log2.time("embed web font");
1459
+ await embedWebFont(clone, context);
1460
+ log2.timeEnd("embed web font");
1461
+ }
1462
+ log2.time("embed node");
1463
+ embedNode(clone, context);
1464
+ const count = tasks.length;
1465
+ let current = 0;
1466
+ const runTask = async () => {
1467
+ while (true) {
1468
+ const task = tasks.pop();
1469
+ if (!task)
1470
+ break;
637
1471
  try {
638
- toArray(sheet.cssRules || []).forEach((item, index) => {
639
- if (item.type === CSSRule.IMPORT_RULE) {
640
- let importIndex = index + 1;
641
- const url = item.href;
642
- const deferred = fetchCSS(url).then((metadata) => embedFonts(metadata, options)).then((cssText) => parseCSS(cssText).forEach((rule) => {
643
- try {
644
- sheet.insertRule(rule, rule.startsWith("@import") ? importIndex += 1 : sheet.cssRules.length);
645
- } catch (error) {
646
- console.error("Error inserting rule from remote css", {
647
- rule,
648
- error
649
- });
650
- }
651
- })).catch((e) => {
652
- console.error("Error loading remote css", e.toString());
653
- });
654
- deferreds.push(deferred);
655
- }
656
- });
657
- } catch (e) {
658
- const inline = styleSheets.find((a) => a.href == null) || document.styleSheets[0];
659
- if (sheet.href != null) {
660
- deferreds.push(fetchCSS(sheet.href).then((metadata) => embedFonts(metadata, options)).then((cssText) => parseCSS(cssText).forEach((rule) => {
661
- inline.insertRule(rule, inline.cssRules.length);
662
- })).catch((err) => {
663
- console.error("Error loading remote stylesheet", err);
664
- }));
665
- }
666
- console.error("Error inlining remote css file", e);
1472
+ await task;
1473
+ } catch (error) {
1474
+ context.log.warn("Failed to run task", error);
667
1475
  }
1476
+ progress == null ? void 0 : progress(++current, count);
668
1477
  }
669
- });
670
- return Promise.all(deferreds).then(() => {
671
- styleSheets.forEach((sheet) => {
672
- if ("cssRules" in sheet) {
673
- try {
674
- toArray(sheet.cssRules || []).forEach((item) => {
675
- ret.push(item);
676
- });
677
- } catch (e) {
678
- console.error(`Error while reading CSS rules from ${sheet.href}`, e);
679
- }
680
- }
681
- });
682
- return ret;
683
- });
1478
+ };
1479
+ progress == null ? void 0 : progress(current, count);
1480
+ await Promise.all([...Array.from({ length: 4 })].map(runTask));
1481
+ log2.timeEnd("embed node");
1482
+ await (onEmbedNode == null ? void 0 : onEmbedNode(clone));
1483
+ const svg = createForeignObjectSvg(clone, context);
1484
+ svgDefsElement && svg.insertBefore(svgDefsElement, svg.children[0]);
1485
+ svgStyleElement && svg.insertBefore(svgStyleElement, svg.children[0]);
1486
+ autoDestruct && destroyContext(context);
1487
+ await (onCreateForeignObjectSvg == null ? void 0 : onCreateForeignObjectSvg(svg));
1488
+ return svg;
684
1489
  }
685
- function getWebFontRules(cssRules) {
686
- return cssRules.filter((rule) => rule.type === CSSRule.FONT_FACE_RULE).filter((rule) => shouldEmbed(rule.style.getPropertyValue("src")));
1490
+ function createForeignObjectSvg(clone, context) {
1491
+ const { width, height } = context;
1492
+ const svg = createSvg(width, height, clone.ownerDocument);
1493
+ const foreignObject = svg.ownerDocument.createElementNS(svg.namespaceURI, "foreignObject");
1494
+ foreignObject.setAttributeNS(null, "x", "0%");
1495
+ foreignObject.setAttributeNS(null, "y", "0%");
1496
+ foreignObject.setAttributeNS(null, "width", "100%");
1497
+ foreignObject.setAttributeNS(null, "height", "100%");
1498
+ foreignObject.append(clone);
1499
+ svg.appendChild(foreignObject);
1500
+ return svg;
687
1501
  }
688
- async function parseWebFontRules(node, options) {
689
- if (node.ownerDocument == null) {
690
- throw new Error("Provided element is not within a Document");
1502
+ async function domToCanvas(node, options) {
1503
+ var _a2;
1504
+ const context = await orCreateContext(node, options);
1505
+ const svg = await domToForeignObjectSvg(context);
1506
+ const dataUrl = svgToDataUrl(svg, context.isEnable("removeControlCharacter"));
1507
+ if (!context.autoDestruct) {
1508
+ context.svgStyleElement = createStyleElement(context.ownerDocument);
1509
+ context.svgDefsElement = (_a2 = context.ownerDocument) == null ? void 0 : _a2.createElementNS(XMLNS, "defs");
1510
+ context.svgStyles.clear();
691
1511
  }
692
- const styleSheets = toArray(node.ownerDocument.styleSheets);
693
- const cssRules = await getCSSRules(styleSheets, options);
694
- return getWebFontRules(cssRules);
695
- }
696
- function normalizeFontFamily(font) {
697
- return font.trim().replace(/["']/g, "");
1512
+ const image = createImage(dataUrl, svg.ownerDocument);
1513
+ return await imageToCanvas(image, context);
698
1514
  }
699
- function getUsedFonts(node) {
700
- const fonts = /* @__PURE__ */ new Set();
701
- function traverse(node2) {
702
- const fontFamily = node2.style.fontFamily || getComputedStyle(node2).fontFamily;
703
- fontFamily.split(",").forEach((font) => {
704
- fonts.add(normalizeFontFamily(font));
705
- });
706
- Array.from(node2.children).forEach((child) => {
707
- if (child instanceof HTMLElement) {
708
- traverse(child);
1515
+ async function domToDataUrl(node, options) {
1516
+ const context = await orCreateContext(node, options);
1517
+ const { log: log2, quality, type, dpi } = context;
1518
+ const canvas = await domToCanvas(context);
1519
+ log2.time("canvas to data url");
1520
+ let dataUrl = canvas.toDataURL(type, quality);
1521
+ if (["image/png", "image/jpeg"].includes(type) && dpi && SUPPORT_ATOB && SUPPORT_BTOA) {
1522
+ const [format, body] = dataUrl.split(",");
1523
+ let headerLength = 0;
1524
+ let overwritepHYs = false;
1525
+ if (type === "image/png") {
1526
+ const b64Index = detectPhysChunkFromDataUrl(body);
1527
+ if (b64Index >= 0) {
1528
+ headerLength = Math.ceil((b64Index + 28) / 3) * 4;
1529
+ overwritepHYs = true;
1530
+ } else {
1531
+ headerLength = 33 / 3 * 4;
709
1532
  }
710
- });
1533
+ } else if (type === "image/jpeg") {
1534
+ headerLength = 18 / 3 * 4;
1535
+ }
1536
+ const stringHeader = body.substring(0, headerLength);
1537
+ const restOfData = body.substring(headerLength);
1538
+ const headerBytes = window.atob(stringHeader);
1539
+ const uint8Array = new Uint8Array(headerBytes.length);
1540
+ for (let i = 0; i < uint8Array.length; i++) {
1541
+ uint8Array[i] = headerBytes.charCodeAt(i);
1542
+ }
1543
+ const finalArray = type === "image/png" ? changePngDpi(uint8Array, dpi, overwritepHYs) : changeJpegDpi(uint8Array, dpi);
1544
+ const base64Header = window.btoa(String.fromCharCode(...finalArray));
1545
+ dataUrl = [format, ",", base64Header, restOfData].join("");
711
1546
  }
712
- traverse(node);
713
- return fonts;
714
- }
715
- async function getWebFontCSS(node, options) {
716
- const rules = await parseWebFontRules(node, options);
717
- const usedFonts = getUsedFonts(node);
718
- const cssTexts = await Promise.all(rules.filter((rule) => usedFonts.has(normalizeFontFamily(rule.style.fontFamily))).map((rule) => {
719
- const baseUrl = rule.parentStyleSheet ? rule.parentStyleSheet.href : null;
720
- return embedResources(rule.cssText, baseUrl, options);
721
- }));
722
- return cssTexts.join("\n");
723
- }
724
- async function embedWebFonts(clonedNode, options) {
725
- const cssText = options.fontEmbedCSS != null ? options.fontEmbedCSS : options.skipFonts ? null : await getWebFontCSS(clonedNode, options);
726
- if (cssText) {
727
- const styleNode = document.createElement("style");
728
- const sytleContent = document.createTextNode(cssText);
729
- styleNode.appendChild(sytleContent);
730
- if (clonedNode.firstChild) {
731
- clonedNode.insertBefore(styleNode, clonedNode.firstChild);
732
- } else {
733
- clonedNode.appendChild(styleNode);
734
- }
735
- }
736
- }
737
- async function toSvg(node, options = {}) {
738
- const { width, height } = getImageSize(node, options);
739
- const clonedNode = await cloneNode(node, options, true);
740
- await embedWebFonts(clonedNode, options);
741
- await embedImages(clonedNode, options);
742
- applyStyle(clonedNode, options);
743
- const datauri = await nodeToDataURL(clonedNode, width, height);
744
- return datauri;
745
- }
746
- async function toCanvas(node, options = {}) {
747
- const { width, height } = getImageSize(node, options);
748
- const svg = await toSvg(node, options);
749
- const img = await createImage(svg);
750
- const canvas = document.createElement("canvas");
751
- const context = canvas.getContext("2d");
752
- const ratio = options.pixelRatio || getPixelRatio();
753
- const canvasWidth = options.canvasWidth || width;
754
- const canvasHeight = options.canvasHeight || height;
755
- canvas.width = canvasWidth * ratio;
756
- canvas.height = canvasHeight * ratio;
757
- if (!options.skipAutoScale) {
758
- checkCanvasDimensions(canvas);
759
- }
760
- canvas.style.width = `${canvasWidth}`;
761
- canvas.style.height = `${canvasHeight}`;
762
- if (options.backgroundColor) {
763
- context.fillStyle = options.backgroundColor;
764
- context.fillRect(0, 0, canvas.width, canvas.height);
765
- }
766
- context.drawImage(img, 0, 0, canvas.width, canvas.height);
767
- return canvas;
1547
+ log2.timeEnd("canvas to data url");
1548
+ return dataUrl;
768
1549
  }
769
- async function toPng(node, options = {}) {
770
- const canvas = await toCanvas(node, options);
771
- return canvas.toDataURL();
1550
+ async function domToSvg(node, options) {
1551
+ const context = await orCreateContext(node, options);
1552
+ const { width, height, ownerDocument } = context;
1553
+ const dataUrl = await domToDataUrl(context);
1554
+ const svg = createSvg(width, height, ownerDocument);
1555
+ const svgImage = svg.ownerDocument.createElementNS(svg.namespaceURI, "image");
1556
+ svgImage.setAttributeNS(null, "href", dataUrl);
1557
+ svgImage.setAttributeNS(null, "height", "100%");
1558
+ svgImage.setAttributeNS(null, "width", "100%");
1559
+ svg.appendChild(svgImage);
1560
+ return svgToDataUrl(svg, context.isEnable("removeControlCharacter"));
772
1561
  }
773
- async function toJpeg(node, options = {}) {
774
- const canvas = await toCanvas(node, options);
775
- return canvas.toDataURL("image/jpeg", options.quality || 1);
1562
+ async function domToJpeg(node, options) {
1563
+ return domToDataUrl(
1564
+ await orCreateContext(node, { ...options, type: "image/jpeg" })
1565
+ );
776
1566
  }
777
- function handleFileNames(imageOptions, filenames) {
778
- let proposedFilename = imageOptions.label;
779
- if (imageOptions.includeScaleInLabel) proposedFilename += `_@${imageOptions.scale}x`;
780
- const extension = `.${imageOptions.format}`;
781
- proposedFilename += extension;
782
- if (!filenames.includes(proposedFilename)) {
783
- filenames.push(proposedFilename);
784
- return proposedFilename;
785
- }
786
- const numberPattern = /-(\d+)$/;
787
- const match = proposedFilename.match(numberPattern);
788
- if (match) {
789
- const baseFilename = proposedFilename.replace(numberPattern, "");
790
- let counter = parseInt(match[1], 10);
791
- while (filenames.includes(`${baseFilename}-${counter}${extension}`)) {
792
- counter++;
793
- }
794
- const newFilename = `${baseFilename}-${counter}${extension}`;
795
- filenames.push(newFilename);
796
- return newFilename;
797
- } else {
798
- const baseFilename = proposedFilename.replace(extension, "");
799
- let counter = 2;
800
- while (filenames.includes(`${baseFilename}-${counter}${extension}`)) {
801
- counter++;
802
- }
803
- const newFilename = `${baseFilename}-${counter}${extension}`;
804
- filenames.push(newFilename);
805
- return newFilename;
806
- }
1567
+ async function domToPng(node, options) {
1568
+ return domToDataUrl(
1569
+ await orCreateContext(node, { ...options, type: "image/png" })
1570
+ );
1571
+ }
1572
+ async function domToWebp(node, options) {
1573
+ return domToDataUrl(
1574
+ await orCreateContext(node, { ...options, type: "image/webp" })
1575
+ );
807
1576
  }
808
1577
  async function captureElement(element, imageOptions, filenames) {
809
1578
  try {
@@ -812,7 +1581,7 @@ async function captureElement(element, imageOptions, filenames) {
812
1581
  // Ensure quality is a number
813
1582
  quality: imageOptions.quality,
814
1583
  // Ensure scale is a number
815
- pixelRatio: imageOptions.scale,
1584
+ scale: imageOptions.scale,
816
1585
  // Ignores elements with data-ignore-capture attribute
817
1586
  filter
818
1587
  };
@@ -826,13 +1595,16 @@ async function captureElement(element, imageOptions, filenames) {
826
1595
  }
827
1596
  switch (imageOptions.format) {
828
1597
  case "jpg":
829
- dataURL = await toJpeg(element, htmlToImageOptions);
1598
+ dataURL = await domToJpeg(element, htmlToImageOptions);
830
1599
  break;
831
1600
  case "png":
832
- dataURL = await toPng(element, htmlToImageOptions);
1601
+ dataURL = await domToPng(element, htmlToImageOptions);
833
1602
  break;
834
1603
  case "svg":
835
- dataURL = await toSvg(element, htmlToImageOptions);
1604
+ dataURL = await domToSvg(element, htmlToImageOptions);
1605
+ break;
1606
+ case "webp":
1607
+ dataURL = await domToWebp(element, htmlToImageOptions);
836
1608
  break;
837
1609
  }
838
1610
  if (cleanUpBackground) {