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>`;
return new Promise(resolve => {
const onload = () => {
try {
ctx.drawImage(img, 0, 0); ctx.drawImage(img, 0, 0);
canvas.toDataURL(); const data = ctx.getImageData(0, 0, size, size).data;
} catch (e) { ctx.fillStyle = 'red';
return resolve(false); ctx.fillRect(0, 0, size, size);
}
return resolve(true); const node = document.createElement('div');
}; node.style.backgroundImage = `url(${greenImageSrc})`;
node.style.height = `${size}px`;
img.onload = onload; // Firefox 55 does not render inline <img /> tags
img.onerror = () => resolve(false); return isGreenPixel(data)
? loadSerializedSVG(createForeignObjectSVG(size, size, node))
if (img.complete === true) { : Promise.reject(false);
setTimeout(() => { })
onload(); .then(img => {
}, 50); ctx.drawImage(img, 0, 0);
} // Edge does not render background-images
}); return isGreenPixel(ctx.getImageData(0, 0, size, size).data);
})
.catch(e => false);
}; };
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
);
return loadSerializedSVG(svg).then(img => {
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);
return this.canvas;
});
}
}
export const createForeignObjectSVG = (width: number, height: number, node: Node) => {
const xmlns = 'http://www.w3.org/2000/svg'; const xmlns = 'http://www.w3.org/2000/svg';
const svg = document.createElementNS(xmlns, 'svg'); const svg = document.createElementNS(xmlns, 'svg');
const foreignObject = document.createElementNS(xmlns, 'foreignObject'); const foreignObject = document.createElementNS(xmlns, 'foreignObject');
svg.setAttributeNS(null, 'width', options.bounds.width); svg.setAttributeNS(null, 'width', width);
svg.setAttributeNS(null, 'height', options.bounds.height); svg.setAttributeNS(null, 'height', height);
foreignObject.setAttributeNS(null, 'width', '100%'); foreignObject.setAttributeNS(null, 'width', '100%');
foreignObject.setAttributeNS(null, 'height', '100%'); foreignObject.setAttributeNS(null, 'height', '100%');
foreignObject.setAttributeNS(null, 'externalResourcesRequired', 'true'); foreignObject.setAttributeNS(null, 'externalResourcesRequired', 'true');
svg.appendChild(foreignObject); svg.appendChild(foreignObject);
foreignObject.appendChild(this.element); foreignObject.appendChild(node);
return svg;
};
export const loadSerializedSVG = (svg: Node) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const img = new Image(); const img = new Image();
img.onload = () => { img.onload = () => resolve(img);
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.onerror = reject;
img.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent( img.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(
new XMLSerializer().serializeToString(svg) new XMLSerializer().serializeToString(svg)
)}`; )}`;
}); });
} };
}