@mkhuda/dom-screenshot 0.0.1 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitattributes +1 -0
- package/EXAMPLES_QUICKSTART.md +240 -0
- package/README.md +542 -25
- package/TESTING.md +269 -0
- package/TESTING_STATUS.md +215 -0
- package/TEST_SETUP_SUMMARY.md +335 -0
- package/dist/dom-screenshot.d.ts +44 -272
- package/dist/dom-screenshot.d.ts.map +1 -0
- package/dist/dom-screenshot.esm.js +753 -0
- package/dist/dom-screenshot.esm.js.map +1 -0
- package/dist/dom-screenshot.min.js +2 -1
- package/dist/dom-screenshot.min.js.map +1 -0
- package/examples/README.md +211 -0
- package/examples/react-app/README.md +161 -0
- package/examples/react-app/index.html +12 -0
- package/examples/react-app/node_modules/.vite/deps/_metadata.json +46 -0
- package/examples/react-app/node_modules/.vite/deps/chunk-FK77NBP6.js +1895 -0
- package/examples/react-app/node_modules/.vite/deps/chunk-FK77NBP6.js.map +7 -0
- package/examples/react-app/node_modules/.vite/deps/chunk-VSODSHUF.js +21647 -0
- package/examples/react-app/node_modules/.vite/deps/chunk-VSODSHUF.js.map +7 -0
- package/examples/react-app/node_modules/.vite/deps/package.json +3 -0
- package/examples/react-app/node_modules/.vite/deps/react-dom.js +5 -0
- package/examples/react-app/node_modules/.vite/deps/react-dom.js.map +7 -0
- package/examples/react-app/node_modules/.vite/deps/react-dom_client.js +38 -0
- package/examples/react-app/node_modules/.vite/deps/react-dom_client.js.map +7 -0
- package/examples/react-app/node_modules/.vite/deps/react.js +4 -0
- package/examples/react-app/node_modules/.vite/deps/react.js.map +7 -0
- package/examples/react-app/node_modules/.vite/deps/react_jsx-dev-runtime.js +898 -0
- package/examples/react-app/node_modules/.vite/deps/react_jsx-dev-runtime.js.map +7 -0
- package/examples/react-app/node_modules/.vite/deps/react_jsx-runtime.js +910 -0
- package/examples/react-app/node_modules/.vite/deps/react_jsx-runtime.js.map +7 -0
- package/examples/react-app/package.json +21 -0
- package/examples/react-app/tsconfig.json +25 -0
- package/examples/react-app/tsconfig.node.json +10 -0
- package/examples/react-app/vite.config.ts +10 -0
- package/package.json +75 -44
- package/rollup.config.mjs +35 -0
- package/tests/README.md +394 -0
- package/tests/fixtures/html.ts +192 -0
- package/tests/fixtures/images.ts +86 -0
- package/tests/fixtures/styles.ts +288 -0
- package/tests/helpers/dom-helpers.ts +242 -0
- package/tests/mocks/canvas-mock.ts +94 -0
- package/tests/mocks/image-mock.ts +147 -0
- package/tests/mocks/xhr-mock.ts +202 -0
- package/tests/setup.ts +103 -0
- package/tests/unit/basic.test.ts +263 -0
- package/tests/unit/simple.test.ts +172 -0
- package/tsconfig.json +44 -20
- package/vitest.config.mts +35 -0
- package/rollup.config.js +0 -20
|
@@ -0,0 +1,753 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default implementation options
|
|
3
|
+
*/
|
|
4
|
+
const defaultOptions = {
|
|
5
|
+
imagePlaceholder: undefined,
|
|
6
|
+
cacheBust: false,
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Main domtoimage module export
|
|
10
|
+
*/
|
|
11
|
+
const domtoimage = {
|
|
12
|
+
toSvg,
|
|
13
|
+
toPng,
|
|
14
|
+
toJpeg,
|
|
15
|
+
toBlob,
|
|
16
|
+
toPixelData,
|
|
17
|
+
impl: {}, // Will be populated after object creation
|
|
18
|
+
};
|
|
19
|
+
// Initialize implementation after domtoimage object exists
|
|
20
|
+
Object.assign(domtoimage.impl, {
|
|
21
|
+
fontFaces: newFontFaces(),
|
|
22
|
+
images: newImages(),
|
|
23
|
+
util: newUtil(),
|
|
24
|
+
inliner: newInliner(),
|
|
25
|
+
options: {},
|
|
26
|
+
});
|
|
27
|
+
/**
|
|
28
|
+
* Render DOM node to SVG data URL
|
|
29
|
+
* @param node - The DOM Node object to render
|
|
30
|
+
* @param options - Rendering options
|
|
31
|
+
* @returns Promise fulfilled with a SVG image data URL
|
|
32
|
+
*/
|
|
33
|
+
function toSvg(node, options) {
|
|
34
|
+
options = options || {};
|
|
35
|
+
copyOptions(options);
|
|
36
|
+
return Promise.resolve(node)
|
|
37
|
+
.then((node) => cloneNode(node, options.filter, true))
|
|
38
|
+
.then(embedFonts)
|
|
39
|
+
.then(inlineImages)
|
|
40
|
+
.then(applyOptions)
|
|
41
|
+
.then((clone) => {
|
|
42
|
+
const util = domtoimage.impl.util;
|
|
43
|
+
return makeSvgDataUri(clone, options.width || util.width(clone), options.height || util.height(clone));
|
|
44
|
+
});
|
|
45
|
+
function applyOptions(clone) {
|
|
46
|
+
if (!(clone instanceof Element))
|
|
47
|
+
return clone;
|
|
48
|
+
if (options.bgcolor)
|
|
49
|
+
clone.style.backgroundColor = options.bgcolor;
|
|
50
|
+
if (options.width)
|
|
51
|
+
clone.style.width = options.width + 'px';
|
|
52
|
+
if (options.height)
|
|
53
|
+
clone.style.height = options.height + 'px';
|
|
54
|
+
if (options.style) {
|
|
55
|
+
const styleObj = options.style;
|
|
56
|
+
Object.keys(styleObj).forEach((property) => {
|
|
57
|
+
clone.style[property] = styleObj[property];
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
return clone;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Render DOM node to PNG data URL
|
|
65
|
+
* @param node - The DOM Node object to render
|
|
66
|
+
* @param options - Rendering options
|
|
67
|
+
* @returns Promise fulfilled with a PNG image data URL
|
|
68
|
+
*/
|
|
69
|
+
function toPng(node, options) {
|
|
70
|
+
return draw(node, options || {}).then((canvas) => {
|
|
71
|
+
return canvas.toDataURL();
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Render DOM node to JPEG data URL
|
|
76
|
+
* @param node - The DOM Node object to render
|
|
77
|
+
* @param options - Rendering options
|
|
78
|
+
* @returns Promise fulfilled with a JPEG image data URL
|
|
79
|
+
*/
|
|
80
|
+
function toJpeg(node, options) {
|
|
81
|
+
options = options || {};
|
|
82
|
+
return draw(node, options).then((canvas) => {
|
|
83
|
+
return canvas.toDataURL('image/jpeg', options.quality || 1.0);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Render DOM node to Blob
|
|
88
|
+
* @param node - The DOM Node object to render
|
|
89
|
+
* @param options - Rendering options
|
|
90
|
+
* @returns Promise fulfilled with a Blob
|
|
91
|
+
*/
|
|
92
|
+
function toBlob(node, options) {
|
|
93
|
+
return draw(node, options || {}).then((canvas) => {
|
|
94
|
+
const util = domtoimage.impl.util;
|
|
95
|
+
return util.canvasToBlob(canvas);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Render DOM node to PixelData
|
|
100
|
+
* @param node - The DOM Node object to render
|
|
101
|
+
* @param options - Rendering options
|
|
102
|
+
* @returns Promise fulfilled with pixel data array
|
|
103
|
+
*/
|
|
104
|
+
function toPixelData(node, options) {
|
|
105
|
+
return draw(node, options || {}).then((canvas) => {
|
|
106
|
+
const util = domtoimage.impl.util;
|
|
107
|
+
return canvas
|
|
108
|
+
.getContext('2d')
|
|
109
|
+
.getImageData(0, 0, util.width(node), util.height(node)).data;
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
function copyOptions(options) {
|
|
113
|
+
// Copy options to impl options for use in impl
|
|
114
|
+
if (typeof options.imagePlaceholder === 'undefined') {
|
|
115
|
+
domtoimage.impl.options.imagePlaceholder =
|
|
116
|
+
defaultOptions.imagePlaceholder;
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
domtoimage.impl.options.imagePlaceholder = options.imagePlaceholder;
|
|
120
|
+
}
|
|
121
|
+
if (typeof options.cacheBust === 'undefined') {
|
|
122
|
+
domtoimage.impl.options.cacheBust = defaultOptions.cacheBust;
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
domtoimage.impl.options.cacheBust = options.cacheBust;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function draw(domNode, options) {
|
|
129
|
+
return toSvg(domNode, options)
|
|
130
|
+
.then((svgUri) => {
|
|
131
|
+
const util = domtoimage.impl.util;
|
|
132
|
+
return util.makeImage(svgUri);
|
|
133
|
+
})
|
|
134
|
+
.then((image) => {
|
|
135
|
+
const util = domtoimage.impl.util;
|
|
136
|
+
return util.delay(100)(image);
|
|
137
|
+
})
|
|
138
|
+
.then((image) => {
|
|
139
|
+
const canvas = newCanvas(domNode);
|
|
140
|
+
canvas.getContext('2d').drawImage(image, 0, 0);
|
|
141
|
+
return canvas;
|
|
142
|
+
});
|
|
143
|
+
function newCanvas(domNode) {
|
|
144
|
+
const util = domtoimage.impl.util;
|
|
145
|
+
const canvas = document.createElement('canvas');
|
|
146
|
+
canvas.width = options.width || util.width(domNode);
|
|
147
|
+
canvas.height = options.height || util.height(domNode);
|
|
148
|
+
if (options.bgcolor) {
|
|
149
|
+
const ctx = canvas.getContext('2d');
|
|
150
|
+
ctx.fillStyle = options.bgcolor;
|
|
151
|
+
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
152
|
+
}
|
|
153
|
+
return canvas;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
function cloneNode(node, filter, root) {
|
|
157
|
+
if (!root && filter && !filter(node))
|
|
158
|
+
return Promise.resolve(undefined);
|
|
159
|
+
const util = domtoimage.impl.util;
|
|
160
|
+
return Promise.resolve(node)
|
|
161
|
+
.then(makeNodeCopy)
|
|
162
|
+
.then((clone) => cloneChildren(node, clone, filter))
|
|
163
|
+
.then((clone) => processClone(node, clone));
|
|
164
|
+
function makeNodeCopy(node) {
|
|
165
|
+
if (node instanceof HTMLCanvasElement)
|
|
166
|
+
return util.makeImage(node.toDataURL());
|
|
167
|
+
return node.cloneNode(false);
|
|
168
|
+
}
|
|
169
|
+
function cloneChildren(original, clone, filter) {
|
|
170
|
+
if (!clone)
|
|
171
|
+
return Promise.resolve(clone);
|
|
172
|
+
const children = original.childNodes;
|
|
173
|
+
if (children.length === 0)
|
|
174
|
+
return Promise.resolve(clone);
|
|
175
|
+
return cloneChildrenInOrder(clone, util.asArray(children), filter).then(() => clone);
|
|
176
|
+
function cloneChildrenInOrder(parent, children, filter) {
|
|
177
|
+
let done = Promise.resolve();
|
|
178
|
+
children.forEach((child) => {
|
|
179
|
+
done = done
|
|
180
|
+
.then(() => cloneNode(child, filter))
|
|
181
|
+
.then((childClone) => {
|
|
182
|
+
if (childClone)
|
|
183
|
+
parent.appendChild(childClone);
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
return done;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
function processClone(original, originalClone) {
|
|
190
|
+
if (!originalClone)
|
|
191
|
+
return Promise.resolve(originalClone);
|
|
192
|
+
if (!(originalClone instanceof Element))
|
|
193
|
+
return Promise.resolve(originalClone);
|
|
194
|
+
const clone = originalClone;
|
|
195
|
+
return Promise.resolve()
|
|
196
|
+
.then(renderVideo)
|
|
197
|
+
.then(cloneStyle)
|
|
198
|
+
.then(clonePseudoElements)
|
|
199
|
+
.then(copyUserInput)
|
|
200
|
+
.then(fixSvg)
|
|
201
|
+
.then(() => {
|
|
202
|
+
return clone;
|
|
203
|
+
});
|
|
204
|
+
function renderVideo() {
|
|
205
|
+
if (!(clone instanceof HTMLVideoElement))
|
|
206
|
+
return;
|
|
207
|
+
const dimensions = window.getComputedStyle(original);
|
|
208
|
+
const canvas = document.createElement('canvas');
|
|
209
|
+
canvas.width = parseInt(dimensions.width, 10);
|
|
210
|
+
canvas.height = parseInt(dimensions.height, 10);
|
|
211
|
+
const ratio = Math.max(original.videoWidth / canvas.width, original.videoHeight / canvas.height);
|
|
212
|
+
const width = original.videoWidth / ratio;
|
|
213
|
+
const height = original.videoHeight / ratio;
|
|
214
|
+
const x = canvas.width / 2 - width / 2;
|
|
215
|
+
const y = canvas.height / 2 - height / 2;
|
|
216
|
+
const ctx = canvas.getContext('2d');
|
|
217
|
+
ctx.drawImage(original, x, y, width, height);
|
|
218
|
+
const newImage = new Image();
|
|
219
|
+
try {
|
|
220
|
+
newImage.src = canvas.toDataURL();
|
|
221
|
+
}
|
|
222
|
+
catch (err) {
|
|
223
|
+
newImage.src =
|
|
224
|
+
'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==';
|
|
225
|
+
}
|
|
226
|
+
// Replace video element with image in the cloned tree
|
|
227
|
+
const parentClone = clone.parentNode;
|
|
228
|
+
if (parentClone) {
|
|
229
|
+
parentClone.replaceChild(newImage, clone);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
function cloneStyle() {
|
|
233
|
+
const computedStyle = window.getComputedStyle(original);
|
|
234
|
+
copyStyle(computedStyle, clone.style);
|
|
235
|
+
function copyStyle(source, target) {
|
|
236
|
+
if (source.cssText) {
|
|
237
|
+
target.cssText = source.cssText;
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
copyProperties(source, target);
|
|
241
|
+
}
|
|
242
|
+
function copyProperties(source, target) {
|
|
243
|
+
const util = domtoimage.impl.util;
|
|
244
|
+
util.asArray(source).forEach((name) => {
|
|
245
|
+
target.setProperty(name, source.getPropertyValue(name), source.getPropertyPriority(name));
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
function clonePseudoElements() {
|
|
251
|
+
[':before', ':after'].forEach((element) => {
|
|
252
|
+
clonePseudoElement(element);
|
|
253
|
+
});
|
|
254
|
+
function clonePseudoElement(element) {
|
|
255
|
+
const style = window.getComputedStyle(original, element);
|
|
256
|
+
const content = style.getPropertyValue('content');
|
|
257
|
+
if (content === '' || content === 'none')
|
|
258
|
+
return;
|
|
259
|
+
const util = domtoimage.impl.util;
|
|
260
|
+
const className = util.uid();
|
|
261
|
+
clone.className = clone.className + ' ' + className;
|
|
262
|
+
const styleElement = document.createElement('style');
|
|
263
|
+
styleElement.appendChild(formatPseudoElementStyle(className, element, style));
|
|
264
|
+
clone.appendChild(styleElement);
|
|
265
|
+
function formatPseudoElementStyle(className, element, style) {
|
|
266
|
+
const selector = '.' + className + ':' + element;
|
|
267
|
+
const cssText = style.cssText
|
|
268
|
+
? formatCssText(style)
|
|
269
|
+
: formatCssProperties(style);
|
|
270
|
+
return document.createTextNode(selector + '{' + cssText + '}');
|
|
271
|
+
function formatCssText(style) {
|
|
272
|
+
const content = style.getPropertyValue('content');
|
|
273
|
+
return style.cssText + ' content: ' + content + ';';
|
|
274
|
+
}
|
|
275
|
+
function formatCssProperties(style) {
|
|
276
|
+
const util = domtoimage.impl.util;
|
|
277
|
+
return (util
|
|
278
|
+
.asArray(style)
|
|
279
|
+
.map((name) => formatProperty(name))
|
|
280
|
+
.join('; ') + ';');
|
|
281
|
+
function formatProperty(name) {
|
|
282
|
+
return (name +
|
|
283
|
+
': ' +
|
|
284
|
+
style.getPropertyValue(name) +
|
|
285
|
+
(style.getPropertyPriority(name) ? ' !important' : ''));
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
function copyUserInput() {
|
|
292
|
+
if (original instanceof HTMLTextAreaElement)
|
|
293
|
+
clone.innerHTML = original.value;
|
|
294
|
+
if (original instanceof HTMLInputElement)
|
|
295
|
+
clone.setAttribute('value', original.value);
|
|
296
|
+
}
|
|
297
|
+
function fixSvg() {
|
|
298
|
+
if (!(clone instanceof SVGElement))
|
|
299
|
+
return;
|
|
300
|
+
clone.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
|
|
301
|
+
if (!(clone instanceof SVGRectElement))
|
|
302
|
+
return;
|
|
303
|
+
['width', 'height'].forEach((attribute) => {
|
|
304
|
+
const value = clone.getAttribute(attribute);
|
|
305
|
+
if (!value)
|
|
306
|
+
return;
|
|
307
|
+
clone.style.setProperty(attribute, value);
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
function embedFonts(node) {
|
|
313
|
+
const fontFaces = domtoimage.impl.fontFaces;
|
|
314
|
+
return fontFaces.resolveAll().then((cssText) => {
|
|
315
|
+
const styleNode = document.createElement('style');
|
|
316
|
+
node.appendChild(styleNode);
|
|
317
|
+
styleNode.appendChild(document.createTextNode(cssText));
|
|
318
|
+
return node;
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
function inlineImages(node) {
|
|
322
|
+
const images = domtoimage.impl.images;
|
|
323
|
+
return images.inlineAll(node).then(() => node);
|
|
324
|
+
}
|
|
325
|
+
function makeSvgDataUri(node, width, height) {
|
|
326
|
+
domtoimage.impl.util;
|
|
327
|
+
return Promise.resolve(node)
|
|
328
|
+
.then((node) => {
|
|
329
|
+
node.setAttribute('xmlns', 'http://www.w3.org/1999/xhtml');
|
|
330
|
+
return new XMLSerializer().serializeToString(node);
|
|
331
|
+
})
|
|
332
|
+
.then((xhtml) => {
|
|
333
|
+
return ('<foreignObject x="0" y="0" width="100%" height="100%">' +
|
|
334
|
+
xhtml +
|
|
335
|
+
'</foreignObject>');
|
|
336
|
+
})
|
|
337
|
+
.then((foreignObject) => {
|
|
338
|
+
return ('<svg xmlns="http://www.w3.org/2000/svg" width="' +
|
|
339
|
+
width +
|
|
340
|
+
'" height="' +
|
|
341
|
+
height +
|
|
342
|
+
'">' +
|
|
343
|
+
foreignObject +
|
|
344
|
+
'</svg>');
|
|
345
|
+
})
|
|
346
|
+
.then((svg) => {
|
|
347
|
+
return 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(svg);
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
// ============================================================================
|
|
351
|
+
// UTILITY FUNCTIONS
|
|
352
|
+
// ============================================================================
|
|
353
|
+
function newUtil() {
|
|
354
|
+
const TIMEOUT = 30000;
|
|
355
|
+
return {
|
|
356
|
+
escapeMain,
|
|
357
|
+
parseExtension,
|
|
358
|
+
mimeType,
|
|
359
|
+
dataAsUrl,
|
|
360
|
+
isDataUrl,
|
|
361
|
+
canvasToBlob,
|
|
362
|
+
resolveUrl,
|
|
363
|
+
getAndEncode,
|
|
364
|
+
uid: uid(),
|
|
365
|
+
delay,
|
|
366
|
+
asArray,
|
|
367
|
+
escapeXhtml,
|
|
368
|
+
makeImage,
|
|
369
|
+
width,
|
|
370
|
+
height,
|
|
371
|
+
};
|
|
372
|
+
function mimes() {
|
|
373
|
+
const WOFF = 'application/font-woff';
|
|
374
|
+
const JPEG = 'image/jpeg';
|
|
375
|
+
return {
|
|
376
|
+
woff: WOFF,
|
|
377
|
+
woff2: WOFF,
|
|
378
|
+
ttf: 'application/font-truetype',
|
|
379
|
+
eot: 'application/vnd.ms-fontobject',
|
|
380
|
+
png: 'image/png',
|
|
381
|
+
jpg: JPEG,
|
|
382
|
+
jpeg: JPEG,
|
|
383
|
+
gif: 'image/gif',
|
|
384
|
+
tiff: 'image/tiff',
|
|
385
|
+
svg: 'image/svg+xml',
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
function parseExtension(url) {
|
|
389
|
+
const match = /\.([^\.\\/]*?)$/g.exec(url);
|
|
390
|
+
if (match)
|
|
391
|
+
return match[1];
|
|
392
|
+
else
|
|
393
|
+
return '';
|
|
394
|
+
}
|
|
395
|
+
function mimeType(url) {
|
|
396
|
+
const extension = parseExtension(url).toLowerCase();
|
|
397
|
+
return mimes()[extension] || '';
|
|
398
|
+
}
|
|
399
|
+
function isDataUrl(url) {
|
|
400
|
+
return url.search(/^(data:)/) !== -1;
|
|
401
|
+
}
|
|
402
|
+
function toBlob(canvas) {
|
|
403
|
+
return new Promise((resolve) => {
|
|
404
|
+
const binaryString = window.atob(canvas.toDataURL().split(',')[1]);
|
|
405
|
+
const length = binaryString.length;
|
|
406
|
+
const binaryArray = new Uint8Array(length);
|
|
407
|
+
for (let i = 0; i < length; i++)
|
|
408
|
+
binaryArray[i] = binaryString.charCodeAt(i);
|
|
409
|
+
resolve(new Blob([binaryArray], {
|
|
410
|
+
type: 'image/png',
|
|
411
|
+
}));
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
function canvasToBlob(canvas) {
|
|
415
|
+
if (canvas.toBlob) {
|
|
416
|
+
return new Promise((resolve) => {
|
|
417
|
+
canvas.toBlob((blob) => {
|
|
418
|
+
resolve(blob);
|
|
419
|
+
});
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
return toBlob(canvas);
|
|
423
|
+
}
|
|
424
|
+
function resolveUrl(url, baseUrl) {
|
|
425
|
+
const doc = document.implementation.createHTMLDocument();
|
|
426
|
+
const base = doc.createElement('base');
|
|
427
|
+
doc.head.appendChild(base);
|
|
428
|
+
const a = doc.createElement('a');
|
|
429
|
+
doc.body.appendChild(a);
|
|
430
|
+
base.href = baseUrl;
|
|
431
|
+
a.href = url;
|
|
432
|
+
return a.href;
|
|
433
|
+
}
|
|
434
|
+
function uid() {
|
|
435
|
+
let index = 0;
|
|
436
|
+
return function () {
|
|
437
|
+
return 'u' + fourRandomChars() + index++;
|
|
438
|
+
function fourRandomChars() {
|
|
439
|
+
return (('0000' + ((Math.random() * Math.pow(36, 4)) << 0).toString(36))).slice(-4);
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
function makeImage(uri) {
|
|
444
|
+
return new Promise((resolve, reject) => {
|
|
445
|
+
const image = new Image();
|
|
446
|
+
image.onload = () => {
|
|
447
|
+
resolve(image);
|
|
448
|
+
};
|
|
449
|
+
image.onerror = () => {
|
|
450
|
+
reject(new Error(`Failed to load image: ${uri}`));
|
|
451
|
+
};
|
|
452
|
+
image.src = uri;
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
function getAndEncode(url) {
|
|
456
|
+
if (domtoimage.impl.options.cacheBust) {
|
|
457
|
+
url +=
|
|
458
|
+
(/\?/.test(url) ? '&' : '?') + new Date().getTime();
|
|
459
|
+
}
|
|
460
|
+
return new Promise((resolve) => {
|
|
461
|
+
const request = new XMLHttpRequest();
|
|
462
|
+
request.onreadystatechange = done;
|
|
463
|
+
request.ontimeout = timeout;
|
|
464
|
+
request.responseType = 'blob';
|
|
465
|
+
request.timeout = TIMEOUT;
|
|
466
|
+
request.open('GET', url, true);
|
|
467
|
+
request.send();
|
|
468
|
+
let placeholder;
|
|
469
|
+
if (domtoimage.impl.options.imagePlaceholder) {
|
|
470
|
+
const split = domtoimage.impl.options.imagePlaceholder.split(/,/);
|
|
471
|
+
if (split && split[1]) {
|
|
472
|
+
placeholder = split[1];
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
function done() {
|
|
476
|
+
if (request.readyState !== 4)
|
|
477
|
+
return;
|
|
478
|
+
if (request.status !== 200) {
|
|
479
|
+
if (placeholder) {
|
|
480
|
+
resolve(placeholder);
|
|
481
|
+
}
|
|
482
|
+
else {
|
|
483
|
+
fail('cannot fetch resource: ' + url + ', status: ' + request.status);
|
|
484
|
+
}
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
const encoder = new FileReader();
|
|
488
|
+
encoder.onloadend = () => {
|
|
489
|
+
const content = encoder.result.split(/,/)[1];
|
|
490
|
+
resolve(content);
|
|
491
|
+
};
|
|
492
|
+
encoder.readAsDataURL(request.response);
|
|
493
|
+
}
|
|
494
|
+
function timeout() {
|
|
495
|
+
if (placeholder) {
|
|
496
|
+
resolve(placeholder);
|
|
497
|
+
}
|
|
498
|
+
else {
|
|
499
|
+
fail('timeout of ' +
|
|
500
|
+
TIMEOUT +
|
|
501
|
+
'ms occured while fetching resource: ' +
|
|
502
|
+
url);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
function fail(message) {
|
|
506
|
+
console.error(message);
|
|
507
|
+
resolve('');
|
|
508
|
+
}
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
function dataAsUrl(content, type) {
|
|
512
|
+
return 'data:' + type + ';base64,' + content;
|
|
513
|
+
}
|
|
514
|
+
function escapeMain(string) {
|
|
515
|
+
return string.replace(/([.*+?^${}()|[\]\/\\])/g, '\\$1');
|
|
516
|
+
}
|
|
517
|
+
function delay(ms) {
|
|
518
|
+
return (arg) => {
|
|
519
|
+
return new Promise((resolve) => {
|
|
520
|
+
setTimeout(() => {
|
|
521
|
+
resolve(arg);
|
|
522
|
+
}, ms);
|
|
523
|
+
});
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
function asArray(arrayLike) {
|
|
527
|
+
const array = [];
|
|
528
|
+
const length = arrayLike.length;
|
|
529
|
+
for (let i = 0; i < length; i++)
|
|
530
|
+
array.push(arrayLike[i]);
|
|
531
|
+
return array;
|
|
532
|
+
}
|
|
533
|
+
function escapeXhtml(string) {
|
|
534
|
+
return string.replace(/#/g, '%23').replace(/\n/g, '%0A');
|
|
535
|
+
}
|
|
536
|
+
function width(node) {
|
|
537
|
+
const leftBorder = px(node, 'border-left-width');
|
|
538
|
+
const rightBorder = px(node, 'border-right-width');
|
|
539
|
+
return node.scrollWidth + leftBorder + rightBorder;
|
|
540
|
+
}
|
|
541
|
+
function height(node) {
|
|
542
|
+
const topBorder = px(node, 'border-top-width');
|
|
543
|
+
const bottomBorder = px(node, 'border-bottom-width');
|
|
544
|
+
return node.scrollHeight + topBorder + bottomBorder;
|
|
545
|
+
}
|
|
546
|
+
function px(node, styleProperty) {
|
|
547
|
+
const value = window
|
|
548
|
+
.getComputedStyle(node)
|
|
549
|
+
.getPropertyValue(styleProperty);
|
|
550
|
+
return parseFloat(value.replace('px', ''));
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
// ============================================================================
|
|
554
|
+
// INLINER
|
|
555
|
+
// ============================================================================
|
|
556
|
+
function newInliner() {
|
|
557
|
+
const URL_REGEX = /url\(['"]?([^'"]+?)['"]?\)/g;
|
|
558
|
+
const util = domtoimage.impl.util;
|
|
559
|
+
return {
|
|
560
|
+
inlineAll,
|
|
561
|
+
shouldProcess,
|
|
562
|
+
impl: {
|
|
563
|
+
readUrls,
|
|
564
|
+
inline,
|
|
565
|
+
},
|
|
566
|
+
};
|
|
567
|
+
function shouldProcess(string) {
|
|
568
|
+
return string.search(URL_REGEX) !== -1;
|
|
569
|
+
}
|
|
570
|
+
function readUrls(string) {
|
|
571
|
+
const result = [];
|
|
572
|
+
let match;
|
|
573
|
+
while ((match = URL_REGEX.exec(string)) !== null) {
|
|
574
|
+
result.push(match[1]);
|
|
575
|
+
}
|
|
576
|
+
return result.filter((url) => {
|
|
577
|
+
return !util.isDataUrl(url);
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
function inline(string, url, baseUrl, get) {
|
|
581
|
+
return Promise.resolve(url)
|
|
582
|
+
.then((url) => {
|
|
583
|
+
return baseUrl ? util.resolveUrl(url, baseUrl) : url;
|
|
584
|
+
})
|
|
585
|
+
.then((url) => {
|
|
586
|
+
return (get || util.getAndEncode)(url);
|
|
587
|
+
})
|
|
588
|
+
.then((data) => {
|
|
589
|
+
return util.dataAsUrl(data, util.mimeType(url));
|
|
590
|
+
})
|
|
591
|
+
.then((dataUrl) => {
|
|
592
|
+
return string.replace(urlAsRegex(url), '$1' + dataUrl + '$3');
|
|
593
|
+
});
|
|
594
|
+
function urlAsRegex(url) {
|
|
595
|
+
return new RegExp("(url\\(['\"]?)(" + util.escapeMain(url) + ")(['\"]?\\))", 'g');
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
function inlineAll(string, baseUrl, get) {
|
|
599
|
+
if (nothingToInline())
|
|
600
|
+
return Promise.resolve(string);
|
|
601
|
+
return Promise.resolve(string)
|
|
602
|
+
.then(() => readUrls(string))
|
|
603
|
+
.then((urls) => {
|
|
604
|
+
let done = Promise.resolve(string);
|
|
605
|
+
urls.forEach((url) => {
|
|
606
|
+
done = done.then((str) => {
|
|
607
|
+
return inline(str, url, baseUrl, get);
|
|
608
|
+
});
|
|
609
|
+
});
|
|
610
|
+
return done;
|
|
611
|
+
});
|
|
612
|
+
function nothingToInline() {
|
|
613
|
+
return !shouldProcess(string);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
// ============================================================================
|
|
618
|
+
// FONT FACES
|
|
619
|
+
// ============================================================================
|
|
620
|
+
function newFontFaces() {
|
|
621
|
+
return {
|
|
622
|
+
resolveAll,
|
|
623
|
+
impl: {
|
|
624
|
+
readAll,
|
|
625
|
+
},
|
|
626
|
+
};
|
|
627
|
+
function resolveAll() {
|
|
628
|
+
return readAll()
|
|
629
|
+
.then((webFonts) => {
|
|
630
|
+
return Promise.all(webFonts.map((webFont) => {
|
|
631
|
+
return webFont.resolve();
|
|
632
|
+
}));
|
|
633
|
+
})
|
|
634
|
+
.then((cssStrings) => {
|
|
635
|
+
return cssStrings.join('\n');
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
function readAll() {
|
|
639
|
+
const util = domtoimage.impl.util;
|
|
640
|
+
return Promise.resolve(util.asArray(document.styleSheets))
|
|
641
|
+
.then(getCssRules)
|
|
642
|
+
.then(selectWebFontRules)
|
|
643
|
+
.then((rules) => {
|
|
644
|
+
return rules.map(newWebFont);
|
|
645
|
+
});
|
|
646
|
+
function selectWebFontRules(cssRules) {
|
|
647
|
+
const inliner = domtoimage.impl.inliner;
|
|
648
|
+
return cssRules
|
|
649
|
+
.filter((rule) => {
|
|
650
|
+
return rule.type === CSSRule.FONT_FACE_RULE;
|
|
651
|
+
})
|
|
652
|
+
.filter((rule) => {
|
|
653
|
+
return inliner.shouldProcess(rule.style.getPropertyValue('src'));
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
function getCssRules(styleSheets) {
|
|
657
|
+
const cssRules = [];
|
|
658
|
+
const sheetArray = Array.isArray(styleSheets) ? styleSheets : util.asArray(styleSheets);
|
|
659
|
+
sheetArray.forEach((sheet) => {
|
|
660
|
+
try {
|
|
661
|
+
util
|
|
662
|
+
.asArray(sheet.cssRules || [])
|
|
663
|
+
.forEach((rule) => {
|
|
664
|
+
cssRules.push(rule);
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
catch (e) {
|
|
668
|
+
console.log('Error while reading CSS rules from ' + sheet.href, e.toString());
|
|
669
|
+
}
|
|
670
|
+
});
|
|
671
|
+
return cssRules;
|
|
672
|
+
}
|
|
673
|
+
function newWebFont(webFontRule) {
|
|
674
|
+
return {
|
|
675
|
+
resolve() {
|
|
676
|
+
const baseUrl = webFontRule.parentStyleSheet?.href;
|
|
677
|
+
const inliner = domtoimage.impl.inliner;
|
|
678
|
+
return inliner.inlineAll(webFontRule.cssText, baseUrl);
|
|
679
|
+
},
|
|
680
|
+
src() {
|
|
681
|
+
return webFontRule.style.getPropertyValue('src');
|
|
682
|
+
},
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
// ============================================================================
|
|
688
|
+
// IMAGES
|
|
689
|
+
// ============================================================================
|
|
690
|
+
function newImages() {
|
|
691
|
+
return {
|
|
692
|
+
inlineAll,
|
|
693
|
+
impl: {
|
|
694
|
+
newImage,
|
|
695
|
+
},
|
|
696
|
+
};
|
|
697
|
+
function newImage(element) {
|
|
698
|
+
return {
|
|
699
|
+
inline,
|
|
700
|
+
};
|
|
701
|
+
function inline(get) {
|
|
702
|
+
const util = domtoimage.impl.util;
|
|
703
|
+
if (util.isDataUrl(element.src))
|
|
704
|
+
return Promise.resolve();
|
|
705
|
+
return Promise.resolve(element.src)
|
|
706
|
+
.then((src) => {
|
|
707
|
+
return (get || util.getAndEncode)(src);
|
|
708
|
+
})
|
|
709
|
+
.then((data) => {
|
|
710
|
+
return util.dataAsUrl(data, util.mimeType(element.src));
|
|
711
|
+
})
|
|
712
|
+
.then((dataUrl) => {
|
|
713
|
+
return new Promise((resolve, reject) => {
|
|
714
|
+
element.onload = () => {
|
|
715
|
+
resolve();
|
|
716
|
+
};
|
|
717
|
+
element.onerror = () => {
|
|
718
|
+
reject(new Error(`Failed to load image: ${element.src}`));
|
|
719
|
+
};
|
|
720
|
+
element.src = dataUrl;
|
|
721
|
+
});
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
function inlineAll(node) {
|
|
726
|
+
if (!(node instanceof Element))
|
|
727
|
+
return Promise.resolve(node);
|
|
728
|
+
const util = domtoimage.impl.util;
|
|
729
|
+
return inlineBackground(node).then(() => {
|
|
730
|
+
if (node instanceof HTMLImageElement)
|
|
731
|
+
return newImage(node).inline();
|
|
732
|
+
else
|
|
733
|
+
return Promise.all(util.asArray(node.childNodes).map((child) => {
|
|
734
|
+
return inlineAll(child);
|
|
735
|
+
})).then(() => undefined);
|
|
736
|
+
});
|
|
737
|
+
function inlineBackground(node) {
|
|
738
|
+
const background = node.style?.getPropertyValue('background');
|
|
739
|
+
if (!background)
|
|
740
|
+
return Promise.resolve(undefined);
|
|
741
|
+
const inliner = domtoimage.impl.inliner;
|
|
742
|
+
return inliner
|
|
743
|
+
.inlineAll(background)
|
|
744
|
+
.then((inlined) => {
|
|
745
|
+
node.style.setProperty('background', inlined, node.style.getPropertyPriority('background'));
|
|
746
|
+
})
|
|
747
|
+
.then(() => undefined);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
export { domtoimage, toBlob, toJpeg, toPixelData, toPng, toSvg };
|
|
753
|
+
//# sourceMappingURL=dom-screenshot.esm.js.map
|