From c013e49192f7ab4f9eb2494edaac35e7c9bf434b Mon Sep 17 00:00:00 2001 From: Niklas von Hertzen Date: Sun, 3 Sep 2017 18:20:12 +0800 Subject: [PATCH] Only use foreignObject rendering if browser is capable of rendering images --- src/Feature.js | 63 +++++++++++++----------- src/renderer/ForeignObjectRenderer.js | 70 ++++++++++++++++----------- 2 files changed, 76 insertions(+), 57 deletions(-) diff --git a/src/Feature.js b/src/Feature.js index 2e158da..a0cda97 100644 --- a/src/Feature.js +++ b/src/Feature.js @@ -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,`; + 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 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 = { diff --git a/src/renderer/ForeignObjectRenderer.js b/src/renderer/ForeignObjectRenderer.js index 81a10eb..10a7f40 100644 --- a/src/renderer/ForeignObjectRenderer.js +++ b/src/renderer/ForeignObjectRenderer.js @@ -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) + )}`; + }); +};