'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var gLite = require('@antv/g-lite'); var util = require('@antv/util'); var glMatrix = require('gl-matrix'); function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; _setPrototypeOf(subClass, superClass); } function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } /** * support 2 modes in rendering: * * immediate * * delayed: render at the end of frame with dirty-rectangle */ var CanvasRendererPlugin = /*#__PURE__*/function () { function CanvasRendererPlugin(canvasRendererPluginOptions) { this.canvasRendererPluginOptions = void 0; this.context = void 0; this.pathGeneratorFactory = void 0; /** * RBush used in dirty rectangle rendering */ this.rBush = void 0; this.removedRBushNodeAABBs = []; this.renderQueue = []; /** * This stack is only used by clipPath for now. */ this.restoreStack = []; 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; } var _proto = CanvasRendererPlugin.prototype; _proto.apply = function apply(context, runtime) { var _this = this; this.context = context; var config = context.config, camera = context.camera, renderingService = context.renderingService, renderingContext = context.renderingContext, rBushRoot = context.rBushRoot, pathGeneratorFactory = context.pathGeneratorFactory; 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 = []; // this.restoreStack = []; }); renderingService.hooks.beginFrame.tap(CanvasRendererPlugin.tag, function () { 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 = renderingService.disableDirtyRectangleRendering() || rendered > dirtyObjectNumThreshold && ratio > dirtyObjectRatioThreshold; if (context) { context.resetTransform ? context.resetTransform() : context.setTransform(1, 0, 0, 1, 0, 0); if (_this.clearFullScreen) { _this.clearRect(context, 0, 0, width * dpr, height * dpr, config.background); } } }); var renderByZIndex = function renderByZIndex(object, context) { if (object.isVisible() && !object.isCulled()) { _this.renderDisplayObject(object, context, _this.context, _this.restoreStack, runtime); // if (object.renderable.) { // if we did a full screen rendering last frame _this.saveDirtyAABB(object); // } } var sorted = object.sortable.sorted || object.childNodes; // should account for z-index sorted.forEach(function (child) { renderByZIndex(child, context); }); }; // render at the end of frame renderingService.hooks.endFrame.tap(CanvasRendererPlugin.tag, function () { 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) { if (_this.clearFullScreen) { // console.log('canvas renderer fcp...'); renderByZIndex(renderingContext.root, context); } else { // console.log('canvas renderer next...'); // merge removed AABB var dirtyRenderBounds = _this.safeMergeAABB.apply(_this, [_this.mergeDirtyAABBs(_this.renderQueue)].concat(_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, _this.restoreStack, 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 _this.restoreStack.forEach(function () { context.restore(); }); // clear restore stack _this.restoreStack = []; }); renderingService.hooks.render.tap(CanvasRendererPlugin.tag, function (object) { if (!_this.clearFullScreen) { // render at the end of frame _this.renderQueue.push(object); } }); }; _proto.clearRect = 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); } }; _proto.renderDisplayObject = function renderDisplayObject(object, context, canvasContext, restoreStack, runtime) { var nodeName = object.nodeName; // console.log('canvas render:', object); // restore to its ancestor var parent = restoreStack[restoreStack.length - 1]; if (parent && !(object.compareDocumentPosition(parent) & Node.DOCUMENT_POSITION_CONTAINS)) { context.restore(); 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 _generatePath = this.pathGeneratorFactory[clipPath.nodeName]; if (_generatePath) { context.save(); // save clip restoreStack.push(object); context.beginPath(); _generatePath(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 (object.nodeName !== gLite.Shape.LINE && object.nodeName !== gLite.Shape.PATH && object.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; }; _proto.convertAABB2Rect = 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 */; _proto.mergeDirtyAABBs = 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; }; _proto.searchDirtyObjects = function searchDirtyObjects(dirtyRectangle) { // search in r-tree, get all affected nodes var _dirtyRectangle$getMi = dirtyRectangle.getMin(), minX = _dirtyRectangle$getMi[0], minY = _dirtyRectangle$getMi[1]; var _dirtyRectangle$getMa = dirtyRectangle.getMax(), maxX = _dirtyRectangle$getMa[0], maxY = _dirtyRectangle$getMa[1]; var rBushNodes = this.rBush.search({ minX: minX, minY: minY, maxX: maxX, maxY: maxY }); return rBushNodes.map(function (_ref2) { var displayObject = _ref2.displayObject; return displayObject; }); }; _proto.saveDirtyAABB = 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); } } /** * TODO: batch the same global attributes */; _proto.applyAttributesToContext = function applyAttributesToContext(context, object) { var _object$parsedStyle = object.parsedStyle, stroke = _object$parsedStyle.stroke, fill = _object$parsedStyle.fill, opacity = _object$parsedStyle.opacity, lineDash = _object$parsedStyle.lineDash, lineDashOffset = _object$parsedStyle.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; } }; _proto.applyWorldTransform = function applyWorldTransform(context, object, matrix) { var tx = 0; var ty = 0; var _ref3 = object.parsedStyle || {}, anchor = _ref3.anchor; var anchorX = anchor && anchor[0] || 0; var anchorY = anchor && anchor[1] || 0; if (anchorX !== 0 || anchorY !== 0) { // const bounds = object.getGeometryBounds(); var bounds = object.geometry.contentBounds; var width = bounds && bounds.halfExtents[0] * 2 || 0; var height = bounds && bounds.halfExtents[1] * 2 || 0; tx = -(anchorX * width); ty = -(anchorY * height); } // apply clip shape's RTS if (matrix) { glMatrix.mat4.copy(this.tmpMat4, object.getLocalTransform()); this.vec3a[0] = tx; this.vec3a[1] = ty; this.vec3a[2] = 0; glMatrix.mat4.translate(this.tmpMat4, this.tmpMat4, this.vec3a); 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()); this.vec3a[0] = tx; this.vec3a[1] = ty; this.vec3a[2] = 0; glMatrix.mat4.translate(this.tmpMat4, this.tmpMat4, this.vec3a); 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]); }; _proto.safeMergeAABB = 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; }; return CanvasRendererPlugin; }(); CanvasRendererPlugin.tag = 'CanvasRenderer'; var DefaultRenderer = /*#__PURE__*/function () { function DefaultRenderer(imagePool) { this.imagePool = void 0; this.imagePool = imagePool; } var _proto = DefaultRenderer.prototype; _proto.render = function render(context, parsedStyle, object, canvasContext, plugin, runtime) { var fill = parsedStyle.fill, fillRule = parsedStyle.fillRule, opacity = parsedStyle.opacity, fillOpacity = parsedStyle.fillOpacity, stroke = parsedStyle.stroke, strokeOpacity = parsedStyle.strokeOpacity, lineWidth = parsedStyle.lineWidth, lineCap = parsedStyle.lineCap, lineJoin = parsedStyle.lineJoin, shadowType = parsedStyle.shadowType, shadowColor = parsedStyle.shadowColor, shadowBlur = parsedStyle.shadowBlur, filter = parsedStyle.filter, miterLimit = parsedStyle.miterLimit; var hasFill = !util.isNil(fill) && !fill.isNone; var hasStroke = !util.isNil(stroke) && !stroke.isNone && lineWidth > 0; var isFillTransparent = 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); } this.fill(context, object, fill, fillRule, canvasContext, plugin, runtime); 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) { this.stroke(context, object, stroke, canvasContext, plugin, runtime); context.globalCompositeOperation = 'source-over'; this.clearShadowAndFilter(context, hasFilter, true); } } this.stroke(context, object, stroke, canvasContext, plugin, runtime); } }; _proto.clearShadowAndFilter = 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'; } } }; _proto.fill = function fill(context, object, _fill, fillRule, canvasContext, plugin, runtime) { var _this = this; if (Array.isArray(_fill)) { _fill.forEach(function (gradient) { context.fillStyle = _this.getColor(gradient, object, context); fillRule ? context.fill(fillRule) : context.fill(); }); } else { if (gLite.isPattern(_fill)) { context.fillStyle = this.getPattern(_fill, object, context, canvasContext, plugin, runtime); } fillRule ? context.fill(fillRule) : context.fill(); } }; _proto.stroke = function stroke(context, object, _stroke, canvasContext, plugin, runtime) { var _this2 = this; if (Array.isArray(_stroke)) { _stroke.forEach(function (gradient) { context.strokeStyle = _this2.getColor(gradient, object, context); context.stroke(); }); } else { if (gLite.isPattern(_stroke)) { context.strokeStyle = this.getPattern(_stroke, object, context, canvasContext, plugin, runtime); } context.stroke(); } }; _proto.getPattern = function getPattern(pattern, object, context, canvasContext, plugin, runtime) { var $offscreenCanvas; var dpr; if (pattern.image.nodeName === 'rect') { var _pattern$image$parsed = pattern.image.parsedStyle, width = _pattern$image$parsed.width, height = _pattern$image$parsed.height; dpr = canvasContext.contextService.getDPR(); var offscreenCanvas = canvasContext.config.offscreenCanvas; $offscreenCanvas = runtime.offscreenCanvas.getOrCreateCanvas(offscreenCanvas); $offscreenCanvas.width = width * dpr; $offscreenCanvas.height = height * dpr; var offscreenCanvasContext = runtime.offscreenCanvas.getOrCreateContext(offscreenCanvas); var restoreStack = []; // offscreenCanvasContext.scale(1 / dpr, 1 / dpr); pattern.image.forEach(function (object) { plugin.renderDisplayObject(object, offscreenCanvasContext, canvasContext, restoreStack, runtime); }); restoreStack.forEach(function () { offscreenCanvasContext.restore(); }); } var canvasPattern = this.imagePool.getOrCreatePatternSync(pattern, context, $offscreenCanvas, dpr, function () { // set dirty rectangle flag object.renderable.dirty = true; canvasContext.renderingService.dirtify(); }); return canvasPattern; }; _proto.getColor = function getColor(parsedColor, object, context) { 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; color = this.imagePool.getOrCreateGradient(_extends({ type: parsedColor.type }, parsedColor.value, { width: width, height: height }), context); } return color; }; return DefaultRenderer; }(); /** * 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; } } var ImageRenderer = /*#__PURE__*/function () { function ImageRenderer(imagePool) { this.imagePool = void 0; this.imagePool = imagePool; } var _proto = ImageRenderer.prototype; _proto.render = function render(context, parsedStyle, object) { var width = parsedStyle.width, height = parsedStyle.height, img = parsedStyle.img, shadowColor = parsedStyle.shadowColor, shadowBlur = parsedStyle.shadowBlur; var image; var iw = width; var ih = height; if (util.isString(img)) { // image has been loaded in `mounted` hook image = this.imagePool.getImageSync(img); } else { iw || (iw = img.width); ih || (ih = img.height); image = img; } if (image) { var hasShadow = !util.isNil(shadowColor) && shadowBlur > 0; setShadowAndFilter(object, context, hasShadow); // node-canvas will throw the following err: // Error: Image given has not completed loading try { context.drawImage(image, 0, 0, iw, ih); } catch (e) {} } }; return ImageRenderer; }(); var TextRenderer = /*#__PURE__*/function () { function TextRenderer() {} var _proto = TextRenderer.prototype; _proto.render = function render(context, parsedStyle, object) { var lineWidth = parsedStyle.lineWidth, textAlign = parsedStyle.textAlign, textBaseline = parsedStyle.textBaseline, lineJoin = parsedStyle.lineJoin, miterLimit = parsedStyle.miterLimit, letterSpacing = parsedStyle.letterSpacing, stroke = parsedStyle.stroke, fill = parsedStyle.fill, fillOpacity = parsedStyle.fillOpacity, strokeOpacity = parsedStyle.strokeOpacity, opacity = parsedStyle.opacity, metrics = parsedStyle.metrics, dx = parsedStyle.dx, dy = parsedStyle.dy, shadowColor = parsedStyle.shadowColor, shadowBlur = parsedStyle.shadowBlur; var font = metrics.font, lines = metrics.lines, height = metrics.height, lineHeight = metrics.lineHeight, lineMetrics = metrics.lineMetrics; context.font = font; context.lineWidth = lineWidth; context.textAlign = textAlign === 'middle' ? 'center' : textAlign; var formattedTextBaseline = textBaseline; if ( // formattedTextBaseline === 'bottom' || !gLite.runtime.enableCSSParsing && formattedTextBaseline === 'alphabetic') { formattedTextBaseline = 'bottom'; } context.lineJoin = lineJoin; if (!util.isNil(miterLimit)) { context.miterLimit = miterLimit; } var linePositionY = 0; // handle vertical text baseline if (textBaseline === 'middle') { linePositionY = -height / 2 - lineHeight / 2; } else if (textBaseline === 'bottom' || textBaseline === 'alphabetic' || textBaseline === 'ideographic') { linePositionY = -height; } else if (textBaseline === 'top' || textBaseline === 'hanging') { linePositionY = -lineHeight; } // account for dx & dy var offsetX = dx || 0; linePositionY += dy || 0; if (lines.length === 1) { if (formattedTextBaseline === 'bottom') { formattedTextBaseline = 'middle'; linePositionY -= 0.5 * height; } else if (formattedTextBaseline === 'top') { formattedTextBaseline = 'middle'; linePositionY += 0.5 * height; } } context.textBaseline = formattedTextBaseline; var hasShadow = !util.isNil(shadowColor) && shadowBlur > 0; setShadowAndFilter(object, context, hasShadow); // draw lines line by line for (var i = 0; i < lines.length; i++) { var linePositionX = lineWidth / 2 + offsetX; linePositionY += lineHeight; // no need to re-position X, cause we already set text align // @see https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/textAlign if (!util.isNil(stroke) && !stroke.isNone && lineWidth) { this.drawLetterSpacing(context, lines[i], lineMetrics[i], textAlign, linePositionX, linePositionY, letterSpacing, fillOpacity, strokeOpacity, opacity, true); } if (!util.isNil(fill)) { this.drawLetterSpacing(context, lines[i], lineMetrics[i], textAlign, linePositionX, linePositionY, letterSpacing, fillOpacity, strokeOpacity, opacity); } } }; _proto.drawLetterSpacing = function drawLetterSpacing(context, text, lineMetrics, textAlign, x, y, letterSpacing, fillOpacity, strokeOpacity, opacity, isStroke) { if (isStroke === void 0) { isStroke = false; } // letterSpacing of 0 means normal, render all texts directly if (letterSpacing === 0) { if (isStroke) { this.strokeText(context, text, x, y, strokeOpacity); } else { this.fillText(context, text, x, y, fillOpacity, opacity); } return; } // draw text using left align var currentTextAlign = context.textAlign; context.textAlign = 'left'; var currentPosition = x; if (textAlign === 'center' || textAlign === 'middle') { currentPosition = x - lineMetrics.width / 2; } else if (textAlign === 'right' || textAlign === 'end') { currentPosition = x - lineMetrics.width; } var stringArray = Array.from(text); var previousWidth = context.measureText(text).width; var currentWidth = 0; for (var i = 0; i < stringArray.length; ++i) { var currentChar = stringArray[i]; if (isStroke) { this.strokeText(context, currentChar, currentPosition, y, strokeOpacity); } else { this.fillText(context, currentChar, currentPosition, y, fillOpacity, opacity); } currentWidth = context.measureText(text.substring(i + 1)).width; currentPosition += previousWidth - currentWidth + letterSpacing; previousWidth = currentWidth; } context.textAlign = currentTextAlign; }; _proto.fillText = function fillText(context, text, x, y, fillOpacity, opacity) { var currentGlobalAlpha; var applyOpacity = !util.isNil(fillOpacity) && fillOpacity !== 1; if (applyOpacity) { currentGlobalAlpha = context.globalAlpha; context.globalAlpha = fillOpacity * opacity; } context.fillText(text, x, y); if (applyOpacity) { context.globalAlpha = currentGlobalAlpha; } }; _proto.strokeText = function strokeText(context, text, x, y, strokeOpacity) { var currentGlobalAlpha; var applyOpacity = !util.isNil(strokeOpacity) && strokeOpacity !== 1; if (applyOpacity) { currentGlobalAlpha = context.globalAlpha; context.globalAlpha = strokeOpacity; } context.strokeText(text, x, y); if (applyOpacity) { context.globalAlpha = currentGlobalAlpha; } }; return TextRenderer; }(); var RectRenderer = /*#__PURE__*/function (_DefaultRenderer) { _inheritsLoose(RectRenderer, _DefaultRenderer); function RectRenderer() { return _DefaultRenderer.apply(this, arguments) || this; } return RectRenderer; }(DefaultRenderer); var CircleRenderer = /*#__PURE__*/function (_DefaultRenderer) { _inheritsLoose(CircleRenderer, _DefaultRenderer); function CircleRenderer() { return _DefaultRenderer.apply(this, arguments) || this; } return CircleRenderer; }(DefaultRenderer); var EllipseRenderer = /*#__PURE__*/function (_DefaultRenderer) { _inheritsLoose(EllipseRenderer, _DefaultRenderer); function EllipseRenderer() { return _DefaultRenderer.apply(this, arguments) || this; } return EllipseRenderer; }(DefaultRenderer); var LineRenderer = /*#__PURE__*/function (_DefaultRenderer) { _inheritsLoose(LineRenderer, _DefaultRenderer); function LineRenderer() { return _DefaultRenderer.apply(this, arguments) || this; } return LineRenderer; }(DefaultRenderer); var PolylineRenderer = /*#__PURE__*/function (_DefaultRenderer) { _inheritsLoose(PolylineRenderer, _DefaultRenderer); function PolylineRenderer() { return _DefaultRenderer.apply(this, arguments) || this; } return PolylineRenderer; }(DefaultRenderer); var PolygonRenderer = /*#__PURE__*/function (_DefaultRenderer) { _inheritsLoose(PolygonRenderer, _DefaultRenderer); function PolygonRenderer() { return _DefaultRenderer.apply(this, arguments) || this; } return PolygonRenderer; }(DefaultRenderer); var PathRenderer = /*#__PURE__*/function (_DefaultRenderer) { _inheritsLoose(PathRenderer, _DefaultRenderer); function PathRenderer() { return _DefaultRenderer.apply(this, arguments) || this; } return PathRenderer; }(DefaultRenderer); var Plugin = /*#__PURE__*/function (_AbstractRendererPlug) { _inheritsLoose(Plugin, _AbstractRendererPlug); function Plugin(options) { var _this; if (options === void 0) { options = {}; } _this = _AbstractRendererPlug.call(this) || this; _this.options = void 0; _this.name = 'canvas-renderer'; _this.options = options; return _this; } var _proto = Plugin.prototype; _proto.init = function init() { var _defaultStyleRenderer; var canvasRendererPluginOptions = _extends({ dirtyObjectNumThreshold: 500, dirtyObjectRatioThreshold: 0.8 }, this.options); // @ts-ignore var imagePool = this.context.imagePool; var defaultRenderer = new DefaultRenderer(imagePool); var defaultStyleRendererFactory = (_defaultStyleRenderer = {}, _defaultStyleRenderer[gLite.Shape.CIRCLE] = defaultRenderer, _defaultStyleRenderer[gLite.Shape.ELLIPSE] = defaultRenderer, _defaultStyleRenderer[gLite.Shape.RECT] = defaultRenderer, _defaultStyleRenderer[gLite.Shape.IMAGE] = new ImageRenderer(imagePool), _defaultStyleRenderer[gLite.Shape.TEXT] = new TextRenderer(), _defaultStyleRenderer[gLite.Shape.LINE] = defaultRenderer, _defaultStyleRenderer[gLite.Shape.POLYLINE] = defaultRenderer, _defaultStyleRenderer[gLite.Shape.POLYGON] = defaultRenderer, _defaultStyleRenderer[gLite.Shape.PATH] = defaultRenderer, _defaultStyleRenderer[gLite.Shape.GROUP] = undefined, _defaultStyleRenderer[gLite.Shape.HTML] = undefined, _defaultStyleRenderer[gLite.Shape.MESH] = undefined, _defaultStyleRenderer); this.context.defaultStyleRendererFactory = defaultStyleRendererFactory; this.context.styleRendererFactory = defaultStyleRendererFactory; this.addRenderingPlugin(new CanvasRendererPlugin(canvasRendererPluginOptions)); }; _proto.destroy = function destroy() { this.removeAllRenderingPlugins(); delete this.context.defaultStyleRendererFactory; delete this.context.styleRendererFactory; }; return Plugin; }(gLite.AbstractRendererPlugin); exports.CircleRenderer = CircleRenderer; exports.EllipseRenderer = EllipseRenderer; exports.ImageRenderer = ImageRenderer; exports.LineRenderer = LineRenderer; exports.PathRenderer = PathRenderer; exports.Plugin = Plugin; exports.PolygonRenderer = PolygonRenderer; exports.PolylineRenderer = PolylineRenderer; exports.RectRenderer = RectRenderer; exports.TextRenderer = TextRenderer;