Skip to content

Commit 92bb4a5

Browse files
authored
Fix SVGImageElements not being inlined
1 parent 2d517e2 commit 92bb4a5

File tree

1 file changed

+24
-156
lines changed

1 file changed

+24
-156
lines changed

src/dom-to-image.js

+24-156
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,6 @@
66
var fontFaces = newFontFaces();
77
var images = newImages();
88

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-
199
var domtoimage = {
2010
toSvg: toSvg,
2111
toPng: toPng,
@@ -26,8 +16,7 @@
2616
fontFaces: fontFaces,
2717
images: images,
2818
util: util,
29-
inliner: inliner,
30-
options: {}
19+
inliner: inliner
3120
}
3221
};
3322

@@ -48,13 +37,10 @@
4837
* @param {Object} options.style - an object whose properties to be copied to node's style before rendering.
4938
* @param {Number} options.quality - a Number between 0 and 1 indicating image quality (applicable to JPEG only),
5039
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
5340
* @return {Promise} - A promise that is fulfilled with a SVG image data URL
5441
* */
5542
function toSvg(node, options) {
5643
options = options || {};
57-
copyOptions(options);
5844
return Promise.resolve(node)
5945
.then(function (node) {
6046
return cloneNode(node, options.filter, true);
@@ -136,27 +122,6 @@
136122
.then(util.canvasToBlob);
137123
}
138124

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-
160125
function draw(domNode, options) {
161126
return toSvg(domNode, options)
162127
.then(util.makeImage)
@@ -236,19 +201,12 @@
236201
});
237202

238203
function cloneStyle() {
239-
var originalStyle = window.getComputedStyle(original);
240-
copyStyle(originalStyle, clone.style);
204+
copyStyle(window.getComputedStyle(original), clone.style);
241205

242206
function copyStyle(source, target) {
243207
if (source.cssText) target.cssText = source.cssText;
244208
else copyProperties(source, target);
245209

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-
252210
function copyProperties(source, target) {
253211
util.asArray(source).forEach(function (name) {
254212
target.setProperty(
@@ -259,87 +217,6 @@
259217
});
260218
}
261219
}
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-
}
343220
}
344221

345222
function clonePseudoElements() {
@@ -391,7 +268,7 @@
391268
}
392269

393270
function fixSvg() {
394-
if (!(clone instanceof SVGElement)) return;
271+
if (!(clone instanceof SVGRectElement) && !(clone instanceof SVGImageElement)) return;
395272
clone.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
396273

397274
if (!(clone instanceof SVGRectElement)) return;
@@ -428,6 +305,7 @@
428305
node.setAttribute('xmlns', 'http://www.w3.org/1999/xhtml');
429306
return new XMLSerializer().serializeToString(node);
430307
})
308+
.then(util.escapeXhtml)
431309
.then(function (xhtml) {
432310
return '<foreignObject x="0" y="0" width="100%" height="100%">' + xhtml + '</foreignObject>';
433311
})
@@ -436,7 +314,7 @@
436314
foreignObject + '</svg>';
437315
})
438316
.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;
440318
});
441319
}
442320

@@ -453,6 +331,7 @@
453331
uid: uid(),
454332
delay: delay,
455333
asArray: asArray,
334+
escapeXhtml: escapeXhtml,
456335
makeImage: makeImage,
457336
width: width,
458337
height: height
@@ -556,11 +435,6 @@
556435

557436
function getAndEncode(url) {
558437
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-
}
564438

565439
return new Promise(function (resolve) {
566440
var request = new XMLHttpRequest();
@@ -572,24 +446,11 @@
572446
request.open('GET', url, true);
573447
request.send();
574448

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-
583449
function done() {
584450
if (request.readyState !== 4) return;
585451

586452
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);
593454
return;
594455
}
595456

@@ -602,11 +463,7 @@
602463
}
603464

604465
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);
610467
}
611468

612469
function fail(message) {
@@ -641,6 +498,10 @@
641498
return array;
642499
}
643500

501+
function escapeXhtml(string) {
502+
return string.replace(/#/g, '%23').replace(/\n/g, '%0A');
503+
}
504+
644505
function width(node) {
645506
var leftBorder = px(node, 'border-left-width');
646507
var rightBorder = px(node, 'border-right-width');
@@ -805,18 +666,25 @@
805666
};
806667

807668
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();
809672

810-
return Promise.resolve(element.src)
673+
return Promise.resolve(src)
811674
.then(get || util.getAndEncode)
812675
.then(function (data) {
813-
return util.dataAsUrl(data, util.mimeType(element.src));
676+
return util.dataAsUrl(data, util.mimeType(src));
814677
})
815678
.then(function (dataUrl) {
816679
return new Promise(function (resolve, reject) {
817680
element.onload = resolve;
818681
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+
820688
});
821689
});
822690
}
@@ -827,7 +695,7 @@
827695

828696
return inlineBackground(node)
829697
.then(function () {
830-
if (node instanceof HTMLImageElement)
698+
if (node instanceof HTMLImageElement || node instanceof SVGImageElement)
831699
return newImage(node).inline();
832700
else
833701
return Promise.all(

0 commit comments

Comments
 (0)