UNPKG

@antv/g-plugin-canvas-renderer

Version:

A G plugin of renderer implementation with Canvas2D API

1,199 lines (1,141 loc) 64.4 kB
/*! * @antv/g-plugin-canvas-renderer * @description A G plugin of renderer implementation with Canvas2D API * @version 2.2.19 * @date 2/27/2025, 8:28:07 AM * @author AntVis * @docs https://g.antv.antgroup.com/ */ 'use strict'; var _defineProperty = require('@babel/runtime/helpers/defineProperty'); var _objectSpread = require('@babel/runtime/helpers/objectSpread2'); var _classCallCheck = require('@babel/runtime/helpers/classCallCheck'); var _createClass = require('@babel/runtime/helpers/createClass'); var _callSuper = require('@babel/runtime/helpers/callSuper'); var _inherits = require('@babel/runtime/helpers/inherits'); var gLite = require('@antv/g-lite'); var _slicedToArray = require('@babel/runtime/helpers/slicedToArray'); var _toConsumableArray = require('@babel/runtime/helpers/toConsumableArray'); var _classPrivateFieldLooseBase = require('@babel/runtime/helpers/classPrivateFieldLooseBase'); var _classPrivateFieldLooseKey = require('@babel/runtime/helpers/classPrivateFieldLooseKey'); var glMatrix = require('gl-matrix'); var util = require('@antv/util'); var gPluginImageLoader = require('@antv/g-plugin-image-loader'); var _renderState = /*#__PURE__*/_classPrivateFieldLooseKey("renderState"); /** * support 2 modes in rendering: * * immediate * * delayed: render at the end of frame with dirty-rectangle */ var CanvasRendererPlugin = /*#__PURE__*/function () { /** * RBush used in dirty rectangle rendering */ function CanvasRendererPlugin(canvasRendererPluginOptions // private styleRendererFactory: Record<Shape, StyleRenderer>, ) { _classCallCheck(this, CanvasRendererPlugin); this.removedRBushNodeAABBs = []; this.renderQueue = []; Object.defineProperty(this, _renderState, { writable: true, value: { restoreStack: [], prevObject: null, currentContext: new Map() } }); this.clearFullScreenLastFrame = false; this.clearFullScreen = false; /** * view projection matrix */ this.vpMatrix = glMatrix.mat4.create(); this.dprMatrix = glMatrix.mat4.create(); this.tmpMat4 = glMatrix.mat4.create(); this.vec3a = glMatrix.vec3.create(); this.vec3b = glMatrix.vec3.create(); this.vec3c = glMatrix.vec3.create(); this.vec3d = glMatrix.vec3.create(); this.canvasRendererPluginOptions = canvasRendererPluginOptions; } return _createClass(CanvasRendererPlugin, [{ key: "apply", value: function apply(context, runtime) { var _this = this; this.context = context; var _this$context = this.context, config = _this$context.config, camera = _this$context.camera, renderingService = _this$context.renderingService, renderingContext = _this$context.renderingContext, rBushRoot = _this$context.rBushRoot, pathGeneratorFactory = _this$context.pathGeneratorFactory; var enableRenderingOptimization = config.renderer.getConfig().enableRenderingOptimization; config.renderer.getConfig().enableDirtyCheck = false; config.renderer.getConfig().enableDirtyRectangleRendering = false; this.rBush = rBushRoot; this.pathGeneratorFactory = pathGeneratorFactory; var contextService = context.contextService; var canvas = renderingContext.root.ownerDocument.defaultView; var handleUnmounted = function handleUnmounted(e) { var object = e.target; // remove r-bush node // @ts-ignore var rBushNode = object.rBushNode; if (rBushNode.aabb) { // save removed aabbs for dirty-rectangle rendering later _this.removedRBushNodeAABBs.push(rBushNode.aabb); } }; var handleCulled = function handleCulled(e) { var object = e.target; // @ts-ignore var rBushNode = object.rBushNode; if (rBushNode.aabb) { // save removed aabbs for dirty-rectangle rendering later _this.removedRBushNodeAABBs.push(rBushNode.aabb); } }; renderingService.hooks.init.tap(CanvasRendererPlugin.tag, function () { canvas.addEventListener(gLite.ElementEvent.UNMOUNTED, handleUnmounted); canvas.addEventListener(gLite.ElementEvent.CULLED, handleCulled); // clear fullscreen var dpr = contextService.getDPR(); var width = config.width, height = config.height; var context = contextService.getContext(); _this.clearRect(context, 0, 0, width * dpr, height * dpr, config.background); }); renderingService.hooks.destroy.tap(CanvasRendererPlugin.tag, function () { canvas.removeEventListener(gLite.ElementEvent.UNMOUNTED, handleUnmounted); canvas.removeEventListener(gLite.ElementEvent.CULLED, handleCulled); _this.renderQueue = []; _this.removedRBushNodeAABBs = []; _classPrivateFieldLooseBase(_this, _renderState)[_renderState] = { restoreStack: [], prevObject: null, currentContext: null }; }); renderingService.hooks.beginFrame.tap(CanvasRendererPlugin.tag, function () { var _canvas$context$rende; var context = contextService.getContext(); var dpr = contextService.getDPR(); var width = config.width, height = config.height; var _this$canvasRendererP = _this.canvasRendererPluginOptions, dirtyObjectNumThreshold = _this$canvasRendererP.dirtyObjectNumThreshold, dirtyObjectRatioThreshold = _this$canvasRendererP.dirtyObjectRatioThreshold; // some heuristic conditions such as 80% object changed var _renderingService$get = renderingService.getStats(), total = _renderingService$get.total, rendered = _renderingService$get.rendered; var ratio = rendered / total; _this.clearFullScreen = _this.clearFullScreenLastFrame || // @ts-ignore !((_canvas$context$rende = canvas.context.renderingPlugins[1]) !== null && _canvas$context$rende !== void 0 && _canvas$context$rende.isFirstTimeRenderingFinished) || renderingService.disableDirtyRectangleRendering() || rendered > dirtyObjectNumThreshold && ratio > dirtyObjectRatioThreshold; if (context) { if (typeof context.resetTransform === 'function') { context.resetTransform(); } else { context.setTransform(1, 0, 0, 1, 0, 0); } if (_this.clearFullScreen) { _this.clearRect(context, 0, 0, width * dpr, height * dpr, config.background); } } }); /** * render objects by z-index * * - The level of the child node will be affected by the level of the parent node */ var renderByZIndex = function renderByZIndex(object, context) { var stack = [object]; while (stack.length > 0) { var currentObject = stack.pop(); if (currentObject.isVisible() && !currentObject.isCulled()) { if (enableRenderingOptimization) { _this.renderDisplayObjectOptimized(currentObject, context, _this.context, _classPrivateFieldLooseBase(_this, _renderState)[_renderState], runtime); } else { _this.renderDisplayObject(currentObject, context, _this.context, _classPrivateFieldLooseBase(_this, _renderState)[_renderState], runtime); } } var objects = currentObject.sortable.sorted || currentObject.childNodes; // should account for z-index for (var i = objects.length - 1; i >= 0; i--) { stack.push(objects[i]); } } }; // render at the end of frame renderingService.hooks.endFrame.tap(CanvasRendererPlugin.tag, function () { // Skip rendering. if (renderingContext.root.childNodes.length === 0) { _this.clearFullScreenLastFrame = true; return; } enableRenderingOptimization = config.renderer.getConfig().enableRenderingOptimization; // init _classPrivateFieldLooseBase(_this, _renderState)[_renderState] = { restoreStack: [], prevObject: null, currentContext: _classPrivateFieldLooseBase(_this, _renderState)[_renderState].currentContext }; _classPrivateFieldLooseBase(_this, _renderState)[_renderState].currentContext.clear(); _this.clearFullScreenLastFrame = false; var context = contextService.getContext(); // clear & clip dirty rectangle var dpr = contextService.getDPR(); glMatrix.mat4.fromScaling(_this.dprMatrix, [dpr, dpr, 1]); glMatrix.mat4.multiply(_this.vpMatrix, _this.dprMatrix, camera.getOrthoMatrix()); if (_this.clearFullScreen) { // console.time('renderByZIndex'); if (enableRenderingOptimization) { context.save(); renderByZIndex(renderingContext.root, context); context.restore(); } else { renderByZIndex(renderingContext.root, context); } // console.timeEnd('renderByZIndex'); _this.removedRBushNodeAABBs = []; } else { // console.log('canvas renderer next...', this.renderQueue); // merge removed AABB var dirtyRenderBounds = _this.safeMergeAABB.apply(_this, [_this.mergeDirtyAABBs(_this.renderQueue)].concat(_toConsumableArray(_this.removedRBushNodeAABBs.map(function (_ref) { var minX = _ref.minX, minY = _ref.minY, maxX = _ref.maxX, maxY = _ref.maxY; var aabb = new gLite.AABB(); aabb.setMinMax( // vec3.fromValues(minX, minY, 0), // vec3.fromValues(maxX, maxY, 0), [minX, minY, 0], [maxX, maxY, 0]); return aabb; })))); _this.removedRBushNodeAABBs = []; if (gLite.AABB.isEmpty(dirtyRenderBounds)) { _this.renderQueue = []; return; } var dirtyRect = _this.convertAABB2Rect(dirtyRenderBounds); var x = dirtyRect.x, y = dirtyRect.y, width = dirtyRect.width, height = dirtyRect.height; var tl = glMatrix.vec3.transformMat4(_this.vec3a, [x, y, 0], _this.vpMatrix); var tr = glMatrix.vec3.transformMat4(_this.vec3b, [x + width, y, 0], _this.vpMatrix); var bl = glMatrix.vec3.transformMat4(_this.vec3c, [x, y + height, 0], _this.vpMatrix); var br = glMatrix.vec3.transformMat4(_this.vec3d, [x + width, y + height, 0], _this.vpMatrix); var minx = Math.min(tl[0], tr[0], br[0], bl[0]); var miny = Math.min(tl[1], tr[1], br[1], bl[1]); var maxx = Math.max(tl[0], tr[0], br[0], bl[0]); var maxy = Math.max(tl[1], tr[1], br[1], bl[1]); var ix = Math.floor(minx); var iy = Math.floor(miny); var iwidth = Math.ceil(maxx - minx); var iheight = Math.ceil(maxy - miny); context.save(); _this.clearRect(context, ix, iy, iwidth, iheight, config.background); context.beginPath(); context.rect(ix, iy, iwidth, iheight); context.clip(); // @see https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Transformations context.setTransform(_this.vpMatrix[0], _this.vpMatrix[1], _this.vpMatrix[4], _this.vpMatrix[5], _this.vpMatrix[12], _this.vpMatrix[13]); // draw dirty rectangle var _config$renderer$getC = config.renderer.getConfig(), enableDirtyRectangleRenderingDebug = _config$renderer$getC.enableDirtyRectangleRenderingDebug; if (enableDirtyRectangleRenderingDebug) { canvas.dispatchEvent(new gLite.CustomEvent(gLite.CanvasEvent.DIRTY_RECTANGLE, { dirtyRect: { x: ix, y: iy, width: iwidth, height: iheight } })); } // search objects intersect with dirty rectangle var dirtyObjects = _this.searchDirtyObjects(dirtyRenderBounds); // do rendering dirtyObjects // sort by z-index .sort(function (a, b) { return a.sortable.renderOrder - b.sortable.renderOrder; }).forEach(function (object) { // culled object should not be rendered if (object && object.isVisible() && !object.isCulled()) { _this.renderDisplayObject(object, context, _this.context, _classPrivateFieldLooseBase(_this, _renderState)[_renderState], runtime); } }); context.restore(); // save dirty AABBs in last frame _this.renderQueue.forEach(function (object) { _this.saveDirtyAABB(object); }); // clear queue _this.renderQueue = []; } // pop restore stack, eg. root -> parent -> child _classPrivateFieldLooseBase(_this, _renderState)[_renderState].restoreStack.forEach(function () { context.restore(); }); // clear restore stack _classPrivateFieldLooseBase(_this, _renderState)[_renderState].restoreStack = []; }); renderingService.hooks.render.tap(CanvasRendererPlugin.tag, function (object) { if (!_this.clearFullScreen) { // render at the end of frame _this.renderQueue.push(object); } }); } }, { key: "clearRect", value: function clearRect(context, x, y, width, height, background) { // clearRect is faster than fillRect @see https://stackoverflow.com/a/30830253 context.clearRect(x, y, width, height); if (background) { context.fillStyle = background; context.fillRect(x, y, width, height); } } }, { key: "renderDisplayObjectOptimized", value: function renderDisplayObjectOptimized(object, context, canvasContext, renderState, runtime) { var nodeName = object.nodeName; var updateTransform = false; var clipDraw = false; // @ts-ignore var styleRenderer = this.context.styleRendererFactory[nodeName]; var generatePath = this.pathGeneratorFactory[nodeName]; // clip path var clipPath = object.parsedStyle.clipPath; if (clipPath) { updateTransform = !renderState.prevObject || !glMatrix.mat4.exactEquals(clipPath.getWorldTransform(), renderState.prevObject.getWorldTransform()); if (updateTransform) { this.applyWorldTransform(context, clipPath); renderState.prevObject = null; } // generate path in local space var _generatePath = this.pathGeneratorFactory[clipPath.nodeName]; if (_generatePath) { context.save(); clipDraw = true; context.beginPath(); _generatePath(context, clipPath.parsedStyle); context.closePath(); context.clip(); } } // fill & stroke if (styleRenderer) { updateTransform = !renderState.prevObject || !glMatrix.mat4.exactEquals(object.getWorldTransform(), renderState.prevObject.getWorldTransform()); if (updateTransform) { this.applyWorldTransform(context, object); } var forceUpdateStyle = !renderState.prevObject; if (!forceUpdateStyle) { var prevNodeName = renderState.prevObject.nodeName; if (nodeName === gLite.Shape.TEXT) { forceUpdateStyle = prevNodeName !== gLite.Shape.TEXT; } else if (nodeName === gLite.Shape.IMAGE) { forceUpdateStyle = prevNodeName !== gLite.Shape.IMAGE; } else { forceUpdateStyle = prevNodeName === gLite.Shape.TEXT || prevNodeName === gLite.Shape.IMAGE; } } styleRenderer.applyStyleToContext(context, object, forceUpdateStyle, renderState); renderState.prevObject = object; } if (generatePath) { context.beginPath(); generatePath(context, object.parsedStyle); if (nodeName !== gLite.Shape.LINE && nodeName !== gLite.Shape.PATH && nodeName !== gLite.Shape.POLYLINE) { context.closePath(); } } // fill & stroke if (styleRenderer) { styleRenderer.drawToContext(context, object, _classPrivateFieldLooseBase(this, _renderState)[_renderState], this, runtime); } if (clipDraw) { context.restore(); } // finish rendering, clear dirty flag object.renderable.dirty = false; } }, { key: "renderDisplayObject", value: function renderDisplayObject(object, context, canvasContext, renderState, runtime) { var nodeName = object.nodeName; // restore to its ancestor var parent = renderState.restoreStack[renderState.restoreStack.length - 1]; if (parent && !(object.compareDocumentPosition(parent) & gLite.Node.DOCUMENT_POSITION_CONTAINS)) { context.restore(); renderState.restoreStack.pop(); } // @ts-ignore var styleRenderer = this.context.styleRendererFactory[nodeName]; var generatePath = this.pathGeneratorFactory[nodeName]; // clip path var clipPath = object.parsedStyle.clipPath; if (clipPath) { this.applyWorldTransform(context, clipPath); // generate path in local space var _generatePath2 = this.pathGeneratorFactory[clipPath.nodeName]; if (_generatePath2) { context.save(); // save clip renderState.restoreStack.push(object); context.beginPath(); _generatePath2(context, clipPath.parsedStyle); context.closePath(); context.clip(); } } // fill & stroke if (styleRenderer) { this.applyWorldTransform(context, object); context.save(); // apply attributes to context this.applyAttributesToContext(context, object); } if (generatePath) { context.beginPath(); generatePath(context, object.parsedStyle); if (nodeName !== gLite.Shape.LINE && nodeName !== gLite.Shape.PATH && nodeName !== gLite.Shape.POLYLINE) { context.closePath(); } } // fill & stroke if (styleRenderer) { styleRenderer.render(context, object.parsedStyle, object, canvasContext, this, runtime); // restore applied attributes, eg. shadowBlur shadowColor... context.restore(); } // finish rendering, clear dirty flag object.renderable.dirty = false; } }, { key: "applyAttributesToContext", value: function applyAttributesToContext(context, object) { var _ref2 = object.parsedStyle, stroke = _ref2.stroke, fill = _ref2.fill, opacity = _ref2.opacity, lineDash = _ref2.lineDash, lineDashOffset = _ref2.lineDashOffset; // @see https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/setLineDash if (lineDash) { context.setLineDash(lineDash); } // @see https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/lineDashOffset if (!util.isNil(lineDashOffset)) { context.lineDashOffset = lineDashOffset; } if (!util.isNil(opacity)) { context.globalAlpha *= opacity; } if (!util.isNil(stroke) && !Array.isArray(stroke) && !stroke.isNone) { context.strokeStyle = object.attributes.stroke; } if (!util.isNil(fill) && !Array.isArray(fill) && !fill.isNone) { context.fillStyle = object.attributes.fill; } } }, { key: "convertAABB2Rect", value: function convertAABB2Rect(aabb) { var min = aabb.getMin(); var max = aabb.getMax(); // expand the rectangle a bit to avoid artifacts // @see https://www.yuque.com/antv/ou292n/bi8nix#ExvCu var minX = Math.floor(min[0]); var minY = Math.floor(min[1]); var maxX = Math.ceil(max[0]); var maxY = Math.ceil(max[1]); var width = maxX - minX; var height = maxY - minY; return { x: minX, y: minY, width: width, height: height }; } /** * TODO: merge dirty rectangles with some strategies. * For now, we just simply merge all the rectangles into one. * @see https://idom.me/articles/841.html */ }, { key: "mergeDirtyAABBs", value: function mergeDirtyAABBs(dirtyObjects) { // merge into a big AABB // TODO: skip descendant if ancestor is caculated, but compareNodePosition is really slow var aabb = new gLite.AABB(); dirtyObjects.forEach(function (object) { var renderBounds = object.getRenderBounds(); aabb.add(renderBounds); var dirtyRenderBounds = object.renderable.dirtyRenderBounds; if (dirtyRenderBounds) { aabb.add(dirtyRenderBounds); } }); return aabb; } }, { key: "searchDirtyObjects", value: function searchDirtyObjects(dirtyRectangle) { // search in r-tree, get all affected nodes var _dirtyRectangle$getMi = dirtyRectangle.getMin(), _dirtyRectangle$getMi2 = _slicedToArray(_dirtyRectangle$getMi, 2), minX = _dirtyRectangle$getMi2[0], minY = _dirtyRectangle$getMi2[1]; var _dirtyRectangle$getMa = dirtyRectangle.getMax(), _dirtyRectangle$getMa2 = _slicedToArray(_dirtyRectangle$getMa, 2), maxX = _dirtyRectangle$getMa2[0], maxY = _dirtyRectangle$getMa2[1]; var rBushNodes = this.rBush.search({ minX: minX, minY: minY, maxX: maxX, maxY: maxY }); return rBushNodes.map(function (_ref3) { var displayObject = _ref3.displayObject; return displayObject; }); } }, { key: "saveDirtyAABB", value: function saveDirtyAABB(object) { var renderable = object.renderable; if (!renderable.dirtyRenderBounds) { renderable.dirtyRenderBounds = new gLite.AABB(); } var renderBounds = object.getRenderBounds(); if (renderBounds) { // save last dirty aabb renderable.dirtyRenderBounds.update(renderBounds.center, renderBounds.halfExtents); } } }, { key: "applyWorldTransform", value: function applyWorldTransform(context, object, matrix) { // apply clip shape's RTS if (matrix) { glMatrix.mat4.copy(this.tmpMat4, object.getLocalTransform()); glMatrix.mat4.multiply(this.tmpMat4, matrix, this.tmpMat4); glMatrix.mat4.multiply(this.tmpMat4, this.vpMatrix, this.tmpMat4); } else { // apply RTS transformation in world space glMatrix.mat4.copy(this.tmpMat4, object.getWorldTransform()); glMatrix.mat4.multiply(this.tmpMat4, this.vpMatrix, this.tmpMat4); } // @see https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Transformations context.setTransform(this.tmpMat4[0], this.tmpMat4[1], this.tmpMat4[4], this.tmpMat4[5], this.tmpMat4[12], this.tmpMat4[13]); } }, { key: "safeMergeAABB", value: function safeMergeAABB() { var merged = new gLite.AABB(); for (var _len = arguments.length, aabbs = new Array(_len), _key = 0; _key < _len; _key++) { aabbs[_key] = arguments[_key]; } aabbs.forEach(function (aabb) { merged.add(aabb); }); return merged; } }]); }(); CanvasRendererPlugin.tag = 'CanvasRenderer'; function getPattern(pattern, object, context, canvasContext, plugin, runtime, imagePool) { var $offscreenCanvas; var dpr; if (pattern.image.nodeName === 'rect') { var _parsedStyle = pattern.image.parsedStyle, width = _parsedStyle.width, height = _parsedStyle.height; dpr = canvasContext.contextService.getDPR(); var offscreenCanvas = canvasContext.config.offscreenCanvas; $offscreenCanvas = runtime.offscreenCanvasCreator.getOrCreateCanvas(offscreenCanvas); $offscreenCanvas.width = width * dpr; $offscreenCanvas.height = height * dpr; var offscreenCanvasContext = runtime.offscreenCanvasCreator.getOrCreateContext(offscreenCanvas); var renderState = { restoreStack: [], prevObject: null, currentContext: new Map() }; // offscreenCanvasContext.scale(1 / dpr, 1 / dpr); pattern.image.forEach(function (object) { plugin.renderDisplayObject(object, offscreenCanvasContext, canvasContext, renderState, runtime); }); renderState.restoreStack.forEach(function () { offscreenCanvasContext.restore(); }); } var canvasPattern = imagePool.getOrCreatePatternSync(object, pattern, context, $offscreenCanvas, dpr, object.getGeometryBounds().min, function () { // set dirty rectangle flag object.renderable.dirty = true; canvasContext.renderingService.dirtify(); }); return canvasPattern; } function getColor(parsedColor, object, context, imagePool) { var color; if (parsedColor.type === gLite.GradientType.LinearGradient || parsedColor.type === gLite.GradientType.RadialGradient) { var bounds = object.getGeometryBounds(); var width = bounds && bounds.halfExtents[0] * 2 || 1; var height = bounds && bounds.halfExtents[1] * 2 || 1; var min = bounds && bounds.min || [0, 0]; color = imagePool.getOrCreateGradient(_objectSpread(_objectSpread({ type: parsedColor.type }, parsedColor.value), {}, { min: min, width: width, height: height }), context); } return color; } var SHADOW_NUMBER_STYLE = ['shadowBlur', 'shadowOffsetX', 'shadowOffsetY']; var STROKE_STYLE = ['lineCap', 'lineJoin', 'miterLimit']; var DEFAULT_STYLE = { // common globalAlpha: 1, shadowBlur: 0, shadowOffsetX: 0, shadowOffsetY: 0, shadowColor: '#000', filter: 'none', globalCompositeOperation: 'source-over', // stroke/fill strokeStyle: '#000', strokeOpacity: 1, lineWidth: 1, lineDash: [], lineDashOffset: 0, lineCap: 'butt', lineJoin: 'miter', miterLimit: 10, fillStyle: '#000', fillOpacity: 1 // image }; var defaultParsedStyle = {}; /** * Updating the canvas context is an expensive operation. The state of the context is cached and the actual update operation is performed only when the cache is not hit. * * In any case, the previous value is returned, which is convenient for temporarily updating the context and restoring it later. */ function updateContextIfNotHitCache(context, key, value, cache) { var prevValue = cache.has(key) ? cache.get(key) : DEFAULT_STYLE[key]; if (prevValue !== value) { // console.log('not hit cache', key, value, prevValue, cache); if (key === 'lineDash') { context.setLineDash(value); } else { // @ts-ignore context[key] = value; } cache.set(key, value); } return prevValue; } var OptimizedDefaultRenderer = /*#__PURE__*/function () { function OptimizedDefaultRenderer(imagePool) { _classCallCheck(this, OptimizedDefaultRenderer); this.imagePool = imagePool; } return _createClass(OptimizedDefaultRenderer, [{ key: "applyAttributesToContext", value: function applyAttributesToContext(context, object) {} }, { key: "render", value: function render(context, parsedStyle, object, canvasContext, plugin, runtime) {} // #region common style }, { key: "applyCommonStyleToContext", value: function applyCommonStyleToContext(context, object, forceUpdate, renderState) { // const dpr = object.ownerDocument.defaultView.getContextService().getDPR(); var prevStyle = forceUpdate ? defaultParsedStyle : renderState.prevObject.parsedStyle; var style = object.parsedStyle; if (forceUpdate || style.opacity !== prevStyle.opacity) { updateContextIfNotHitCache(context, 'globalAlpha', !util.isNil(style.opacity) ? style.opacity : DEFAULT_STYLE.globalAlpha, renderState.currentContext); } // TODO blend prop // @ts-ignore if (forceUpdate || style.blend !== prevStyle.blend) { updateContextIfNotHitCache(context, 'globalCompositeOperation', // @ts-ignore !util.isNil(style.blend) ? // @ts-ignore style.blend : DEFAULT_STYLE.globalCompositeOperation, renderState.currentContext); } } // #endregion common style // #region stroke/fill style }, { key: "applyStrokeFillStyleToContext", value: function applyStrokeFillStyleToContext(context, object, forceUpdate, renderState) { var prevStyle = forceUpdate ? defaultParsedStyle : renderState.prevObject.parsedStyle; var style = object.parsedStyle; var _style$lineWidth = style.lineWidth, lineWidth = _style$lineWidth === void 0 ? DEFAULT_STYLE.lineWidth : _style$lineWidth; var hasFill = style.fill && !style.fill.isNone; var hasStroke = style.stroke && !style.stroke.isNone && lineWidth > 0; if (hasStroke) { if (forceUpdate || object.attributes.stroke !== renderState.prevObject.attributes.stroke) { var value = !util.isNil(style.stroke) && !Array.isArray(style.stroke) && !style.stroke.isNone ? object.attributes.stroke : DEFAULT_STYLE.strokeStyle; updateContextIfNotHitCache(context, 'strokeStyle', value, renderState.currentContext); } if (forceUpdate || style.lineWidth !== prevStyle.lineWidth) { updateContextIfNotHitCache(context, 'lineWidth', !util.isNil(style.lineWidth) ? style.lineWidth : DEFAULT_STYLE.lineWidth, renderState.currentContext); } if (forceUpdate || style.lineDash !== prevStyle.lineDash) { updateContextIfNotHitCache(context, 'lineDash', style.lineDash || DEFAULT_STYLE.lineDash, renderState.currentContext); } if (forceUpdate || style.lineDashOffset !== prevStyle.lineDashOffset) { updateContextIfNotHitCache(context, 'lineDashOffset', !util.isNil(style.lineDashOffset) ? style.lineDashOffset : DEFAULT_STYLE.lineDashOffset, renderState.currentContext); } for (var i = 0; i < STROKE_STYLE.length; i++) { var styleName = STROKE_STYLE[i]; if (forceUpdate || style[styleName] !== prevStyle[styleName]) { updateContextIfNotHitCache(context, styleName, !util.isNil(style[styleName]) ? style[styleName] : DEFAULT_STYLE[styleName], renderState.currentContext); } } } if (hasFill && (forceUpdate || object.attributes.fill !== renderState.prevObject.attributes.fill)) { var _value = !util.isNil(style.fill) && !Array.isArray(style.fill) && !style.fill.isNone ? object.attributes.fill : DEFAULT_STYLE.fillStyle; updateContextIfNotHitCache(context, 'fillStyle', _value, renderState.currentContext); } } // #endregion stroke/fill style }, { key: "applyStyleToContext", value: function applyStyleToContext(context, object, forceUpdate, renderState) { var nodeName = object.nodeName; this.applyCommonStyleToContext(context, object, forceUpdate, renderState); if (nodeName === gLite.Shape.IMAGE) ; else { this.applyStrokeFillStyleToContext(context, object, forceUpdate, renderState); } } }, { key: "applyShadowAndFilterStyleToContext", value: function applyShadowAndFilterStyleToContext(context, object, hasShadow, renderState) { var style = object.parsedStyle; if (hasShadow) { updateContextIfNotHitCache(context, 'shadowColor', style.shadowColor.toString(), renderState.currentContext); for (var i = 0; i < SHADOW_NUMBER_STYLE.length; i++) { var styleName = SHADOW_NUMBER_STYLE[i]; updateContextIfNotHitCache(context, styleName, style[styleName] || DEFAULT_STYLE[styleName], renderState.currentContext); } } if (style.filter && style.filter.length) { updateContextIfNotHitCache(context, 'filter', // use raw filter string object.attributes.filter, renderState.currentContext); } } }, { key: "clearShadowAndFilterStyleForContext", value: function clearShadowAndFilterStyleForContext(context, hasShadow, hasFilter, renderState) { var onlyClearShadowFilter = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false; if (hasShadow) { updateContextIfNotHitCache(context, 'shadowColor', DEFAULT_STYLE.shadowColor, renderState.currentContext); for (var i = 0; i < SHADOW_NUMBER_STYLE.length; i++) { var styleName = SHADOW_NUMBER_STYLE[i]; updateContextIfNotHitCache(context, styleName, DEFAULT_STYLE[styleName], renderState.currentContext); } } if (hasFilter) { if (hasShadow && onlyClearShadowFilter) { // save drop-shadow filter var oldFilter = context.filter; if (!util.isNil(oldFilter) && oldFilter.indexOf('drop-shadow') > -1) { updateContextIfNotHitCache(context, 'filter', oldFilter.replace(/drop-shadow\([^)]*\)/, '').trim() || DEFAULT_STYLE.filter, renderState.currentContext); } } else { updateContextIfNotHitCache(context, 'filter', DEFAULT_STYLE.filter, renderState.currentContext); } } } }, { key: "fillToContext", value: function fillToContext(context, object, renderState, plugin, runtime) { var _this = this; var _object$parsedStyle = object.parsedStyle, fill = _object$parsedStyle.fill, fillRule = _object$parsedStyle.fillRule; var resetStyle = null; if (Array.isArray(fill) && fill.length > 0) { fill.forEach(function (gradient) { var prevStyle = updateContextIfNotHitCache(context, 'fillStyle', getColor(gradient, object, context, _this.imagePool), renderState.currentContext); resetStyle = resetStyle !== null && resetStyle !== void 0 ? resetStyle : prevStyle; if (fillRule) { context.fill(fillRule); } else { context.fill(); } }); } else { if (gLite.isPattern(fill)) { var pattern = getPattern(fill, object, context, object.ownerDocument.defaultView.context, plugin, runtime, this.imagePool); if (pattern) { context.fillStyle = pattern; resetStyle = true; } } if (fillRule) { context.fill(fillRule); } else { context.fill(); } } if (resetStyle !== null) { updateContextIfNotHitCache(context, 'fillStyle', resetStyle, renderState.currentContext); } } }, { key: "strokeToContext", value: function strokeToContext(context, object, renderState, plugin, runtime) { var _this2 = this; var stroke = object.parsedStyle.stroke; var resetStyle = null; if (Array.isArray(stroke) && stroke.length > 0) { stroke.forEach(function (gradient) { var prevStyle = updateContextIfNotHitCache(context, 'strokeStyle', getColor(gradient, object, context, _this2.imagePool), renderState.currentContext); resetStyle = resetStyle !== null && resetStyle !== void 0 ? resetStyle : prevStyle; context.stroke(); }); } else { if (gLite.isPattern(stroke)) { var pattern = getPattern(stroke, object, context, object.ownerDocument.defaultView.context, plugin, runtime, this.imagePool); if (pattern) { var prevStyle = updateContextIfNotHitCache(context, 'strokeStyle', pattern, renderState.currentContext); resetStyle = resetStyle !== null && resetStyle !== void 0 ? resetStyle : prevStyle; } } context.stroke(); } if (resetStyle !== null) { updateContextIfNotHitCache(context, 'strokeStyle', resetStyle, renderState.currentContext); } } }, { key: "drawToContext", value: function drawToContext(context, object, renderState, plugin, runtime) { var _style$fill; var nodeName = object.nodeName; var style = object.parsedStyle; var _style$opacity = style.opacity, opacity = _style$opacity === void 0 ? DEFAULT_STYLE.globalAlpha : _style$opacity, _style$fillOpacity = style.fillOpacity, fillOpacity = _style$fillOpacity === void 0 ? DEFAULT_STYLE.fillOpacity : _style$fillOpacity, _style$strokeOpacity = style.strokeOpacity, strokeOpacity = _style$strokeOpacity === void 0 ? DEFAULT_STYLE.strokeOpacity : _style$strokeOpacity, _style$lineWidth2 = style.lineWidth, lineWidth = _style$lineWidth2 === void 0 ? DEFAULT_STYLE.lineWidth : _style$lineWidth2; var hasFill = style.fill && !style.fill.isNone; var hasStroke = style.stroke && !style.stroke.isNone && lineWidth > 0; if (!hasFill && !hasStroke) { return; } var hasShadow = !util.isNil(style.shadowColor) && style.shadowBlur > 0; var isInnerShadow = style.shadowType === 'inner'; var isFillTransparent = ((_style$fill = style.fill) === null || _style$fill === void 0 ? void 0 : _style$fill.alpha) === 0; var hasFilter = !!(style.filter && style.filter.length); // Shadows can only be applied to fill() or stroke(), the default is fill() var shouldDrawShadowWithStroke = hasShadow && hasStroke && (nodeName === gLite.Shape.PATH || nodeName === gLite.Shape.LINE || nodeName === gLite.Shape.POLYLINE || isFillTransparent || isInnerShadow); // TODO https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/paint-order var originGlobalAlpha = null; if (hasFill) { if (!shouldDrawShadowWithStroke) { this.applyShadowAndFilterStyleToContext(context, object, hasShadow, renderState); } var updateOpacity = opacity * fillOpacity; originGlobalAlpha = updateContextIfNotHitCache(context, 'globalAlpha', updateOpacity, renderState.currentContext); this.fillToContext(context, object, renderState, plugin, runtime); if (!shouldDrawShadowWithStroke) { this.clearShadowAndFilterStyleForContext(context, hasShadow, hasFilter, renderState); } } if (hasStroke) { var clearShadowAndFilter = false; var _updateOpacity = opacity * strokeOpacity; var prevOpacity = updateContextIfNotHitCache(context, 'globalAlpha', _updateOpacity, renderState.currentContext); originGlobalAlpha = hasFill ? originGlobalAlpha : prevOpacity; if (shouldDrawShadowWithStroke) { this.applyShadowAndFilterStyleToContext(context, object, hasShadow, renderState); clearShadowAndFilter = true; if (isInnerShadow) { var originBlend = context.globalCompositeOperation; context.globalCompositeOperation = 'source-atop'; this.strokeToContext(context, object, renderState, plugin, runtime); context.globalCompositeOperation = originBlend; this.clearShadowAndFilterStyleForContext(context, hasShadow, hasFilter, renderState, true); } } this.strokeToContext(context, object, renderState, plugin, runtime); if (clearShadowAndFilter) { this.clearShadowAndFilterStyleForContext(context, hasShadow, hasFilter, renderState); } } // clear if (originGlobalAlpha !== null) { updateContextIfNotHitCache(context, 'globalAlpha', originGlobalAlpha, renderState.currentContext); } } }]); }(); var DefaultRenderer = /*#__PURE__*/function (_OptimizedDefaultRend) { function DefaultRenderer() { _classCallCheck(this, DefaultRenderer); return _callSuper(this, DefaultRenderer, arguments); } _inherits(DefaultRenderer, _OptimizedDefaultRend); return _createClass(DefaultRenderer, [{ key: "render", value: function render(context, parsedStyle, object, canvasContext, plugin, runtime) { var fill = parsedStyle.fill, fillRule = parsedStyle.fillRule, _parsedStyle$opacity = parsedStyle.opacity, opacity = _parsedStyle$opacity === void 0 ? 1 : _parsedStyle$opacity, _parsedStyle$fillOpac = parsedStyle.fillOpacity, fillOpacity = _parsedStyle$fillOpac === void 0 ? 1 : _parsedStyle$fillOpac, stroke = parsedStyle.stroke, _parsedStyle$strokeOp = parsedStyle.strokeOpacity, strokeOpacity = _parsedStyle$strokeOp === void 0 ? 1 : _parsedStyle$strokeOp, _parsedStyle$lineWidt = parsedStyle.lineWidth, lineWidth = _parsedStyle$lineWidt === void 0 ? 1 : _parsedStyle$lineWidt, lineCap = parsedStyle.lineCap, lineJoin = parsedStyle.lineJoin, shadowType = parsedStyle.shadowType, shadowColor = parsedStyle.shadowColor, shadowBlur = parsedStyle.shadowBlur, filter = parsedStyle.filter, miterLimit = parsedStyle.miterLimit; var hasFill = fill && !fill.isNone; var hasStroke = stroke && !stroke.isNone && lineWidth > 0; var isFillTransparent = (fill === null || fill === void 0 ? void 0 : fill.alpha) === 0; var hasFilter = !!(filter && filter.length); var hasShadow = !util.isNil(shadowColor) && shadowBlur > 0; var nodeName = object.nodeName; var isInnerShadow = shadowType === 'inner'; var shouldDrawShadowWithStroke = hasStroke && hasShadow && (nodeName === gLite.Shape.PATH || nodeName === gLite.Shape.LINE || nodeName === gLite.Shape.POLYLINE || isFillTransparent || isInnerShadow); if (hasFill) { context.globalAlpha = opacity * fillOpacity; if (!shouldDrawShadowWithStroke) { setShadowAndFilter(object, context, hasShadow); } applyFill(context, object, fill, fillRule, canvasContext, plugin, runtime, this.imagePool); if (!shouldDrawShadowWithStroke) { this.clearShadowAndFilter(context, hasFilter, hasShadow); } } if (hasStroke) { context.globalAlpha = opacity * strokeOpacity; context.lineWidth = lineWidth; if (!util.isNil(miterLimit)) { context.miterLimit = miterLimit; } if (!util.isNil(lineCap)) { context.lineCap = lineCap; } if (!util.isNil(lineJoin)) { context.lineJoin = lineJoin; } if (shouldDrawShadowWithStroke) { if (isInnerShadow) { context.globalCompositeOperation = 'source-atop'; } setShadowAndFilter(object, context, true); if (isInnerShadow) { applyStroke(context, object, stroke, canvasContext, plugin, runtime, this.imagePool); context.globalCompositeOperation = DEFAULT_STYLE.globalCompositeOperation; this.clearShadowAndFilter(context, hasFilter, true); } } applyStroke(context, object, stroke, canvasContext, plugin, runtime, this.imagePool); } } }, { key: "clearShadowAndFilter", value: function clearShadowAndFilter(context, hasFilter, hasShadow) { if (hasShadow) { context.shadowColor = 'transparent'; context.shadowBlur = 0; } if (hasFilter) { // save drop-shadow filter var oldFilter = context.filter; if (!util.isNil(oldFilter) && oldFilter.indexOf('drop-shadow') > -1) { context.filter = oldFilter.replace(/drop-shadow\([^)]*\)/, '').trim() || 'none'; } } } }]); }(OptimizedDefaultRenderer); /** * apply before fill and stroke but only once */ function setShadowAndFilter(object, context, hasShadow) { var _object$parsedStyle = object.parsedStyle, filter = _object$parsedStyle.filter, shadowColor = _object$parsedStyle.shadowColor, shadowBlur = _object$parsedStyle.shadowBlur, shadowOffsetX = _object$parsedStyle.shadowOffsetX, shadowOffsetY = _object$parsedStyle.shadowOffsetY; if (filter && filter.length) { // use raw filter string context.filter = object.style.filter; } if (hasShadow) { context.shadowColor = shadowColor.toString(); context.shadowBlur = shadowBlur || 0; context.shadowOffsetX = shadowOffsetX || 0; context.shadowOffsetY = shadowOffsetY || 0; } } function applyFill(context, object, fill, fillRule, canvasContext, plugin, runtime, imagePool) { var skipFill = arguments.length > 8 && arguments[8] !== undefined ? arguments[8] : false; if (Array.isArray(fill)) { fill.forEach(function (gradient) { context.fillStyle = getColor(gradient, object, context, imagePool); if (!skipFill) { fillRule ? context.fill(fillRule) : context.fill(); } }); } else { if (gLite.isPattern(fill)) { context.fillStyle = getPattern(fill, object, context, canvasContext, plugin, runtime, imagePool); } if (!skipFill) { fillRule ? context.fill(fillRule) : context.fill(); } } } function applyStroke(context, object, stroke, canvasContext, plugin, runtime, imagePool) { var skipStroke = arguments.length > 7 && arguments[7] !== undefined ? arguments[7] : false; if (Array.isArray(stroke)) { stroke.forEach(function (gradient) { context.strokeStyle = getColor(gradient, object, context, imagePool); if (!skipStroke) { context.stroke(); } }); } else { if (gLite.isPattern(stroke)) { context.strokeStyle = getPattern(stroke, object, context, canvasContext, plugin, runtime, imagePool); } if (!skipStroke) { context.stroke(); } } } function calculateOverlapRect(rect1, rect2) { var _rect = _slicedToArray(rect1, 4), x1 = _rect[0], y1 = _rect[1], w1 = _rect[2], h1 = _rect[3]; var _rect2 = _slicedToArray(rect2, 4), x2 = _rect2[0], y2 = _rect2[1], w2 = _rect2[2], h2 = _rect2[3]; // 计算重叠区域的左上角和右下角 var overlapLeft = Math.max(x1, x2); var overlapTop = Math.max(y1, y2); var overlapRight = Math.min(x1 + w1, x2 + w2); var overlapBottom = Math.min(y1 + h1, y2 + h2); if (overlapRight <= overlapLeft || overlapBottom <= overlapTop) { return null; } return [overlapLeft, overlapTop, overlapRight - overlapLeft, overlapBottom - overlapTop]; } function transformRect(rect, matrix) { var tl = glMatrix.vec3.transformMat4(glMatrix.vec3.create(), [rect[0], rect[1], 0], matrix); var tr = glMatrix.vec3.transformMat4(glMatrix.vec3.create(), [rect[0] + rect[2], rect[1], 0], matrix); var bl = glMatrix.vec3.transformMat4(glMatrix.vec3.create(), [rect[0], rect[1] + rect[3], 0], matrix); var br = glMatrix.vec3.transformMat4(glMatrix.vec3.create(), [rect[0] + rect[2], rect[1] + rect[3], 0], matrix); return [Math.min(tl[0], tr[0], bl[0], br[0]), Math.min(tl[1], tr[1], bl[1], br[1]), Math.max(tl[0], tr[0], bl[0], br[0]) - Math.min(tl[0], tr[0], bl[0], br[0]), Math.max(tl[1], tr[1], bl[1], br[1]) - Math.min(tl[1], tr[1], bl[1], br[1])]; } var ImageRenderer = /*#__PURE__*/function (_DefaultRenderer) { function ImageRenderer() { _classCallCheck(this, ImageRenderer); return _callSuper(this, ImageRenderer, arguments); } _inherits(ImageRenderer, _DefaultRenderer); return _createClass(ImageRenderer, [{ key: "renderDownSampled", value: function renderDownSampled(context, parsedStyle, object, data) { var src = data.src, imageCache = data.imageCache; if (!imageCache.downSampled) { this.imagePool.createDownSampledImage(src, object).then(function () { // be removed from dom tree if (!object.ownerDocument) { return; } // rerender // object.dirty(); object.renderable.dirty = true; object.ownerDocument.defaultView.context.renderingService.dirtify(); })["catch"](function (reason) { console.error(reason); }); return; } context.drawImage(imageCache.downSampled, Math.floor(data.drawRect[0]), Math.floor(data.drawRect[1]), Math.ceil(data.drawRect[2]), Math.ceil(data.drawRect[3])); } }, { key: "renderTile", value: function renderTile(context, parsedStyle, object, data) { var src = data.src, imageCache = data.imageCache, imageRect = data.imageRect, drawRect = data.drawRect; var originalSize = imageCache.size; var _context$getTransform = context.getTransform(), a = _context$getTransform.a, b = _context$getTransform.b, c = _context$getTransform.c, d = _context$getTransform.d, e = _context$getTransform.e, f = _context$getTransform.f; context.resetTransform(); if (!(imageCache !== null && imageCache !== void 0 && imageCache.gridSize)) { this.imagePool.createImageTiles(src, [], function () { // be removed from dom tree if (!object.ownerDocument) { return; } // rerender // object.dirty(); object.renderable.dirty = true; object.ownerDocument.defaultView.context.renderingService.dirtify(); }, object)["catch"](function (reason) { console.error(reason); }); return; } var scaleToOrigin = [originalSize[0] / imageRect[2], originalSize[1] / imageRect[3]]; var scaledTileSize = [imageCache.tileSize[0] / scaleToOrigin[0], imageCache.tileSize[1] / scaleToOrigin[1]]; var _ref = [Math.floor((drawRect[0] - imageRect[0]) / scaledTileSize[0]), Math.ceil((drawRect[0] + drawRect[2] - imageRect[0]) / scaledTileSize[0])], startTileX = _ref[0], endTileX = _ref[1]; var _ref2 = [Math.floor((drawRect[1] - imageRect[1]) / scaledTileSize[1]), Math.ceil((drawRect[1] + drawRect[3] - imageRect[1]) / scaledTileSize[1])], startTileY = _ref2[0], endTileY = _ref2[1]; for (var tileY = startTileY; tileY <= endTileY; tileY++) { for (var tileX = startTileX; tileX <= endTileX; tileX++) { var item = imageCache.tiles[tileY][tileX]; if (item) { var tileRect = [Math.floor(imageRect[0] + item.tileX * scaledTileSize[0]), Math.floor(imageRect[1] + item.tileY * scaledTileSize[1]), Math.ceil