Only use foreignObject rendering if browser is capable of rendering images

This commit is contained in:
Niklas von Hertzen 2017-09-03 18:20:12 +08:00
parent c28263ddc2
commit c013e49192
2 changed files with 76 additions and 57 deletions

View File

@ -1,6 +1,8 @@
/* @flow */ /* @flow */
'use strict'; 'use strict';
import {createForeignObjectSVG, loadSerializedSVG} from './renderer/ForeignObjectRenderer';
const testRangeBounds = document => { const testRangeBounds = document => {
const TEST_HEIGHT = 123; const TEST_HEIGHT = 123;
@ -72,40 +74,45 @@ const testSVG = document => {
return true; return true;
}; };
const isGreenPixel = data => data[0] === 0 && data[1] === 255 && data[2] === 0 && data[3] === 255;
const testForeignObject = document => { const testForeignObject = document => {
const img = new Image();
const canvas = document.createElement('canvas'); const canvas = document.createElement('canvas');
canvas.width = 1; const size = 100;
canvas.height = 1; canvas.width = size;
canvas.height = size;
const ctx = canvas.getContext('2d'); const ctx = canvas.getContext('2d');
ctx.fillStyle = 'rgb(0, 255, 0)';
ctx.fillRect(0, 0, size, size);
const img = new Image();
const greenImageSrc = canvas.toDataURL();
img.src = greenImageSrc;
const svg = createForeignObjectSVG(size, size, img);
ctx.fillStyle = 'red'; ctx.fillStyle = 'red';
ctx.fillRect(0, 0, 1, 1); ctx.fillRect(0, 0, size, size);
img.src = `data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'><foreignObject><img src="${encodeURIComponent( return loadSerializedSVG(svg)
canvas.toDataURL() .then(img => {
)}" /></foreignObject></svg>`; ctx.drawImage(img, 0, 0);
const data = ctx.getImageData(0, 0, size, size).data;
ctx.fillStyle = 'red';
ctx.fillRect(0, 0, size, size);
return new Promise(resolve => { const node = document.createElement('div');
const onload = () => { node.style.backgroundImage = `url(${greenImageSrc})`;
try { node.style.height = `${size}px`;
ctx.drawImage(img, 0, 0); // Firefox 55 does not render inline <img /> tags
canvas.toDataURL(); return isGreenPixel(data)
} catch (e) { ? loadSerializedSVG(createForeignObjectSVG(size, size, node))
return resolve(false); : Promise.reject(false);
} })
.then(img => {
return resolve(true); ctx.drawImage(img, 0, 0);
}; // Edge does not render background-images
return isGreenPixel(ctx.getImageData(0, 0, size, size).data);
img.onload = onload; })
img.onerror = () => resolve(false); .catch(e => false);
if (img.complete === true) {
setTimeout(() => {
onload();
}, 50);
}
});
}; };
const FEATURES = { const FEATURES = {

View File

@ -19,36 +19,48 @@ export default class ForeignObjectRenderer {
this.ctx.scale(this.options.scale, this.options.scale); this.ctx.scale(this.options.scale, this.options.scale);
options.logger.log(`ForeignObject renderer initialized with scale ${this.options.scale}`); options.logger.log(`ForeignObject renderer initialized with scale ${this.options.scale}`);
const svg = createForeignObjectSVG(
options.bounds.width,
options.bounds.height,
this.element
);
const xmlns = 'http://www.w3.org/2000/svg'; return loadSerializedSVG(svg).then(img => {
const svg = document.createElementNS(xmlns, 'svg'); if (options.backgroundColor) {
const foreignObject = document.createElementNS(xmlns, 'foreignObject'); this.ctx.fillStyle = options.backgroundColor.toString();
svg.setAttributeNS(null, 'width', options.bounds.width); this.ctx.fillRect(0, 0, options.bounds.width, options.bounds.height);
svg.setAttributeNS(null, 'height', options.bounds.height); }
this.ctx.drawImage(img, 0, 0);
foreignObject.setAttributeNS(null, 'width', '100%'); return this.canvas;
foreignObject.setAttributeNS(null, 'height', '100%');
foreignObject.setAttributeNS(null, 'externalResourcesRequired', 'true');
svg.appendChild(foreignObject);
foreignObject.appendChild(this.element);
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => {
if (options.backgroundColor) {
this.ctx.fillStyle = options.backgroundColor.toString();
this.ctx.fillRect(0, 0, options.bounds.width, options.bounds.height);
}
this.ctx.drawImage(img, 0, 0);
resolve(this.canvas);
};
img.onerror = reject;
img.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(
new XMLSerializer().serializeToString(svg)
)}`;
}); });
} }
} }
export const createForeignObjectSVG = (width: number, height: number, node: Node) => {
const xmlns = 'http://www.w3.org/2000/svg';
const svg = document.createElementNS(xmlns, 'svg');
const foreignObject = document.createElementNS(xmlns, 'foreignObject');
svg.setAttributeNS(null, 'width', width);
svg.setAttributeNS(null, 'height', height);
foreignObject.setAttributeNS(null, 'width', '100%');
foreignObject.setAttributeNS(null, 'height', '100%');
foreignObject.setAttributeNS(null, 'externalResourcesRequired', 'true');
svg.appendChild(foreignObject);
foreignObject.appendChild(node);
return svg;
};
export const loadSerializedSVG = (svg: Node) => {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = reject;
img.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(
new XMLSerializer().serializeToString(svg)
)}`;
});
};