|
6 | 6 | var fontFaces = newFontFaces();
|
7 | 7 | var images = newImages();
|
8 | 8 |
|
9 |
| - // Default impl options |
10 |
| - var defaultOptions = { |
11 |
| - // Default is to fail on error, no placeholder |
12 |
| - imagePlaceholder: undefined, |
13 |
| - // Default cache bust is false, it will use the cache |
14 |
| - cacheBust: false, |
15 |
| - // Default scroll fix is false, it will not try to fix scrollbars |
16 |
| - scrollFix: false |
17 |
| - }; |
18 |
| - |
19 | 9 | var domtoimage = {
|
20 | 10 | toSvg: toSvg,
|
21 | 11 | toPng: toPng,
|
|
26 | 16 | fontFaces: fontFaces,
|
27 | 17 | images: images,
|
28 | 18 | util: util,
|
29 |
| - inliner: inliner, |
30 |
| - options: {} |
| 19 | + inliner: inliner |
31 | 20 | }
|
32 | 21 | };
|
33 | 22 |
|
|
48 | 37 | * @param {Object} options.style - an object whose properties to be copied to node's style before rendering.
|
49 | 38 | * @param {Number} options.quality - a Number between 0 and 1 indicating image quality (applicable to JPEG only),
|
50 | 39 | defaults to 1.0.
|
51 |
| - * @param {String} options.imagePlaceholder - dataURL to use as a placeholder for failed images, default behaviour is to fail fast on images we can't fetch |
52 |
| - * @param {Boolean} options.cacheBust - set to true to cache bust by appending the time to the request url |
53 | 40 | * @return {Promise} - A promise that is fulfilled with a SVG image data URL
|
54 | 41 | * */
|
55 | 42 | function toSvg(node, options) {
|
56 | 43 | options = options || {};
|
57 |
| - copyOptions(options); |
58 | 44 | return Promise.resolve(node)
|
59 | 45 | .then(function (node) {
|
60 | 46 | return cloneNode(node, options.filter, true);
|
|
136 | 122 | .then(util.canvasToBlob);
|
137 | 123 | }
|
138 | 124 |
|
139 |
| - function copyOptions(options) { |
140 |
| - // Copy options to impl options for use in impl |
141 |
| - if(typeof(options.imagePlaceholder) === 'undefined') { |
142 |
| - domtoimage.impl.options.imagePlaceholder = defaultOptions.imagePlaceholder; |
143 |
| - } else { |
144 |
| - domtoimage.impl.options.imagePlaceholder = options.imagePlaceholder; |
145 |
| - } |
146 |
| - |
147 |
| - if(typeof(options.cacheBust) === 'undefined') { |
148 |
| - domtoimage.impl.options.cacheBust = defaultOptions.cacheBust; |
149 |
| - } else { |
150 |
| - domtoimage.impl.options.cacheBust = options.cacheBust; |
151 |
| - } |
152 |
| - |
153 |
| - if(typeof(options.scrollFix) === 'undefined') { |
154 |
| - domtoimage.impl.options.scrollFix = defaultOptions.scrollFix; |
155 |
| - } else { |
156 |
| - domtoimage.impl.options.scrollFix = options.scrollFix; |
157 |
| - } |
158 |
| - } |
159 |
| - |
160 | 125 | function draw(domNode, options) {
|
161 | 126 | return toSvg(domNode, options)
|
162 | 127 | .then(util.makeImage)
|
|
236 | 201 | });
|
237 | 202 |
|
238 | 203 | function cloneStyle() {
|
239 |
| - var originalStyle = window.getComputedStyle(original); |
240 |
| - copyStyle(originalStyle, clone.style); |
| 204 | + copyStyle(window.getComputedStyle(original), clone.style); |
241 | 205 |
|
242 | 206 | function copyStyle(source, target) {
|
243 | 207 | if (source.cssText) target.cssText = source.cssText;
|
244 | 208 | else copyProperties(source, target);
|
245 | 209 |
|
246 |
| - // Chrome returns fontStretch from getComputedStyle in percent |
247 |
| - // but it does not accept a percent value when using the short hand |
248 |
| - // font = .... |
249 |
| - // Thus we have to reset fontStretch or we lose all our font styles |
250 |
| - target.fontStretch = 'normal'; |
251 |
| - |
252 | 210 | function copyProperties(source, target) {
|
253 | 211 | util.asArray(source).forEach(function (name) {
|
254 | 212 | target.setProperty(
|
|
259 | 217 | });
|
260 | 218 | }
|
261 | 219 | }
|
262 |
| - |
263 |
| - if(domtoimage.impl.options.scrollFix && |
264 |
| - (original.scrollTop || original.scrollLeft)) { |
265 |
| - // Setup container for absolute positioning of children |
266 |
| - clone.style.position = 'relative'; |
267 |
| - clone.style.overflow = 'hidden'; |
268 |
| - clone.style.width = original.offsetWidth + 'px'; |
269 |
| - clone.style.height = original.offsetHeight + 'px'; |
270 |
| - var scrollTopRemaining = original.scrollTop > 0 ? original.scrollTop : null; |
271 |
| - var scrollLeftRemaining = original.scrollLeft > 0 ? original.scrollLeft : null; |
272 |
| - var originalIsPositionRelative = originalStyle['position'] === 'relative'; |
273 |
| - var computedStylesCache = {}; |
274 |
| - var boundingRectCache = {}; |
275 |
| - |
276 |
| - // Loop through children and set position based on original |
277 |
| - // childs position and original containers scroll position |
278 |
| - for(var i = 0; i < clone.children.length; i++) { |
279 |
| - var cloneChild = clone.children[i]; |
280 |
| - // Make sure this element is stylable |
281 |
| - if(typeof(cloneChild) === 'undefined' || |
282 |
| - cloneChild === null || |
283 |
| - typeof(cloneChild.style) === 'undefined') { |
284 |
| - |
285 |
| - continue; |
286 |
| - } |
287 |
| - var originalChildStyles = computedStylesCache[i] || window.getComputedStyle(original.children[i]); |
288 |
| - computedStylesCache[i] = originalChildStyles; |
289 |
| - |
290 |
| - // Set child to absolute positioning relative to parent (container) |
291 |
| - cloneChild.style.position = 'absolute'; |
292 |
| - |
293 |
| - // Take into account the fact that there may be children which were already |
294 |
| - // positioned absolute relative to its parent, thus we need to use the original position |
295 |
| - if (originalIsPositionRelative && originalChildStyles['position'] === 'absolute') { |
296 |
| - var top = parseInt(originalChildStyles['top']); |
297 |
| - var left = parseInt(originalChildStyles['left']); |
298 |
| - top = isNaN(top) ? 0 : top; |
299 |
| - left = isNaN(left) ? 0 : left; |
300 |
| - top -= original.scrollTop; |
301 |
| - left -= original.scrollLeft; |
302 |
| - cloneChild.style.top = top + 'px'; |
303 |
| - cloneChild.style.left = left + 'px'; |
304 |
| - continue; |
305 |
| - } |
306 |
| - |
307 |
| - var currentChildBoundingRect = boundingRectCache[i] || original.children[i].getBoundingClientRect(); |
308 |
| - boundingRectCache[i] = currentChildBoundingRect; |
309 |
| - |
310 |
| - // Find last child that was not position absolute |
311 |
| - var lastChild, lastChildIndex, lastChildBoundingRect; |
312 |
| - for(lastChildIndex = i - 1; lastChildIndex >= 0; lastChildIndex--) { |
313 |
| - var childStyles = computedStylesCache[lastChildIndex] || window.getComputedStyle(original.children[lastChildIndex]); |
314 |
| - computedStylesCache[lastChildIndex] = childStyles; |
315 |
| - if (childStyles['position'] !== 'absolute') { |
316 |
| - lastChild = original.children[lastChildIndex]; |
317 |
| - break; |
318 |
| - } |
319 |
| - } |
320 |
| - |
321 |
| - // If we found a child then subtract its height/width from the scroll position |
322 |
| - if(typeof(lastChild) !== 'undefined') { |
323 |
| - lastChildBoundingRect = boundingRectCache[lastChildIndex] || lastChild.getBoundingClientRect(); |
324 |
| - boundingRectCache[lastChildIndex] = lastChildBoundingRect; |
325 |
| - |
326 |
| - // isStackingTop is true when elements are being displayed block |
327 |
| - if(lastChildBoundingRect.top !== currentChildBoundingRect.top) { |
328 |
| - // Subtract last child real height to get the next items position |
329 |
| - scrollTopRemaining -= (currentChildBoundingRect.top - lastChildBoundingRect.top); |
330 |
| - } |
331 |
| - // isStackingLeft is true when elements are being displayed inline |
332 |
| - if(lastChildBoundingRect.left !== currentChildBoundingRect.left) { |
333 |
| - // Subtract the last child real width to get the next items position |
334 |
| - scrollLeftRemaining -= (currentChildBoundingRect.left - lastChildBoundingRect.left); |
335 |
| - } |
336 |
| - } |
337 |
| - |
338 |
| - // Set the childs position based on our current scroll position |
339 |
| - cloneChild.style.top = -scrollTopRemaining + 'px'; |
340 |
| - cloneChild.style.left = -scrollLeftRemaining + 'px'; |
341 |
| - } |
342 |
| - } |
343 | 220 | }
|
344 | 221 |
|
345 | 222 | function clonePseudoElements() {
|
|
391 | 268 | }
|
392 | 269 |
|
393 | 270 | function fixSvg() {
|
394 |
| - if (!(clone instanceof SVGElement)) return; |
| 271 | + if (!(clone instanceof SVGRectElement) && !(clone instanceof SVGImageElement)) return; |
395 | 272 | clone.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
|
396 | 273 |
|
397 | 274 | if (!(clone instanceof SVGRectElement)) return;
|
|
428 | 305 | node.setAttribute('xmlns', 'http://www.w3.org/1999/xhtml');
|
429 | 306 | return new XMLSerializer().serializeToString(node);
|
430 | 307 | })
|
| 308 | + .then(util.escapeXhtml) |
431 | 309 | .then(function (xhtml) {
|
432 | 310 | return '<foreignObject x="0" y="0" width="100%" height="100%">' + xhtml + '</foreignObject>';
|
433 | 311 | })
|
|
436 | 314 | foreignObject + '</svg>';
|
437 | 315 | })
|
438 | 316 | .then(function (svg) {
|
439 |
| - return encodeURI('data:image/svg+xml;charset=utf-8,') + encodeURIComponent(svg); |
| 317 | + return 'data:image/svg+xml;charset=utf-8,' + svg; |
440 | 318 | });
|
441 | 319 | }
|
442 | 320 |
|
|
453 | 331 | uid: uid(),
|
454 | 332 | delay: delay,
|
455 | 333 | asArray: asArray,
|
| 334 | + escapeXhtml: escapeXhtml, |
456 | 335 | makeImage: makeImage,
|
457 | 336 | width: width,
|
458 | 337 | height: height
|
|
556 | 435 |
|
557 | 436 | function getAndEncode(url) {
|
558 | 437 | var TIMEOUT = 30000;
|
559 |
| - if(domtoimage.impl.options.cacheBust) { |
560 |
| - // Cache bypass so we dont have CORS issues with cached images |
561 |
| - // Source: https://developer.mozilla.org/en/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest#Bypassing_the_cache |
562 |
| - url += ((/\?/).test(url) ? "&" : "?") + (new Date()).getTime(); |
563 |
| - } |
564 | 438 |
|
565 | 439 | return new Promise(function (resolve) {
|
566 | 440 | var request = new XMLHttpRequest();
|
|
572 | 446 | request.open('GET', url, true);
|
573 | 447 | request.send();
|
574 | 448 |
|
575 |
| - var placeholder; |
576 |
| - if(domtoimage.impl.options.imagePlaceholder) { |
577 |
| - var split = domtoimage.impl.options.imagePlaceholder.split(/,/); |
578 |
| - if(split && split[1]) { |
579 |
| - placeholder = split[1]; |
580 |
| - } |
581 |
| - } |
582 |
| - |
583 | 449 | function done() {
|
584 | 450 | if (request.readyState !== 4) return;
|
585 | 451 |
|
586 | 452 | if (request.status !== 200) {
|
587 |
| - if(placeholder) { |
588 |
| - resolve(placeholder); |
589 |
| - } else { |
590 |
| - fail('cannot fetch resource: ' + url + ', status: ' + request.status); |
591 |
| - } |
592 |
| - |
| 453 | + fail('cannot fetch resource: ' + url + ', status: ' + request.status); |
593 | 454 | return;
|
594 | 455 | }
|
595 | 456 |
|
|
602 | 463 | }
|
603 | 464 |
|
604 | 465 | function timeout() {
|
605 |
| - if(placeholder) { |
606 |
| - resolve(placeholder); |
607 |
| - } else { |
608 |
| - fail('timeout of ' + TIMEOUT + 'ms occured while fetching resource: ' + url); |
609 |
| - } |
| 466 | + fail('timeout of ' + TIMEOUT + 'ms occured while fetching resource: ' + url); |
610 | 467 | }
|
611 | 468 |
|
612 | 469 | function fail(message) {
|
|
641 | 498 | return array;
|
642 | 499 | }
|
643 | 500 |
|
| 501 | + function escapeXhtml(string) { |
| 502 | + return string.replace(/#/g, '%23').replace(/\n/g, '%0A'); |
| 503 | + } |
| 504 | + |
644 | 505 | function width(node) {
|
645 | 506 | var leftBorder = px(node, 'border-left-width');
|
646 | 507 | var rightBorder = px(node, 'border-right-width');
|
|
805 | 666 | };
|
806 | 667 |
|
807 | 668 | function inline(get) {
|
808 |
| - if (util.isDataUrl(element.src)) return Promise.resolve(); |
| 669 | + var src = (element instanceof SVGImageElement) ? element.href.baseVal : element.src; |
| 670 | + |
| 671 | + if (util.isDataUrl(src)) return Promise.resolve(); |
809 | 672 |
|
810 |
| - return Promise.resolve(element.src) |
| 673 | + return Promise.resolve(src) |
811 | 674 | .then(get || util.getAndEncode)
|
812 | 675 | .then(function (data) {
|
813 |
| - return util.dataAsUrl(data, util.mimeType(element.src)); |
| 676 | + return util.dataAsUrl(data, util.mimeType(src)); |
814 | 677 | })
|
815 | 678 | .then(function (dataUrl) {
|
816 | 679 | return new Promise(function (resolve, reject) {
|
817 | 680 | element.onload = resolve;
|
818 | 681 | element.onerror = reject;
|
819 |
| - element.src = dataUrl; |
| 682 | + if (element instanceof SVGImageElement) { |
| 683 | + element.href.baseVal = dataUrl; |
| 684 | + } else { |
| 685 | + element.src = dataUrl; |
| 686 | + } |
| 687 | + |
820 | 688 | });
|
821 | 689 | });
|
822 | 690 | }
|
|
827 | 695 |
|
828 | 696 | return inlineBackground(node)
|
829 | 697 | .then(function () {
|
830 |
| - if (node instanceof HTMLImageElement) |
| 698 | + if (node instanceof HTMLImageElement || node instanceof SVGImageElement) |
831 | 699 | return newImage(node).inline();
|
832 | 700 | else
|
833 | 701 | return Promise.all(
|
|
0 commit comments