This commit is contained in:
ChuXun
2025-10-25 19:18:43 +08:00
parent 4ce487588a
commit 02a830145e
3971 changed files with 1549956 additions and 2 deletions

511
ems-frontend/node_modules/zrender/src/canvas/Layer.ts generated vendored Normal file
View File

@@ -0,0 +1,511 @@
import * as util from '../core/util';
import {devicePixelRatio} from '../config';
import { ImagePatternObject } from '../graphic/Pattern';
import CanvasPainter from './Painter';
import { GradientObject, InnerGradientObject } from '../graphic/Gradient';
import { ZRCanvasRenderingContext } from '../core/types';
import Eventful from '../core/Eventful';
import { ElementEventCallback } from '../Element';
import { getCanvasGradient } from './helper';
import { createCanvasPattern } from './graphic';
import Displayable from '../graphic/Displayable';
import BoundingRect from '../core/BoundingRect';
import { REDRAW_BIT } from '../graphic/constants';
import { platformApi } from '../core/platform';
function createDom(id: string, painter: CanvasPainter, dpr: number) {
const newDom = platformApi.createCanvas();
const width = painter.getWidth();
const height = painter.getHeight();
const newDomStyle = newDom.style;
if (newDomStyle) { // In node or some other non-browser environment
newDomStyle.position = 'absolute';
newDomStyle.left = '0';
newDomStyle.top = '0';
newDomStyle.width = width + 'px';
newDomStyle.height = height + 'px';
newDom.setAttribute('data-zr-dom-id', id);
}
newDom.width = width * dpr;
newDom.height = height * dpr;
return newDom;
}
export interface LayerConfig {
// 每次清空画布的颜色
clearColor?: string | GradientObject | ImagePatternObject
// 是否开启动态模糊
motionBlur?: boolean
// 在开启动态模糊的时候使用与上一帧混合的alpha值值越大尾迹越明显
lastFrameAlpha?: number
};
export default class Layer extends Eventful {
id: string
dom: HTMLCanvasElement
domBack: HTMLCanvasElement
ctx: CanvasRenderingContext2D
ctxBack: CanvasRenderingContext2D
painter: CanvasPainter
// Configs
/**
* 每次清空画布的颜色
*/
clearColor: string | GradientObject | ImagePatternObject
/**
* 是否开启动态模糊
*/
motionBlur = false
/**
* 在开启动态模糊的时候使用与上一帧混合的alpha值值越大尾迹越明显
*/
lastFrameAlpha = 0.7
/**
* Layer dpr
*/
dpr = 1
/**
* Virtual layer will not be inserted into dom.
*/
virtual = false
config = {}
incremental = false
zlevel = 0
maxRepaintRectCount = 5
private _paintRects: BoundingRect[]
__dirty = true
__firstTimePaint = true
__used = false
__drawIndex = 0
__startIndex = 0
__endIndex = 0
// indices in the previous frame
__prevStartIndex: number = null
__prevEndIndex: number = null
__builtin__: boolean
constructor(id: string | HTMLCanvasElement, painter: CanvasPainter, dpr?: number) {
super();
let dom;
dpr = dpr || devicePixelRatio;
if (typeof id === 'string') {
dom = createDom(id, painter, dpr);
}
// Not using isDom because in node it will return false
else if (util.isObject(id)) {
dom = id;
id = dom.id;
}
this.id = id as string;
this.dom = dom;
const domStyle = dom.style;
if (domStyle) { // Not in node
util.disableUserSelect(dom);
dom.onselectstart = () => false;
domStyle.padding = '0';
domStyle.margin = '0';
domStyle.borderWidth = '0';
}
this.painter = painter;
this.dpr = dpr;
}
getElementCount() {
return this.__endIndex - this.__startIndex;
}
afterBrush() {
this.__prevStartIndex = this.__startIndex;
this.__prevEndIndex = this.__endIndex;
}
initContext() {
this.ctx = this.dom.getContext('2d');
(this.ctx as ZRCanvasRenderingContext).dpr = this.dpr;
}
setUnpainted() {
this.__firstTimePaint = true;
}
createBackBuffer() {
const dpr = this.dpr;
this.domBack = createDom('back-' + this.id, this.painter, dpr);
this.ctxBack = this.domBack.getContext('2d');
if (dpr !== 1) {
this.ctxBack.scale(dpr, dpr);
}
}
/**
* Create repaint list when using dirty rect rendering.
*
* @param displayList current rendering list
* @param prevList last frame rendering list
* @return repaint rects. null for the first frame, [] for no element dirty
*/
createRepaintRects(
displayList: Displayable[],
prevList: Displayable[],
viewWidth: number,
viewHeight: number
) {
if (this.__firstTimePaint) {
this.__firstTimePaint = false;
return null;
}
const mergedRepaintRects: BoundingRect[] = [];
const maxRepaintRectCount = this.maxRepaintRectCount;
let full = false;
const pendingRect = new BoundingRect(0, 0, 0, 0);
function addRectToMergePool(rect: BoundingRect) {
if (!rect.isFinite() || rect.isZero()) {
return;
}
if (mergedRepaintRects.length === 0) {
// First rect, create new merged rect
const boundingRect = new BoundingRect(0, 0, 0, 0);
boundingRect.copy(rect);
mergedRepaintRects.push(boundingRect);
}
else {
let isMerged = false;
let minDeltaArea = Infinity;
let bestRectToMergeIdx = 0;
for (let i = 0; i < mergedRepaintRects.length; ++i) {
const mergedRect = mergedRepaintRects[i];
// Merge if has intersection
if (mergedRect.intersect(rect)) {
const pendingRect = new BoundingRect(0, 0, 0, 0);
pendingRect.copy(mergedRect);
pendingRect.union(rect);
mergedRepaintRects[i] = pendingRect;
isMerged = true;
break;
}
else if (full) {
// Merged to exists rectangles if full
pendingRect.copy(rect);
pendingRect.union(mergedRect);
const aArea = rect.width * rect.height;
const bArea = mergedRect.width * mergedRect.height;
const pendingArea = pendingRect.width * pendingRect.height;
const deltaArea = pendingArea - aArea - bArea;
if (deltaArea < minDeltaArea) {
minDeltaArea = deltaArea;
bestRectToMergeIdx = i;
}
}
}
if (full) {
mergedRepaintRects[bestRectToMergeIdx].union(rect);
isMerged = true;
}
if (!isMerged) {
// Create new merged rect if cannot merge with current
const boundingRect = new BoundingRect(0, 0, 0, 0);
boundingRect.copy(rect);
mergedRepaintRects.push(boundingRect);
}
if (!full) {
full = mergedRepaintRects.length >= maxRepaintRectCount;
}
}
}
/**
* Loop the paint list of this frame and get the dirty rects of elements
* in this frame.
*/
for (let i = this.__startIndex; i < this.__endIndex; ++i) {
const el = displayList[i];
if (el) {
/**
* `shouldPaint` is true only when the element is not ignored or
* invisible and all its ancestors are not ignored.
* `shouldPaint` being true means it will be brushed this frame.
*
* `__isRendered` being true means the element is currently on
* the canvas.
*
* `__dirty` being true means the element should be brushed this
* frame.
*
* We only need to repaint the element's previous painting rect
* if it's currently on the canvas and needs repaint this frame
* or not painted this frame.
*/
const shouldPaint = el.shouldBePainted(viewWidth, viewHeight, true, true);
const prevRect = el.__isRendered && ((el.__dirty & REDRAW_BIT) || !shouldPaint)
? el.getPrevPaintRect()
: null;
if (prevRect) {
addRectToMergePool(prevRect);
}
/**
* On the other hand, we only need to paint the current rect
* if the element should be brushed this frame and either being
* dirty or not rendered before.
*/
const curRect = shouldPaint && ((el.__dirty & REDRAW_BIT) || !el.__isRendered)
? el.getPaintRect()
: null;
if (curRect) {
addRectToMergePool(curRect);
}
}
}
/**
* The above loop calculates the dirty rects of elements that are in the
* paint list this frame, which does not include those elements removed
* in this frame. So we loop the `prevList` to get the removed elements.
*/
for (let i = this.__prevStartIndex; i < this.__prevEndIndex; ++i) {
const el = prevList[i];
/**
* Consider the elements whose ancestors are invisible, they should
* not be painted and their previous painting rects should be
* cleared if they are rendered on the canvas (`__isRendered` being
* true). `!shouldPaint` means the element is not brushed in this
* frame.
*
* `!el.__zr` means it's removed from the storage.
*
* In conclusion, an element needs to repaint the previous painting
* rect if and only if it's not painted this frame and was
* previously painted on the canvas.
*/
const shouldPaint = el && el.shouldBePainted(viewWidth, viewHeight, true, true);
if (el && (!shouldPaint || !el.__zr) && el.__isRendered) {
// el was removed
const prevRect = el.getPrevPaintRect();
if (prevRect) {
addRectToMergePool(prevRect);
}
}
}
// Merge intersected rects in the result
let hasIntersections;
do {
hasIntersections = false;
for (let i = 0; i < mergedRepaintRects.length;) {
if (mergedRepaintRects[i].isZero()) {
mergedRepaintRects.splice(i, 1);
continue;
}
for (let j = i + 1; j < mergedRepaintRects.length;) {
if (mergedRepaintRects[i].intersect(mergedRepaintRects[j])) {
hasIntersections = true;
mergedRepaintRects[i].union(mergedRepaintRects[j]);
mergedRepaintRects.splice(j, 1);
}
else {
j++;
}
}
i++;
}
} while (hasIntersections);
this._paintRects = mergedRepaintRects;
return mergedRepaintRects;
}
/**
* Get paint rects for debug usage.
*/
debugGetPaintRects() {
return (this._paintRects || []).slice();
}
resize(width: number, height: number) {
const dpr = this.dpr;
const dom = this.dom;
const domStyle = dom.style;
const domBack = this.domBack;
if (domStyle) {
domStyle.width = width + 'px';
domStyle.height = height + 'px';
}
dom.width = width * dpr;
dom.height = height * dpr;
if (domBack) {
domBack.width = width * dpr;
domBack.height = height * dpr;
if (dpr !== 1) {
this.ctxBack.scale(dpr, dpr);
}
}
}
/**
* 清空该层画布
*/
clear(
clearAll?: boolean,
clearColor?: string | GradientObject | ImagePatternObject,
repaintRects?: BoundingRect[]
) {
const dom = this.dom;
const ctx = this.ctx;
const width = dom.width;
const height = dom.height;
clearColor = clearColor || this.clearColor;
const haveMotionBLur = this.motionBlur && !clearAll;
const lastFrameAlpha = this.lastFrameAlpha;
const dpr = this.dpr;
const self = this;
if (haveMotionBLur) {
if (!this.domBack) {
this.createBackBuffer();
}
this.ctxBack.globalCompositeOperation = 'copy';
this.ctxBack.drawImage(
dom, 0, 0,
width / dpr,
height / dpr
);
}
const domBack = this.domBack;
function doClear(x: number, y: number, width: number, height: number) {
ctx.clearRect(x, y, width, height);
if (clearColor && clearColor !== 'transparent') {
let clearColorGradientOrPattern;
// Gradient
if (util.isGradientObject(clearColor)) {
// shouldn't cache when clearColor is not global and size changed
const shouldCache = clearColor.global || (
(clearColor as InnerGradientObject).__width === width
&& (clearColor as InnerGradientObject).__height === height
);
// Cache canvas gradient
clearColorGradientOrPattern = shouldCache
&& (clearColor as InnerGradientObject).__canvasGradient
|| getCanvasGradient(ctx, clearColor, {
x: 0,
y: 0,
width: width,
height: height
});
(clearColor as InnerGradientObject).__canvasGradient = clearColorGradientOrPattern;
(clearColor as InnerGradientObject).__width = width;
(clearColor as InnerGradientObject).__height = height;
}
// Pattern
else if (util.isImagePatternObject(clearColor)) {
// scale pattern by dpr
clearColor.scaleX = clearColor.scaleX || dpr;
clearColor.scaleY = clearColor.scaleY || dpr;
clearColorGradientOrPattern = createCanvasPattern(
ctx, clearColor, {
dirty() {
self.setUnpainted();
self.painter.refresh();
}
}
);
}
ctx.save();
ctx.fillStyle = clearColorGradientOrPattern || (clearColor as string);
ctx.fillRect(x, y, width, height);
ctx.restore();
}
if (haveMotionBLur) {
ctx.save();
ctx.globalAlpha = lastFrameAlpha;
ctx.drawImage(domBack, x, y, width, height);
ctx.restore();
}
};
if (!repaintRects || haveMotionBLur) {
// Clear the full canvas
doClear(0, 0, width, height);
}
else if (repaintRects.length) {
// Clear the repaint areas
util.each(repaintRects, rect => {
doClear(
rect.x * dpr,
rect.y * dpr,
rect.width * dpr,
rect.height * dpr
);
});
}
}
// Interface of refresh
refresh: (clearColor?: string | GradientObject | ImagePatternObject) => void
// Interface of renderToCanvas in getRenderedCanvas
renderToCanvas: (ctx: CanvasRenderingContext2D) => void
// Events
onclick: ElementEventCallback<unknown, this>
ondblclick: ElementEventCallback<unknown, this>
onmouseover: ElementEventCallback<unknown, this>
onmouseout: ElementEventCallback<unknown, this>
onmousemove: ElementEventCallback<unknown, this>
onmousewheel: ElementEventCallback<unknown, this>
onmousedown: ElementEventCallback<unknown, this>
onmouseup: ElementEventCallback<unknown, this>
oncontextmenu: ElementEventCallback<unknown, this>
ondrag: ElementEventCallback<unknown, this>
ondragstart: ElementEventCallback<unknown, this>
ondragend: ElementEventCallback<unknown, this>
ondragenter: ElementEventCallback<unknown, this>
ondragleave: ElementEventCallback<unknown, this>
ondragover: ElementEventCallback<unknown, this>
ondrop: ElementEventCallback<unknown, this>
}

977
ems-frontend/node_modules/zrender/src/canvas/Painter.ts generated vendored Normal file
View File

@@ -0,0 +1,977 @@
import {devicePixelRatio} from '../config';
import * as util from '../core/util';
import Layer, { LayerConfig } from './Layer';
import requestAnimationFrame from '../animation/requestAnimationFrame';
import env from '../core/env';
import Displayable from '../graphic/Displayable';
import { WXCanvasRenderingContext } from '../core/types';
import { GradientObject } from '../graphic/Gradient';
import { ImagePatternObject } from '../graphic/Pattern';
import Storage from '../Storage';
import { brush, BrushScope, brushSingle } from './graphic';
import { PainterBase } from '../PainterBase';
import BoundingRect from '../core/BoundingRect';
import { REDRAW_BIT } from '../graphic/constants';
import { getSize } from './helper';
import type IncrementalDisplayable from '../graphic/IncrementalDisplayable';
const HOVER_LAYER_ZLEVEL = 1e5;
const CANVAS_ZLEVEL = 314159;
const EL_AFTER_INCREMENTAL_INC = 0.01;
const INCREMENTAL_INC = 0.001;
function isLayerValid(layer: Layer) {
if (!layer) {
return false;
}
if (layer.__builtin__) {
return true;
}
if (typeof (layer.resize) !== 'function'
|| typeof (layer.refresh) !== 'function'
) {
return false;
}
return true;
}
function createRoot(width: number, height: number) {
const 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;
}
interface CanvasPainterOption {
devicePixelRatio?: number
width?: number | string // Can be 10 / 10px / auto
height?: number | string,
useDirtyRect?: boolean
}
export default class CanvasPainter implements PainterBase {
type = 'canvas'
root: HTMLElement
dpr: number
storage: Storage
private _singleCanvas: boolean
private _opts: CanvasPainterOption
private _zlevelList: number[] = []
private _prevDisplayList: Displayable[] = []
private _layers: {[key: number]: Layer} = {} // key is zlevel
private _layerConfig: {[key: number]: LayerConfig} = {} // key is zlevel
/**
* zrender will do compositing when root is a canvas and have multiple zlevels.
*/
private _needsManuallyCompositing = false
private _width: number
private _height: number
private _domRoot: HTMLElement
private _hoverlayer: Layer
private _redrawId: number
private _backgroundColor: string | GradientObject | ImagePatternObject
constructor(root: HTMLElement, storage: Storage, opts: CanvasPainterOption, id: number) {
this.type = 'canvas';
// In node environment using node-canvas
const singleCanvas = !root.nodeName // In node ?
|| root.nodeName.toUpperCase() === 'CANVAS';
this._opts = opts = util.extend({}, opts || {}) as CanvasPainterOption;
/**
* @type {number}
*/
this.dpr = opts.devicePixelRatio || devicePixelRatio;
/**
* @type {boolean}
* @private
*/
this._singleCanvas = singleCanvas;
/**
* 绘图容器
* @type {HTMLElement}
*/
this.root = root;
const rootStyle = root.style;
if (rootStyle) {
// @ts-ignore
util.disableUserSelect(root);
root.innerHTML = '';
}
/**
* @type {module:zrender/Storage}
*/
this.storage = storage;
const zlevelList: number[] = this._zlevelList;
this._prevDisplayList = [];
const layers = this._layers;
if (!singleCanvas) {
this._width = getSize(root, 0, opts);
this._height = getSize(root, 1, opts);
const domRoot = this._domRoot = createRoot(
this._width, this._height
);
root.appendChild(domRoot);
}
else {
const rootCanvas = root as HTMLCanvasElement;
let width = rootCanvas.width;
let height = rootCanvas.height;
if (opts.width != null) {
// TODO sting?
width = opts.width as number;
}
if (opts.height != null) {
// TODO sting?
height = opts.height as number;
}
this.dpr = opts.devicePixelRatio || 1;
// Use canvas width and height directly
rootCanvas.width = width * this.dpr;
rootCanvas.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.
const mainLayer = new Layer(rootCanvas, 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;
}
}
getType() {
return 'canvas';
}
/**
* If painter use a single canvas
*/
isSingleCanvas() {
return this._singleCanvas;
}
getViewportRoot() {
return this._domRoot;
}
getViewportRootOffset() {
const viewportRoot = this.getViewportRoot();
if (viewportRoot) {
return {
offsetLeft: viewportRoot.offsetLeft || 0,
offsetTop: viewportRoot.offsetTop || 0
};
}
}
/**
* 刷新
* @param paintAll 强制绘制所有displayable
*/
refresh(paintAll?: boolean) {
const list = this.storage.getDisplayList(true);
const prevList = this._prevDisplayList;
const zlevelList = this._zlevelList;
this._redrawId = Math.random();
this._paintList(list, prevList, paintAll, this._redrawId);
// Paint custum layers
for (let i = 0; i < zlevelList.length; i++) {
const z = zlevelList[i];
const layer = this._layers[z];
if (!layer.__builtin__ && layer.refresh) {
const clearColor = i === 0 ? this._backgroundColor : null;
layer.refresh(clearColor);
}
}
if (this._opts.useDirtyRect) {
this._prevDisplayList = list.slice();
}
return this;
}
refreshHover() {
this._paintHoverList(this.storage.getDisplayList(false));
}
private _paintHoverList(list: Displayable[]) {
let len = list.length;
let hoverLayer = this._hoverlayer;
hoverLayer && hoverLayer.clear();
if (!len) {
return;
}
const scope: BrushScope = {
inHover: true,
viewWidth: this._width,
viewHeight: this._height
};
let ctx;
for (let i = 0; i < len; i++) {
const el = list[i];
if (el.__inHover) {
// Use a extream large zlevel
// FIXME?
if (!hoverLayer) {
hoverLayer = this._hoverlayer = this.getLayer(HOVER_LAYER_ZLEVEL);
}
if (!ctx) {
ctx = hoverLayer.ctx;
ctx.save();
}
brush(ctx, el, scope, i === len - 1);
}
}
if (ctx) {
ctx.restore();
}
}
getHoverLayer() {
return this.getLayer(HOVER_LAYER_ZLEVEL);
}
paintOne(ctx: CanvasRenderingContext2D, el: Displayable) {
brushSingle(ctx, el);
}
private _paintList(list: Displayable[], prevList: Displayable[], paintAll: boolean, redrawId?: number) {
if (this._redrawId !== redrawId) {
return;
}
paintAll = paintAll || false;
this._updateLayerStatus(list);
const {finished, needsRefreshHover} = this._doPaintList(list, prevList, paintAll);
if (this._needsManuallyCompositing) {
this._compositeManually();
}
if (needsRefreshHover) {
this._paintHoverList(list);
}
if (!finished) {
const self = this;
requestAnimationFrame(function () {
self._paintList(list, prevList, paintAll, redrawId);
});
}
else {
this.eachLayer(layer => {
layer.afterBrush && layer.afterBrush();
});
}
}
private _compositeManually() {
const ctx = this.getLayer(CANVAS_ZLEVEL).ctx;
const width = (this._domRoot as HTMLCanvasElement).width;
const height = (this._domRoot as HTMLCanvasElement).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);
}
});
}
private _doPaintList(
list: Displayable[],
prevList: Displayable[],
paintAll?: boolean
): {
finished: boolean
needsRefreshHover: boolean
} {
const layerList = [];
const useDirtyRect = this._opts.useDirtyRect;
for (let zi = 0; zi < this._zlevelList.length; zi++) {
const zlevel = this._zlevelList[zi];
const layer = this._layers[zlevel];
if (layer.__builtin__
&& layer !== this._hoverlayer
&& (layer.__dirty || paintAll)
// Layer with hover elements can't be redrawn.
// && !layer.__hasHoverLayerELement
) {
layerList.push(layer);
}
}
let finished = true;
let needsRefreshHover = false;
for (let k = 0; k < layerList.length; k++) {
const layer = layerList[k];
const ctx = layer.ctx;
const repaintRects = useDirtyRect
&& layer.createRepaintRects(list, prevList, this._width, this._height);
let start = paintAll ? layer.__startIndex : layer.__drawIndex;
const useTimer = !paintAll && layer.incremental && Date.now;
const startTime = useTimer && Date.now();
const clearColor = layer.zlevel === this._zlevelList[0]
? this._backgroundColor : null;
// All elements in this layer are removed.
if (layer.__startIndex === layer.__endIndex) {
layer.clear(false, clearColor, repaintRects);
}
else if (start === layer.__startIndex) {
const firstEl = list[start];
if (!firstEl.incremental || !(firstEl as IncrementalDisplayable).notClear || paintAll) {
layer.clear(false, clearColor, repaintRects);
}
}
if (start === -1) {
console.error('For some unknown reason. drawIndex is -1');
start = layer.__startIndex;
}
let i: number;
/* eslint-disable-next-line */
const repaint = (repaintRect?: BoundingRect) => {
const scope: BrushScope = {
inHover: false,
allClipped: false,
prevEl: null,
viewWidth: this._width,
viewHeight: this._height
};
for (i = start; i < layer.__endIndex; i++) {
const el = list[i];
if (el.__inHover) {
needsRefreshHover = true;
}
this._doPaintEl(el, layer, useDirtyRect, repaintRect, scope, i === layer.__endIndex - 1);
if (useTimer) {
// Date.now can be executed in 13,025,305 ops/second.
const dTime = Date.now() - startTime;
// Give 15 millisecond to draw.
// The rest elements will be drawn in the next frame.
if (dTime > 15) {
break;
}
}
}
if (scope.prevElClipPaths) {
// Needs restore the state. If last drawn element is in the clipping area.
ctx.restore();
}
};
if (repaintRects) {
if (repaintRects.length === 0) {
// Nothing to repaint, mark as finished
i = layer.__endIndex;
}
else {
const dpr = this.dpr;
// Set repaintRect as clipPath
for (var r = 0; r < repaintRects.length; ++r) {
const rect = repaintRects[r];
ctx.save();
ctx.beginPath();
ctx.rect(
rect.x * dpr,
rect.y * dpr,
rect.width * dpr,
rect.height * dpr
);
ctx.clip();
repaint(rect);
ctx.restore();
}
}
}
else {
// Paint all once
ctx.save();
repaint();
ctx.restore();
}
layer.__drawIndex = i;
if (layer.__drawIndex < layer.__endIndex) {
finished = false;
}
}
if (env.wxa) {
// Flush for weixin application
util.each(this._layers, function (layer) {
if (layer && layer.ctx && (layer.ctx as WXCanvasRenderingContext).draw) {
(layer.ctx as WXCanvasRenderingContext).draw();
}
});
}
return {
finished,
needsRefreshHover
};
}
private _doPaintEl(
el: Displayable,
currentLayer: Layer,
useDirtyRect: boolean,
repaintRect: BoundingRect,
scope: BrushScope,
isLast: boolean
) {
const ctx = currentLayer.ctx;
if (useDirtyRect) {
const paintRect = el.getPaintRect();
if (!repaintRect || paintRect && paintRect.intersect(repaintRect)) {
brush(ctx, el, scope, isLast);
el.setPrevPaintRect(paintRect);
}
}
else {
brush(ctx, el, scope, isLast);
}
}
/**
* 获取 zlevel 所在层,如果不存在则会创建一个新的层
* @param zlevel
* @param virtual Virtual layer will not be inserted into dom.
*/
getLayer(zlevel: number, virtual?: boolean) {
if (this._singleCanvas && !this._needsManuallyCompositing) {
zlevel = CANVAS_ZLEVEL;
}
let 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(zlevel: number, layer: Layer) {
const layersMap = this._layers;
const zlevelList = this._zlevelList;
const len = zlevelList.length;
const domRoot = this._domRoot;
let prevLayer = null;
let i = -1;
if (layersMap[zlevel]) {
if (process.env.NODE_ENV !== 'production') {
util.logError('ZLevel ' + zlevel + ' has been used already');
}
return;
}
// Check if is a valid layer
if (!isLayerValid(layer)) {
if (process.env.NODE_ENV !== 'production') {
util.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;
// Virtual layer will not directly show on the screen.
// (It can be a WebGL layer and assigned to a ZRImage element)
// But it still under management of zrender.
if (!layer.virtual) {
if (prevLayer) {
const 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);
}
}
}
layer.painter || (layer.painter = this);
}
// Iterate each layer
eachLayer<T>(cb: (this: T, layer: Layer, z: number) => void, context?: T) {
const zlevelList = this._zlevelList;
for (let i = 0; i < zlevelList.length; i++) {
const z = zlevelList[i];
cb.call(context, this._layers[z], z);
}
}
// Iterate each buildin layer
eachBuiltinLayer<T>(cb: (this: T, layer: Layer, z: number) => void, context?: T) {
const zlevelList = this._zlevelList;
for (let i = 0; i < zlevelList.length; i++) {
const z = zlevelList[i];
const layer = this._layers[z];
if (layer.__builtin__) {
cb.call(context, layer, z);
}
}
}
// Iterate each other layer except buildin layer
eachOtherLayer<T>(cb: (this: T, layer: Layer, z: number) => void, context?: T) {
const zlevelList = this._zlevelList;
for (let i = 0; i < zlevelList.length; i++) {
const z = zlevelList[i];
const layer = this._layers[z];
if (!layer.__builtin__) {
cb.call(context, layer, z);
}
}
}
/**
* 获取所有已创建的层
* @param prevLayer
*/
getLayers() {
return this._layers;
}
_updateLayerStatus(list: Displayable[]) {
this.eachBuiltinLayer(function (layer, z) {
layer.__dirty = layer.__used = false;
});
function updatePrevLayer(idx: number) {
if (prevLayer) {
if (prevLayer.__endIndex !== idx) {
prevLayer.__dirty = true;
}
prevLayer.__endIndex = idx;
}
}
if (this._singleCanvas) {
for (let i = 1; i < list.length; i++) {
const el = list[i];
if (el.zlevel !== list[i - 1].zlevel || el.incremental) {
this._needsManuallyCompositing = true;
break;
}
}
}
let prevLayer: Layer = null;
let incrementalLayerCount = 0;
let prevZlevel;
let i;
for (i = 0; i < list.length; i++) {
const el = list[i];
const zlevel = el.zlevel;
let 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__) {
util.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 & REDRAW_BIT) && !el.__inHover) { // Ignore dirty elements in hover layer.
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() {
this.eachBuiltinLayer(this._clearLayer);
return this;
}
_clearLayer(layer: Layer) {
layer.clear();
}
setBackgroundColor(backgroundColor: string | GradientObject | ImagePatternObject) {
this._backgroundColor = backgroundColor;
util.each(this._layers, layer => {
layer.setUnpainted();
});
}
/**
* 修改指定zlevel的绘制参数
*/
configLayer(zlevel: number, config: LayerConfig) {
if (config) {
const layerConfig = this._layerConfig;
if (!layerConfig[zlevel]) {
layerConfig[zlevel] = config;
}
else {
util.merge(layerConfig[zlevel], config, true);
}
for (let i = 0; i < this._zlevelList.length; i++) {
const _zlevel = this._zlevelList[i];
// TODO Remove EL_AFTER_INCREMENTAL_INC magic number
if (_zlevel === zlevel || _zlevel === zlevel + EL_AFTER_INCREMENTAL_INC) {
const layer = this._layers[_zlevel];
util.merge(layer, layerConfig[zlevel], true);
}
}
}
}
/**
* 删除指定层
* @param zlevel 层所在的zlevel
*/
delLayer(zlevel: number) {
const layers = this._layers;
const zlevelList = this._zlevelList;
const layer = layers[zlevel];
if (!layer) {
return;
}
layer.dom.parentNode.removeChild(layer.dom);
delete layers[zlevel];
zlevelList.splice(util.indexOf(zlevelList, zlevel), 1);
}
/**
* 区域大小变化后重绘
*/
resize(
width?: number | string,
height?: number | string
) {
if (!this._domRoot.style) { // Maybe in node or worker
if (width == null || height == null) {
return;
}
// TODO width / height may be string
this._width = width as number;
this._height = height as number;
this.getLayer(CANVAS_ZLEVEL).resize(width as number, height as number);
}
else {
const domRoot = this._domRoot;
// FIXME Why ?
domRoot.style.display = 'none';
// Save input w/h
const opts = this._opts;
const root = this.root;
width != null && (opts.width = width);
height != null && (opts.height = height);
width = getSize(root, 0, opts);
height = getSize(root, 1, opts);
domRoot.style.display = '';
// 优化没有实际改变的resize
if (this._width !== width || height !== this._height) {
domRoot.style.width = width + 'px';
domRoot.style.height = height + 'px';
for (let id in this._layers) {
if (this._layers.hasOwnProperty(id)) {
this._layers[id].resize(width, height);
}
}
this.refresh(true);
}
this._width = width;
this._height = height;
}
return this;
}
/**
* 清除单独的一个层
* @param {number} zlevel
*/
clearLayer(zlevel: number) {
const layer = this._layers[zlevel];
if (layer) {
layer.clear();
}
}
/**
* 释放
*/
dispose() {
this.root.innerHTML = '';
this.root =
this.storage =
this._domRoot =
this._layers = null;
}
/**
* Get canvas which has all thing rendered
*/
getRenderedCanvas(opts?: {
backgroundColor?: string | GradientObject | ImagePatternObject
pixelRatio?: number
}) {
opts = opts || {};
if (this._singleCanvas && !this._compositeManually) {
return this._layers[CANVAS_ZLEVEL].dom;
}
const imageLayer = new Layer('image', this, opts.pixelRatio || this.dpr);
imageLayer.initContext();
imageLayer.clear(false, opts.backgroundColor || this._backgroundColor);
const ctx = imageLayer.ctx;
if (opts.pixelRatio <= this.dpr) {
this.refresh();
const width = imageLayer.dom.width;
const height = imageLayer.dom.height;
this.eachLayer(function (layer) {
if (layer.__builtin__) {
ctx.drawImage(layer.dom, 0, 0, width, height);
}
else if (layer.renderToCanvas) {
ctx.save();
layer.renderToCanvas(ctx);
ctx.restore();
}
});
}
else {
// PENDING, echarts-gl and incremental rendering.
const scope = {
inHover: false,
viewWidth: this._width,
viewHeight: this._height
};
const displayList = this.storage.getDisplayList(true);
for (let i = 0, len = displayList.length; i < len; i++) {
const el = displayList[i];
brush(ctx, el, scope, i === len - 1);
}
}
return imageLayer.dom;
}
/**
* 获取绘图区域宽度
*/
getWidth() {
return this._width;
}
/**
* 获取绘图区域高度
*/
getHeight() {
return this._height;
}
};

View File

@@ -0,0 +1,4 @@
import {registerPainter} from '../zrender';
import Painter from './Painter';
registerPainter('canvas', Painter);

View File

@@ -0,0 +1,32 @@
import { isArray, isNumber, map } from '../core/util';
import Path from '../graphic/Path';
import TSpan from '../graphic/TSpan';
export function normalizeLineDash(lineType: any, lineWidth?: number): number[] | false {
if (!lineType || lineType === 'solid' || !(lineWidth > 0)) {
return null;
}
return lineType === 'dashed'
? [4 * lineWidth, 2 * lineWidth]
: lineType === 'dotted'
? [lineWidth]
: isNumber(lineType)
? [lineType] : isArray(lineType) ? lineType : null;
}
export function getLineDash(el: Path | TSpan): [number[] | false, number] {
const style = el.style;
let lineDash = style.lineDash && style.lineWidth > 0 && normalizeLineDash(style.lineDash, style.lineWidth);
let lineDashOffset = style.lineDashOffset;
if (lineDash) {
const lineScale = (style.strokeNoScale && el.getLineScale) ? el.getLineScale() : 1;
if (lineScale && lineScale !== 1) {
lineDash = map(lineDash, function (rawVal) {
return rawVal / lineScale;
});
lineDashOffset /= lineScale;
}
}
return [lineDash, lineDashOffset];
}

816
ems-frontend/node_modules/zrender/src/canvas/graphic.ts generated vendored Normal file
View File

@@ -0,0 +1,816 @@
import Displayable, { DEFAULT_COMMON_STYLE } from '../graphic/Displayable';
import PathProxy from '../core/PathProxy';
import { GradientObject } from '../graphic/Gradient';
import { ImagePatternObject, InnerImagePatternObject } from '../graphic/Pattern';
import { LinearGradientObject } from '../graphic/LinearGradient';
import { RadialGradientObject } from '../graphic/RadialGradient';
import { ZRCanvasRenderingContext } from '../core/types';
import { createOrUpdateImage, isImageReady } from '../graphic/helper/image';
import { getCanvasGradient, isClipPathChanged } from './helper';
import Path, { PathStyleProps } from '../graphic/Path';
import ZRImage, { ImageStyleProps } from '../graphic/Image';
import TSpan, {TSpanStyleProps} from '../graphic/TSpan';
import { MatrixArray } from '../core/matrix';
import { RADIAN_TO_DEGREE } from '../core/util';
import { getLineDash } from './dashStyle';
import { REDRAW_BIT, SHAPE_CHANGED_BIT } from '../graphic/constants';
import type IncrementalDisplayable from '../graphic/IncrementalDisplayable';
import { DEFAULT_FONT } from '../core/platform';
const pathProxyForDraw = new PathProxy(true);
// Not use el#hasStroke because style may be different.
function styleHasStroke(style: PathStyleProps) {
const stroke = style.stroke;
return !(stroke == null || stroke === 'none' || !(style.lineWidth > 0));
}
// ignore lineWidth and must be string
// Expected color but found '[' when color is gradient
function isValidStrokeFillStyle(
strokeOrFill: PathStyleProps['stroke'] | PathStyleProps['fill']
): strokeOrFill is string {
return typeof strokeOrFill === 'string' && strokeOrFill !== 'none';
}
function styleHasFill(style: PathStyleProps) {
const fill = style.fill;
return fill != null && fill !== 'none';
}
function doFillPath(ctx: CanvasRenderingContext2D, style: PathStyleProps) {
if (style.fillOpacity != null && style.fillOpacity !== 1) {
const originalGlobalAlpha = ctx.globalAlpha;
ctx.globalAlpha = style.fillOpacity * style.opacity;
ctx.fill();
// Set back globalAlpha
ctx.globalAlpha = originalGlobalAlpha;
}
else {
ctx.fill();
}
}
function doStrokePath(ctx: CanvasRenderingContext2D, style: PathStyleProps) {
if (style.strokeOpacity != null && style.strokeOpacity !== 1) {
const originalGlobalAlpha = ctx.globalAlpha;
ctx.globalAlpha = style.strokeOpacity * style.opacity;
ctx.stroke();
// Set back globalAlpha
ctx.globalAlpha = originalGlobalAlpha;
}
else {
ctx.stroke();
}
}
export function createCanvasPattern(
this: void,
ctx: CanvasRenderingContext2D,
pattern: ImagePatternObject,
el: {dirty: () => void}
): CanvasPattern {
const image = createOrUpdateImage(pattern.image, (pattern as InnerImagePatternObject).__image, el);
if (isImageReady(image)) {
const canvasPattern = ctx.createPattern(image, pattern.repeat || 'repeat');
if (
typeof DOMMatrix === 'function'
&& canvasPattern // image may be not ready
&& canvasPattern.setTransform // setTransform may not be supported in some old devices.
) {
const matrix = new DOMMatrix();
matrix.translateSelf((pattern.x || 0), (pattern.y || 0));
matrix.rotateSelf(0, 0, (pattern.rotation || 0) * RADIAN_TO_DEGREE);
matrix.scaleSelf((pattern.scaleX || 1), (pattern.scaleY || 1));
canvasPattern.setTransform(matrix);
}
return canvasPattern;
}
}
// Draw Path Elements
function brushPath(ctx: CanvasRenderingContext2D, el: Path, style: PathStyleProps, inBatch: boolean) {
let hasStroke = styleHasStroke(style);
let hasFill = styleHasFill(style);
const strokePercent = style.strokePercent;
const strokePart = strokePercent < 1;
// TODO Reduce path memory cost.
const firstDraw = !el.path;
// Create path for each element when:
// 1. Element has interactions.
// 2. Element draw part of the line.
if ((!el.silent || strokePart) && firstDraw) {
el.createPathProxy();
}
const path = el.path || pathProxyForDraw;
const dirtyFlag = el.__dirty;
if (!inBatch) {
const fill = style.fill;
const stroke = style.stroke;
const hasFillGradient = hasFill && !!(fill as GradientObject).colorStops;
const hasStrokeGradient = hasStroke && !!(stroke as GradientObject).colorStops;
const hasFillPattern = hasFill && !!(fill as ImagePatternObject).image;
const hasStrokePattern = hasStroke && !!(stroke as ImagePatternObject).image;
let fillGradient;
let strokeGradient;
let fillPattern;
let strokePattern;
let rect;
if (hasFillGradient || hasStrokeGradient) {
rect = el.getBoundingRect();
}
// Update gradient because bounding rect may changed
if (hasFillGradient) {
fillGradient = dirtyFlag
? getCanvasGradient(ctx, fill as (LinearGradientObject | RadialGradientObject), rect)
: el.__canvasFillGradient;
// No need to clear cache when fill is not gradient.
// It will always been updated when fill changed back to gradient.
el.__canvasFillGradient = fillGradient;
}
if (hasStrokeGradient) {
strokeGradient = dirtyFlag
? getCanvasGradient(ctx, stroke as (LinearGradientObject | RadialGradientObject), rect)
: el.__canvasStrokeGradient;
el.__canvasStrokeGradient = strokeGradient;
}
if (hasFillPattern) {
// Pattern might be null if image not ready (even created from dataURI)
fillPattern = (dirtyFlag || !el.__canvasFillPattern)
? createCanvasPattern(ctx, fill as ImagePatternObject, el)
: el.__canvasFillPattern;
el.__canvasFillPattern = fillPattern;
}
if (hasStrokePattern) {
// Pattern might be null if image not ready (even created from dataURI)
strokePattern = (dirtyFlag || !el.__canvasStrokePattern)
? createCanvasPattern(ctx, stroke as ImagePatternObject, el)
: el.__canvasStrokePattern;
el.__canvasStrokePattern = fillPattern;
}
// Use the gradient or pattern
if (hasFillGradient) {
// PENDING If may have affect the state
ctx.fillStyle = fillGradient;
}
else if (hasFillPattern) {
if (fillPattern) { // createCanvasPattern may return false if image is not ready.
ctx.fillStyle = fillPattern;
}
else {
// Don't fill if image is not ready
hasFill = false;
}
}
if (hasStrokeGradient) {
ctx.strokeStyle = strokeGradient;
}
else if (hasStrokePattern) {
if (strokePattern) {
ctx.strokeStyle = strokePattern;
}
else {
// Don't stroke if image is not ready
hasStroke = false;
}
}
}
// Update path sx, sy
const scale = el.getGlobalScale();
path.setScale(scale[0], scale[1], el.segmentIgnoreThreshold);
let lineDash;
let lineDashOffset;
if (ctx.setLineDash && style.lineDash) {
[lineDash, lineDashOffset] = getLineDash(el);
}
let needsRebuild = true;
if (firstDraw || (dirtyFlag & SHAPE_CHANGED_BIT)) {
path.setDPR((ctx as any).dpr);
if (strokePart) {
// Use rebuildPath for percent stroke, so no context.
path.setContext(null);
}
else {
path.setContext(ctx);
needsRebuild = false;
}
path.reset();
el.buildPath(path, el.shape, inBatch);
path.toStatic();
// Clear path dirty flag
el.pathUpdated();
}
// Not support separate fill and stroke. For the compatibility of SVG
if (needsRebuild) {
path.rebuildPath(ctx, strokePart ? strokePercent : 1);
}
if (lineDash) {
ctx.setLineDash(lineDash);
ctx.lineDashOffset = lineDashOffset;
}
if (!inBatch) {
if (style.strokeFirst) {
if (hasStroke) {
doStrokePath(ctx, style);
}
if (hasFill) {
doFillPath(ctx, style);
}
}
else {
if (hasFill) {
doFillPath(ctx, style);
}
if (hasStroke) {
doStrokePath(ctx, style);
}
}
}
if (lineDash) {
// PENDING
// Remove lineDash
ctx.setLineDash([]);
}
}
// Draw Image Elements
function brushImage(ctx: CanvasRenderingContext2D, el: ZRImage, style: ImageStyleProps) {
const image = el.__image = createOrUpdateImage(
style.image,
el.__image,
el,
el.onload
);
if (!image || !isImageReady(image)) {
return;
}
const x = style.x || 0;
const y = style.y || 0;
let width = el.getWidth();
let height = el.getHeight();
const aspect = image.width / image.height;
if (width == null && height != null) {
// Keep image/height ratio
width = height * aspect;
}
else if (height == null && width != null) {
height = width / aspect;
}
else if (width == null && height == null) {
width = image.width;
height = image.height;
}
if (style.sWidth && style.sHeight) {
const sx = style.sx || 0;
const sy = style.sy || 0;
ctx.drawImage(
image,
sx, sy, style.sWidth, style.sHeight,
x, y, width, height
);
}
else if (style.sx && style.sy) {
const sx = style.sx;
const sy = style.sy;
const sWidth = width - sx;
const sHeight = height - sy;
ctx.drawImage(
image,
sx, sy, sWidth, sHeight,
x, y, width, height
);
}
else {
ctx.drawImage(image, x, y, width, height);
}
}
// Draw Text Elements
function brushText(ctx: CanvasRenderingContext2D, el: TSpan, style: TSpanStyleProps) {
let text = style.text;
// Convert to string
text != null && (text += '');
if (text) {
ctx.font = style.font || DEFAULT_FONT;
ctx.textAlign = style.textAlign;
ctx.textBaseline = style.textBaseline;
let lineDash;
let lineDashOffset;
if (ctx.setLineDash && style.lineDash) {
[lineDash, lineDashOffset] = getLineDash(el);
}
if (lineDash) {
ctx.setLineDash(lineDash);
ctx.lineDashOffset = lineDashOffset;
}
if (style.strokeFirst) {
if (styleHasStroke(style)) {
ctx.strokeText(text, style.x, style.y);
}
if (styleHasFill(style)) {
ctx.fillText(text, style.x, style.y);
}
}
else {
if (styleHasFill(style)) {
ctx.fillText(text, style.x, style.y);
}
if (styleHasStroke(style)) {
ctx.strokeText(text, style.x, style.y);
}
}
if (lineDash) {
// Remove lineDash
ctx.setLineDash([]);
}
}
}
const SHADOW_NUMBER_PROPS = ['shadowBlur', 'shadowOffsetX', 'shadowOffsetY'] as const;
const STROKE_PROPS = [
['lineCap', 'butt'], ['lineJoin', 'miter'], ['miterLimit', 10]
] as const;
type AllStyleOption = PathStyleProps | TSpanStyleProps | ImageStyleProps;
// type ShadowPropNames = typeof SHADOW_PROPS[number][0];
// type StrokePropNames = typeof STROKE_PROPS[number][0];
// type DrawPropNames = typeof DRAW_PROPS[number][0];
function bindCommonProps(
ctx: CanvasRenderingContext2D,
style: AllStyleOption,
prevStyle: AllStyleOption,
forceSetAll: boolean,
scope: BrushScope
): boolean {
let styleChanged = false;
if (!forceSetAll) {
prevStyle = prevStyle || {};
// Shared same style.
if (style === prevStyle) {
return false;
}
}
if (forceSetAll || style.opacity !== prevStyle.opacity) {
flushPathDrawn(ctx, scope);
styleChanged = true;
// Ensure opacity is between 0 ~ 1. Invalid opacity will lead to a failure set and use the leaked opacity from the previous.
const opacity = Math.max(Math.min(style.opacity, 1), 0);
ctx.globalAlpha = isNaN(opacity) ? DEFAULT_COMMON_STYLE.opacity : opacity;
}
if (forceSetAll || style.blend !== prevStyle.blend) {
if (!styleChanged) {
flushPathDrawn(ctx, scope);
styleChanged = true;
}
ctx.globalCompositeOperation = style.blend || DEFAULT_COMMON_STYLE.blend;
}
for (let i = 0; i < SHADOW_NUMBER_PROPS.length; i++) {
const propName = SHADOW_NUMBER_PROPS[i];
if (forceSetAll || style[propName] !== prevStyle[propName]) {
if (!styleChanged) {
flushPathDrawn(ctx, scope);
styleChanged = true;
}
// FIXME Invalid property value will cause style leak from previous element.
ctx[propName] = (ctx as ZRCanvasRenderingContext).dpr * (style[propName] || 0);
}
}
if (forceSetAll || style.shadowColor !== prevStyle.shadowColor) {
if (!styleChanged) {
flushPathDrawn(ctx, scope);
styleChanged = true;
}
ctx.shadowColor = style.shadowColor || DEFAULT_COMMON_STYLE.shadowColor;
}
return styleChanged;
}
function bindPathAndTextCommonStyle(
ctx: CanvasRenderingContext2D,
el: TSpan | Path,
prevEl: TSpan | Path,
forceSetAll: boolean,
scope: BrushScope
) {
const style = getStyle(el, scope.inHover);
const prevStyle = forceSetAll
? null
: (prevEl && getStyle(prevEl, scope.inHover) || {});
// Shared same style. prevStyle will be null if forceSetAll.
if (style === prevStyle) {
return false;
}
let styleChanged = bindCommonProps(ctx, style, prevStyle, forceSetAll, scope);
if (forceSetAll || style.fill !== prevStyle.fill) {
if (!styleChanged) {
// Flush before set
flushPathDrawn(ctx, scope);
styleChanged = true;
}
isValidStrokeFillStyle(style.fill) && (ctx.fillStyle = style.fill);
}
if (forceSetAll || style.stroke !== prevStyle.stroke) {
if (!styleChanged) {
flushPathDrawn(ctx, scope);
styleChanged = true;
}
isValidStrokeFillStyle(style.stroke) && (ctx.strokeStyle = style.stroke);
}
if (forceSetAll || style.opacity !== prevStyle.opacity) {
if (!styleChanged) {
flushPathDrawn(ctx, scope);
styleChanged = true;
}
ctx.globalAlpha = style.opacity == null ? 1 : style.opacity;
}
if (el.hasStroke()) {
const lineWidth = style.lineWidth;
const newLineWidth = lineWidth / (
(style.strokeNoScale && el.getLineScale) ? el.getLineScale() : 1
);
if (ctx.lineWidth !== newLineWidth) {
if (!styleChanged) {
flushPathDrawn(ctx, scope);
styleChanged = true;
}
ctx.lineWidth = newLineWidth;
}
}
for (let i = 0; i < STROKE_PROPS.length; i++) {
const prop = STROKE_PROPS[i];
const propName = prop[0];
if (forceSetAll || style[propName] !== prevStyle[propName]) {
if (!styleChanged) {
flushPathDrawn(ctx, scope);
styleChanged = true;
}
// FIXME Invalid property value will cause style leak from previous element.
(ctx as any)[propName] = style[propName] || prop[1];
}
}
return styleChanged;
}
function bindImageStyle(
ctx: CanvasRenderingContext2D,
el: ZRImage,
prevEl: ZRImage,
// forceSetAll must be true if prevEl is null
forceSetAll: boolean,
scope: BrushScope
) {
return bindCommonProps(
ctx,
getStyle(el, scope.inHover),
prevEl && getStyle(prevEl, scope.inHover),
forceSetAll,
scope
);
}
function setContextTransform(ctx: CanvasRenderingContext2D, el: Displayable) {
const m = el.transform;
const dpr = (ctx as ZRCanvasRenderingContext).dpr || 1;
if (m) {
ctx.setTransform(dpr * m[0], dpr * m[1], dpr * m[2], dpr * m[3], dpr * m[4], dpr * m[5]);
}
else {
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
}
}
function updateClipStatus(clipPaths: Path[], ctx: CanvasRenderingContext2D, scope: BrushScope) {
let allClipped = false;
for (let i = 0; i < clipPaths.length; i++) {
const clipPath = clipPaths[i];
// Ignore draw following elements if clipPath has zero area.
allClipped = allClipped || clipPath.isZeroArea();
setContextTransform(ctx, clipPath);
ctx.beginPath();
clipPath.buildPath(ctx, clipPath.shape);
ctx.clip();
}
scope.allClipped = allClipped;
}
function isTransformChanged(m0: MatrixArray, m1: MatrixArray): boolean {
if (m0 && m1) {
return m0[0] !== m1[0]
|| m0[1] !== m1[1]
|| m0[2] !== m1[2]
|| m0[3] !== m1[3]
|| m0[4] !== m1[4]
|| m0[5] !== m1[5];
}
else if (!m0 && !m1) { // All identity matrix.
return false;
}
return true;
}
const DRAW_TYPE_PATH = 1;
const DRAW_TYPE_IMAGE = 2;
const DRAW_TYPE_TEXT = 3;
const DRAW_TYPE_INCREMENTAL = 4;
export type BrushScope = {
inHover: boolean
// width / height of viewport
viewWidth: number
viewHeight: number
// Status for clipping
prevElClipPaths?: Path[]
prevEl?: Displayable
allClipped?: boolean // If the whole element can be clipped
// Status for batching
batchFill?: string
batchStroke?: string
lastDrawType?: number
}
// If path can be batched
function canPathBatch(style: PathStyleProps) {
const hasFill = styleHasFill(style);
const hasStroke = styleHasStroke(style);
return !(
// Line dash is dynamically set in brush function.
style.lineDash
// Can't batch if element is both set fill and stroke. Or both not set
|| !(+hasFill ^ +hasStroke)
// Can't batch if element is drawn with gradient or pattern.
|| (hasFill && typeof style.fill !== 'string')
|| (hasStroke && typeof style.stroke !== 'string')
// Can't batch if element only stroke part of line.
|| style.strokePercent < 1
// Has stroke or fill opacity
|| style.strokeOpacity < 1
|| style.fillOpacity < 1
);
}
function flushPathDrawn(ctx: CanvasRenderingContext2D, scope: BrushScope) {
// Force flush all after drawn last element
scope.batchFill && ctx.fill();
scope.batchStroke && ctx.stroke();
scope.batchFill = '';
scope.batchStroke = '';
}
function getStyle(el: Displayable, inHover?: boolean) {
return inHover ? (el.__hoverStyle || el.style) : el.style;
}
export function brushSingle(ctx: CanvasRenderingContext2D, el: Displayable) {
brush(ctx, el, { inHover: false, viewWidth: 0, viewHeight: 0 }, true);
}
// Brush different type of elements.
export function brush(
ctx: CanvasRenderingContext2D,
el: Displayable,
scope: BrushScope,
isLast: boolean
) {
const m = el.transform;
if (!el.shouldBePainted(scope.viewWidth, scope.viewHeight, false, false)) {
// Needs to mark el rendered.
// Or this element will always been rendered in progressive rendering.
// But other dirty bit should not be cleared, otherwise it cause the shape
// can not be updated in this case.
el.__dirty &= ~REDRAW_BIT;
el.__isRendered = false;
return;
}
// HANDLE CLIPPING
const clipPaths = el.__clipPaths;
const prevElClipPaths = scope.prevElClipPaths;
let forceSetTransform = false;
let forceSetStyle = false;
// Optimize when clipping on group with several elements
if (!prevElClipPaths || isClipPathChanged(clipPaths, prevElClipPaths)) {
// If has previous clipping state, restore from it
if (prevElClipPaths && prevElClipPaths.length) {
// Flush restore
flushPathDrawn(ctx, scope);
ctx.restore();
// Must set all style and transform because context changed by restore
forceSetStyle = forceSetTransform = true;
scope.prevElClipPaths = null;
scope.allClipped = false;
// Reset prevEl since context has been restored
scope.prevEl = null;
}
// New clipping state
if (clipPaths && clipPaths.length) {
// Flush before clip
flushPathDrawn(ctx, scope);
ctx.save();
updateClipStatus(clipPaths, ctx, scope);
// Must set transform because it's changed when clip.
forceSetTransform = true;
}
scope.prevElClipPaths = clipPaths;
}
// Not rendering elements if it's clipped by a zero area path.
// Or it may cause bug on some version of IE11 (like 11.0.9600.178**),
// where exception "unexpected call to method or property access"
// might be thrown when calling ctx.fill or ctx.stroke after a path
// whose area size is zero is drawn and ctx.clip() is called and
// shadowBlur is set. See #4572, #3112, #5777.
// (e.g.,
// ctx.moveTo(10, 10);
// ctx.lineTo(20, 10);
// ctx.closePath();
// ctx.clip();
// ctx.shadowBlur = 10;
// ...
// ctx.fill();
// )
if (scope.allClipped) {
el.__isRendered = false;
return;
}
// START BRUSH
el.beforeBrush && el.beforeBrush();
el.innerBeforeBrush();
const prevEl = scope.prevEl;
// TODO el type changed.
if (!prevEl) {
forceSetStyle = forceSetTransform = true;
}
let canBatchPath = el instanceof Path // Only path supports batch
&& el.autoBatch
&& canPathBatch(el.style);
if (forceSetTransform || isTransformChanged(m, prevEl.transform)) {
// Flush
flushPathDrawn(ctx, scope);
setContextTransform(ctx, el);
}
else if (!canBatchPath) {
// Flush
flushPathDrawn(ctx, scope);
}
const style = getStyle(el, scope.inHover);
if (el instanceof Path) {
// PENDING do we need to rebind all style if displayable type changed?
if (scope.lastDrawType !== DRAW_TYPE_PATH) {
forceSetStyle = true;
scope.lastDrawType = DRAW_TYPE_PATH;
}
bindPathAndTextCommonStyle(ctx, el as Path, prevEl as Path, forceSetStyle, scope);
// Begin path at start
if (!canBatchPath || (!scope.batchFill && !scope.batchStroke)) {
ctx.beginPath();
}
brushPath(ctx, el as Path, style, canBatchPath);
if (canBatchPath) {
scope.batchFill = style.fill as string || '';
scope.batchStroke = style.stroke as string || '';
}
}
else {
if (el instanceof TSpan) {
if (scope.lastDrawType !== DRAW_TYPE_TEXT) {
forceSetStyle = true;
scope.lastDrawType = DRAW_TYPE_TEXT;
}
bindPathAndTextCommonStyle(ctx, el as TSpan, prevEl as TSpan, forceSetStyle, scope);
brushText(ctx, el as TSpan, style);
}
else if (el instanceof ZRImage) {
if (scope.lastDrawType !== DRAW_TYPE_IMAGE) {
forceSetStyle = true;
scope.lastDrawType = DRAW_TYPE_IMAGE;
}
bindImageStyle(ctx, el as ZRImage, prevEl as ZRImage, forceSetStyle, scope);
brushImage(ctx, el as ZRImage, style);
}
// Assume it's a IncrementalDisplayable
else if ((el as IncrementalDisplayable).getTemporalDisplayables) {
if (scope.lastDrawType !== DRAW_TYPE_INCREMENTAL) {
forceSetStyle = true;
scope.lastDrawType = DRAW_TYPE_INCREMENTAL;
}
brushIncremental(ctx, el as IncrementalDisplayable, scope);
}
}
if (canBatchPath && isLast) {
flushPathDrawn(ctx, scope);
}
el.innerAfterBrush();
el.afterBrush && el.afterBrush();
scope.prevEl = el;
// Mark as painted.
el.__dirty = 0;
el.__isRendered = true;
}
function brushIncremental(
ctx: CanvasRenderingContext2D,
el: IncrementalDisplayable,
scope: BrushScope
) {
let displayables = el.getDisplayables();
let temporalDisplayables = el.getTemporalDisplayables();
// Provide an inner scope.
// Save current context and restore after brushed.
ctx.save();
let innerScope: BrushScope = {
prevElClipPaths: null,
prevEl: null,
allClipped: false,
viewWidth: scope.viewWidth,
viewHeight: scope.viewHeight,
inHover: scope.inHover
};
let i;
let len;
// Render persistant displayables.
for (i = el.getCursor(), len = displayables.length; i < len; i++) {
const displayable = displayables[i];
displayable.beforeBrush && displayable.beforeBrush();
displayable.innerBeforeBrush();
brush(ctx, displayable, innerScope, i === len - 1);
displayable.innerAfterBrush();
displayable.afterBrush && displayable.afterBrush();
innerScope.prevEl = displayable;
}
// Render temporary displayables.
for (let i = 0, len = temporalDisplayables.length; i < len; i++) {
const displayable = temporalDisplayables[i];
displayable.beforeBrush && displayable.beforeBrush();
displayable.innerBeforeBrush();
brush(ctx, displayable, innerScope, i === len - 1);
displayable.innerAfterBrush();
displayable.afterBrush && displayable.afterBrush();
innerScope.prevEl = displayable;
}
el.clearTemporalDisplayables();
el.notClear = true;
ctx.restore();
}

127
ems-frontend/node_modules/zrender/src/canvas/helper.ts generated vendored Normal file
View File

@@ -0,0 +1,127 @@
import { LinearGradientObject } from '../graphic/LinearGradient';
import { RadialGradientObject } from '../graphic/RadialGradient';
import { GradientObject } from '../graphic/Gradient';
import { RectLike } from '../core/BoundingRect';
import Path from '../graphic/Path';
function isSafeNum(num: number) {
// NaN、Infinity、undefined、'xx'
return isFinite(num);
}
export function createLinearGradient(
this: void,
ctx: CanvasRenderingContext2D,
obj: LinearGradientObject,
rect: RectLike
) {
let x = obj.x == null ? 0 : obj.x;
let x2 = obj.x2 == null ? 1 : obj.x2;
let y = obj.y == null ? 0 : obj.y;
let y2 = obj.y2 == null ? 0 : obj.y2;
if (!obj.global) {
x = x * rect.width + rect.x;
x2 = x2 * rect.width + rect.x;
y = y * rect.height + rect.y;
y2 = y2 * rect.height + rect.y;
}
// Fix NaN when rect is Infinity
x = isSafeNum(x) ? x : 0;
x2 = isSafeNum(x2) ? x2 : 1;
y = isSafeNum(y) ? y : 0;
y2 = isSafeNum(y2) ? y2 : 0;
const canvasGradient = ctx.createLinearGradient(x, y, x2, y2);
return canvasGradient;
}
export function createRadialGradient(
this: void,
ctx: CanvasRenderingContext2D,
obj: RadialGradientObject,
rect: RectLike
) {
const width = rect.width;
const height = rect.height;
const min = Math.min(width, height);
let x = obj.x == null ? 0.5 : obj.x;
let y = obj.y == null ? 0.5 : obj.y;
let r = obj.r == null ? 0.5 : obj.r;
if (!obj.global) {
x = x * width + rect.x;
y = y * height + rect.y;
r = r * min;
}
x = isSafeNum(x) ? x : 0.5;
y = isSafeNum(y) ? y : 0.5;
r = r >= 0 && isSafeNum(r) ? r : 0.5;
const canvasGradient = ctx.createRadialGradient(x, y, 0, x, y, r);
return canvasGradient;
}
export function getCanvasGradient(this: void, ctx: CanvasRenderingContext2D, obj: GradientObject, rect: RectLike) {
// TODO Cache?
const canvasGradient = obj.type === 'radial'
? createRadialGradient(ctx, obj as RadialGradientObject, rect)
: createLinearGradient(ctx, obj as LinearGradientObject, rect);
const colorStops = obj.colorStops;
for (let i = 0; i < colorStops.length; i++) {
canvasGradient.addColorStop(
colorStops[i].offset, colorStops[i].color
);
}
return canvasGradient;
}
export function isClipPathChanged(clipPaths: Path[], prevClipPaths: Path[]): boolean {
// displayable.__clipPaths can only be `null`/`undefined` or an non-empty array.
if (clipPaths === prevClipPaths || (!clipPaths && !prevClipPaths)) {
return false;
}
if (!clipPaths || !prevClipPaths || (clipPaths.length !== prevClipPaths.length)) {
return true;
}
for (let i = 0; i < clipPaths.length; i++) {
if (clipPaths[i] !== prevClipPaths[i]) {
return true;
}
}
return false;
}
function parseInt10(val: string) {
return parseInt(val, 10);
}
export function getSize(
root: HTMLElement,
whIdx: number,
opts: { width?: number | string, height?: number | string}
) {
const wh = ['width', 'height'][whIdx] as 'width' | 'height';
const cwh = ['clientWidth', 'clientHeight'][whIdx] as 'clientWidth' | 'clientHeight';
const plt = ['paddingLeft', 'paddingTop'][whIdx] as 'paddingLeft' | 'paddingTop';
const prb = ['paddingRight', 'paddingBottom'][whIdx] as 'paddingRight' | 'paddingBottom';
if (opts[wh] != null && opts[wh] !== 'auto') {
return parseFloat(opts[wh] as string);
}
// IE8 does not support getComputedStyle, but it use VML.
const stl = document.defaultView.getComputedStyle(root);
return (
(root[cwh] || parseInt10(stl[wh]) || parseInt10(root.style[wh]))
- (parseInt10(stl[plt]) || 0)
- (parseInt10(stl[prb]) || 0)
) | 0;
}