Current File : /home/tradevaly/www/node_modules/zrender/src/Painter.js |
import {devicePixelRatio} from './config';
import * as util from './core/util';
import logError from './core/log';
import BoundingRect from './core/BoundingRect';
import timsort from './core/timsort';
import Layer from './Layer';
import requestAnimationFrame from './animation/requestAnimationFrame';
import Image from './graphic/Image';
import env from './core/env';
var HOVER_LAYER_ZLEVEL = 1e5;
var CANVAS_ZLEVEL = 314159;
var EL_AFTER_INCREMENTAL_INC = 0.01;
var INCREMENTAL_INC = 0.001;
function parseInt10(val) {
return parseInt(val, 10);
}
function isLayerValid(layer) {
if (!layer) {
return false;
}
if (layer.__builtin__) {
return true;
}
if (typeof (layer.resize) !== 'function'
|| typeof (layer.refresh) !== 'function'
) {
return false;
}
return true;
}
var tmpRect = new BoundingRect(0, 0, 0, 0);
var viewRect = new BoundingRect(0, 0, 0, 0);
function isDisplayableCulled(el, width, height) {
tmpRect.copy(el.getBoundingRect());
if (el.transform) {
tmpRect.applyTransform(el.transform);
}
viewRect.width = width;
viewRect.height = height;
return !tmpRect.intersect(viewRect);
}
function isClipPathChanged(clipPaths, prevClipPaths) {
// displayable.__clipPaths can only be `null`/`undefined` or an non-empty array.
if (clipPaths === prevClipPaths) {
return false;
}
if (!clipPaths || !prevClipPaths || (clipPaths.length !== prevClipPaths.length)) {
return true;
}
for (var i = 0; i < clipPaths.length; i++) {
if (clipPaths[i] !== prevClipPaths[i]) {
return true;
}
}
return false;
}
function doClip(clipPaths, ctx) {
for (var i = 0; i < clipPaths.length; i++) {
var clipPath = clipPaths[i];
clipPath.setTransform(ctx);
ctx.beginPath();
clipPath.buildPath(ctx, clipPath.shape);
ctx.clip();
// Transform back
clipPath.restoreTransform(ctx);
}
}
function createRoot(width, height) {
var domRoot = document.createElement('div');
// domRoot.onselectstart = returnFalse; // Avoid page selected
domRoot.style.cssText = [
'position:relative',
// IOS13 safari probably has a compositing bug (z order of the canvas and the consequent
// dom does not act as expected) when some of the parent dom has
// `-webkit-overflow-scrolling: touch;` and the webpage is longer than one screen and
// the canvas is not at the top part of the page.
// Check `https://bugs.webkit.org/show_bug.cgi?id=203681` for more details. We remove
// this `overflow:hidden` to avoid the bug.
// 'overflow:hidden',
'width:' + width + 'px',
'height:' + height + 'px',
'padding:0',
'margin:0',
'border-width:0'
].join(';') + ';';
return domRoot;
}
/**
* @alias module:zrender/Painter
* @constructor
* @param {HTMLElement} root 绘图容器
* @param {module:zrender/Storage} storage
* @param {Object} opts
*/
var Painter = function (root, storage, opts) {
this.type = 'canvas';
// In node environment using node-canvas
var singleCanvas = !root.nodeName // In node ?
|| root.nodeName.toUpperCase() === 'CANVAS';
this._opts = opts = util.extend({}, opts || {});
/**
* @type {number}
*/
this.dpr = opts.devicePixelRatio || devicePixelRatio;
/**
* @type {boolean}
* @private
*/
this._singleCanvas = singleCanvas;
/**
* 绘图容器
* @type {HTMLElement}
*/
this.root = root;
var rootStyle = root.style;
if (rootStyle) {
rootStyle['-webkit-tap-highlight-color'] = 'transparent';
rootStyle['-webkit-user-select'] =
rootStyle['user-select'] =
rootStyle['-webkit-touch-callout'] = 'none';
root.innerHTML = '';
}
/**
* @type {module:zrender/Storage}
*/
this.storage = storage;
/**
* @type {Array.<number>}
* @private
*/
var zlevelList = this._zlevelList = [];
/**
* @type {Object.<string, module:zrender/Layer>}
* @private
*/
var layers = this._layers = {};
/**
* @type {Object.<string, Object>}
* @private
*/
this._layerConfig = {};
/**
* zrender will do compositing when root is a canvas and have multiple zlevels.
*/
this._needsManuallyCompositing = false;
if (!singleCanvas) {
this._width = this._getSize(0);
this._height = this._getSize(1);
var domRoot = this._domRoot = createRoot(
this._width, this._height
);
root.appendChild(domRoot);
}
else {
var width = root.width;
var height = root.height;
if (opts.width != null) {
width = opts.width;
}
if (opts.height != null) {
height = opts.height;
}
this.dpr = opts.devicePixelRatio || 1;
// Use canvas width and height directly
root.width = width * this.dpr;
root.height = height * this.dpr;
this._width = width;
this._height = height;
// Create layer if only one given canvas
// Device can be specified to create a high dpi image.
var mainLayer = new Layer(root, this, this.dpr);
mainLayer.__builtin__ = true;
mainLayer.initContext();
// FIXME Use canvas width and height
// mainLayer.resize(width, height);
layers[CANVAS_ZLEVEL] = mainLayer;
mainLayer.zlevel = CANVAS_ZLEVEL;
// Not use common zlevel.
zlevelList.push(CANVAS_ZLEVEL);
this._domRoot = root;
}
/**
* @type {module:zrender/Layer}
* @private
*/
this._hoverlayer = null;
this._hoverElements = [];
};
Painter.prototype = {
constructor: Painter,
getType: function () {
return 'canvas';
},
/**
* If painter use a single canvas
* @return {boolean}
*/
isSingleCanvas: function () {
return this._singleCanvas;
},
/**
* @return {HTMLDivElement}
*/
getViewportRoot: function () {
return this._domRoot;
},
getViewportRootOffset: function () {
var viewportRoot = this.getViewportRoot();
if (viewportRoot) {
return {
offsetLeft: viewportRoot.offsetLeft || 0,
offsetTop: viewportRoot.offsetTop || 0
};
}
},
/**
* 刷新
* @param {boolean} [paintAll=false] 强制绘制所有displayable
*/
refresh: function (paintAll) {
var list = this.storage.getDisplayList(true);
var zlevelList = this._zlevelList;
this._redrawId = Math.random();
this._paintList(list, paintAll, this._redrawId);
// Paint custum layers
for (var i = 0; i < zlevelList.length; i++) {
var z = zlevelList[i];
var layer = this._layers[z];
if (!layer.__builtin__ && layer.refresh) {
var clearColor = i === 0 ? this._backgroundColor : null;
layer.refresh(clearColor);
}
}
this.refreshHover();
return this;
},
addHover: function (el, hoverStyle) {
if (el.__hoverMir) {
return;
}
var elMirror = new el.constructor({
style: el.style,
shape: el.shape,
z: el.z,
z2: el.z2,
silent: el.silent
});
elMirror.__from = el;
el.__hoverMir = elMirror;
hoverStyle && elMirror.setStyle(hoverStyle);
this._hoverElements.push(elMirror);
return elMirror;
},
removeHover: function (el) {
var elMirror = el.__hoverMir;
var hoverElements = this._hoverElements;
var idx = util.indexOf(hoverElements, elMirror);
if (idx >= 0) {
hoverElements.splice(idx, 1);
}
el.__hoverMir = null;
},
clearHover: function (el) {
var hoverElements = this._hoverElements;
for (var i = 0; i < hoverElements.length; i++) {
var from = hoverElements[i].__from;
if (from) {
from.__hoverMir = null;
}
}
hoverElements.length = 0;
},
refreshHover: function () {
var hoverElements = this._hoverElements;
var len = hoverElements.length;
var hoverLayer = this._hoverlayer;
hoverLayer && hoverLayer.clear();
if (!len) {
return;
}
timsort(hoverElements, this.storage.displayableSortFunc);
// Use a extream large zlevel
// FIXME?
if (!hoverLayer) {
hoverLayer = this._hoverlayer = this.getLayer(HOVER_LAYER_ZLEVEL);
}
var scope = {};
hoverLayer.ctx.save();
for (var i = 0; i < len;) {
var el = hoverElements[i];
var originalEl = el.__from;
// Original el is removed
// PENDING
if (!(originalEl && originalEl.__zr)) {
hoverElements.splice(i, 1);
originalEl.__hoverMir = null;
len--;
continue;
}
i++;
// Use transform
// FIXME style and shape ?
if (!originalEl.invisible) {
el.transform = originalEl.transform;
el.invTransform = originalEl.invTransform;
el.__clipPaths = originalEl.__clipPaths;
// el.
this._doPaintEl(el, hoverLayer, true, scope);
}
}
hoverLayer.ctx.restore();
},
getHoverLayer: function () {
return this.getLayer(HOVER_LAYER_ZLEVEL);
},
_paintList: function (list, paintAll, redrawId) {
if (this._redrawId !== redrawId) {
return;
}
paintAll = paintAll || false;
this._updateLayerStatus(list);
var finished = this._doPaintList(list, paintAll);
if (this._needsManuallyCompositing) {
this._compositeManually();
}
if (!finished) {
var self = this;
requestAnimationFrame(function () {
self._paintList(list, paintAll, redrawId);
});
}
},
_compositeManually: function () {
var ctx = this.getLayer(CANVAS_ZLEVEL).ctx;
var width = this._domRoot.width;
var height = this._domRoot.height;
ctx.clearRect(0, 0, width, height);
// PENDING, If only builtin layer?
this.eachBuiltinLayer(function (layer) {
if (layer.virtual) {
ctx.drawImage(layer.dom, 0, 0, width, height);
}
});
},
_doPaintList: function (list, paintAll) {
var layerList = [];
for (var zi = 0; zi < this._zlevelList.length; zi++) {
var zlevel = this._zlevelList[zi];
var layer = this._layers[zlevel];
if (layer.__builtin__
&& layer !== this._hoverlayer
&& (layer.__dirty || paintAll)
) {
layerList.push(layer);
}
}
var finished = true;
for (var k = 0; k < layerList.length; k++) {
var layer = layerList[k];
var ctx = layer.ctx;
var scope = {};
ctx.save();
var start = paintAll ? layer.__startIndex : layer.__drawIndex;
var useTimer = !paintAll && layer.incremental && Date.now;
var startTime = useTimer && Date.now();
var clearColor = layer.zlevel === this._zlevelList[0]
? this._backgroundColor : null;
// All elements in this layer are cleared.
if (layer.__startIndex === layer.__endIndex) {
layer.clear(false, clearColor);
}
else if (start === layer.__startIndex) {
var firstEl = list[start];
if (!firstEl.incremental || !firstEl.notClear || paintAll) {
layer.clear(false, clearColor);
}
}
if (start === -1) {
console.error('For some unknown reason. drawIndex is -1');
start = layer.__startIndex;
}
for (var i = start; i < layer.__endIndex; i++) {
var el = list[i];
this._doPaintEl(el, layer, paintAll, scope);
el.__dirty = el.__dirtyText = false;
if (useTimer) {
// Date.now can be executed in 13,025,305 ops/second.
var dTime = Date.now() - startTime;
// Give 15 millisecond to draw.
// The rest elements will be drawn in the next frame.
if (dTime > 15) {
break;
}
}
}
layer.__drawIndex = i;
if (layer.__drawIndex < layer.__endIndex) {
finished = false;
}
if (scope.prevElClipPaths) {
// Needs restore the state. If last drawn element is in the clipping area.
ctx.restore();
}
ctx.restore();
}
if (env.wxa) {
// Flush for weixin application
util.each(this._layers, function (layer) {
if (layer && layer.ctx && layer.ctx.draw) {
layer.ctx.draw();
}
});
}
return finished;
},
_doPaintEl: function (el, currentLayer, forcePaint, scope) {
var ctx = currentLayer.ctx;
var m = el.transform;
if (
(currentLayer.__dirty || forcePaint)
// Ignore invisible element
&& !el.invisible
// Ignore transparent element
&& el.style.opacity !== 0
// Ignore scale 0 element, in some environment like node-canvas
// Draw a scale 0 element can cause all following draw wrong
// And setTransform with scale 0 will cause set back transform failed.
&& !(m && !m[0] && !m[3])
// Ignore culled element
&& !(el.culling && isDisplayableCulled(el, this._width, this._height))
) {
var clipPaths = el.__clipPaths;
var prevElClipPaths = scope.prevElClipPaths;
// Optimize when clipping on group with several elements
if (!prevElClipPaths || isClipPathChanged(clipPaths, prevElClipPaths)) {
// If has previous clipping state, restore from it
if (prevElClipPaths) {
ctx.restore();
scope.prevElClipPaths = null;
// Reset prevEl since context has been restored
scope.prevEl = null;
}
// New clipping state
if (clipPaths) {
ctx.save();
doClip(clipPaths, ctx);
scope.prevElClipPaths = clipPaths;
}
}
el.beforeBrush && el.beforeBrush(ctx);
el.brush(ctx, scope.prevEl || null);
scope.prevEl = el;
el.afterBrush && el.afterBrush(ctx);
}
},
/**
* 获取 zlevel 所在层,如果不存在则会创建一个新的层
* @param {number} zlevel
* @param {boolean} virtual Virtual layer will not be inserted into dom.
* @return {module:zrender/Layer}
*/
getLayer: function (zlevel, virtual) {
if (this._singleCanvas && !this._needsManuallyCompositing) {
zlevel = CANVAS_ZLEVEL;
}
var layer = this._layers[zlevel];
if (!layer) {
// Create a new layer
layer = new Layer('zr_' + zlevel, this, this.dpr);
layer.zlevel = zlevel;
layer.__builtin__ = true;
if (this._layerConfig[zlevel]) {
util.merge(layer, this._layerConfig[zlevel], true);
}
// TODO Remove EL_AFTER_INCREMENTAL_INC magic number
else if (this._layerConfig[zlevel - EL_AFTER_INCREMENTAL_INC]) {
util.merge(layer, this._layerConfig[zlevel - EL_AFTER_INCREMENTAL_INC], true);
}
if (virtual) {
layer.virtual = virtual;
}
this.insertLayer(zlevel, layer);
// Context is created after dom inserted to document
// Or excanvas will get 0px clientWidth and clientHeight
layer.initContext();
}
return layer;
},
insertLayer: function (zlevel, layer) {
var layersMap = this._layers;
var zlevelList = this._zlevelList;
var len = zlevelList.length;
var prevLayer = null;
var i = -1;
var domRoot = this._domRoot;
if (layersMap[zlevel]) {
logError('ZLevel ' + zlevel + ' has been used already');
return;
}
// Check if is a valid layer
if (!isLayerValid(layer)) {
logError('Layer of zlevel ' + zlevel + ' is not valid');
return;
}
if (len > 0 && zlevel > zlevelList[0]) {
for (i = 0; i < len - 1; i++) {
if (
zlevelList[i] < zlevel
&& zlevelList[i + 1] > zlevel
) {
break;
}
}
prevLayer = layersMap[zlevelList[i]];
}
zlevelList.splice(i + 1, 0, zlevel);
layersMap[zlevel] = layer;
// Vitual layer will not directly show on the screen.
// (It can be a WebGL layer and assigned to a ZImage element)
// But it still under management of zrender.
if (!layer.virtual) {
if (prevLayer) {
var prevDom = prevLayer.dom;
if (prevDom.nextSibling) {
domRoot.insertBefore(
layer.dom,
prevDom.nextSibling
);
}
else {
domRoot.appendChild(layer.dom);
}
}
else {
if (domRoot.firstChild) {
domRoot.insertBefore(layer.dom, domRoot.firstChild);
}
else {
domRoot.appendChild(layer.dom);
}
}
}
},
// Iterate each layer
eachLayer: function (cb, context) {
var zlevelList = this._zlevelList;
var z;
var i;
for (i = 0; i < zlevelList.length; i++) {
z = zlevelList[i];
cb.call(context, this._layers[z], z);
}
},
// Iterate each buildin layer
eachBuiltinLayer: function (cb, context) {
var zlevelList = this._zlevelList;
var layer;
var z;
var i;
for (i = 0; i < zlevelList.length; i++) {
z = zlevelList[i];
layer = this._layers[z];
if (layer.__builtin__) {
cb.call(context, layer, z);
}
}
},
// Iterate each other layer except buildin layer
eachOtherLayer: function (cb, context) {
var zlevelList = this._zlevelList;
var layer;
var z;
var i;
for (i = 0; i < zlevelList.length; i++) {
z = zlevelList[i];
layer = this._layers[z];
if (!layer.__builtin__) {
cb.call(context, layer, z);
}
}
},
/**
* 获取所有已创建的层
* @param {Array.<module:zrender/Layer>} [prevLayer]
*/
getLayers: function () {
return this._layers;
},
_updateLayerStatus: function (list) {
this.eachBuiltinLayer(function (layer, z) {
layer.__dirty = layer.__used = false;
});
function updatePrevLayer(idx) {
if (prevLayer) {
if (prevLayer.__endIndex !== idx) {
prevLayer.__dirty = true;
}
prevLayer.__endIndex = idx;
}
}
if (this._singleCanvas) {
for (var i = 1; i < list.length; i++) {
var el = list[i];
if (el.zlevel !== list[i - 1].zlevel || el.incremental) {
this._needsManuallyCompositing = true;
break;
}
}
}
var prevLayer = null;
var incrementalLayerCount = 0;
var prevZlevel;
for (var i = 0; i < list.length; i++) {
var el = list[i];
var zlevel = el.zlevel;
var layer;
if (prevZlevel !== zlevel) {
prevZlevel = zlevel;
incrementalLayerCount = 0;
}
// TODO Not use magic number on zlevel.
// Each layer with increment element can be separated to 3 layers.
// (Other Element drawn after incremental element)
// -----------------zlevel + EL_AFTER_INCREMENTAL_INC--------------------
// (Incremental element)
// ----------------------zlevel + INCREMENTAL_INC------------------------
// (Element drawn before incremental element)
// --------------------------------zlevel--------------------------------
if (el.incremental) {
layer = this.getLayer(zlevel + INCREMENTAL_INC, this._needsManuallyCompositing);
layer.incremental = true;
incrementalLayerCount = 1;
}
else {
layer = this.getLayer(
zlevel + (incrementalLayerCount > 0 ? EL_AFTER_INCREMENTAL_INC : 0),
this._needsManuallyCompositing
);
}
if (!layer.__builtin__) {
logError('ZLevel ' + zlevel + ' has been used by unkown layer ' + layer.id);
}
if (layer !== prevLayer) {
layer.__used = true;
if (layer.__startIndex !== i) {
layer.__dirty = true;
}
layer.__startIndex = i;
if (!layer.incremental) {
layer.__drawIndex = i;
}
else {
// Mark layer draw index needs to update.
layer.__drawIndex = -1;
}
updatePrevLayer(i);
prevLayer = layer;
}
if (el.__dirty) {
layer.__dirty = true;
if (layer.incremental && layer.__drawIndex < 0) {
// Start draw from the first dirty element.
layer.__drawIndex = i;
}
}
}
updatePrevLayer(i);
this.eachBuiltinLayer(function (layer, z) {
// Used in last frame but not in this frame. Needs clear
if (!layer.__used && layer.getElementCount() > 0) {
layer.__dirty = true;
layer.__startIndex = layer.__endIndex = layer.__drawIndex = 0;
}
// For incremental layer. In case start index changed and no elements are dirty.
if (layer.__dirty && layer.__drawIndex < 0) {
layer.__drawIndex = layer.__startIndex;
}
});
},
/**
* 清除hover层外所有内容
*/
clear: function () {
this.eachBuiltinLayer(this._clearLayer);
return this;
},
_clearLayer: function (layer) {
layer.clear();
},
setBackgroundColor: function (backgroundColor) {
this._backgroundColor = backgroundColor;
},
/**
* 修改指定zlevel的绘制参数
*
* @param {string} zlevel
* @param {Object} config 配置对象
* @param {string} [config.clearColor=0] 每次清空画布的颜色
* @param {string} [config.motionBlur=false] 是否开启动态模糊
* @param {number} [config.lastFrameAlpha=0.7]
* 在开启动态模糊的时候使用,与上一帧混合的alpha值,值越大尾迹越明显
*/
configLayer: function (zlevel, config) {
if (config) {
var layerConfig = this._layerConfig;
if (!layerConfig[zlevel]) {
layerConfig[zlevel] = config;
}
else {
util.merge(layerConfig[zlevel], config, true);
}
for (var i = 0; i < this._zlevelList.length; i++) {
var _zlevel = this._zlevelList[i];
// TODO Remove EL_AFTER_INCREMENTAL_INC magic number
if (_zlevel === zlevel || _zlevel === zlevel + EL_AFTER_INCREMENTAL_INC) {
var layer = this._layers[_zlevel];
util.merge(layer, layerConfig[zlevel], true);
}
}
}
},
/**
* 删除指定层
* @param {number} zlevel 层所在的zlevel
*/
delLayer: function (zlevel) {
var layers = this._layers;
var zlevelList = this._zlevelList;
var layer = layers[zlevel];
if (!layer) {
return;
}
layer.dom.parentNode.removeChild(layer.dom);
delete layers[zlevel];
zlevelList.splice(util.indexOf(zlevelList, zlevel), 1);
},
/**
* 区域大小变化后重绘
*/
resize: function (width, height) {
if (!this._domRoot.style) { // Maybe in node or worker
if (width == null || height == null) {
return;
}
this._width = width;
this._height = height;
this.getLayer(CANVAS_ZLEVEL).resize(width, height);
}
else {
var domRoot = this._domRoot;
// FIXME Why ?
domRoot.style.display = 'none';
// Save input w/h
var opts = this._opts;
width != null && (opts.width = width);
height != null && (opts.height = height);
width = this._getSize(0);
height = this._getSize(1);
domRoot.style.display = '';
// 优化没有实际改变的resize
if (this._width !== width || height !== this._height) {
domRoot.style.width = width + 'px';
domRoot.style.height = height + 'px';
for (var id in this._layers) {
if (this._layers.hasOwnProperty(id)) {
this._layers[id].resize(width, height);
}
}
util.each(this._progressiveLayers, function (layer) {
layer.resize(width, height);
});
this.refresh(true);
}
this._width = width;
this._height = height;
}
return this;
},
/**
* 清除单独的一个层
* @param {number} zlevel
*/
clearLayer: function (zlevel) {
var layer = this._layers[zlevel];
if (layer) {
layer.clear();
}
},
/**
* 释放
*/
dispose: function () {
this.root.innerHTML = '';
this.root =
this.storage =
this._domRoot =
this._layers = null;
},
/**
* Get canvas which has all thing rendered
* @param {Object} opts
* @param {string} [opts.backgroundColor]
* @param {number} [opts.pixelRatio]
*/
getRenderedCanvas: function (opts) {
opts = opts || {};
if (this._singleCanvas && !this._compositeManually) {
return this._layers[CANVAS_ZLEVEL].dom;
}
var imageLayer = new Layer('image', this, opts.pixelRatio || this.dpr);
imageLayer.initContext();
imageLayer.clear(false, opts.backgroundColor || this._backgroundColor);
if (opts.pixelRatio <= this.dpr) {
this.refresh();
var width = imageLayer.dom.width;
var height = imageLayer.dom.height;
var ctx = imageLayer.ctx;
this.eachLayer(function (layer) {
if (layer.__builtin__) {
ctx.drawImage(layer.dom, 0, 0, width, height);
}
else if (layer.renderToCanvas) {
imageLayer.ctx.save();
layer.renderToCanvas(imageLayer.ctx);
imageLayer.ctx.restore();
}
});
}
else {
// PENDING, echarts-gl and incremental rendering.
var scope = {};
var displayList = this.storage.getDisplayList(true);
for (var i = 0; i < displayList.length; i++) {
var el = displayList[i];
this._doPaintEl(el, imageLayer, true, scope);
}
}
return imageLayer.dom;
},
/**
* 获取绘图区域宽度
*/
getWidth: function () {
return this._width;
},
/**
* 获取绘图区域高度
*/
getHeight: function () {
return this._height;
},
_getSize: function (whIdx) {
var opts = this._opts;
var wh = ['width', 'height'][whIdx];
var cwh = ['clientWidth', 'clientHeight'][whIdx];
var plt = ['paddingLeft', 'paddingTop'][whIdx];
var prb = ['paddingRight', 'paddingBottom'][whIdx];
if (opts[wh] != null && opts[wh] !== 'auto') {
return parseFloat(opts[wh]);
}
var root = this.root;
// IE8 does not support getComputedStyle, but it use VML.
var stl = document.defaultView.getComputedStyle(root);
return (
(root[cwh] || parseInt10(stl[wh]) || parseInt10(root.style[wh]))
- (parseInt10(stl[plt]) || 0)
- (parseInt10(stl[prb]) || 0)
) | 0;
},
pathToImage: function (path, dpr) {
dpr = dpr || this.dpr;
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
var rect = path.getBoundingRect();
var style = path.style;
var shadowBlurSize = style.shadowBlur * dpr;
var shadowOffsetX = style.shadowOffsetX * dpr;
var shadowOffsetY = style.shadowOffsetY * dpr;
var lineWidth = style.hasStroke() ? style.lineWidth : 0;
var leftMargin = Math.max(lineWidth / 2, -shadowOffsetX + shadowBlurSize);
var rightMargin = Math.max(lineWidth / 2, shadowOffsetX + shadowBlurSize);
var topMargin = Math.max(lineWidth / 2, -shadowOffsetY + shadowBlurSize);
var bottomMargin = Math.max(lineWidth / 2, shadowOffsetY + shadowBlurSize);
var width = rect.width + leftMargin + rightMargin;
var height = rect.height + topMargin + bottomMargin;
canvas.width = width * dpr;
canvas.height = height * dpr;
ctx.scale(dpr, dpr);
ctx.clearRect(0, 0, width, height);
ctx.dpr = dpr;
var pathTransform = {
position: path.position,
rotation: path.rotation,
scale: path.scale
};
path.position = [leftMargin - rect.x, topMargin - rect.y];
path.rotation = 0;
path.scale = [1, 1];
path.updateTransform();
if (path) {
path.brush(ctx);
}
var ImageShape = Image;
var imgShape = new ImageShape({
style: {
x: 0,
y: 0,
image: canvas
}
});
if (pathTransform.position != null) {
imgShape.position = path.position = pathTransform.position;
}
if (pathTransform.rotation != null) {
imgShape.rotation = path.rotation = pathTransform.rotation;
}
if (pathTransform.scale != null) {
imgShape.scale = path.scale = pathTransform.scale;
}
return imgShape;
}
};
export default Painter;