index.js 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915
  1. 'use strict';
  2. Object.defineProperty(exports, '__esModule', { value: true });
  3. var gLite = require('@antv/g-lite');
  4. var util = require('@antv/util');
  5. var glMatrix = require('gl-matrix');
  6. function _extends() {
  7. _extends = Object.assign ? Object.assign.bind() : function (target) {
  8. for (var i = 1; i < arguments.length; i++) {
  9. var source = arguments[i];
  10. for (var key in source) {
  11. if (Object.prototype.hasOwnProperty.call(source, key)) {
  12. target[key] = source[key];
  13. }
  14. }
  15. }
  16. return target;
  17. };
  18. return _extends.apply(this, arguments);
  19. }
  20. function _inheritsLoose(subClass, superClass) {
  21. subClass.prototype = Object.create(superClass.prototype);
  22. subClass.prototype.constructor = subClass;
  23. _setPrototypeOf(subClass, superClass);
  24. }
  25. function _setPrototypeOf(o, p) {
  26. _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) {
  27. o.__proto__ = p;
  28. return o;
  29. };
  30. return _setPrototypeOf(o, p);
  31. }
  32. /**
  33. * support 2 modes in rendering:
  34. * * immediate
  35. * * delayed: render at the end of frame with dirty-rectangle
  36. */
  37. var CanvasRendererPlugin = /*#__PURE__*/function () {
  38. function CanvasRendererPlugin(canvasRendererPluginOptions) {
  39. this.canvasRendererPluginOptions = void 0;
  40. this.context = void 0;
  41. this.pathGeneratorFactory = void 0;
  42. /**
  43. * RBush used in dirty rectangle rendering
  44. */
  45. this.rBush = void 0;
  46. this.removedRBushNodeAABBs = [];
  47. this.renderQueue = [];
  48. /**
  49. * This stack is only used by clipPath for now.
  50. */
  51. this.restoreStack = [];
  52. this.clearFullScreen = false;
  53. /**
  54. * view projection matrix
  55. */
  56. this.vpMatrix = glMatrix.mat4.create();
  57. this.dprMatrix = glMatrix.mat4.create();
  58. this.tmpMat4 = glMatrix.mat4.create();
  59. this.vec3a = glMatrix.vec3.create();
  60. this.vec3b = glMatrix.vec3.create();
  61. this.vec3c = glMatrix.vec3.create();
  62. this.vec3d = glMatrix.vec3.create();
  63. this.canvasRendererPluginOptions = canvasRendererPluginOptions;
  64. }
  65. var _proto = CanvasRendererPlugin.prototype;
  66. _proto.apply = function apply(context, runtime) {
  67. var _this = this;
  68. this.context = context;
  69. var config = context.config,
  70. camera = context.camera,
  71. renderingService = context.renderingService,
  72. renderingContext = context.renderingContext,
  73. rBushRoot = context.rBushRoot,
  74. pathGeneratorFactory = context.pathGeneratorFactory;
  75. this.rBush = rBushRoot;
  76. this.pathGeneratorFactory = pathGeneratorFactory;
  77. var contextService = context.contextService;
  78. var canvas = renderingContext.root.ownerDocument.defaultView;
  79. var handleUnmounted = function handleUnmounted(e) {
  80. var object = e.target;
  81. // remove r-bush node
  82. // @ts-ignore
  83. var rBushNode = object.rBushNode;
  84. if (rBushNode.aabb) {
  85. // save removed aabbs for dirty-rectangle rendering later
  86. _this.removedRBushNodeAABBs.push(rBushNode.aabb);
  87. }
  88. };
  89. var handleCulled = function handleCulled(e) {
  90. var object = e.target;
  91. // @ts-ignore
  92. var rBushNode = object.rBushNode;
  93. if (rBushNode.aabb) {
  94. // save removed aabbs for dirty-rectangle rendering later
  95. _this.removedRBushNodeAABBs.push(rBushNode.aabb);
  96. }
  97. };
  98. renderingService.hooks.init.tap(CanvasRendererPlugin.tag, function () {
  99. canvas.addEventListener(gLite.ElementEvent.UNMOUNTED, handleUnmounted);
  100. canvas.addEventListener(gLite.ElementEvent.CULLED, handleCulled);
  101. // clear fullscreen
  102. var dpr = contextService.getDPR();
  103. var width = config.width,
  104. height = config.height;
  105. var context = contextService.getContext();
  106. _this.clearRect(context, 0, 0, width * dpr, height * dpr, config.background);
  107. });
  108. renderingService.hooks.destroy.tap(CanvasRendererPlugin.tag, function () {
  109. canvas.removeEventListener(gLite.ElementEvent.UNMOUNTED, handleUnmounted);
  110. canvas.removeEventListener(gLite.ElementEvent.CULLED, handleCulled);
  111. // this.renderQueue = [];
  112. // this.removedRBushNodeAABBs = [];
  113. // this.restoreStack = [];
  114. });
  115. renderingService.hooks.beginFrame.tap(CanvasRendererPlugin.tag, function () {
  116. var context = contextService.getContext();
  117. var dpr = contextService.getDPR();
  118. var width = config.width,
  119. height = config.height;
  120. var _this$canvasRendererP = _this.canvasRendererPluginOptions,
  121. dirtyObjectNumThreshold = _this$canvasRendererP.dirtyObjectNumThreshold,
  122. dirtyObjectRatioThreshold = _this$canvasRendererP.dirtyObjectRatioThreshold;
  123. // some heuristic conditions such as 80% object changed
  124. var _renderingService$get = renderingService.getStats(),
  125. total = _renderingService$get.total,
  126. rendered = _renderingService$get.rendered;
  127. var ratio = rendered / total;
  128. _this.clearFullScreen = renderingService.disableDirtyRectangleRendering() || rendered > dirtyObjectNumThreshold && ratio > dirtyObjectRatioThreshold;
  129. if (context) {
  130. context.resetTransform ? context.resetTransform() : context.setTransform(1, 0, 0, 1, 0, 0);
  131. if (_this.clearFullScreen) {
  132. _this.clearRect(context, 0, 0, width * dpr, height * dpr, config.background);
  133. }
  134. }
  135. });
  136. var renderByZIndex = function renderByZIndex(object, context) {
  137. if (object.isVisible() && !object.isCulled()) {
  138. _this.renderDisplayObject(object, context, _this.context, _this.restoreStack, runtime);
  139. // if (object.renderable.) {
  140. // if we did a full screen rendering last frame
  141. _this.saveDirtyAABB(object);
  142. // }
  143. }
  144. var sorted = object.sortable.sorted || object.childNodes;
  145. // should account for z-index
  146. sorted.forEach(function (child) {
  147. renderByZIndex(child, context);
  148. });
  149. };
  150. // render at the end of frame
  151. renderingService.hooks.endFrame.tap(CanvasRendererPlugin.tag, function () {
  152. var context = contextService.getContext();
  153. // clear & clip dirty rectangle
  154. var dpr = contextService.getDPR();
  155. glMatrix.mat4.fromScaling(_this.dprMatrix, [dpr, dpr, 1]);
  156. glMatrix.mat4.multiply(_this.vpMatrix, _this.dprMatrix, camera.getOrthoMatrix());
  157. // if (this.clearFullScreen) {
  158. if (_this.clearFullScreen) {
  159. // console.log('canvas renderer fcp...');
  160. renderByZIndex(renderingContext.root, context);
  161. } else {
  162. // console.log('canvas renderer next...');
  163. // merge removed AABB
  164. var dirtyRenderBounds = _this.safeMergeAABB.apply(_this, [_this.mergeDirtyAABBs(_this.renderQueue)].concat(_this.removedRBushNodeAABBs.map(function (_ref) {
  165. var minX = _ref.minX,
  166. minY = _ref.minY,
  167. maxX = _ref.maxX,
  168. maxY = _ref.maxY;
  169. var aabb = new gLite.AABB();
  170. aabb.setMinMax(
  171. // vec3.fromValues(minX, minY, 0),
  172. // vec3.fromValues(maxX, maxY, 0),
  173. [minX, minY, 0], [maxX, maxY, 0]);
  174. return aabb;
  175. })));
  176. _this.removedRBushNodeAABBs = [];
  177. if (gLite.AABB.isEmpty(dirtyRenderBounds)) {
  178. _this.renderQueue = [];
  179. return;
  180. }
  181. var dirtyRect = _this.convertAABB2Rect(dirtyRenderBounds);
  182. var x = dirtyRect.x,
  183. y = dirtyRect.y,
  184. width = dirtyRect.width,
  185. height = dirtyRect.height;
  186. var tl = glMatrix.vec3.transformMat4(_this.vec3a, [x, y, 0], _this.vpMatrix);
  187. var tr = glMatrix.vec3.transformMat4(_this.vec3b, [x + width, y, 0], _this.vpMatrix);
  188. var bl = glMatrix.vec3.transformMat4(_this.vec3c, [x, y + height, 0], _this.vpMatrix);
  189. var br = glMatrix.vec3.transformMat4(_this.vec3d, [x + width, y + height, 0], _this.vpMatrix);
  190. var minx = Math.min(tl[0], tr[0], br[0], bl[0]);
  191. var miny = Math.min(tl[1], tr[1], br[1], bl[1]);
  192. var maxx = Math.max(tl[0], tr[0], br[0], bl[0]);
  193. var maxy = Math.max(tl[1], tr[1], br[1], bl[1]);
  194. var ix = Math.floor(minx);
  195. var iy = Math.floor(miny);
  196. var iwidth = Math.ceil(maxx - minx);
  197. var iheight = Math.ceil(maxy - miny);
  198. context.save();
  199. _this.clearRect(context, ix, iy, iwidth, iheight, config.background);
  200. context.beginPath();
  201. context.rect(ix, iy, iwidth, iheight);
  202. context.clip();
  203. // @see https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Transformations
  204. context.setTransform(_this.vpMatrix[0], _this.vpMatrix[1], _this.vpMatrix[4], _this.vpMatrix[5], _this.vpMatrix[12], _this.vpMatrix[13]);
  205. // draw dirty rectangle
  206. var _config$renderer$getC = config.renderer.getConfig(),
  207. enableDirtyRectangleRenderingDebug = _config$renderer$getC.enableDirtyRectangleRenderingDebug;
  208. if (enableDirtyRectangleRenderingDebug) {
  209. canvas.dispatchEvent(new gLite.CustomEvent(gLite.CanvasEvent.DIRTY_RECTANGLE, {
  210. dirtyRect: {
  211. x: ix,
  212. y: iy,
  213. width: iwidth,
  214. height: iheight
  215. }
  216. }));
  217. }
  218. // search objects intersect with dirty rectangle
  219. var dirtyObjects = _this.searchDirtyObjects(dirtyRenderBounds);
  220. // do rendering
  221. dirtyObjects
  222. // sort by z-index
  223. .sort(function (a, b) {
  224. return a.sortable.renderOrder - b.sortable.renderOrder;
  225. }).forEach(function (object) {
  226. // culled object should not be rendered
  227. if (object && object.isVisible() && !object.isCulled()) {
  228. _this.renderDisplayObject(object, context, _this.context, _this.restoreStack, runtime);
  229. }
  230. });
  231. context.restore();
  232. // save dirty AABBs in last frame
  233. _this.renderQueue.forEach(function (object) {
  234. _this.saveDirtyAABB(object);
  235. });
  236. // clear queue
  237. _this.renderQueue = [];
  238. }
  239. // pop restore stack, eg. root -> parent -> child
  240. _this.restoreStack.forEach(function () {
  241. context.restore();
  242. });
  243. // clear restore stack
  244. _this.restoreStack = [];
  245. });
  246. renderingService.hooks.render.tap(CanvasRendererPlugin.tag, function (object) {
  247. if (!_this.clearFullScreen) {
  248. // render at the end of frame
  249. _this.renderQueue.push(object);
  250. }
  251. });
  252. };
  253. _proto.clearRect = function clearRect(context, x, y, width, height, background) {
  254. // clearRect is faster than fillRect @see https://stackoverflow.com/a/30830253
  255. context.clearRect(x, y, width, height);
  256. if (background) {
  257. context.fillStyle = background;
  258. context.fillRect(x, y, width, height);
  259. }
  260. };
  261. _proto.renderDisplayObject = function renderDisplayObject(object, context, canvasContext, restoreStack, runtime) {
  262. var nodeName = object.nodeName;
  263. // console.log('canvas render:', object);
  264. // restore to its ancestor
  265. var parent = restoreStack[restoreStack.length - 1];
  266. if (parent && !(object.compareDocumentPosition(parent) & Node.DOCUMENT_POSITION_CONTAINS)) {
  267. context.restore();
  268. restoreStack.pop();
  269. }
  270. // @ts-ignore
  271. var styleRenderer = this.context.styleRendererFactory[nodeName];
  272. var generatePath = this.pathGeneratorFactory[nodeName];
  273. // clip path
  274. var clipPath = object.parsedStyle.clipPath;
  275. if (clipPath) {
  276. this.applyWorldTransform(context, clipPath);
  277. // generate path in local space
  278. var _generatePath = this.pathGeneratorFactory[clipPath.nodeName];
  279. if (_generatePath) {
  280. context.save();
  281. // save clip
  282. restoreStack.push(object);
  283. context.beginPath();
  284. _generatePath(context, clipPath.parsedStyle);
  285. context.closePath();
  286. context.clip();
  287. }
  288. }
  289. // fill & stroke
  290. if (styleRenderer) {
  291. this.applyWorldTransform(context, object);
  292. context.save();
  293. // apply attributes to context
  294. this.applyAttributesToContext(context, object);
  295. }
  296. if (generatePath) {
  297. context.beginPath();
  298. generatePath(context, object.parsedStyle);
  299. if (object.nodeName !== gLite.Shape.LINE && object.nodeName !== gLite.Shape.PATH && object.nodeName !== gLite.Shape.POLYLINE) {
  300. context.closePath();
  301. }
  302. }
  303. // fill & stroke
  304. if (styleRenderer) {
  305. styleRenderer.render(context, object.parsedStyle, object, canvasContext, this, runtime);
  306. // restore applied attributes, eg. shadowBlur shadowColor...
  307. context.restore();
  308. }
  309. // finish rendering, clear dirty flag
  310. object.renderable.dirty = false;
  311. };
  312. _proto.convertAABB2Rect = function convertAABB2Rect(aabb) {
  313. var min = aabb.getMin();
  314. var max = aabb.getMax();
  315. // expand the rectangle a bit to avoid artifacts
  316. // @see https://www.yuque.com/antv/ou292n/bi8nix#ExvCu
  317. var minX = Math.floor(min[0]);
  318. var minY = Math.floor(min[1]);
  319. var maxX = Math.ceil(max[0]);
  320. var maxY = Math.ceil(max[1]);
  321. var width = maxX - minX;
  322. var height = maxY - minY;
  323. return {
  324. x: minX,
  325. y: minY,
  326. width: width,
  327. height: height
  328. };
  329. }
  330. /**
  331. * TODO: merge dirty rectangles with some strategies.
  332. * For now, we just simply merge all the rectangles into one.
  333. * @see https://idom.me/articles/841.html
  334. */;
  335. _proto.mergeDirtyAABBs = function mergeDirtyAABBs(dirtyObjects) {
  336. // merge into a big AABB
  337. // TODO: skip descendant if ancestor is caculated, but compareNodePosition is really slow
  338. var aabb = new gLite.AABB();
  339. dirtyObjects.forEach(function (object) {
  340. var renderBounds = object.getRenderBounds();
  341. aabb.add(renderBounds);
  342. var dirtyRenderBounds = object.renderable.dirtyRenderBounds;
  343. if (dirtyRenderBounds) {
  344. aabb.add(dirtyRenderBounds);
  345. }
  346. });
  347. return aabb;
  348. };
  349. _proto.searchDirtyObjects = function searchDirtyObjects(dirtyRectangle) {
  350. // search in r-tree, get all affected nodes
  351. var _dirtyRectangle$getMi = dirtyRectangle.getMin(),
  352. minX = _dirtyRectangle$getMi[0],
  353. minY = _dirtyRectangle$getMi[1];
  354. var _dirtyRectangle$getMa = dirtyRectangle.getMax(),
  355. maxX = _dirtyRectangle$getMa[0],
  356. maxY = _dirtyRectangle$getMa[1];
  357. var rBushNodes = this.rBush.search({
  358. minX: minX,
  359. minY: minY,
  360. maxX: maxX,
  361. maxY: maxY
  362. });
  363. return rBushNodes.map(function (_ref2) {
  364. var displayObject = _ref2.displayObject;
  365. return displayObject;
  366. });
  367. };
  368. _proto.saveDirtyAABB = function saveDirtyAABB(object) {
  369. var renderable = object.renderable;
  370. if (!renderable.dirtyRenderBounds) {
  371. renderable.dirtyRenderBounds = new gLite.AABB();
  372. }
  373. var renderBounds = object.getRenderBounds();
  374. if (renderBounds) {
  375. // save last dirty aabb
  376. renderable.dirtyRenderBounds.update(renderBounds.center, renderBounds.halfExtents);
  377. }
  378. }
  379. /**
  380. * TODO: batch the same global attributes
  381. */;
  382. _proto.applyAttributesToContext = function applyAttributesToContext(context, object) {
  383. var _object$parsedStyle = object.parsedStyle,
  384. stroke = _object$parsedStyle.stroke,
  385. fill = _object$parsedStyle.fill,
  386. opacity = _object$parsedStyle.opacity,
  387. lineDash = _object$parsedStyle.lineDash,
  388. lineDashOffset = _object$parsedStyle.lineDashOffset;
  389. // @see https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/setLineDash
  390. if (lineDash) {
  391. context.setLineDash(lineDash);
  392. }
  393. // @see https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/lineDashOffset
  394. if (!util.isNil(lineDashOffset)) {
  395. context.lineDashOffset = lineDashOffset;
  396. }
  397. if (!util.isNil(opacity)) {
  398. context.globalAlpha *= opacity;
  399. }
  400. if (!util.isNil(stroke) && !Array.isArray(stroke) && !stroke.isNone) {
  401. context.strokeStyle = object.attributes.stroke;
  402. }
  403. if (!util.isNil(fill) && !Array.isArray(fill) && !fill.isNone) {
  404. context.fillStyle = object.attributes.fill;
  405. }
  406. };
  407. _proto.applyWorldTransform = function applyWorldTransform(context, object, matrix) {
  408. var tx = 0;
  409. var ty = 0;
  410. var _ref3 = object.parsedStyle || {},
  411. anchor = _ref3.anchor;
  412. var anchorX = anchor && anchor[0] || 0;
  413. var anchorY = anchor && anchor[1] || 0;
  414. if (anchorX !== 0 || anchorY !== 0) {
  415. // const bounds = object.getGeometryBounds();
  416. var bounds = object.geometry.contentBounds;
  417. var width = bounds && bounds.halfExtents[0] * 2 || 0;
  418. var height = bounds && bounds.halfExtents[1] * 2 || 0;
  419. tx = -(anchorX * width);
  420. ty = -(anchorY * height);
  421. }
  422. // apply clip shape's RTS
  423. if (matrix) {
  424. glMatrix.mat4.copy(this.tmpMat4, object.getLocalTransform());
  425. this.vec3a[0] = tx;
  426. this.vec3a[1] = ty;
  427. this.vec3a[2] = 0;
  428. glMatrix.mat4.translate(this.tmpMat4, this.tmpMat4, this.vec3a);
  429. glMatrix.mat4.multiply(this.tmpMat4, matrix, this.tmpMat4);
  430. glMatrix.mat4.multiply(this.tmpMat4, this.vpMatrix, this.tmpMat4);
  431. } else {
  432. // apply RTS transformation in world space
  433. glMatrix.mat4.copy(this.tmpMat4, object.getWorldTransform());
  434. this.vec3a[0] = tx;
  435. this.vec3a[1] = ty;
  436. this.vec3a[2] = 0;
  437. glMatrix.mat4.translate(this.tmpMat4, this.tmpMat4, this.vec3a);
  438. glMatrix.mat4.multiply(this.tmpMat4, this.vpMatrix, this.tmpMat4);
  439. }
  440. // @see https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Transformations
  441. context.setTransform(this.tmpMat4[0], this.tmpMat4[1], this.tmpMat4[4], this.tmpMat4[5], this.tmpMat4[12], this.tmpMat4[13]);
  442. };
  443. _proto.safeMergeAABB = function safeMergeAABB() {
  444. var merged = new gLite.AABB();
  445. for (var _len = arguments.length, aabbs = new Array(_len), _key = 0; _key < _len; _key++) {
  446. aabbs[_key] = arguments[_key];
  447. }
  448. aabbs.forEach(function (aabb) {
  449. merged.add(aabb);
  450. });
  451. return merged;
  452. };
  453. return CanvasRendererPlugin;
  454. }();
  455. CanvasRendererPlugin.tag = 'CanvasRenderer';
  456. var DefaultRenderer = /*#__PURE__*/function () {
  457. function DefaultRenderer(imagePool) {
  458. this.imagePool = void 0;
  459. this.imagePool = imagePool;
  460. }
  461. var _proto = DefaultRenderer.prototype;
  462. _proto.render = function render(context, parsedStyle, object, canvasContext, plugin, runtime) {
  463. var fill = parsedStyle.fill,
  464. fillRule = parsedStyle.fillRule,
  465. opacity = parsedStyle.opacity,
  466. fillOpacity = parsedStyle.fillOpacity,
  467. stroke = parsedStyle.stroke,
  468. strokeOpacity = parsedStyle.strokeOpacity,
  469. lineWidth = parsedStyle.lineWidth,
  470. lineCap = parsedStyle.lineCap,
  471. lineJoin = parsedStyle.lineJoin,
  472. shadowType = parsedStyle.shadowType,
  473. shadowColor = parsedStyle.shadowColor,
  474. shadowBlur = parsedStyle.shadowBlur,
  475. filter = parsedStyle.filter,
  476. miterLimit = parsedStyle.miterLimit;
  477. var hasFill = !util.isNil(fill) && !fill.isNone;
  478. var hasStroke = !util.isNil(stroke) && !stroke.isNone && lineWidth > 0;
  479. var isFillTransparent = fill.alpha === 0;
  480. var hasFilter = !!(filter && filter.length);
  481. var hasShadow = !util.isNil(shadowColor) && shadowBlur > 0;
  482. var nodeName = object.nodeName;
  483. var isInnerShadow = shadowType === 'inner';
  484. var shouldDrawShadowWithStroke = hasStroke && hasShadow && (nodeName === gLite.Shape.PATH || nodeName === gLite.Shape.LINE || nodeName === gLite.Shape.POLYLINE || isFillTransparent || isInnerShadow);
  485. if (hasFill) {
  486. context.globalAlpha = opacity * fillOpacity;
  487. if (!shouldDrawShadowWithStroke) {
  488. setShadowAndFilter(object, context, hasShadow);
  489. }
  490. this.fill(context, object, fill, fillRule, canvasContext, plugin, runtime);
  491. if (!shouldDrawShadowWithStroke) {
  492. this.clearShadowAndFilter(context, hasFilter, hasShadow);
  493. }
  494. }
  495. if (hasStroke) {
  496. context.globalAlpha = opacity * strokeOpacity;
  497. context.lineWidth = lineWidth;
  498. if (!util.isNil(miterLimit)) {
  499. context.miterLimit = miterLimit;
  500. }
  501. if (!util.isNil(lineCap)) {
  502. context.lineCap = lineCap;
  503. }
  504. if (!util.isNil(lineJoin)) {
  505. context.lineJoin = lineJoin;
  506. }
  507. if (shouldDrawShadowWithStroke) {
  508. if (isInnerShadow) {
  509. context.globalCompositeOperation = 'source-atop';
  510. }
  511. setShadowAndFilter(object, context, true);
  512. if (isInnerShadow) {
  513. this.stroke(context, object, stroke, canvasContext, plugin, runtime);
  514. context.globalCompositeOperation = 'source-over';
  515. this.clearShadowAndFilter(context, hasFilter, true);
  516. }
  517. }
  518. this.stroke(context, object, stroke, canvasContext, plugin, runtime);
  519. }
  520. };
  521. _proto.clearShadowAndFilter = function clearShadowAndFilter(context, hasFilter, hasShadow) {
  522. if (hasShadow) {
  523. context.shadowColor = 'transparent';
  524. context.shadowBlur = 0;
  525. }
  526. if (hasFilter) {
  527. // save drop-shadow filter
  528. var oldFilter = context.filter;
  529. if (!util.isNil(oldFilter) && oldFilter.indexOf('drop-shadow') > -1) {
  530. context.filter = oldFilter.replace(/drop-shadow\([^)]*\)/, '').trim() || 'none';
  531. }
  532. }
  533. };
  534. _proto.fill = function fill(context, object, _fill, fillRule, canvasContext, plugin, runtime) {
  535. var _this = this;
  536. if (Array.isArray(_fill)) {
  537. _fill.forEach(function (gradient) {
  538. context.fillStyle = _this.getColor(gradient, object, context);
  539. fillRule ? context.fill(fillRule) : context.fill();
  540. });
  541. } else {
  542. if (gLite.isPattern(_fill)) {
  543. context.fillStyle = this.getPattern(_fill, object, context, canvasContext, plugin, runtime);
  544. }
  545. fillRule ? context.fill(fillRule) : context.fill();
  546. }
  547. };
  548. _proto.stroke = function stroke(context, object, _stroke, canvasContext, plugin, runtime) {
  549. var _this2 = this;
  550. if (Array.isArray(_stroke)) {
  551. _stroke.forEach(function (gradient) {
  552. context.strokeStyle = _this2.getColor(gradient, object, context);
  553. context.stroke();
  554. });
  555. } else {
  556. if (gLite.isPattern(_stroke)) {
  557. context.strokeStyle = this.getPattern(_stroke, object, context, canvasContext, plugin, runtime);
  558. }
  559. context.stroke();
  560. }
  561. };
  562. _proto.getPattern = function getPattern(pattern, object, context, canvasContext, plugin, runtime) {
  563. var $offscreenCanvas;
  564. var dpr;
  565. if (pattern.image.nodeName === 'rect') {
  566. var _pattern$image$parsed = pattern.image.parsedStyle,
  567. width = _pattern$image$parsed.width,
  568. height = _pattern$image$parsed.height;
  569. dpr = canvasContext.contextService.getDPR();
  570. var offscreenCanvas = canvasContext.config.offscreenCanvas;
  571. $offscreenCanvas = runtime.offscreenCanvas.getOrCreateCanvas(offscreenCanvas);
  572. $offscreenCanvas.width = width * dpr;
  573. $offscreenCanvas.height = height * dpr;
  574. var offscreenCanvasContext = runtime.offscreenCanvas.getOrCreateContext(offscreenCanvas);
  575. var restoreStack = [];
  576. // offscreenCanvasContext.scale(1 / dpr, 1 / dpr);
  577. pattern.image.forEach(function (object) {
  578. plugin.renderDisplayObject(object, offscreenCanvasContext, canvasContext, restoreStack, runtime);
  579. });
  580. restoreStack.forEach(function () {
  581. offscreenCanvasContext.restore();
  582. });
  583. }
  584. var canvasPattern = this.imagePool.getOrCreatePatternSync(pattern, context, $offscreenCanvas, dpr, function () {
  585. // set dirty rectangle flag
  586. object.renderable.dirty = true;
  587. canvasContext.renderingService.dirtify();
  588. });
  589. return canvasPattern;
  590. };
  591. _proto.getColor = function getColor(parsedColor, object, context) {
  592. var color;
  593. if (parsedColor.type === gLite.GradientType.LinearGradient || parsedColor.type === gLite.GradientType.RadialGradient) {
  594. var bounds = object.getGeometryBounds();
  595. var width = bounds && bounds.halfExtents[0] * 2 || 1;
  596. var height = bounds && bounds.halfExtents[1] * 2 || 1;
  597. color = this.imagePool.getOrCreateGradient(_extends({
  598. type: parsedColor.type
  599. }, parsedColor.value, {
  600. width: width,
  601. height: height
  602. }), context);
  603. }
  604. return color;
  605. };
  606. return DefaultRenderer;
  607. }();
  608. /**
  609. * apply before fill and stroke but only once
  610. */
  611. function setShadowAndFilter(object, context, hasShadow) {
  612. var _object$parsedStyle = object.parsedStyle,
  613. filter = _object$parsedStyle.filter,
  614. shadowColor = _object$parsedStyle.shadowColor,
  615. shadowBlur = _object$parsedStyle.shadowBlur,
  616. shadowOffsetX = _object$parsedStyle.shadowOffsetX,
  617. shadowOffsetY = _object$parsedStyle.shadowOffsetY;
  618. if (filter && filter.length) {
  619. // use raw filter string
  620. context.filter = object.style.filter;
  621. }
  622. if (hasShadow) {
  623. context.shadowColor = shadowColor.toString();
  624. context.shadowBlur = shadowBlur || 0;
  625. context.shadowOffsetX = shadowOffsetX || 0;
  626. context.shadowOffsetY = shadowOffsetY || 0;
  627. }
  628. }
  629. var ImageRenderer = /*#__PURE__*/function () {
  630. function ImageRenderer(imagePool) {
  631. this.imagePool = void 0;
  632. this.imagePool = imagePool;
  633. }
  634. var _proto = ImageRenderer.prototype;
  635. _proto.render = function render(context, parsedStyle, object) {
  636. var width = parsedStyle.width,
  637. height = parsedStyle.height,
  638. img = parsedStyle.img,
  639. shadowColor = parsedStyle.shadowColor,
  640. shadowBlur = parsedStyle.shadowBlur;
  641. var image;
  642. var iw = width;
  643. var ih = height;
  644. if (util.isString(img)) {
  645. // image has been loaded in `mounted` hook
  646. image = this.imagePool.getImageSync(img);
  647. } else {
  648. iw || (iw = img.width);
  649. ih || (ih = img.height);
  650. image = img;
  651. }
  652. if (image) {
  653. var hasShadow = !util.isNil(shadowColor) && shadowBlur > 0;
  654. setShadowAndFilter(object, context, hasShadow);
  655. // node-canvas will throw the following err:
  656. // Error: Image given has not completed loading
  657. try {
  658. context.drawImage(image, 0, 0, iw, ih);
  659. } catch (e) {}
  660. }
  661. };
  662. return ImageRenderer;
  663. }();
  664. var TextRenderer = /*#__PURE__*/function () {
  665. function TextRenderer() {}
  666. var _proto = TextRenderer.prototype;
  667. _proto.render = function render(context, parsedStyle, object) {
  668. var lineWidth = parsedStyle.lineWidth,
  669. textAlign = parsedStyle.textAlign,
  670. textBaseline = parsedStyle.textBaseline,
  671. lineJoin = parsedStyle.lineJoin,
  672. miterLimit = parsedStyle.miterLimit,
  673. letterSpacing = parsedStyle.letterSpacing,
  674. stroke = parsedStyle.stroke,
  675. fill = parsedStyle.fill,
  676. fillOpacity = parsedStyle.fillOpacity,
  677. strokeOpacity = parsedStyle.strokeOpacity,
  678. opacity = parsedStyle.opacity,
  679. metrics = parsedStyle.metrics,
  680. dx = parsedStyle.dx,
  681. dy = parsedStyle.dy,
  682. shadowColor = parsedStyle.shadowColor,
  683. shadowBlur = parsedStyle.shadowBlur;
  684. var font = metrics.font,
  685. lines = metrics.lines,
  686. height = metrics.height,
  687. lineHeight = metrics.lineHeight,
  688. lineMetrics = metrics.lineMetrics;
  689. context.font = font;
  690. context.lineWidth = lineWidth;
  691. context.textAlign = textAlign === 'middle' ? 'center' : textAlign;
  692. var formattedTextBaseline = textBaseline;
  693. if (
  694. // formattedTextBaseline === 'bottom' ||
  695. !gLite.runtime.enableCSSParsing && formattedTextBaseline === 'alphabetic') {
  696. formattedTextBaseline = 'bottom';
  697. }
  698. context.lineJoin = lineJoin;
  699. if (!util.isNil(miterLimit)) {
  700. context.miterLimit = miterLimit;
  701. }
  702. var linePositionY = 0;
  703. // handle vertical text baseline
  704. if (textBaseline === 'middle') {
  705. linePositionY = -height / 2 - lineHeight / 2;
  706. } else if (textBaseline === 'bottom' || textBaseline === 'alphabetic' || textBaseline === 'ideographic') {
  707. linePositionY = -height;
  708. } else if (textBaseline === 'top' || textBaseline === 'hanging') {
  709. linePositionY = -lineHeight;
  710. }
  711. // account for dx & dy
  712. var offsetX = dx || 0;
  713. linePositionY += dy || 0;
  714. if (lines.length === 1) {
  715. if (formattedTextBaseline === 'bottom') {
  716. formattedTextBaseline = 'middle';
  717. linePositionY -= 0.5 * height;
  718. } else if (formattedTextBaseline === 'top') {
  719. formattedTextBaseline = 'middle';
  720. linePositionY += 0.5 * height;
  721. }
  722. }
  723. context.textBaseline = formattedTextBaseline;
  724. var hasShadow = !util.isNil(shadowColor) && shadowBlur > 0;
  725. setShadowAndFilter(object, context, hasShadow);
  726. // draw lines line by line
  727. for (var i = 0; i < lines.length; i++) {
  728. var linePositionX = lineWidth / 2 + offsetX;
  729. linePositionY += lineHeight;
  730. // no need to re-position X, cause we already set text align
  731. // @see https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/textAlign
  732. if (!util.isNil(stroke) && !stroke.isNone && lineWidth) {
  733. this.drawLetterSpacing(context, lines[i], lineMetrics[i], textAlign, linePositionX, linePositionY, letterSpacing, fillOpacity, strokeOpacity, opacity, true);
  734. }
  735. if (!util.isNil(fill)) {
  736. this.drawLetterSpacing(context, lines[i], lineMetrics[i], textAlign, linePositionX, linePositionY, letterSpacing, fillOpacity, strokeOpacity, opacity);
  737. }
  738. }
  739. };
  740. _proto.drawLetterSpacing = function drawLetterSpacing(context, text, lineMetrics, textAlign, x, y, letterSpacing, fillOpacity, strokeOpacity, opacity, isStroke) {
  741. if (isStroke === void 0) {
  742. isStroke = false;
  743. }
  744. // letterSpacing of 0 means normal, render all texts directly
  745. if (letterSpacing === 0) {
  746. if (isStroke) {
  747. this.strokeText(context, text, x, y, strokeOpacity);
  748. } else {
  749. this.fillText(context, text, x, y, fillOpacity, opacity);
  750. }
  751. return;
  752. }
  753. // draw text using left align
  754. var currentTextAlign = context.textAlign;
  755. context.textAlign = 'left';
  756. var currentPosition = x;
  757. if (textAlign === 'center' || textAlign === 'middle') {
  758. currentPosition = x - lineMetrics.width / 2;
  759. } else if (textAlign === 'right' || textAlign === 'end') {
  760. currentPosition = x - lineMetrics.width;
  761. }
  762. var stringArray = Array.from(text);
  763. var previousWidth = context.measureText(text).width;
  764. var currentWidth = 0;
  765. for (var i = 0; i < stringArray.length; ++i) {
  766. var currentChar = stringArray[i];
  767. if (isStroke) {
  768. this.strokeText(context, currentChar, currentPosition, y, strokeOpacity);
  769. } else {
  770. this.fillText(context, currentChar, currentPosition, y, fillOpacity, opacity);
  771. }
  772. currentWidth = context.measureText(text.substring(i + 1)).width;
  773. currentPosition += previousWidth - currentWidth + letterSpacing;
  774. previousWidth = currentWidth;
  775. }
  776. context.textAlign = currentTextAlign;
  777. };
  778. _proto.fillText = function fillText(context, text, x, y, fillOpacity, opacity) {
  779. var currentGlobalAlpha;
  780. var applyOpacity = !util.isNil(fillOpacity) && fillOpacity !== 1;
  781. if (applyOpacity) {
  782. currentGlobalAlpha = context.globalAlpha;
  783. context.globalAlpha = fillOpacity * opacity;
  784. }
  785. context.fillText(text, x, y);
  786. if (applyOpacity) {
  787. context.globalAlpha = currentGlobalAlpha;
  788. }
  789. };
  790. _proto.strokeText = function strokeText(context, text, x, y, strokeOpacity) {
  791. var currentGlobalAlpha;
  792. var applyOpacity = !util.isNil(strokeOpacity) && strokeOpacity !== 1;
  793. if (applyOpacity) {
  794. currentGlobalAlpha = context.globalAlpha;
  795. context.globalAlpha = strokeOpacity;
  796. }
  797. context.strokeText(text, x, y);
  798. if (applyOpacity) {
  799. context.globalAlpha = currentGlobalAlpha;
  800. }
  801. };
  802. return TextRenderer;
  803. }();
  804. var RectRenderer = /*#__PURE__*/function (_DefaultRenderer) {
  805. _inheritsLoose(RectRenderer, _DefaultRenderer);
  806. function RectRenderer() {
  807. return _DefaultRenderer.apply(this, arguments) || this;
  808. }
  809. return RectRenderer;
  810. }(DefaultRenderer);
  811. var CircleRenderer = /*#__PURE__*/function (_DefaultRenderer) {
  812. _inheritsLoose(CircleRenderer, _DefaultRenderer);
  813. function CircleRenderer() {
  814. return _DefaultRenderer.apply(this, arguments) || this;
  815. }
  816. return CircleRenderer;
  817. }(DefaultRenderer);
  818. var EllipseRenderer = /*#__PURE__*/function (_DefaultRenderer) {
  819. _inheritsLoose(EllipseRenderer, _DefaultRenderer);
  820. function EllipseRenderer() {
  821. return _DefaultRenderer.apply(this, arguments) || this;
  822. }
  823. return EllipseRenderer;
  824. }(DefaultRenderer);
  825. var LineRenderer = /*#__PURE__*/function (_DefaultRenderer) {
  826. _inheritsLoose(LineRenderer, _DefaultRenderer);
  827. function LineRenderer() {
  828. return _DefaultRenderer.apply(this, arguments) || this;
  829. }
  830. return LineRenderer;
  831. }(DefaultRenderer);
  832. var PolylineRenderer = /*#__PURE__*/function (_DefaultRenderer) {
  833. _inheritsLoose(PolylineRenderer, _DefaultRenderer);
  834. function PolylineRenderer() {
  835. return _DefaultRenderer.apply(this, arguments) || this;
  836. }
  837. return PolylineRenderer;
  838. }(DefaultRenderer);
  839. var PolygonRenderer = /*#__PURE__*/function (_DefaultRenderer) {
  840. _inheritsLoose(PolygonRenderer, _DefaultRenderer);
  841. function PolygonRenderer() {
  842. return _DefaultRenderer.apply(this, arguments) || this;
  843. }
  844. return PolygonRenderer;
  845. }(DefaultRenderer);
  846. var PathRenderer = /*#__PURE__*/function (_DefaultRenderer) {
  847. _inheritsLoose(PathRenderer, _DefaultRenderer);
  848. function PathRenderer() {
  849. return _DefaultRenderer.apply(this, arguments) || this;
  850. }
  851. return PathRenderer;
  852. }(DefaultRenderer);
  853. var Plugin = /*#__PURE__*/function (_AbstractRendererPlug) {
  854. _inheritsLoose(Plugin, _AbstractRendererPlug);
  855. function Plugin(options) {
  856. var _this;
  857. if (options === void 0) {
  858. options = {};
  859. }
  860. _this = _AbstractRendererPlug.call(this) || this;
  861. _this.options = void 0;
  862. _this.name = 'canvas-renderer';
  863. _this.options = options;
  864. return _this;
  865. }
  866. var _proto = Plugin.prototype;
  867. _proto.init = function init() {
  868. var _defaultStyleRenderer;
  869. var canvasRendererPluginOptions = _extends({
  870. dirtyObjectNumThreshold: 500,
  871. dirtyObjectRatioThreshold: 0.8
  872. }, this.options);
  873. // @ts-ignore
  874. var imagePool = this.context.imagePool;
  875. var defaultRenderer = new DefaultRenderer(imagePool);
  876. 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);
  877. this.context.defaultStyleRendererFactory = defaultStyleRendererFactory;
  878. this.context.styleRendererFactory = defaultStyleRendererFactory;
  879. this.addRenderingPlugin(new CanvasRendererPlugin(canvasRendererPluginOptions));
  880. };
  881. _proto.destroy = function destroy() {
  882. this.removeAllRenderingPlugins();
  883. delete this.context.defaultStyleRendererFactory;
  884. delete this.context.styleRendererFactory;
  885. };
  886. return Plugin;
  887. }(gLite.AbstractRendererPlugin);
  888. exports.CircleRenderer = CircleRenderer;
  889. exports.EllipseRenderer = EllipseRenderer;
  890. exports.ImageRenderer = ImageRenderer;
  891. exports.LineRenderer = LineRenderer;
  892. exports.PathRenderer = PathRenderer;
  893. exports.Plugin = Plugin;
  894. exports.PolygonRenderer = PolygonRenderer;
  895. exports.PolylineRenderer = PolylineRenderer;
  896. exports.RectRenderer = RectRenderer;
  897. exports.TextRenderer = TextRenderer;