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 */
'use strict';
import {createForeignObjectSVG, loadSerializedSVG} from './renderer/ForeignObjectRenderer';
const testRangeBounds = document => {
const TEST_HEIGHT = 123;
@ -72,40 +74,45 @@ const testSVG = document => {
return true;
};
const isGreenPixel = data => data[0] === 0 && data[1] === 255 && data[2] === 0 && data[3] === 255;
const testForeignObject = document => {
const img = new Image();
const canvas = document.createElement('canvas');
canvas.width = 1;
canvas.height = 1;
const size = 100;
canvas.width = size;
canvas.height = size;
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.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(
canvas.toDataURL()
)}" /></foreignObject></svg>`;
return loadSerializedSVG(svg)
.then(img => {
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 onload = () => {
try {
ctx.drawImage(img, 0, 0);
canvas.toDataURL();
} catch (e) {
return resolve(false);
}
return resolve(true);
};
img.onload = onload;
img.onerror = () => resolve(false);
if (img.complete === true) {
setTimeout(() => {
onload();
}, 50);
}
});
const node = document.createElement('div');
node.style.backgroundImage = `url(${greenImageSrc})`;
node.style.height = `${size}px`;
// Firefox 55 does not render inline <img /> tags
return isGreenPixel(data)
? loadSerializedSVG(createForeignObjectSVG(size, size, node))
: Promise.reject(false);
})
.then(img => {
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 = {

View File

@ -19,36 +19,48 @@ export default class ForeignObjectRenderer {
this.ctx.scale(this.options.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';
const svg = document.createElementNS(xmlns, 'svg');
const foreignObject = document.createElementNS(xmlns, 'foreignObject');
svg.setAttributeNS(null, 'width', options.bounds.width);
svg.setAttributeNS(null, 'height', options.bounds.height);
foreignObject.setAttributeNS(null, 'width', '100%');
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)
)}`;
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 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)
)}`;
});
};