@d3plus/export 3.0.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -0
- package/es/index.js +1 -0
- package/es/src/saveElement.js +41 -0
- package/package.json +40 -0
- package/umd/d3plus-export.full.js +1145 -0
- package/umd/d3plus-export.full.js.map +1 -0
- package/umd/d3plus-export.full.min.js +90 -0
- package/umd/d3plus-export.js +157 -0
- package/umd/d3plus-export.js.map +1 -0
- package/umd/d3plus-export.min.js +20 -0
|
@@ -0,0 +1,1145 @@
|
|
|
1
|
+
/*
|
|
2
|
+
@d3plus/export v3.0.0
|
|
3
|
+
Export methods for transforming and downloading SVG.
|
|
4
|
+
Copyright (c) 2025 D3plus - https://d3plus.org
|
|
5
|
+
@license MIT
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
(function (factory) {
|
|
9
|
+
typeof define === 'function' && define.amd ? define(factory) :
|
|
10
|
+
factory();
|
|
11
|
+
})((function () { 'use strict';
|
|
12
|
+
|
|
13
|
+
if (typeof window !== "undefined") {
|
|
14
|
+
(function () {
|
|
15
|
+
try {
|
|
16
|
+
if (typeof SVGElement === 'undefined' || Boolean(SVGElement.prototype.innerHTML)) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
} catch (e) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function serializeNode (node) {
|
|
24
|
+
switch (node.nodeType) {
|
|
25
|
+
case 1:
|
|
26
|
+
return serializeElementNode(node);
|
|
27
|
+
case 3:
|
|
28
|
+
return serializeTextNode(node);
|
|
29
|
+
case 8:
|
|
30
|
+
return serializeCommentNode(node);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function serializeTextNode (node) {
|
|
35
|
+
return node.textContent.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function serializeCommentNode (node) {
|
|
39
|
+
return '<!--' + node.nodeValue + '-->'
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function serializeElementNode (node) {
|
|
43
|
+
var output = '';
|
|
44
|
+
|
|
45
|
+
output += '<' + node.tagName;
|
|
46
|
+
|
|
47
|
+
if (node.hasAttributes()) {
|
|
48
|
+
[].forEach.call(node.attributes, function(attrNode) {
|
|
49
|
+
output += ' ' + attrNode.name + '="' + attrNode.value + '"';
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
output += '>';
|
|
54
|
+
|
|
55
|
+
if (node.hasChildNodes()) {
|
|
56
|
+
[].forEach.call(node.childNodes, function(childNode) {
|
|
57
|
+
output += serializeNode(childNode);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
output += '</' + node.tagName + '>';
|
|
62
|
+
|
|
63
|
+
return output;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
Object.defineProperty(SVGElement.prototype, 'innerHTML', {
|
|
67
|
+
get: function () {
|
|
68
|
+
var output = '';
|
|
69
|
+
|
|
70
|
+
[].forEach.call(this.childNodes, function(childNode) {
|
|
71
|
+
output += serializeNode(childNode);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
return output;
|
|
75
|
+
},
|
|
76
|
+
set: function (markup) {
|
|
77
|
+
while (this.firstChild) {
|
|
78
|
+
this.removeChild(this.firstChild);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
var dXML = new DOMParser();
|
|
83
|
+
dXML.async = false;
|
|
84
|
+
|
|
85
|
+
var sXML = '<svg xmlns=\'http://www.w3.org/2000/svg\' xmlns:xlink=\'http://www.w3.org/1999/xlink\'>' + markup + '</svg>';
|
|
86
|
+
var svgDocElement = dXML.parseFromString(sXML, 'text/xml').documentElement;
|
|
87
|
+
|
|
88
|
+
[].forEach.call(svgDocElement.childNodes, function(childNode) {
|
|
89
|
+
this.appendChild(this.ownerDocument.importNode(childNode, true));
|
|
90
|
+
}.bind(this));
|
|
91
|
+
} catch (e) {
|
|
92
|
+
throw new Error('Error parsing markup string');
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
Object.defineProperty(SVGElement.prototype, 'innerSVG', {
|
|
98
|
+
get: function () {
|
|
99
|
+
return this.innerHTML;
|
|
100
|
+
},
|
|
101
|
+
set: function (markup) {
|
|
102
|
+
this.innerHTML = markup;
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
})();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
}));
|
|
110
|
+
|
|
111
|
+
(function (global, factory) {
|
|
112
|
+
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
|
|
113
|
+
typeof define === 'function' && define.amd ? define('@d3plus/export', ['exports'], factory) :
|
|
114
|
+
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.d3plus = {}));
|
|
115
|
+
})(this, (function (exports) {
|
|
116
|
+
function resolveUrl(url, baseUrl) {
|
|
117
|
+
// url is absolute already
|
|
118
|
+
if (url.match(/^[a-z]+:\/\//i)) {
|
|
119
|
+
return url;
|
|
120
|
+
}
|
|
121
|
+
// url is absolute already, without protocol
|
|
122
|
+
if (url.match(/^\/\//)) {
|
|
123
|
+
return window.location.protocol + url;
|
|
124
|
+
}
|
|
125
|
+
// dataURI, mailto:, tel:, etc.
|
|
126
|
+
if (url.match(/^[a-z]+:/i)) {
|
|
127
|
+
return url;
|
|
128
|
+
}
|
|
129
|
+
const doc = document.implementation.createHTMLDocument();
|
|
130
|
+
const base = doc.createElement('base');
|
|
131
|
+
const a = doc.createElement('a');
|
|
132
|
+
doc.head.appendChild(base);
|
|
133
|
+
doc.body.appendChild(a);
|
|
134
|
+
if (baseUrl) {
|
|
135
|
+
base.href = baseUrl;
|
|
136
|
+
}
|
|
137
|
+
a.href = url;
|
|
138
|
+
return a.href;
|
|
139
|
+
}
|
|
140
|
+
const uuid = (()=>{
|
|
141
|
+
// generate uuid for className of pseudo elements.
|
|
142
|
+
// We should not use GUIDs, otherwise pseudo elements sometimes cannot be captured.
|
|
143
|
+
let counter = 0;
|
|
144
|
+
// ref: http://stackoverflow.com/a/6248722/2519373
|
|
145
|
+
const random = ()=>// eslint-disable-next-line no-bitwise
|
|
146
|
+
`0000${(Math.random() * 36 ** 4 << 0).toString(36)}`.slice(-4);
|
|
147
|
+
return ()=>{
|
|
148
|
+
counter += 1;
|
|
149
|
+
return `u${random()}${counter}`;
|
|
150
|
+
};
|
|
151
|
+
})();
|
|
152
|
+
function toArray(arrayLike) {
|
|
153
|
+
const arr = [];
|
|
154
|
+
for(let i = 0, l = arrayLike.length; i < l; i++){
|
|
155
|
+
arr.push(arrayLike[i]);
|
|
156
|
+
}
|
|
157
|
+
return arr;
|
|
158
|
+
}
|
|
159
|
+
let styleProps = null;
|
|
160
|
+
function getStyleProperties(options = {}) {
|
|
161
|
+
if (styleProps) {
|
|
162
|
+
return styleProps;
|
|
163
|
+
}
|
|
164
|
+
if (options.includeStyleProperties) {
|
|
165
|
+
styleProps = options.includeStyleProperties;
|
|
166
|
+
return styleProps;
|
|
167
|
+
}
|
|
168
|
+
styleProps = toArray(window.getComputedStyle(document.documentElement));
|
|
169
|
+
return styleProps;
|
|
170
|
+
}
|
|
171
|
+
function px(node, styleProperty) {
|
|
172
|
+
const win = node.ownerDocument.defaultView || window;
|
|
173
|
+
const val = win.getComputedStyle(node).getPropertyValue(styleProperty);
|
|
174
|
+
return val ? parseFloat(val.replace('px', '')) : 0;
|
|
175
|
+
}
|
|
176
|
+
function getNodeWidth(node) {
|
|
177
|
+
const leftBorder = px(node, 'border-left-width');
|
|
178
|
+
const rightBorder = px(node, 'border-right-width');
|
|
179
|
+
return node.clientWidth + leftBorder + rightBorder;
|
|
180
|
+
}
|
|
181
|
+
function getNodeHeight(node) {
|
|
182
|
+
const topBorder = px(node, 'border-top-width');
|
|
183
|
+
const bottomBorder = px(node, 'border-bottom-width');
|
|
184
|
+
return node.clientHeight + topBorder + bottomBorder;
|
|
185
|
+
}
|
|
186
|
+
function getImageSize(targetNode, options = {}) {
|
|
187
|
+
const width = options.width || getNodeWidth(targetNode);
|
|
188
|
+
const height = options.height || getNodeHeight(targetNode);
|
|
189
|
+
return {
|
|
190
|
+
width,
|
|
191
|
+
height
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
function getPixelRatio() {
|
|
195
|
+
let ratio;
|
|
196
|
+
let FINAL_PROCESS;
|
|
197
|
+
try {
|
|
198
|
+
FINAL_PROCESS = process;
|
|
199
|
+
} catch (e) {
|
|
200
|
+
// pass
|
|
201
|
+
}
|
|
202
|
+
const val = FINAL_PROCESS && FINAL_PROCESS.env ? FINAL_PROCESS.env.devicePixelRatio : null;
|
|
203
|
+
if (val) {
|
|
204
|
+
ratio = parseInt(val, 10);
|
|
205
|
+
if (Number.isNaN(ratio)) {
|
|
206
|
+
ratio = 1;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return ratio || window.devicePixelRatio || 1;
|
|
210
|
+
}
|
|
211
|
+
// @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/canvas#maximum_canvas_size
|
|
212
|
+
const canvasDimensionLimit = 16384;
|
|
213
|
+
function checkCanvasDimensions(canvas) {
|
|
214
|
+
if (canvas.width > canvasDimensionLimit || canvas.height > canvasDimensionLimit) {
|
|
215
|
+
if (canvas.width > canvasDimensionLimit && canvas.height > canvasDimensionLimit) {
|
|
216
|
+
if (canvas.width > canvas.height) {
|
|
217
|
+
canvas.height *= canvasDimensionLimit / canvas.width;
|
|
218
|
+
canvas.width = canvasDimensionLimit;
|
|
219
|
+
} else {
|
|
220
|
+
canvas.width *= canvasDimensionLimit / canvas.height;
|
|
221
|
+
canvas.height = canvasDimensionLimit;
|
|
222
|
+
}
|
|
223
|
+
} else if (canvas.width > canvasDimensionLimit) {
|
|
224
|
+
canvas.height *= canvasDimensionLimit / canvas.width;
|
|
225
|
+
canvas.width = canvasDimensionLimit;
|
|
226
|
+
} else {
|
|
227
|
+
canvas.width *= canvasDimensionLimit / canvas.height;
|
|
228
|
+
canvas.height = canvasDimensionLimit;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
function canvasToBlob(canvas, options = {}) {
|
|
233
|
+
if (canvas.toBlob) {
|
|
234
|
+
return new Promise((resolve)=>{
|
|
235
|
+
canvas.toBlob(resolve, options.type ? options.type : 'image/png', options.quality ? options.quality : 1);
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
return new Promise((resolve)=>{
|
|
239
|
+
const binaryString = window.atob(canvas.toDataURL(options.type ? options.type : undefined, options.quality ? options.quality : undefined).split(',')[1]);
|
|
240
|
+
const len = binaryString.length;
|
|
241
|
+
const binaryArray = new Uint8Array(len);
|
|
242
|
+
for(let i = 0; i < len; i += 1){
|
|
243
|
+
binaryArray[i] = binaryString.charCodeAt(i);
|
|
244
|
+
}
|
|
245
|
+
resolve(new Blob([
|
|
246
|
+
binaryArray
|
|
247
|
+
], {
|
|
248
|
+
type: options.type ? options.type : 'image/png'
|
|
249
|
+
}));
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
function createImage(url) {
|
|
253
|
+
return new Promise((resolve, reject)=>{
|
|
254
|
+
const img = new Image();
|
|
255
|
+
img.onload = ()=>{
|
|
256
|
+
img.decode().then(()=>{
|
|
257
|
+
requestAnimationFrame(()=>resolve(img));
|
|
258
|
+
});
|
|
259
|
+
};
|
|
260
|
+
img.onerror = reject;
|
|
261
|
+
img.crossOrigin = 'anonymous';
|
|
262
|
+
img.decoding = 'async';
|
|
263
|
+
img.src = url;
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
async function svgToDataURL(svg) {
|
|
267
|
+
return Promise.resolve().then(()=>new XMLSerializer().serializeToString(svg)).then(encodeURIComponent).then((html)=>`data:image/svg+xml;charset=utf-8,${html}`);
|
|
268
|
+
}
|
|
269
|
+
async function nodeToDataURL(node, width, height) {
|
|
270
|
+
const xmlns = 'http://www.w3.org/2000/svg';
|
|
271
|
+
const svg = document.createElementNS(xmlns, 'svg');
|
|
272
|
+
const foreignObject = document.createElementNS(xmlns, 'foreignObject');
|
|
273
|
+
svg.setAttribute('width', `${width}`);
|
|
274
|
+
svg.setAttribute('height', `${height}`);
|
|
275
|
+
svg.setAttribute('viewBox', `0 0 ${width} ${height}`);
|
|
276
|
+
foreignObject.setAttribute('width', '100%');
|
|
277
|
+
foreignObject.setAttribute('height', '100%');
|
|
278
|
+
foreignObject.setAttribute('x', '0');
|
|
279
|
+
foreignObject.setAttribute('y', '0');
|
|
280
|
+
foreignObject.setAttribute('externalResourcesRequired', 'true');
|
|
281
|
+
svg.appendChild(foreignObject);
|
|
282
|
+
foreignObject.appendChild(node);
|
|
283
|
+
return svgToDataURL(svg);
|
|
284
|
+
}
|
|
285
|
+
const isInstanceOfElement = (node, instance)=>{
|
|
286
|
+
if (node instanceof instance) return true;
|
|
287
|
+
const nodePrototype = Object.getPrototypeOf(node);
|
|
288
|
+
if (nodePrototype === null) return false;
|
|
289
|
+
return nodePrototype.constructor.name === instance.name || isInstanceOfElement(nodePrototype, instance);
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
function formatCSSText(style) {
|
|
293
|
+
const content = style.getPropertyValue('content');
|
|
294
|
+
return `${style.cssText} content: '${content.replace(/'|"/g, '')}';`;
|
|
295
|
+
}
|
|
296
|
+
function formatCSSProperties(style, options) {
|
|
297
|
+
return getStyleProperties(options).map((name)=>{
|
|
298
|
+
const value = style.getPropertyValue(name);
|
|
299
|
+
const priority = style.getPropertyPriority(name);
|
|
300
|
+
return `${name}: ${value}${priority ? ' !important' : ''};`;
|
|
301
|
+
}).join(' ');
|
|
302
|
+
}
|
|
303
|
+
function getPseudoElementStyle(className, pseudo, style, options) {
|
|
304
|
+
const selector = `.${className}:${pseudo}`;
|
|
305
|
+
const cssText = style.cssText ? formatCSSText(style) : formatCSSProperties(style, options);
|
|
306
|
+
return document.createTextNode(`${selector}{${cssText}}`);
|
|
307
|
+
}
|
|
308
|
+
function clonePseudoElement(nativeNode, clonedNode, pseudo, options) {
|
|
309
|
+
const style = window.getComputedStyle(nativeNode, pseudo);
|
|
310
|
+
const content = style.getPropertyValue('content');
|
|
311
|
+
if (content === '' || content === 'none') {
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
const className = uuid();
|
|
315
|
+
try {
|
|
316
|
+
clonedNode.className = `${clonedNode.className} ${className}`;
|
|
317
|
+
} catch (err) {
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
const styleElement = document.createElement('style');
|
|
321
|
+
styleElement.appendChild(getPseudoElementStyle(className, pseudo, style, options));
|
|
322
|
+
clonedNode.appendChild(styleElement);
|
|
323
|
+
}
|
|
324
|
+
function clonePseudoElements(nativeNode, clonedNode, options) {
|
|
325
|
+
clonePseudoElement(nativeNode, clonedNode, ':before', options);
|
|
326
|
+
clonePseudoElement(nativeNode, clonedNode, ':after', options);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const WOFF = 'application/font-woff';
|
|
330
|
+
const JPEG = 'image/jpeg';
|
|
331
|
+
const mimes = {
|
|
332
|
+
woff: WOFF,
|
|
333
|
+
woff2: WOFF,
|
|
334
|
+
ttf: 'application/font-truetype',
|
|
335
|
+
eot: 'application/vnd.ms-fontobject',
|
|
336
|
+
png: 'image/png',
|
|
337
|
+
jpg: JPEG,
|
|
338
|
+
jpeg: JPEG,
|
|
339
|
+
gif: 'image/gif',
|
|
340
|
+
tiff: 'image/tiff',
|
|
341
|
+
svg: 'image/svg+xml',
|
|
342
|
+
webp: 'image/webp'
|
|
343
|
+
};
|
|
344
|
+
function getExtension(url) {
|
|
345
|
+
const match = /\.([^./]*?)$/g.exec(url);
|
|
346
|
+
return match ? match[1] : '';
|
|
347
|
+
}
|
|
348
|
+
function getMimeType(url) {
|
|
349
|
+
const extension = getExtension(url).toLowerCase();
|
|
350
|
+
return mimes[extension] || '';
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function getContentFromDataUrl(dataURL) {
|
|
354
|
+
return dataURL.split(/,/)[1];
|
|
355
|
+
}
|
|
356
|
+
function isDataUrl(url) {
|
|
357
|
+
return url.search(/^(data:)/) !== -1;
|
|
358
|
+
}
|
|
359
|
+
function makeDataUrl(content, mimeType) {
|
|
360
|
+
return `data:${mimeType};base64,${content}`;
|
|
361
|
+
}
|
|
362
|
+
async function fetchAsDataURL(url, init, process) {
|
|
363
|
+
const res = await fetch(url, init);
|
|
364
|
+
if (res.status === 404) {
|
|
365
|
+
throw new Error(`Resource "${res.url}" not found`);
|
|
366
|
+
}
|
|
367
|
+
const blob = await res.blob();
|
|
368
|
+
return new Promise((resolve, reject)=>{
|
|
369
|
+
const reader = new FileReader();
|
|
370
|
+
reader.onerror = reject;
|
|
371
|
+
reader.onloadend = ()=>{
|
|
372
|
+
try {
|
|
373
|
+
resolve(process({
|
|
374
|
+
res,
|
|
375
|
+
result: reader.result
|
|
376
|
+
}));
|
|
377
|
+
} catch (error) {
|
|
378
|
+
reject(error);
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
reader.readAsDataURL(blob);
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
const cache = {};
|
|
385
|
+
function getCacheKey(url, contentType, includeQueryParams) {
|
|
386
|
+
let key = url.replace(/\?.*/, '');
|
|
387
|
+
if (includeQueryParams) {
|
|
388
|
+
key = url;
|
|
389
|
+
}
|
|
390
|
+
// font resource
|
|
391
|
+
if (/ttf|otf|eot|woff2?/i.test(key)) {
|
|
392
|
+
key = key.replace(/.*\//, '');
|
|
393
|
+
}
|
|
394
|
+
return contentType ? `[${contentType}]${key}` : key;
|
|
395
|
+
}
|
|
396
|
+
async function resourceToDataURL(resourceUrl, contentType, options) {
|
|
397
|
+
const cacheKey = getCacheKey(resourceUrl, contentType, options.includeQueryParams);
|
|
398
|
+
if (cache[cacheKey] != null) {
|
|
399
|
+
return cache[cacheKey];
|
|
400
|
+
}
|
|
401
|
+
// ref: https://developer.mozilla.org/en/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest#Bypassing_the_cache
|
|
402
|
+
if (options.cacheBust) {
|
|
403
|
+
// eslint-disable-next-line no-param-reassign
|
|
404
|
+
resourceUrl += (/\?/.test(resourceUrl) ? '&' : '?') + new Date().getTime();
|
|
405
|
+
}
|
|
406
|
+
let dataURL;
|
|
407
|
+
try {
|
|
408
|
+
const content = await fetchAsDataURL(resourceUrl, options.fetchRequestInit, ({ res, result })=>{
|
|
409
|
+
if (!contentType) {
|
|
410
|
+
// eslint-disable-next-line no-param-reassign
|
|
411
|
+
contentType = res.headers.get('Content-Type') || '';
|
|
412
|
+
}
|
|
413
|
+
return getContentFromDataUrl(result);
|
|
414
|
+
});
|
|
415
|
+
dataURL = makeDataUrl(content, contentType);
|
|
416
|
+
} catch (error) {
|
|
417
|
+
dataURL = options.imagePlaceholder || '';
|
|
418
|
+
let msg = `Failed to fetch resource: ${resourceUrl}`;
|
|
419
|
+
if (error) {
|
|
420
|
+
msg = typeof error === 'string' ? error : error.message;
|
|
421
|
+
}
|
|
422
|
+
if (msg) {
|
|
423
|
+
console.warn(msg);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
cache[cacheKey] = dataURL;
|
|
427
|
+
return dataURL;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
async function cloneCanvasElement(canvas) {
|
|
431
|
+
const dataURL = canvas.toDataURL();
|
|
432
|
+
if (dataURL === 'data:,') {
|
|
433
|
+
return canvas.cloneNode(false);
|
|
434
|
+
}
|
|
435
|
+
return createImage(dataURL);
|
|
436
|
+
}
|
|
437
|
+
async function cloneVideoElement(video, options) {
|
|
438
|
+
if (video.currentSrc) {
|
|
439
|
+
const canvas = document.createElement('canvas');
|
|
440
|
+
const ctx = canvas.getContext('2d');
|
|
441
|
+
canvas.width = video.clientWidth;
|
|
442
|
+
canvas.height = video.clientHeight;
|
|
443
|
+
ctx === null || ctx === void 0 ? void 0 : ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
|
|
444
|
+
const dataURL = canvas.toDataURL();
|
|
445
|
+
return createImage(dataURL);
|
|
446
|
+
}
|
|
447
|
+
const poster = video.poster;
|
|
448
|
+
const contentType = getMimeType(poster);
|
|
449
|
+
const dataURL = await resourceToDataURL(poster, contentType, options);
|
|
450
|
+
return createImage(dataURL);
|
|
451
|
+
}
|
|
452
|
+
async function cloneIFrameElement(iframe, options) {
|
|
453
|
+
var _a;
|
|
454
|
+
try {
|
|
455
|
+
if ((_a = iframe === null || iframe === void 0 ? void 0 : iframe.contentDocument) === null || _a === void 0 ? void 0 : _a.body) {
|
|
456
|
+
return await cloneNode(iframe.contentDocument.body, options, true);
|
|
457
|
+
}
|
|
458
|
+
} catch (_b) {
|
|
459
|
+
// Failed to clone iframe
|
|
460
|
+
}
|
|
461
|
+
return iframe.cloneNode(false);
|
|
462
|
+
}
|
|
463
|
+
async function cloneSingleNode(node, options) {
|
|
464
|
+
if (isInstanceOfElement(node, HTMLCanvasElement)) {
|
|
465
|
+
return cloneCanvasElement(node);
|
|
466
|
+
}
|
|
467
|
+
if (isInstanceOfElement(node, HTMLVideoElement)) {
|
|
468
|
+
return cloneVideoElement(node, options);
|
|
469
|
+
}
|
|
470
|
+
if (isInstanceOfElement(node, HTMLIFrameElement)) {
|
|
471
|
+
return cloneIFrameElement(node, options);
|
|
472
|
+
}
|
|
473
|
+
return node.cloneNode(isSVGElement(node));
|
|
474
|
+
}
|
|
475
|
+
const isSlotElement = (node)=>node.tagName != null && node.tagName.toUpperCase() === 'SLOT';
|
|
476
|
+
const isSVGElement = (node)=>node.tagName != null && node.tagName.toUpperCase() === 'SVG';
|
|
477
|
+
async function cloneChildren(nativeNode, clonedNode, options) {
|
|
478
|
+
var _a, _b;
|
|
479
|
+
if (isSVGElement(clonedNode)) {
|
|
480
|
+
return clonedNode;
|
|
481
|
+
}
|
|
482
|
+
let children = [];
|
|
483
|
+
if (isSlotElement(nativeNode) && nativeNode.assignedNodes) {
|
|
484
|
+
children = toArray(nativeNode.assignedNodes());
|
|
485
|
+
} else if (isInstanceOfElement(nativeNode, HTMLIFrameElement) && ((_a = nativeNode.contentDocument) === null || _a === void 0 ? void 0 : _a.body)) {
|
|
486
|
+
children = toArray(nativeNode.contentDocument.body.childNodes);
|
|
487
|
+
} else {
|
|
488
|
+
children = toArray(((_b = nativeNode.shadowRoot) !== null && _b !== void 0 ? _b : nativeNode).childNodes);
|
|
489
|
+
}
|
|
490
|
+
if (children.length === 0 || isInstanceOfElement(nativeNode, HTMLVideoElement)) {
|
|
491
|
+
return clonedNode;
|
|
492
|
+
}
|
|
493
|
+
await children.reduce((deferred, child)=>deferred.then(()=>cloneNode(child, options)).then((clonedChild)=>{
|
|
494
|
+
if (clonedChild) {
|
|
495
|
+
clonedNode.appendChild(clonedChild);
|
|
496
|
+
}
|
|
497
|
+
}), Promise.resolve());
|
|
498
|
+
return clonedNode;
|
|
499
|
+
}
|
|
500
|
+
function cloneCSSStyle(nativeNode, clonedNode, options) {
|
|
501
|
+
const targetStyle = clonedNode.style;
|
|
502
|
+
if (!targetStyle) {
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
const sourceStyle = window.getComputedStyle(nativeNode);
|
|
506
|
+
if (sourceStyle.cssText) {
|
|
507
|
+
targetStyle.cssText = sourceStyle.cssText;
|
|
508
|
+
targetStyle.transformOrigin = sourceStyle.transformOrigin;
|
|
509
|
+
} else {
|
|
510
|
+
getStyleProperties(options).forEach((name)=>{
|
|
511
|
+
let value = sourceStyle.getPropertyValue(name);
|
|
512
|
+
if (name === 'font-size' && value.endsWith('px')) {
|
|
513
|
+
const reducedFont = Math.floor(parseFloat(value.substring(0, value.length - 2))) - 0.1;
|
|
514
|
+
value = `${reducedFont}px`;
|
|
515
|
+
}
|
|
516
|
+
if (isInstanceOfElement(nativeNode, HTMLIFrameElement) && name === 'display' && value === 'inline') {
|
|
517
|
+
value = 'block';
|
|
518
|
+
}
|
|
519
|
+
if (name === 'd' && clonedNode.getAttribute('d')) {
|
|
520
|
+
value = `path(${clonedNode.getAttribute('d')})`;
|
|
521
|
+
}
|
|
522
|
+
targetStyle.setProperty(name, value, sourceStyle.getPropertyPriority(name));
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
function cloneInputValue(nativeNode, clonedNode) {
|
|
527
|
+
if (isInstanceOfElement(nativeNode, HTMLTextAreaElement)) {
|
|
528
|
+
clonedNode.innerHTML = nativeNode.value;
|
|
529
|
+
}
|
|
530
|
+
if (isInstanceOfElement(nativeNode, HTMLInputElement)) {
|
|
531
|
+
clonedNode.setAttribute('value', nativeNode.value);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
function cloneSelectValue(nativeNode, clonedNode) {
|
|
535
|
+
if (isInstanceOfElement(nativeNode, HTMLSelectElement)) {
|
|
536
|
+
const clonedSelect = clonedNode;
|
|
537
|
+
const selectedOption = Array.from(clonedSelect.children).find((child)=>nativeNode.value === child.getAttribute('value'));
|
|
538
|
+
if (selectedOption) {
|
|
539
|
+
selectedOption.setAttribute('selected', '');
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
function decorate(nativeNode, clonedNode, options) {
|
|
544
|
+
if (isInstanceOfElement(clonedNode, Element)) {
|
|
545
|
+
cloneCSSStyle(nativeNode, clonedNode, options);
|
|
546
|
+
clonePseudoElements(nativeNode, clonedNode, options);
|
|
547
|
+
cloneInputValue(nativeNode, clonedNode);
|
|
548
|
+
cloneSelectValue(nativeNode, clonedNode);
|
|
549
|
+
}
|
|
550
|
+
return clonedNode;
|
|
551
|
+
}
|
|
552
|
+
async function ensureSVGSymbols(clone, options) {
|
|
553
|
+
const uses = clone.querySelectorAll ? clone.querySelectorAll('use') : [];
|
|
554
|
+
if (uses.length === 0) {
|
|
555
|
+
return clone;
|
|
556
|
+
}
|
|
557
|
+
const processedDefs = {};
|
|
558
|
+
for(let i = 0; i < uses.length; i++){
|
|
559
|
+
const use = uses[i];
|
|
560
|
+
const id = use.getAttribute('xlink:href');
|
|
561
|
+
if (id) {
|
|
562
|
+
const exist = clone.querySelector(id);
|
|
563
|
+
const definition = document.querySelector(id);
|
|
564
|
+
if (!exist && definition && !processedDefs[id]) {
|
|
565
|
+
// eslint-disable-next-line no-await-in-loop
|
|
566
|
+
processedDefs[id] = await cloneNode(definition, options, true);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
const nodes = Object.values(processedDefs);
|
|
571
|
+
if (nodes.length) {
|
|
572
|
+
const ns = 'http://www.w3.org/1999/xhtml';
|
|
573
|
+
const svg = document.createElementNS(ns, 'svg');
|
|
574
|
+
svg.setAttribute('xmlns', ns);
|
|
575
|
+
svg.style.position = 'absolute';
|
|
576
|
+
svg.style.width = '0';
|
|
577
|
+
svg.style.height = '0';
|
|
578
|
+
svg.style.overflow = 'hidden';
|
|
579
|
+
svg.style.display = 'none';
|
|
580
|
+
const defs = document.createElementNS(ns, 'defs');
|
|
581
|
+
svg.appendChild(defs);
|
|
582
|
+
for(let i = 0; i < nodes.length; i++){
|
|
583
|
+
defs.appendChild(nodes[i]);
|
|
584
|
+
}
|
|
585
|
+
clone.appendChild(svg);
|
|
586
|
+
}
|
|
587
|
+
return clone;
|
|
588
|
+
}
|
|
589
|
+
async function cloneNode(node, options, isRoot) {
|
|
590
|
+
if (!isRoot && options.filter && !options.filter(node)) {
|
|
591
|
+
return null;
|
|
592
|
+
}
|
|
593
|
+
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));
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
const URL_REGEX = /url\((['"]?)([^'"]+?)\1\)/g;
|
|
597
|
+
const URL_WITH_FORMAT_REGEX = /url\([^)]+\)\s*format\((["']?)([^"']+)\1\)/g;
|
|
598
|
+
const FONT_SRC_REGEX = /src:\s*(?:url\([^)]+\)\s*format\([^)]+\)[,;]\s*)+/g;
|
|
599
|
+
function toRegex(url) {
|
|
600
|
+
// eslint-disable-next-line no-useless-escape
|
|
601
|
+
const escaped = url.replace(/([.*+?^${}()|\[\]\/\\])/g, '\\$1');
|
|
602
|
+
return new RegExp(`(url\\(['"]?)(${escaped})(['"]?\\))`, 'g');
|
|
603
|
+
}
|
|
604
|
+
function parseURLs(cssText) {
|
|
605
|
+
const urls = [];
|
|
606
|
+
cssText.replace(URL_REGEX, (raw, quotation, url)=>{
|
|
607
|
+
urls.push(url);
|
|
608
|
+
return raw;
|
|
609
|
+
});
|
|
610
|
+
return urls.filter((url)=>!isDataUrl(url));
|
|
611
|
+
}
|
|
612
|
+
async function embed(cssText, resourceURL, baseURL, options, getContentFromUrl) {
|
|
613
|
+
try {
|
|
614
|
+
const resolvedURL = baseURL ? resolveUrl(resourceURL, baseURL) : resourceURL;
|
|
615
|
+
const contentType = getMimeType(resourceURL);
|
|
616
|
+
let dataURL;
|
|
617
|
+
if (getContentFromUrl) ; else {
|
|
618
|
+
dataURL = await resourceToDataURL(resolvedURL, contentType, options);
|
|
619
|
+
}
|
|
620
|
+
return cssText.replace(toRegex(resourceURL), `$1${dataURL}$3`);
|
|
621
|
+
} catch (error) {
|
|
622
|
+
// pass
|
|
623
|
+
}
|
|
624
|
+
return cssText;
|
|
625
|
+
}
|
|
626
|
+
function filterPreferredFontFormat(str, { preferredFontFormat }) {
|
|
627
|
+
return !preferredFontFormat ? str : str.replace(FONT_SRC_REGEX, (match)=>{
|
|
628
|
+
// eslint-disable-next-line no-constant-condition
|
|
629
|
+
while(true){
|
|
630
|
+
const [src, , format] = URL_WITH_FORMAT_REGEX.exec(match) || [];
|
|
631
|
+
if (!format) {
|
|
632
|
+
return '';
|
|
633
|
+
}
|
|
634
|
+
if (format === preferredFontFormat) {
|
|
635
|
+
return `src: ${src};`;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
function shouldEmbed(url) {
|
|
641
|
+
return url.search(URL_REGEX) !== -1;
|
|
642
|
+
}
|
|
643
|
+
async function embedResources(cssText, baseUrl, options) {
|
|
644
|
+
if (!shouldEmbed(cssText)) {
|
|
645
|
+
return cssText;
|
|
646
|
+
}
|
|
647
|
+
const filteredCSSText = filterPreferredFontFormat(cssText, options);
|
|
648
|
+
const urls = parseURLs(filteredCSSText);
|
|
649
|
+
return urls.reduce((deferred, url)=>deferred.then((css)=>embed(css, url, baseUrl, options)), Promise.resolve(filteredCSSText));
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
async function embedProp(propName, node, options) {
|
|
653
|
+
var _a;
|
|
654
|
+
const propValue = (_a = node.style) === null || _a === void 0 ? void 0 : _a.getPropertyValue(propName);
|
|
655
|
+
if (propValue) {
|
|
656
|
+
const cssString = await embedResources(propValue, null, options);
|
|
657
|
+
node.style.setProperty(propName, cssString, node.style.getPropertyPriority(propName));
|
|
658
|
+
return true;
|
|
659
|
+
}
|
|
660
|
+
return false;
|
|
661
|
+
}
|
|
662
|
+
async function embedBackground(clonedNode, options) {
|
|
663
|
+
await embedProp('background', clonedNode, options) || await embedProp('background-image', clonedNode, options);
|
|
664
|
+
await embedProp('mask', clonedNode, options) || await embedProp('-webkit-mask', clonedNode, options) || await embedProp('mask-image', clonedNode, options) || await embedProp('-webkit-mask-image', clonedNode, options);
|
|
665
|
+
}
|
|
666
|
+
async function embedImageNode(clonedNode, options) {
|
|
667
|
+
const isImageElement = isInstanceOfElement(clonedNode, HTMLImageElement);
|
|
668
|
+
if (!(isImageElement && !isDataUrl(clonedNode.src)) && !(isInstanceOfElement(clonedNode, SVGImageElement) && !isDataUrl(clonedNode.href.baseVal))) {
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
const url = isImageElement ? clonedNode.src : clonedNode.href.baseVal;
|
|
672
|
+
const dataURL = await resourceToDataURL(url, getMimeType(url), options);
|
|
673
|
+
await new Promise((resolve, reject)=>{
|
|
674
|
+
clonedNode.onload = resolve;
|
|
675
|
+
clonedNode.onerror = options.onImageErrorHandler ? (...attributes)=>{
|
|
676
|
+
try {
|
|
677
|
+
resolve(options.onImageErrorHandler(...attributes));
|
|
678
|
+
} catch (error) {
|
|
679
|
+
reject(error);
|
|
680
|
+
}
|
|
681
|
+
} : reject;
|
|
682
|
+
const image = clonedNode;
|
|
683
|
+
if (image.decode) {
|
|
684
|
+
image.decode = resolve;
|
|
685
|
+
}
|
|
686
|
+
if (image.loading === 'lazy') {
|
|
687
|
+
image.loading = 'eager';
|
|
688
|
+
}
|
|
689
|
+
if (isImageElement) {
|
|
690
|
+
clonedNode.srcset = '';
|
|
691
|
+
clonedNode.src = dataURL;
|
|
692
|
+
} else {
|
|
693
|
+
clonedNode.href.baseVal = dataURL;
|
|
694
|
+
}
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
async function embedChildren(clonedNode, options) {
|
|
698
|
+
const children = toArray(clonedNode.childNodes);
|
|
699
|
+
const deferreds = children.map((child)=>embedImages(child, options));
|
|
700
|
+
await Promise.all(deferreds).then(()=>clonedNode);
|
|
701
|
+
}
|
|
702
|
+
async function embedImages(clonedNode, options) {
|
|
703
|
+
if (isInstanceOfElement(clonedNode, Element)) {
|
|
704
|
+
await embedBackground(clonedNode, options);
|
|
705
|
+
await embedImageNode(clonedNode, options);
|
|
706
|
+
await embedChildren(clonedNode, options);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
function applyStyle(node, options) {
|
|
711
|
+
const { style } = node;
|
|
712
|
+
if (options.backgroundColor) {
|
|
713
|
+
style.backgroundColor = options.backgroundColor;
|
|
714
|
+
}
|
|
715
|
+
if (options.width) {
|
|
716
|
+
style.width = `${options.width}px`;
|
|
717
|
+
}
|
|
718
|
+
if (options.height) {
|
|
719
|
+
style.height = `${options.height}px`;
|
|
720
|
+
}
|
|
721
|
+
const manual = options.style;
|
|
722
|
+
if (manual != null) {
|
|
723
|
+
Object.keys(manual).forEach((key)=>{
|
|
724
|
+
style[key] = manual[key];
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
return node;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
const cssFetchCache = {};
|
|
731
|
+
async function fetchCSS(url) {
|
|
732
|
+
let cache = cssFetchCache[url];
|
|
733
|
+
if (cache != null) {
|
|
734
|
+
return cache;
|
|
735
|
+
}
|
|
736
|
+
const res = await fetch(url);
|
|
737
|
+
const cssText = await res.text();
|
|
738
|
+
cache = {
|
|
739
|
+
url,
|
|
740
|
+
cssText
|
|
741
|
+
};
|
|
742
|
+
cssFetchCache[url] = cache;
|
|
743
|
+
return cache;
|
|
744
|
+
}
|
|
745
|
+
async function embedFonts(data, options) {
|
|
746
|
+
let cssText = data.cssText;
|
|
747
|
+
const regexUrl = /url\(["']?([^"')]+)["']?\)/g;
|
|
748
|
+
const fontLocs = cssText.match(/url\([^)]+\)/g) || [];
|
|
749
|
+
const loadFonts = fontLocs.map(async (loc)=>{
|
|
750
|
+
let url = loc.replace(regexUrl, '$1');
|
|
751
|
+
if (!url.startsWith('https://')) {
|
|
752
|
+
url = new URL(url, data.url).href;
|
|
753
|
+
}
|
|
754
|
+
return fetchAsDataURL(url, options.fetchRequestInit, ({ result })=>{
|
|
755
|
+
cssText = cssText.replace(loc, `url(${result})`);
|
|
756
|
+
return [
|
|
757
|
+
loc,
|
|
758
|
+
result
|
|
759
|
+
];
|
|
760
|
+
});
|
|
761
|
+
});
|
|
762
|
+
return Promise.all(loadFonts).then(()=>cssText);
|
|
763
|
+
}
|
|
764
|
+
function parseCSS(source) {
|
|
765
|
+
if (source == null) {
|
|
766
|
+
return [];
|
|
767
|
+
}
|
|
768
|
+
const result = [];
|
|
769
|
+
const commentsRegex = /(\/\*[\s\S]*?\*\/)/gi;
|
|
770
|
+
// strip out comments
|
|
771
|
+
let cssText = source.replace(commentsRegex, '');
|
|
772
|
+
// eslint-disable-next-line prefer-regex-literals
|
|
773
|
+
const keyframesRegex = new RegExp('((@.*?keyframes [\\s\\S]*?){([\\s\\S]*?}\\s*?)})', 'gi');
|
|
774
|
+
// eslint-disable-next-line no-constant-condition
|
|
775
|
+
while(true){
|
|
776
|
+
const matches = keyframesRegex.exec(cssText);
|
|
777
|
+
if (matches === null) {
|
|
778
|
+
break;
|
|
779
|
+
}
|
|
780
|
+
result.push(matches[0]);
|
|
781
|
+
}
|
|
782
|
+
cssText = cssText.replace(keyframesRegex, '');
|
|
783
|
+
const importRegex = /@import[\s\S]*?url\([^)]*\)[\s\S]*?;/gi;
|
|
784
|
+
// to match css & media queries together
|
|
785
|
+
const combinedCSSRegex = '((\\s*?(?:\\/\\*[\\s\\S]*?\\*\\/)?\\s*?@media[\\s\\S]' + '*?){([\\s\\S]*?)}\\s*?})|(([\\s\\S]*?){([\\s\\S]*?)})';
|
|
786
|
+
// unified regex
|
|
787
|
+
const unifiedRegex = new RegExp(combinedCSSRegex, 'gi');
|
|
788
|
+
// eslint-disable-next-line no-constant-condition
|
|
789
|
+
while(true){
|
|
790
|
+
let matches = importRegex.exec(cssText);
|
|
791
|
+
if (matches === null) {
|
|
792
|
+
matches = unifiedRegex.exec(cssText);
|
|
793
|
+
if (matches === null) {
|
|
794
|
+
break;
|
|
795
|
+
} else {
|
|
796
|
+
importRegex.lastIndex = unifiedRegex.lastIndex;
|
|
797
|
+
}
|
|
798
|
+
} else {
|
|
799
|
+
unifiedRegex.lastIndex = importRegex.lastIndex;
|
|
800
|
+
}
|
|
801
|
+
result.push(matches[0]);
|
|
802
|
+
}
|
|
803
|
+
return result;
|
|
804
|
+
}
|
|
805
|
+
async function getCSSRules(styleSheets, options) {
|
|
806
|
+
const ret = [];
|
|
807
|
+
const deferreds = [];
|
|
808
|
+
// First loop inlines imports
|
|
809
|
+
styleSheets.forEach((sheet)=>{
|
|
810
|
+
if ('cssRules' in sheet) {
|
|
811
|
+
try {
|
|
812
|
+
toArray(sheet.cssRules || []).forEach((item, index)=>{
|
|
813
|
+
if (item.type === CSSRule.IMPORT_RULE) {
|
|
814
|
+
let importIndex = index + 1;
|
|
815
|
+
const url = item.href;
|
|
816
|
+
const deferred = fetchCSS(url).then((metadata)=>embedFonts(metadata, options)).then((cssText)=>parseCSS(cssText).forEach((rule)=>{
|
|
817
|
+
try {
|
|
818
|
+
sheet.insertRule(rule, rule.startsWith('@import') ? importIndex += 1 : sheet.cssRules.length);
|
|
819
|
+
} catch (error) {
|
|
820
|
+
console.error('Error inserting rule from remote css', {
|
|
821
|
+
rule,
|
|
822
|
+
error
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
})).catch((e)=>{
|
|
826
|
+
console.error('Error loading remote css', e.toString());
|
|
827
|
+
});
|
|
828
|
+
deferreds.push(deferred);
|
|
829
|
+
}
|
|
830
|
+
});
|
|
831
|
+
} catch (e) {
|
|
832
|
+
const inline = styleSheets.find((a)=>a.href == null) || document.styleSheets[0];
|
|
833
|
+
if (sheet.href != null) {
|
|
834
|
+
deferreds.push(fetchCSS(sheet.href).then((metadata)=>embedFonts(metadata, options)).then((cssText)=>parseCSS(cssText).forEach((rule)=>{
|
|
835
|
+
inline.insertRule(rule, inline.cssRules.length);
|
|
836
|
+
})).catch((err)=>{
|
|
837
|
+
console.error('Error loading remote stylesheet', err);
|
|
838
|
+
}));
|
|
839
|
+
}
|
|
840
|
+
console.error('Error inlining remote css file', e);
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
});
|
|
844
|
+
return Promise.all(deferreds).then(()=>{
|
|
845
|
+
// Second loop parses rules
|
|
846
|
+
styleSheets.forEach((sheet)=>{
|
|
847
|
+
if ('cssRules' in sheet) {
|
|
848
|
+
try {
|
|
849
|
+
toArray(sheet.cssRules || []).forEach((item)=>{
|
|
850
|
+
ret.push(item);
|
|
851
|
+
});
|
|
852
|
+
} catch (e) {
|
|
853
|
+
console.error(`Error while reading CSS rules from ${sheet.href}`, e);
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
});
|
|
857
|
+
return ret;
|
|
858
|
+
});
|
|
859
|
+
}
|
|
860
|
+
function getWebFontRules(cssRules) {
|
|
861
|
+
return cssRules.filter((rule)=>rule.type === CSSRule.FONT_FACE_RULE).filter((rule)=>shouldEmbed(rule.style.getPropertyValue('src')));
|
|
862
|
+
}
|
|
863
|
+
async function parseWebFontRules(node, options) {
|
|
864
|
+
if (node.ownerDocument == null) {
|
|
865
|
+
throw new Error('Provided element is not within a Document');
|
|
866
|
+
}
|
|
867
|
+
const styleSheets = toArray(node.ownerDocument.styleSheets);
|
|
868
|
+
const cssRules = await getCSSRules(styleSheets, options);
|
|
869
|
+
return getWebFontRules(cssRules);
|
|
870
|
+
}
|
|
871
|
+
function normalizeFontFamily(font) {
|
|
872
|
+
return font.trim().replace(/["']/g, '');
|
|
873
|
+
}
|
|
874
|
+
function getUsedFonts(node) {
|
|
875
|
+
const fonts = new Set();
|
|
876
|
+
function traverse(node) {
|
|
877
|
+
const fontFamily = node.style.fontFamily || getComputedStyle(node).fontFamily;
|
|
878
|
+
fontFamily.split(',').forEach((font)=>{
|
|
879
|
+
fonts.add(normalizeFontFamily(font));
|
|
880
|
+
});
|
|
881
|
+
Array.from(node.children).forEach((child)=>{
|
|
882
|
+
if (child instanceof HTMLElement) {
|
|
883
|
+
traverse(child);
|
|
884
|
+
}
|
|
885
|
+
});
|
|
886
|
+
}
|
|
887
|
+
traverse(node);
|
|
888
|
+
return fonts;
|
|
889
|
+
}
|
|
890
|
+
async function getWebFontCSS(node, options) {
|
|
891
|
+
const rules = await parseWebFontRules(node, options);
|
|
892
|
+
const usedFonts = getUsedFonts(node);
|
|
893
|
+
const cssTexts = await Promise.all(rules.filter((rule)=>usedFonts.has(normalizeFontFamily(rule.style.fontFamily))).map((rule)=>{
|
|
894
|
+
const baseUrl = rule.parentStyleSheet ? rule.parentStyleSheet.href : null;
|
|
895
|
+
return embedResources(rule.cssText, baseUrl, options);
|
|
896
|
+
}));
|
|
897
|
+
return cssTexts.join('\n');
|
|
898
|
+
}
|
|
899
|
+
async function embedWebFonts(clonedNode, options) {
|
|
900
|
+
const cssText = options.fontEmbedCSS != null ? options.fontEmbedCSS : options.skipFonts ? null : await getWebFontCSS(clonedNode, options);
|
|
901
|
+
if (cssText) {
|
|
902
|
+
const styleNode = document.createElement('style');
|
|
903
|
+
const sytleContent = document.createTextNode(cssText);
|
|
904
|
+
styleNode.appendChild(sytleContent);
|
|
905
|
+
if (clonedNode.firstChild) {
|
|
906
|
+
clonedNode.insertBefore(styleNode, clonedNode.firstChild);
|
|
907
|
+
} else {
|
|
908
|
+
clonedNode.appendChild(styleNode);
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
async function toSvg(node, options = {}) {
|
|
914
|
+
const { width, height } = getImageSize(node, options);
|
|
915
|
+
const clonedNode = await cloneNode(node, options, true);
|
|
916
|
+
await embedWebFonts(clonedNode, options);
|
|
917
|
+
await embedImages(clonedNode, options);
|
|
918
|
+
applyStyle(clonedNode, options);
|
|
919
|
+
const datauri = await nodeToDataURL(clonedNode, width, height);
|
|
920
|
+
return datauri;
|
|
921
|
+
}
|
|
922
|
+
async function toCanvas(node, options = {}) {
|
|
923
|
+
const { width, height } = getImageSize(node, options);
|
|
924
|
+
const svg = await toSvg(node, options);
|
|
925
|
+
const img = await createImage(svg);
|
|
926
|
+
const canvas = document.createElement('canvas');
|
|
927
|
+
const context = canvas.getContext('2d');
|
|
928
|
+
const ratio = options.pixelRatio || getPixelRatio();
|
|
929
|
+
const canvasWidth = options.canvasWidth || width;
|
|
930
|
+
const canvasHeight = options.canvasHeight || height;
|
|
931
|
+
canvas.width = canvasWidth * ratio;
|
|
932
|
+
canvas.height = canvasHeight * ratio;
|
|
933
|
+
if (!options.skipAutoScale) {
|
|
934
|
+
checkCanvasDimensions(canvas);
|
|
935
|
+
}
|
|
936
|
+
canvas.style.width = `${canvasWidth}`;
|
|
937
|
+
canvas.style.height = `${canvasHeight}`;
|
|
938
|
+
if (options.backgroundColor) {
|
|
939
|
+
context.fillStyle = options.backgroundColor;
|
|
940
|
+
context.fillRect(0, 0, canvas.width, canvas.height);
|
|
941
|
+
}
|
|
942
|
+
context.drawImage(img, 0, 0, canvas.width, canvas.height);
|
|
943
|
+
return canvas;
|
|
944
|
+
}
|
|
945
|
+
async function toBlob(node, options = {}) {
|
|
946
|
+
const canvas = await toCanvas(node, options);
|
|
947
|
+
const blob = await canvasToBlob(canvas);
|
|
948
|
+
return blob;
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
var FileSaver$1 = {exports: {}};
|
|
952
|
+
|
|
953
|
+
/* FileSaver.js
|
|
954
|
+
* A saveAs() FileSaver implementation.
|
|
955
|
+
* 1.3.2
|
|
956
|
+
* 2016-06-16 18:25:19
|
|
957
|
+
*
|
|
958
|
+
* By Eli Grey, http://eligrey.com
|
|
959
|
+
* License: MIT
|
|
960
|
+
* See https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md
|
|
961
|
+
*/
|
|
962
|
+
var FileSaver = FileSaver$1.exports;
|
|
963
|
+
|
|
964
|
+
var hasRequiredFileSaver;
|
|
965
|
+
|
|
966
|
+
function requireFileSaver () {
|
|
967
|
+
if (hasRequiredFileSaver) return FileSaver$1.exports;
|
|
968
|
+
hasRequiredFileSaver = 1;
|
|
969
|
+
(function (module) {
|
|
970
|
+
/*global self */ /*jslint bitwise: true, indent: 4, laxbreak: true, laxcomma: true, smarttabs: true, plusplus: true */ /*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */ var saveAs = saveAs || function(view) {
|
|
971
|
+
// IE <10 is explicitly unsupported
|
|
972
|
+
if (typeof view === "undefined" || typeof navigator !== "undefined" && /MSIE [1-9]\./.test(navigator.userAgent)) {
|
|
973
|
+
return;
|
|
974
|
+
}
|
|
975
|
+
var doc = view.document, get_URL = function() {
|
|
976
|
+
return view.URL || view.webkitURL || view;
|
|
977
|
+
}, save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a"), can_use_save_link = "download" in save_link, click = function(node) {
|
|
978
|
+
var event = new MouseEvent("click");
|
|
979
|
+
node.dispatchEvent(event);
|
|
980
|
+
}, is_safari = /constructor/i.test(view.HTMLElement) || view.safari, is_chrome_ios = /CriOS\/[\d]+/.test(navigator.userAgent), throw_outside = function(ex) {
|
|
981
|
+
(view.setImmediate || view.setTimeout)(function() {
|
|
982
|
+
throw ex;
|
|
983
|
+
}, 0);
|
|
984
|
+
}, force_saveable_type = "application/octet-stream", arbitrary_revoke_timeout = 1000 * 40 // in ms
|
|
985
|
+
, revoke = function(file) {
|
|
986
|
+
var revoker = function() {
|
|
987
|
+
if (typeof file === "string") {
|
|
988
|
+
get_URL().revokeObjectURL(file);
|
|
989
|
+
} else {
|
|
990
|
+
file.remove();
|
|
991
|
+
}
|
|
992
|
+
};
|
|
993
|
+
setTimeout(revoker, arbitrary_revoke_timeout);
|
|
994
|
+
}, dispatch = function(filesaver, event_types, event) {
|
|
995
|
+
event_types = [].concat(event_types);
|
|
996
|
+
var i = event_types.length;
|
|
997
|
+
while(i--){
|
|
998
|
+
var listener = filesaver["on" + event_types[i]];
|
|
999
|
+
if (typeof listener === "function") {
|
|
1000
|
+
try {
|
|
1001
|
+
listener.call(filesaver, event || filesaver);
|
|
1002
|
+
} catch (ex) {
|
|
1003
|
+
throw_outside(ex);
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
}, auto_bom = function(blob) {
|
|
1008
|
+
// prepend BOM for UTF-8 XML and text/* types (including HTML)
|
|
1009
|
+
// note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF
|
|
1010
|
+
if (/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) {
|
|
1011
|
+
return new Blob([
|
|
1012
|
+
String.fromCharCode(0xFEFF),
|
|
1013
|
+
blob
|
|
1014
|
+
], {
|
|
1015
|
+
type: blob.type
|
|
1016
|
+
});
|
|
1017
|
+
}
|
|
1018
|
+
return blob;
|
|
1019
|
+
}, FileSaver = function(blob, name, no_auto_bom) {
|
|
1020
|
+
if (!no_auto_bom) {
|
|
1021
|
+
blob = auto_bom(blob);
|
|
1022
|
+
}
|
|
1023
|
+
// First try a.download, then web filesystem, then object URLs
|
|
1024
|
+
var filesaver = this, type = blob.type, force = type === force_saveable_type, object_url, dispatch_all = function() {
|
|
1025
|
+
dispatch(filesaver, "writestart progress write writeend".split(" "));
|
|
1026
|
+
}, fs_error = function() {
|
|
1027
|
+
if ((is_chrome_ios || force && is_safari) && view.FileReader) {
|
|
1028
|
+
// Safari doesn't allow downloading of blob urls
|
|
1029
|
+
var reader = new FileReader();
|
|
1030
|
+
reader.onloadend = function() {
|
|
1031
|
+
var url = is_chrome_ios ? reader.result : reader.result.replace(/^data:[^;]*;/, 'data:attachment/file;');
|
|
1032
|
+
var popup = view.open(url, '_blank');
|
|
1033
|
+
if (!popup) view.location.href = url;
|
|
1034
|
+
url = undefined; // release reference before dispatching
|
|
1035
|
+
filesaver.readyState = filesaver.DONE;
|
|
1036
|
+
dispatch_all();
|
|
1037
|
+
};
|
|
1038
|
+
reader.readAsDataURL(blob);
|
|
1039
|
+
filesaver.readyState = filesaver.INIT;
|
|
1040
|
+
return;
|
|
1041
|
+
}
|
|
1042
|
+
// don't create more object URLs than needed
|
|
1043
|
+
if (!object_url) {
|
|
1044
|
+
object_url = get_URL().createObjectURL(blob);
|
|
1045
|
+
}
|
|
1046
|
+
if (force) {
|
|
1047
|
+
view.location.href = object_url;
|
|
1048
|
+
} else {
|
|
1049
|
+
var opened = view.open(object_url, "_blank");
|
|
1050
|
+
if (!opened) {
|
|
1051
|
+
// Apple does not allow window.open, see https://developer.apple.com/library/safari/documentation/Tools/Conceptual/SafariExtensionGuide/WorkingwithWindowsandTabs/WorkingwithWindowsandTabs.html
|
|
1052
|
+
view.location.href = object_url;
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
filesaver.readyState = filesaver.DONE;
|
|
1056
|
+
dispatch_all();
|
|
1057
|
+
revoke(object_url);
|
|
1058
|
+
};
|
|
1059
|
+
filesaver.readyState = filesaver.INIT;
|
|
1060
|
+
if (can_use_save_link) {
|
|
1061
|
+
object_url = get_URL().createObjectURL(blob);
|
|
1062
|
+
setTimeout(function() {
|
|
1063
|
+
save_link.href = object_url;
|
|
1064
|
+
save_link.download = name;
|
|
1065
|
+
click(save_link);
|
|
1066
|
+
dispatch_all();
|
|
1067
|
+
revoke(object_url);
|
|
1068
|
+
filesaver.readyState = filesaver.DONE;
|
|
1069
|
+
});
|
|
1070
|
+
return;
|
|
1071
|
+
}
|
|
1072
|
+
fs_error();
|
|
1073
|
+
}, FS_proto = FileSaver.prototype, saveAs = function(blob, name, no_auto_bom) {
|
|
1074
|
+
return new FileSaver(blob, name || blob.name || "download", no_auto_bom);
|
|
1075
|
+
};
|
|
1076
|
+
// IE 10+ (native saveAs)
|
|
1077
|
+
if (typeof navigator !== "undefined" && navigator.msSaveOrOpenBlob) {
|
|
1078
|
+
return function(blob, name, no_auto_bom) {
|
|
1079
|
+
name = name || blob.name || "download";
|
|
1080
|
+
if (!no_auto_bom) {
|
|
1081
|
+
blob = auto_bom(blob);
|
|
1082
|
+
}
|
|
1083
|
+
return navigator.msSaveOrOpenBlob(blob, name);
|
|
1084
|
+
};
|
|
1085
|
+
}
|
|
1086
|
+
FS_proto.abort = function() {};
|
|
1087
|
+
FS_proto.readyState = FS_proto.INIT = 0;
|
|
1088
|
+
FS_proto.WRITING = 1;
|
|
1089
|
+
FS_proto.DONE = 2;
|
|
1090
|
+
FS_proto.error = FS_proto.onwritestart = FS_proto.onprogress = FS_proto.onwrite = FS_proto.onabort = FS_proto.onerror = FS_proto.onwriteend = null;
|
|
1091
|
+
return saveAs;
|
|
1092
|
+
}(typeof self !== "undefined" && self || typeof window !== "undefined" && window || FileSaver.content);
|
|
1093
|
+
// `self` is undefined in Firefox for Android content script context
|
|
1094
|
+
// while `this` is nsIContentFrameMessageManager
|
|
1095
|
+
// with an attribute `content` that corresponds to the window
|
|
1096
|
+
if (module.exports) {
|
|
1097
|
+
module.exports.saveAs = saveAs;
|
|
1098
|
+
}
|
|
1099
|
+
} (FileSaver$1));
|
|
1100
|
+
return FileSaver$1.exports;
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
var FileSaverExports = requireFileSaver();
|
|
1104
|
+
|
|
1105
|
+
const defaultOptions = {
|
|
1106
|
+
filename: "download",
|
|
1107
|
+
type: "png"
|
|
1108
|
+
};
|
|
1109
|
+
/**
|
|
1110
|
+
@function saveElement
|
|
1111
|
+
@desc Downloads an HTML Element as a bitmap PNG image.
|
|
1112
|
+
@param {HTMLElement} elem A single element to be saved to one file.
|
|
1113
|
+
@param {Object} [options] Additional options to specify.
|
|
1114
|
+
@param {String} [options.filename = "download"] Filename for the downloaded file, without the extension.
|
|
1115
|
+
@param {String} [options.type = "png"] File type of the saved document. Accepted values are `"png"` and `"jpg"`.
|
|
1116
|
+
@param {Function} [options.callback] Function to be invoked after saving is complete.
|
|
1117
|
+
@param {Object} [renderOptions] Custom options to be passed to the html-to-image function.
|
|
1118
|
+
*/ function saveElement(elem, options = {}, renderOptions = {}) {
|
|
1119
|
+
if (!elem) return;
|
|
1120
|
+
options = Object.assign({}, defaultOptions, options);
|
|
1121
|
+
// rename renderOptions.background to backgroundColor for backwards compatibility
|
|
1122
|
+
renderOptions = Object.assign({
|
|
1123
|
+
backgroundColor: renderOptions.background
|
|
1124
|
+
}, renderOptions);
|
|
1125
|
+
function finish(blob) {
|
|
1126
|
+
FileSaverExports.saveAs(blob, `${options.filename}.${options.type}`);
|
|
1127
|
+
if (options.callback) options.callback();
|
|
1128
|
+
}
|
|
1129
|
+
if (options.type === "svg") {
|
|
1130
|
+
toSvg(elem, renderOptions).then((dataUrl)=>{
|
|
1131
|
+
const xhr = new XMLHttpRequest();
|
|
1132
|
+
xhr.open("GET", dataUrl);
|
|
1133
|
+
xhr.responseType = "blob";
|
|
1134
|
+
xhr.onload = ()=>finish(xhr.response);
|
|
1135
|
+
xhr.send();
|
|
1136
|
+
});
|
|
1137
|
+
} else {
|
|
1138
|
+
toBlob(elem, renderOptions).then(finish);
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
exports.saveElement = saveElement;
|
|
1143
|
+
|
|
1144
|
+
}));
|
|
1145
|
+
//# sourceMappingURL=d3plus-export.full.js.map
|