index.esm.js 34 KB

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