graphic.ts 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816
  1. import Displayable, { DEFAULT_COMMON_STYLE } from '../graphic/Displayable';
  2. import PathProxy from '../core/PathProxy';
  3. import { GradientObject } from '../graphic/Gradient';
  4. import { ImagePatternObject, InnerImagePatternObject } from '../graphic/Pattern';
  5. import { LinearGradientObject } from '../graphic/LinearGradient';
  6. import { RadialGradientObject } from '../graphic/RadialGradient';
  7. import { ZRCanvasRenderingContext } from '../core/types';
  8. import { createOrUpdateImage, isImageReady } from '../graphic/helper/image';
  9. import { getCanvasGradient, isClipPathChanged } from './helper';
  10. import Path, { PathStyleProps } from '../graphic/Path';
  11. import ZRImage, { ImageStyleProps } from '../graphic/Image';
  12. import TSpan, {TSpanStyleProps} from '../graphic/TSpan';
  13. import { MatrixArray } from '../core/matrix';
  14. import { RADIAN_TO_DEGREE } from '../core/util';
  15. import { getLineDash } from './dashStyle';
  16. import { REDRAW_BIT, SHAPE_CHANGED_BIT } from '../graphic/constants';
  17. import type IncrementalDisplayable from '../graphic/IncrementalDisplayable';
  18. import { DEFAULT_FONT } from '../core/platform';
  19. const pathProxyForDraw = new PathProxy(true);
  20. // Not use el#hasStroke because style may be different.
  21. function styleHasStroke(style: PathStyleProps) {
  22. const stroke = style.stroke;
  23. return !(stroke == null || stroke === 'none' || !(style.lineWidth > 0));
  24. }
  25. // ignore lineWidth and must be string
  26. // Expected color but found '[' when color is gradient
  27. function isValidStrokeFillStyle(
  28. strokeOrFill: PathStyleProps['stroke'] | PathStyleProps['fill']
  29. ): strokeOrFill is string {
  30. return typeof strokeOrFill === 'string' && strokeOrFill !== 'none';
  31. }
  32. function styleHasFill(style: PathStyleProps) {
  33. const fill = style.fill;
  34. return fill != null && fill !== 'none';
  35. }
  36. function doFillPath(ctx: CanvasRenderingContext2D, style: PathStyleProps) {
  37. if (style.fillOpacity != null && style.fillOpacity !== 1) {
  38. const originalGlobalAlpha = ctx.globalAlpha;
  39. ctx.globalAlpha = style.fillOpacity * style.opacity;
  40. ctx.fill();
  41. // Set back globalAlpha
  42. ctx.globalAlpha = originalGlobalAlpha;
  43. }
  44. else {
  45. ctx.fill();
  46. }
  47. }
  48. function doStrokePath(ctx: CanvasRenderingContext2D, style: PathStyleProps) {
  49. if (style.strokeOpacity != null && style.strokeOpacity !== 1) {
  50. const originalGlobalAlpha = ctx.globalAlpha;
  51. ctx.globalAlpha = style.strokeOpacity * style.opacity;
  52. ctx.stroke();
  53. // Set back globalAlpha
  54. ctx.globalAlpha = originalGlobalAlpha;
  55. }
  56. else {
  57. ctx.stroke();
  58. }
  59. }
  60. export function createCanvasPattern(
  61. this: void,
  62. ctx: CanvasRenderingContext2D,
  63. pattern: ImagePatternObject,
  64. el: {dirty: () => void}
  65. ): CanvasPattern {
  66. const image = createOrUpdateImage(pattern.image, (pattern as InnerImagePatternObject).__image, el);
  67. if (isImageReady(image)) {
  68. const canvasPattern = ctx.createPattern(image, pattern.repeat || 'repeat');
  69. if (
  70. typeof DOMMatrix === 'function'
  71. && canvasPattern // image may be not ready
  72. && canvasPattern.setTransform // setTransform may not be supported in some old devices.
  73. ) {
  74. const matrix = new DOMMatrix();
  75. matrix.translateSelf((pattern.x || 0), (pattern.y || 0));
  76. matrix.rotateSelf(0, 0, (pattern.rotation || 0) * RADIAN_TO_DEGREE);
  77. matrix.scaleSelf((pattern.scaleX || 1), (pattern.scaleY || 1));
  78. canvasPattern.setTransform(matrix);
  79. }
  80. return canvasPattern;
  81. }
  82. }
  83. // Draw Path Elements
  84. function brushPath(ctx: CanvasRenderingContext2D, el: Path, style: PathStyleProps, inBatch: boolean) {
  85. let hasStroke = styleHasStroke(style);
  86. let hasFill = styleHasFill(style);
  87. const strokePercent = style.strokePercent;
  88. const strokePart = strokePercent < 1;
  89. // TODO Reduce path memory cost.
  90. const firstDraw = !el.path;
  91. // Create path for each element when:
  92. // 1. Element has interactions.
  93. // 2. Element draw part of the line.
  94. if ((!el.silent || strokePart) && firstDraw) {
  95. el.createPathProxy();
  96. }
  97. const path = el.path || pathProxyForDraw;
  98. const dirtyFlag = el.__dirty;
  99. if (!inBatch) {
  100. const fill = style.fill;
  101. const stroke = style.stroke;
  102. const hasFillGradient = hasFill && !!(fill as GradientObject).colorStops;
  103. const hasStrokeGradient = hasStroke && !!(stroke as GradientObject).colorStops;
  104. const hasFillPattern = hasFill && !!(fill as ImagePatternObject).image;
  105. const hasStrokePattern = hasStroke && !!(stroke as ImagePatternObject).image;
  106. let fillGradient;
  107. let strokeGradient;
  108. let fillPattern;
  109. let strokePattern;
  110. let rect;
  111. if (hasFillGradient || hasStrokeGradient) {
  112. rect = el.getBoundingRect();
  113. }
  114. // Update gradient because bounding rect may changed
  115. if (hasFillGradient) {
  116. fillGradient = dirtyFlag
  117. ? getCanvasGradient(ctx, fill as (LinearGradientObject | RadialGradientObject), rect)
  118. : el.__canvasFillGradient;
  119. // No need to clear cache when fill is not gradient.
  120. // It will always been updated when fill changed back to gradient.
  121. el.__canvasFillGradient = fillGradient;
  122. }
  123. if (hasStrokeGradient) {
  124. strokeGradient = dirtyFlag
  125. ? getCanvasGradient(ctx, stroke as (LinearGradientObject | RadialGradientObject), rect)
  126. : el.__canvasStrokeGradient;
  127. el.__canvasStrokeGradient = strokeGradient;
  128. }
  129. if (hasFillPattern) {
  130. // Pattern might be null if image not ready (even created from dataURI)
  131. fillPattern = (dirtyFlag || !el.__canvasFillPattern)
  132. ? createCanvasPattern(ctx, fill as ImagePatternObject, el)
  133. : el.__canvasFillPattern;
  134. el.__canvasFillPattern = fillPattern;
  135. }
  136. if (hasStrokePattern) {
  137. // Pattern might be null if image not ready (even created from dataURI)
  138. strokePattern = (dirtyFlag || !el.__canvasStrokePattern)
  139. ? createCanvasPattern(ctx, stroke as ImagePatternObject, el)
  140. : el.__canvasStrokePattern;
  141. el.__canvasStrokePattern = fillPattern;
  142. }
  143. // Use the gradient or pattern
  144. if (hasFillGradient) {
  145. // PENDING If may have affect the state
  146. ctx.fillStyle = fillGradient;
  147. }
  148. else if (hasFillPattern) {
  149. if (fillPattern) { // createCanvasPattern may return false if image is not ready.
  150. ctx.fillStyle = fillPattern;
  151. }
  152. else {
  153. // Don't fill if image is not ready
  154. hasFill = false;
  155. }
  156. }
  157. if (hasStrokeGradient) {
  158. ctx.strokeStyle = strokeGradient;
  159. }
  160. else if (hasStrokePattern) {
  161. if (strokePattern) {
  162. ctx.strokeStyle = strokePattern;
  163. }
  164. else {
  165. // Don't stroke if image is not ready
  166. hasStroke = false;
  167. }
  168. }
  169. }
  170. // Update path sx, sy
  171. const scale = el.getGlobalScale();
  172. path.setScale(scale[0], scale[1], el.segmentIgnoreThreshold);
  173. let lineDash;
  174. let lineDashOffset;
  175. if (ctx.setLineDash && style.lineDash) {
  176. [lineDash, lineDashOffset] = getLineDash(el);
  177. }
  178. let needsRebuild = true;
  179. if (firstDraw || (dirtyFlag & SHAPE_CHANGED_BIT)) {
  180. path.setDPR((ctx as any).dpr);
  181. if (strokePart) {
  182. // Use rebuildPath for percent stroke, so no context.
  183. path.setContext(null);
  184. }
  185. else {
  186. path.setContext(ctx);
  187. needsRebuild = false;
  188. }
  189. path.reset();
  190. el.buildPath(path, el.shape, inBatch);
  191. path.toStatic();
  192. // Clear path dirty flag
  193. el.pathUpdated();
  194. }
  195. // Not support separate fill and stroke. For the compatibility of SVG
  196. if (needsRebuild) {
  197. path.rebuildPath(ctx, strokePart ? strokePercent : 1);
  198. }
  199. if (lineDash) {
  200. ctx.setLineDash(lineDash);
  201. ctx.lineDashOffset = lineDashOffset;
  202. }
  203. if (!inBatch) {
  204. if (style.strokeFirst) {
  205. if (hasStroke) {
  206. doStrokePath(ctx, style);
  207. }
  208. if (hasFill) {
  209. doFillPath(ctx, style);
  210. }
  211. }
  212. else {
  213. if (hasFill) {
  214. doFillPath(ctx, style);
  215. }
  216. if (hasStroke) {
  217. doStrokePath(ctx, style);
  218. }
  219. }
  220. }
  221. if (lineDash) {
  222. // PENDING
  223. // Remove lineDash
  224. ctx.setLineDash([]);
  225. }
  226. }
  227. // Draw Image Elements
  228. function brushImage(ctx: CanvasRenderingContext2D, el: ZRImage, style: ImageStyleProps) {
  229. const image = el.__image = createOrUpdateImage(
  230. style.image,
  231. el.__image,
  232. el,
  233. el.onload
  234. );
  235. if (!image || !isImageReady(image)) {
  236. return;
  237. }
  238. const x = style.x || 0;
  239. const y = style.y || 0;
  240. let width = el.getWidth();
  241. let height = el.getHeight();
  242. const aspect = image.width / image.height;
  243. if (width == null && height != null) {
  244. // Keep image/height ratio
  245. width = height * aspect;
  246. }
  247. else if (height == null && width != null) {
  248. height = width / aspect;
  249. }
  250. else if (width == null && height == null) {
  251. width = image.width;
  252. height = image.height;
  253. }
  254. if (style.sWidth && style.sHeight) {
  255. const sx = style.sx || 0;
  256. const sy = style.sy || 0;
  257. ctx.drawImage(
  258. image,
  259. sx, sy, style.sWidth, style.sHeight,
  260. x, y, width, height
  261. );
  262. }
  263. else if (style.sx && style.sy) {
  264. const sx = style.sx;
  265. const sy = style.sy;
  266. const sWidth = width - sx;
  267. const sHeight = height - sy;
  268. ctx.drawImage(
  269. image,
  270. sx, sy, sWidth, sHeight,
  271. x, y, width, height
  272. );
  273. }
  274. else {
  275. ctx.drawImage(image, x, y, width, height);
  276. }
  277. }
  278. // Draw Text Elements
  279. function brushText(ctx: CanvasRenderingContext2D, el: TSpan, style: TSpanStyleProps) {
  280. let text = style.text;
  281. // Convert to string
  282. text != null && (text += '');
  283. if (text) {
  284. ctx.font = style.font || DEFAULT_FONT;
  285. ctx.textAlign = style.textAlign;
  286. ctx.textBaseline = style.textBaseline;
  287. let lineDash;
  288. let lineDashOffset;
  289. if (ctx.setLineDash && style.lineDash) {
  290. [lineDash, lineDashOffset] = getLineDash(el);
  291. }
  292. if (lineDash) {
  293. ctx.setLineDash(lineDash);
  294. ctx.lineDashOffset = lineDashOffset;
  295. }
  296. if (style.strokeFirst) {
  297. if (styleHasStroke(style)) {
  298. ctx.strokeText(text, style.x, style.y);
  299. }
  300. if (styleHasFill(style)) {
  301. ctx.fillText(text, style.x, style.y);
  302. }
  303. }
  304. else {
  305. if (styleHasFill(style)) {
  306. ctx.fillText(text, style.x, style.y);
  307. }
  308. if (styleHasStroke(style)) {
  309. ctx.strokeText(text, style.x, style.y);
  310. }
  311. }
  312. if (lineDash) {
  313. // Remove lineDash
  314. ctx.setLineDash([]);
  315. }
  316. }
  317. }
  318. const SHADOW_NUMBER_PROPS = ['shadowBlur', 'shadowOffsetX', 'shadowOffsetY'] as const;
  319. const STROKE_PROPS = [
  320. ['lineCap', 'butt'], ['lineJoin', 'miter'], ['miterLimit', 10]
  321. ] as const;
  322. type AllStyleOption = PathStyleProps | TSpanStyleProps | ImageStyleProps;
  323. // type ShadowPropNames = typeof SHADOW_PROPS[number][0];
  324. // type StrokePropNames = typeof STROKE_PROPS[number][0];
  325. // type DrawPropNames = typeof DRAW_PROPS[number][0];
  326. function bindCommonProps(
  327. ctx: CanvasRenderingContext2D,
  328. style: AllStyleOption,
  329. prevStyle: AllStyleOption,
  330. forceSetAll: boolean,
  331. scope: BrushScope
  332. ): boolean {
  333. let styleChanged = false;
  334. if (!forceSetAll) {
  335. prevStyle = prevStyle || {};
  336. // Shared same style.
  337. if (style === prevStyle) {
  338. return false;
  339. }
  340. }
  341. if (forceSetAll || style.opacity !== prevStyle.opacity) {
  342. flushPathDrawn(ctx, scope);
  343. styleChanged = true;
  344. // Ensure opacity is between 0 ~ 1. Invalid opacity will lead to a failure set and use the leaked opacity from the previous.
  345. const opacity = Math.max(Math.min(style.opacity, 1), 0);
  346. ctx.globalAlpha = isNaN(opacity) ? DEFAULT_COMMON_STYLE.opacity : opacity;
  347. }
  348. if (forceSetAll || style.blend !== prevStyle.blend) {
  349. if (!styleChanged) {
  350. flushPathDrawn(ctx, scope);
  351. styleChanged = true;
  352. }
  353. ctx.globalCompositeOperation = style.blend || DEFAULT_COMMON_STYLE.blend;
  354. }
  355. for (let i = 0; i < SHADOW_NUMBER_PROPS.length; i++) {
  356. const propName = SHADOW_NUMBER_PROPS[i];
  357. if (forceSetAll || style[propName] !== prevStyle[propName]) {
  358. if (!styleChanged) {
  359. flushPathDrawn(ctx, scope);
  360. styleChanged = true;
  361. }
  362. // FIXME Invalid property value will cause style leak from previous element.
  363. ctx[propName] = (ctx as ZRCanvasRenderingContext).dpr * (style[propName] || 0);
  364. }
  365. }
  366. if (forceSetAll || style.shadowColor !== prevStyle.shadowColor) {
  367. if (!styleChanged) {
  368. flushPathDrawn(ctx, scope);
  369. styleChanged = true;
  370. }
  371. ctx.shadowColor = style.shadowColor || DEFAULT_COMMON_STYLE.shadowColor;
  372. }
  373. return styleChanged;
  374. }
  375. function bindPathAndTextCommonStyle(
  376. ctx: CanvasRenderingContext2D,
  377. el: TSpan | Path,
  378. prevEl: TSpan | Path,
  379. forceSetAll: boolean,
  380. scope: BrushScope
  381. ) {
  382. const style = getStyle(el, scope.inHover);
  383. const prevStyle = forceSetAll
  384. ? null
  385. : (prevEl && getStyle(prevEl, scope.inHover) || {});
  386. // Shared same style. prevStyle will be null if forceSetAll.
  387. if (style === prevStyle) {
  388. return false;
  389. }
  390. let styleChanged = bindCommonProps(ctx, style, prevStyle, forceSetAll, scope);
  391. if (forceSetAll || style.fill !== prevStyle.fill) {
  392. if (!styleChanged) {
  393. // Flush before set
  394. flushPathDrawn(ctx, scope);
  395. styleChanged = true;
  396. }
  397. isValidStrokeFillStyle(style.fill) && (ctx.fillStyle = style.fill);
  398. }
  399. if (forceSetAll || style.stroke !== prevStyle.stroke) {
  400. if (!styleChanged) {
  401. flushPathDrawn(ctx, scope);
  402. styleChanged = true;
  403. }
  404. isValidStrokeFillStyle(style.stroke) && (ctx.strokeStyle = style.stroke);
  405. }
  406. if (forceSetAll || style.opacity !== prevStyle.opacity) {
  407. if (!styleChanged) {
  408. flushPathDrawn(ctx, scope);
  409. styleChanged = true;
  410. }
  411. ctx.globalAlpha = style.opacity == null ? 1 : style.opacity;
  412. }
  413. if (el.hasStroke()) {
  414. const lineWidth = style.lineWidth;
  415. const newLineWidth = lineWidth / (
  416. (style.strokeNoScale && el.getLineScale) ? el.getLineScale() : 1
  417. );
  418. if (ctx.lineWidth !== newLineWidth) {
  419. if (!styleChanged) {
  420. flushPathDrawn(ctx, scope);
  421. styleChanged = true;
  422. }
  423. ctx.lineWidth = newLineWidth;
  424. }
  425. }
  426. for (let i = 0; i < STROKE_PROPS.length; i++) {
  427. const prop = STROKE_PROPS[i];
  428. const propName = prop[0];
  429. if (forceSetAll || style[propName] !== prevStyle[propName]) {
  430. if (!styleChanged) {
  431. flushPathDrawn(ctx, scope);
  432. styleChanged = true;
  433. }
  434. // FIXME Invalid property value will cause style leak from previous element.
  435. (ctx as any)[propName] = style[propName] || prop[1];
  436. }
  437. }
  438. return styleChanged;
  439. }
  440. function bindImageStyle(
  441. ctx: CanvasRenderingContext2D,
  442. el: ZRImage,
  443. prevEl: ZRImage,
  444. // forceSetAll must be true if prevEl is null
  445. forceSetAll: boolean,
  446. scope: BrushScope
  447. ) {
  448. return bindCommonProps(
  449. ctx,
  450. getStyle(el, scope.inHover),
  451. prevEl && getStyle(prevEl, scope.inHover),
  452. forceSetAll,
  453. scope
  454. );
  455. }
  456. function setContextTransform(ctx: CanvasRenderingContext2D, el: Displayable) {
  457. const m = el.transform;
  458. const dpr = (ctx as ZRCanvasRenderingContext).dpr || 1;
  459. if (m) {
  460. ctx.setTransform(dpr * m[0], dpr * m[1], dpr * m[2], dpr * m[3], dpr * m[4], dpr * m[5]);
  461. }
  462. else {
  463. ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
  464. }
  465. }
  466. function updateClipStatus(clipPaths: Path[], ctx: CanvasRenderingContext2D, scope: BrushScope) {
  467. let allClipped = false;
  468. for (let i = 0; i < clipPaths.length; i++) {
  469. const clipPath = clipPaths[i];
  470. // Ignore draw following elements if clipPath has zero area.
  471. allClipped = allClipped || clipPath.isZeroArea();
  472. setContextTransform(ctx, clipPath);
  473. ctx.beginPath();
  474. clipPath.buildPath(ctx, clipPath.shape);
  475. ctx.clip();
  476. }
  477. scope.allClipped = allClipped;
  478. }
  479. function isTransformChanged(m0: MatrixArray, m1: MatrixArray): boolean {
  480. if (m0 && m1) {
  481. return m0[0] !== m1[0]
  482. || m0[1] !== m1[1]
  483. || m0[2] !== m1[2]
  484. || m0[3] !== m1[3]
  485. || m0[4] !== m1[4]
  486. || m0[5] !== m1[5];
  487. }
  488. else if (!m0 && !m1) { // All identity matrix.
  489. return false;
  490. }
  491. return true;
  492. }
  493. const DRAW_TYPE_PATH = 1;
  494. const DRAW_TYPE_IMAGE = 2;
  495. const DRAW_TYPE_TEXT = 3;
  496. const DRAW_TYPE_INCREMENTAL = 4;
  497. export type BrushScope = {
  498. inHover: boolean
  499. // width / height of viewport
  500. viewWidth: number
  501. viewHeight: number
  502. // Status for clipping
  503. prevElClipPaths?: Path[]
  504. prevEl?: Displayable
  505. allClipped?: boolean // If the whole element can be clipped
  506. // Status for batching
  507. batchFill?: string
  508. batchStroke?: string
  509. lastDrawType?: number
  510. }
  511. // If path can be batched
  512. function canPathBatch(style: PathStyleProps) {
  513. const hasFill = styleHasFill(style);
  514. const hasStroke = styleHasStroke(style);
  515. return !(
  516. // Line dash is dynamically set in brush function.
  517. style.lineDash
  518. // Can't batch if element is both set fill and stroke. Or both not set
  519. || !(+hasFill ^ +hasStroke)
  520. // Can't batch if element is drawn with gradient or pattern.
  521. || (hasFill && typeof style.fill !== 'string')
  522. || (hasStroke && typeof style.stroke !== 'string')
  523. // Can't batch if element only stroke part of line.
  524. || style.strokePercent < 1
  525. // Has stroke or fill opacity
  526. || style.strokeOpacity < 1
  527. || style.fillOpacity < 1
  528. );
  529. }
  530. function flushPathDrawn(ctx: CanvasRenderingContext2D, scope: BrushScope) {
  531. // Force flush all after drawn last element
  532. scope.batchFill && ctx.fill();
  533. scope.batchStroke && ctx.stroke();
  534. scope.batchFill = '';
  535. scope.batchStroke = '';
  536. }
  537. function getStyle(el: Displayable, inHover?: boolean) {
  538. return inHover ? (el.__hoverStyle || el.style) : el.style;
  539. }
  540. export function brushSingle(ctx: CanvasRenderingContext2D, el: Displayable) {
  541. brush(ctx, el, { inHover: false, viewWidth: 0, viewHeight: 0 }, true);
  542. }
  543. // Brush different type of elements.
  544. export function brush(
  545. ctx: CanvasRenderingContext2D,
  546. el: Displayable,
  547. scope: BrushScope,
  548. isLast: boolean
  549. ) {
  550. const m = el.transform;
  551. if (!el.shouldBePainted(scope.viewWidth, scope.viewHeight, false, false)) {
  552. // Needs to mark el rendered.
  553. // Or this element will always been rendered in progressive rendering.
  554. // But other dirty bit should not be cleared, otherwise it cause the shape
  555. // can not be updated in this case.
  556. el.__dirty &= ~REDRAW_BIT;
  557. el.__isRendered = false;
  558. return;
  559. }
  560. // HANDLE CLIPPING
  561. const clipPaths = el.__clipPaths;
  562. const prevElClipPaths = scope.prevElClipPaths;
  563. let forceSetTransform = false;
  564. let forceSetStyle = false;
  565. // Optimize when clipping on group with several elements
  566. if (!prevElClipPaths || isClipPathChanged(clipPaths, prevElClipPaths)) {
  567. // If has previous clipping state, restore from it
  568. if (prevElClipPaths && prevElClipPaths.length) {
  569. // Flush restore
  570. flushPathDrawn(ctx, scope);
  571. ctx.restore();
  572. // Must set all style and transform because context changed by restore
  573. forceSetStyle = forceSetTransform = true;
  574. scope.prevElClipPaths = null;
  575. scope.allClipped = false;
  576. // Reset prevEl since context has been restored
  577. scope.prevEl = null;
  578. }
  579. // New clipping state
  580. if (clipPaths && clipPaths.length) {
  581. // Flush before clip
  582. flushPathDrawn(ctx, scope);
  583. ctx.save();
  584. updateClipStatus(clipPaths, ctx, scope);
  585. // Must set transform because it's changed when clip.
  586. forceSetTransform = true;
  587. }
  588. scope.prevElClipPaths = clipPaths;
  589. }
  590. // Not rendering elements if it's clipped by a zero area path.
  591. // Or it may cause bug on some version of IE11 (like 11.0.9600.178**),
  592. // where exception "unexpected call to method or property access"
  593. // might be thrown when calling ctx.fill or ctx.stroke after a path
  594. // whose area size is zero is drawn and ctx.clip() is called and
  595. // shadowBlur is set. See #4572, #3112, #5777.
  596. // (e.g.,
  597. // ctx.moveTo(10, 10);
  598. // ctx.lineTo(20, 10);
  599. // ctx.closePath();
  600. // ctx.clip();
  601. // ctx.shadowBlur = 10;
  602. // ...
  603. // ctx.fill();
  604. // )
  605. if (scope.allClipped) {
  606. el.__isRendered = false;
  607. return;
  608. }
  609. // START BRUSH
  610. el.beforeBrush && el.beforeBrush();
  611. el.innerBeforeBrush();
  612. const prevEl = scope.prevEl;
  613. // TODO el type changed.
  614. if (!prevEl) {
  615. forceSetStyle = forceSetTransform = true;
  616. }
  617. let canBatchPath = el instanceof Path // Only path supports batch
  618. && el.autoBatch
  619. && canPathBatch(el.style);
  620. if (forceSetTransform || isTransformChanged(m, prevEl.transform)) {
  621. // Flush
  622. flushPathDrawn(ctx, scope);
  623. setContextTransform(ctx, el);
  624. }
  625. else if (!canBatchPath) {
  626. // Flush
  627. flushPathDrawn(ctx, scope);
  628. }
  629. const style = getStyle(el, scope.inHover);
  630. if (el instanceof Path) {
  631. // PENDING do we need to rebind all style if displayable type changed?
  632. if (scope.lastDrawType !== DRAW_TYPE_PATH) {
  633. forceSetStyle = true;
  634. scope.lastDrawType = DRAW_TYPE_PATH;
  635. }
  636. bindPathAndTextCommonStyle(ctx, el as Path, prevEl as Path, forceSetStyle, scope);
  637. // Begin path at start
  638. if (!canBatchPath || (!scope.batchFill && !scope.batchStroke)) {
  639. ctx.beginPath();
  640. }
  641. brushPath(ctx, el as Path, style, canBatchPath);
  642. if (canBatchPath) {
  643. scope.batchFill = style.fill as string || '';
  644. scope.batchStroke = style.stroke as string || '';
  645. }
  646. }
  647. else {
  648. if (el instanceof TSpan) {
  649. if (scope.lastDrawType !== DRAW_TYPE_TEXT) {
  650. forceSetStyle = true;
  651. scope.lastDrawType = DRAW_TYPE_TEXT;
  652. }
  653. bindPathAndTextCommonStyle(ctx, el as TSpan, prevEl as TSpan, forceSetStyle, scope);
  654. brushText(ctx, el as TSpan, style);
  655. }
  656. else if (el instanceof ZRImage) {
  657. if (scope.lastDrawType !== DRAW_TYPE_IMAGE) {
  658. forceSetStyle = true;
  659. scope.lastDrawType = DRAW_TYPE_IMAGE;
  660. }
  661. bindImageStyle(ctx, el as ZRImage, prevEl as ZRImage, forceSetStyle, scope);
  662. brushImage(ctx, el as ZRImage, style);
  663. }
  664. // Assume it's a IncrementalDisplayable
  665. else if ((el as IncrementalDisplayable).getTemporalDisplayables) {
  666. if (scope.lastDrawType !== DRAW_TYPE_INCREMENTAL) {
  667. forceSetStyle = true;
  668. scope.lastDrawType = DRAW_TYPE_INCREMENTAL;
  669. }
  670. brushIncremental(ctx, el as IncrementalDisplayable, scope);
  671. }
  672. }
  673. if (canBatchPath && isLast) {
  674. flushPathDrawn(ctx, scope);
  675. }
  676. el.innerAfterBrush();
  677. el.afterBrush && el.afterBrush();
  678. scope.prevEl = el;
  679. // Mark as painted.
  680. el.__dirty = 0;
  681. el.__isRendered = true;
  682. }
  683. function brushIncremental(
  684. ctx: CanvasRenderingContext2D,
  685. el: IncrementalDisplayable,
  686. scope: BrushScope
  687. ) {
  688. let displayables = el.getDisplayables();
  689. let temporalDisplayables = el.getTemporalDisplayables();
  690. // Provide an inner scope.
  691. // Save current context and restore after brushed.
  692. ctx.save();
  693. let innerScope: BrushScope = {
  694. prevElClipPaths: null,
  695. prevEl: null,
  696. allClipped: false,
  697. viewWidth: scope.viewWidth,
  698. viewHeight: scope.viewHeight,
  699. inHover: scope.inHover
  700. };
  701. let i;
  702. let len;
  703. // Render persistant displayables.
  704. for (i = el.getCursor(), len = displayables.length; i < len; i++) {
  705. const displayable = displayables[i];
  706. displayable.beforeBrush && displayable.beforeBrush();
  707. displayable.innerBeforeBrush();
  708. brush(ctx, displayable, innerScope, i === len - 1);
  709. displayable.innerAfterBrush();
  710. displayable.afterBrush && displayable.afterBrush();
  711. innerScope.prevEl = displayable;
  712. }
  713. // Render temporary displayables.
  714. for (let i = 0, len = temporalDisplayables.length; i < len; i++) {
  715. const displayable = temporalDisplayables[i];
  716. displayable.beforeBrush && displayable.beforeBrush();
  717. displayable.innerBeforeBrush();
  718. brush(ctx, displayable, innerScope, i === len - 1);
  719. displayable.innerAfterBrush();
  720. displayable.afterBrush && displayable.afterBrush();
  721. innerScope.prevEl = displayable;
  722. }
  723. el.clearTemporalDisplayables();
  724. el.notClear = true;
  725. ctx.restore();
  726. }