98f4b424282d984672a273cb8503528dca6792e9d43eed29ea30a7ad2a6ab7e02309a18d85b0b7baa2832532fa6a15f098fd438444b5b760548c4a41c8a968 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996
  1. /**
  2. * Path 代理,可以在`buildPath`中用于替代`ctx`, 会保存每个path操作的命令到pathCommands属性中
  3. * 可以用于 isInsidePath 判断以及获取boundingRect
  4. */
  5. // TODO getTotalLength, getPointAtLength, arcTo
  6. /* global Float32Array */
  7. import * as vec2 from './vector';
  8. import BoundingRect from './BoundingRect';
  9. import {devicePixelRatio as dpr} from '../config';
  10. import { fromLine, fromCubic, fromQuadratic, fromArc } from './bbox';
  11. import { cubicLength, cubicSubdivide, quadraticLength, quadraticSubdivide } from './curve';
  12. const CMD = {
  13. M: 1,
  14. L: 2,
  15. C: 3,
  16. Q: 4,
  17. A: 5,
  18. Z: 6,
  19. // Rect
  20. R: 7
  21. };
  22. // const CMD_MEM_SIZE = {
  23. // M: 3,
  24. // L: 3,
  25. // C: 7,
  26. // Q: 5,
  27. // A: 9,
  28. // R: 5,
  29. // Z: 1
  30. // };
  31. interface ExtendedCanvasRenderingContext2D extends CanvasRenderingContext2D {
  32. dpr?: number
  33. }
  34. const tmpOutX: number[] = [];
  35. const tmpOutY: number[] = [];
  36. const min: number[] = [];
  37. const max: number[] = [];
  38. const min2: number[] = [];
  39. const max2: number[] = [];
  40. const mathMin = Math.min;
  41. const mathMax = Math.max;
  42. const mathCos = Math.cos;
  43. const mathSin = Math.sin;
  44. const mathAbs = Math.abs;
  45. const PI = Math.PI;
  46. const PI2 = PI * 2;
  47. const hasTypedArray = typeof Float32Array !== 'undefined';
  48. const tmpAngles: number[] = [];
  49. function modPI2(radian: number) {
  50. // It's much more stable to mod N instedof PI
  51. const n = Math.round(radian / PI * 1e8) / 1e8;
  52. return (n % 2) * PI;
  53. }
  54. /**
  55. * Normalize start and end angles.
  56. * startAngle will be normalized to 0 ~ PI*2
  57. * sweepAngle(endAngle - startAngle) will be normalized to 0 ~ PI*2 if clockwise.
  58. * -PI*2 ~ 0 if anticlockwise.
  59. */
  60. export function normalizeArcAngles(angles: number[], anticlockwise: boolean): void {
  61. let newStartAngle = modPI2(angles[0]);
  62. if (newStartAngle < 0) {
  63. // Normlize to 0 - PI2
  64. newStartAngle += PI2;
  65. }
  66. let delta = newStartAngle - angles[0];
  67. let newEndAngle = angles[1];
  68. newEndAngle += delta;
  69. // https://github.com/chromium/chromium/blob/c20d681c9c067c4e15bb1408f17114b9e8cba294/third_party/blink/renderer/modules/canvas/canvas2d/canvas_path.cc#L184
  70. // Is circle
  71. if (!anticlockwise && newEndAngle - newStartAngle >= PI2) {
  72. newEndAngle = newStartAngle + PI2;
  73. }
  74. else if (anticlockwise && newStartAngle - newEndAngle >= PI2) {
  75. newEndAngle = newStartAngle - PI2;
  76. }
  77. // Make startAngle < endAngle when clockwise, otherwise endAngle < startAngle.
  78. // The sweep angle can never been larger than P2.
  79. else if (!anticlockwise && newStartAngle > newEndAngle) {
  80. newEndAngle = newStartAngle + (PI2 - modPI2(newStartAngle - newEndAngle));
  81. }
  82. else if (anticlockwise && newStartAngle < newEndAngle) {
  83. newEndAngle = newStartAngle - (PI2 - modPI2(newEndAngle - newStartAngle));
  84. }
  85. angles[0] = newStartAngle;
  86. angles[1] = newEndAngle;
  87. }
  88. export default class PathProxy {
  89. dpr = 1
  90. data: number[] | Float32Array
  91. /**
  92. * Version is for tracking if the path has been changed.
  93. */
  94. private _version: number
  95. /**
  96. * If save path data.
  97. */
  98. private _saveData: boolean
  99. /**
  100. * If the line segment is too small to draw. It will be added to the pending pt.
  101. * It will be added if the subpath needs to be finished before stroke, fill, or starting a new subpath.
  102. */
  103. private _pendingPtX: number;
  104. private _pendingPtY: number;
  105. // Distance of pending pt to previous point.
  106. // 0 if there is no pending point.
  107. // Only update the pending pt when distance is larger.
  108. private _pendingPtDist: number;
  109. private _ctx: ExtendedCanvasRenderingContext2D
  110. private _xi = 0
  111. private _yi = 0
  112. private _x0 = 0
  113. private _y0 = 0
  114. private _len = 0
  115. // Calculating path len and seg len.
  116. private _pathSegLen: number[]
  117. private _pathLen: number
  118. // Unit x, Unit y. Provide for avoiding drawing that too short line segment
  119. private _ux: number
  120. private _uy: number
  121. static CMD = CMD
  122. constructor(notSaveData?: boolean) {
  123. if (notSaveData) {
  124. this._saveData = false;
  125. }
  126. if (this._saveData) {
  127. this.data = [];
  128. }
  129. }
  130. increaseVersion() {
  131. this._version++;
  132. }
  133. /**
  134. * Version can be used outside for compare if the path is changed.
  135. * For example to determine if need to update svg d str in svg renderer.
  136. */
  137. getVersion() {
  138. return this._version;
  139. }
  140. /**
  141. * @readOnly
  142. */
  143. setScale(sx: number, sy: number, segmentIgnoreThreshold?: number) {
  144. // Compat. Previously there is no segmentIgnoreThreshold.
  145. segmentIgnoreThreshold = segmentIgnoreThreshold || 0;
  146. if (segmentIgnoreThreshold > 0) {
  147. this._ux = mathAbs(segmentIgnoreThreshold / dpr / sx) || 0;
  148. this._uy = mathAbs(segmentIgnoreThreshold / dpr / sy) || 0;
  149. }
  150. }
  151. setDPR(dpr: number) {
  152. this.dpr = dpr;
  153. }
  154. setContext(ctx: ExtendedCanvasRenderingContext2D) {
  155. this._ctx = ctx;
  156. }
  157. getContext(): ExtendedCanvasRenderingContext2D {
  158. return this._ctx;
  159. }
  160. beginPath() {
  161. this._ctx && this._ctx.beginPath();
  162. this.reset();
  163. return this;
  164. }
  165. /**
  166. * Reset path data.
  167. */
  168. reset() {
  169. // Reset
  170. if (this._saveData) {
  171. this._len = 0;
  172. }
  173. if (this._pathSegLen) {
  174. this._pathSegLen = null;
  175. this._pathLen = 0;
  176. }
  177. // Update version
  178. this._version++;
  179. }
  180. moveTo(x: number, y: number) {
  181. // Add pending point for previous path.
  182. this._drawPendingPt();
  183. this.addData(CMD.M, x, y);
  184. this._ctx && this._ctx.moveTo(x, y);
  185. // x0, y0, xi, yi 是记录在 _dashedXXXXTo 方法中使用
  186. // xi, yi 记录当前点, x0, y0 在 closePath 的时候回到起始点。
  187. // 有可能在 beginPath 之后直接调用 lineTo,这时候 x0, y0 需要
  188. // 在 lineTo 方法中记录,这里先不考虑这种情况,dashed line 也只在 IE10- 中不支持
  189. this._x0 = x;
  190. this._y0 = y;
  191. this._xi = x;
  192. this._yi = y;
  193. return this;
  194. }
  195. lineTo(x: number, y: number) {
  196. const dx = mathAbs(x - this._xi);
  197. const dy = mathAbs(y - this._yi);
  198. const exceedUnit = dx > this._ux || dy > this._uy;
  199. this.addData(CMD.L, x, y);
  200. if (this._ctx && exceedUnit) {
  201. this._ctx.lineTo(x, y);
  202. }
  203. if (exceedUnit) {
  204. this._xi = x;
  205. this._yi = y;
  206. this._pendingPtDist = 0;
  207. }
  208. else {
  209. const d2 = dx * dx + dy * dy;
  210. // Only use the farthest pending point.
  211. if (d2 > this._pendingPtDist) {
  212. this._pendingPtX = x;
  213. this._pendingPtY = y;
  214. this._pendingPtDist = d2;
  215. }
  216. }
  217. return this;
  218. }
  219. bezierCurveTo(x1: number, y1: number, x2: number, y2: number, x3: number, y3: number) {
  220. this._drawPendingPt();
  221. this.addData(CMD.C, x1, y1, x2, y2, x3, y3);
  222. if (this._ctx) {
  223. this._ctx.bezierCurveTo(x1, y1, x2, y2, x3, y3);
  224. }
  225. this._xi = x3;
  226. this._yi = y3;
  227. return this;
  228. }
  229. quadraticCurveTo(x1: number, y1: number, x2: number, y2: number) {
  230. this._drawPendingPt();
  231. this.addData(CMD.Q, x1, y1, x2, y2);
  232. if (this._ctx) {
  233. this._ctx.quadraticCurveTo(x1, y1, x2, y2);
  234. }
  235. this._xi = x2;
  236. this._yi = y2;
  237. return this;
  238. }
  239. arc(cx: number, cy: number, r: number, startAngle: number, endAngle: number, anticlockwise?: boolean) {
  240. this._drawPendingPt();
  241. tmpAngles[0] = startAngle;
  242. tmpAngles[1] = endAngle;
  243. normalizeArcAngles(tmpAngles, anticlockwise);
  244. startAngle = tmpAngles[0];
  245. endAngle = tmpAngles[1];
  246. let delta = endAngle - startAngle;
  247. this.addData(
  248. CMD.A, cx, cy, r, r, startAngle, delta, 0, anticlockwise ? 0 : 1
  249. );
  250. this._ctx && this._ctx.arc(cx, cy, r, startAngle, endAngle, anticlockwise);
  251. this._xi = mathCos(endAngle) * r + cx;
  252. this._yi = mathSin(endAngle) * r + cy;
  253. return this;
  254. }
  255. // TODO
  256. arcTo(x1: number, y1: number, x2: number, y2: number, radius: number) {
  257. this._drawPendingPt();
  258. if (this._ctx) {
  259. this._ctx.arcTo(x1, y1, x2, y2, radius);
  260. }
  261. return this;
  262. }
  263. // TODO
  264. rect(x: number, y: number, w: number, h: number) {
  265. this._drawPendingPt();
  266. this._ctx && this._ctx.rect(x, y, w, h);
  267. this.addData(CMD.R, x, y, w, h);
  268. return this;
  269. }
  270. closePath() {
  271. // Add pending point for previous path.
  272. this._drawPendingPt();
  273. this.addData(CMD.Z);
  274. const ctx = this._ctx;
  275. const x0 = this._x0;
  276. const y0 = this._y0;
  277. if (ctx) {
  278. ctx.closePath();
  279. }
  280. this._xi = x0;
  281. this._yi = y0;
  282. return this;
  283. }
  284. fill(ctx: CanvasRenderingContext2D) {
  285. ctx && ctx.fill();
  286. this.toStatic();
  287. }
  288. stroke(ctx: CanvasRenderingContext2D) {
  289. ctx && ctx.stroke();
  290. this.toStatic();
  291. }
  292. len() {
  293. return this._len;
  294. }
  295. setData(data: Float32Array | number[]) {
  296. const len = data.length;
  297. if (!(this.data && this.data.length === len) && hasTypedArray) {
  298. this.data = new Float32Array(len);
  299. }
  300. for (let i = 0; i < len; i++) {
  301. this.data[i] = data[i];
  302. }
  303. this._len = len;
  304. }
  305. appendPath(path: PathProxy | PathProxy[]) {
  306. if (!(path instanceof Array)) {
  307. path = [path];
  308. }
  309. const len = path.length;
  310. let appendSize = 0;
  311. let offset = this._len;
  312. for (let i = 0; i < len; i++) {
  313. appendSize += path[i].len();
  314. }
  315. if (hasTypedArray && (this.data instanceof Float32Array)) {
  316. this.data = new Float32Array(offset + appendSize);
  317. }
  318. for (let i = 0; i < len; i++) {
  319. const appendPathData = path[i].data;
  320. for (let k = 0; k < appendPathData.length; k++) {
  321. this.data[offset++] = appendPathData[k];
  322. }
  323. }
  324. this._len = offset;
  325. }
  326. /**
  327. * 填充 Path 数据。
  328. * 尽量复用而不申明新的数组。大部分图形重绘的指令数据长度都是不变的。
  329. */
  330. addData(
  331. cmd: number,
  332. a?: number,
  333. b?: number,
  334. c?: number,
  335. d?: number,
  336. e?: number,
  337. f?: number,
  338. g?: number,
  339. h?: number
  340. ) {
  341. if (!this._saveData) {
  342. return;
  343. }
  344. let data = this.data;
  345. if (this._len + arguments.length > data.length) {
  346. // 因为之前的数组已经转换成静态的 Float32Array
  347. // 所以不够用时需要扩展一个新的动态数组
  348. this._expandData();
  349. data = this.data;
  350. }
  351. for (let i = 0; i < arguments.length; i++) {
  352. data[this._len++] = arguments[i];
  353. }
  354. }
  355. private _drawPendingPt() {
  356. if (this._pendingPtDist > 0) {
  357. this._ctx && this._ctx.lineTo(this._pendingPtX, this._pendingPtY);
  358. this._pendingPtDist = 0;
  359. }
  360. }
  361. private _expandData() {
  362. // Only if data is Float32Array
  363. if (!(this.data instanceof Array)) {
  364. const newData = [];
  365. for (let i = 0; i < this._len; i++) {
  366. newData[i] = this.data[i];
  367. }
  368. this.data = newData;
  369. }
  370. }
  371. /**
  372. * Convert dynamic array to static Float32Array
  373. *
  374. * It will still use a normal array if command buffer length is less than 10
  375. * Because Float32Array itself may take more memory than a normal array.
  376. *
  377. * 10 length will make sure at least one M command and one A(arc) command.
  378. */
  379. toStatic() {
  380. if (!this._saveData) {
  381. return;
  382. }
  383. this._drawPendingPt();
  384. const data = this.data;
  385. if (data instanceof Array) {
  386. data.length = this._len;
  387. if (hasTypedArray && this._len > 11) {
  388. this.data = new Float32Array(data);
  389. }
  390. }
  391. }
  392. getBoundingRect() {
  393. min[0] = min[1] = min2[0] = min2[1] = Number.MAX_VALUE;
  394. max[0] = max[1] = max2[0] = max2[1] = -Number.MAX_VALUE;
  395. const data = this.data;
  396. let xi = 0;
  397. let yi = 0;
  398. let x0 = 0;
  399. let y0 = 0;
  400. let i;
  401. for (i = 0; i < this._len;) {
  402. const cmd = data[i++] as number;
  403. const isFirst = i === 1;
  404. if (isFirst) {
  405. // 如果第一个命令是 L, C, Q
  406. // 则 previous point 同绘制命令的第一个 point
  407. // 第一个命令为 Arc 的情况下会在后面特殊处理
  408. xi = data[i];
  409. yi = data[i + 1];
  410. x0 = xi;
  411. y0 = yi;
  412. }
  413. switch (cmd) {
  414. case CMD.M:
  415. // moveTo 命令重新创建一个新的 subpath, 并且更新新的起点
  416. // 在 closePath 的时候使用
  417. xi = x0 = data[i++];
  418. yi = y0 = data[i++];
  419. min2[0] = x0;
  420. min2[1] = y0;
  421. max2[0] = x0;
  422. max2[1] = y0;
  423. break;
  424. case CMD.L:
  425. fromLine(xi, yi, data[i], data[i + 1], min2, max2);
  426. xi = data[i++];
  427. yi = data[i++];
  428. break;
  429. case CMD.C:
  430. fromCubic(
  431. xi, yi, data[i++], data[i++], data[i++], data[i++], data[i], data[i + 1],
  432. min2, max2
  433. );
  434. xi = data[i++];
  435. yi = data[i++];
  436. break;
  437. case CMD.Q:
  438. fromQuadratic(
  439. xi, yi, data[i++], data[i++], data[i], data[i + 1],
  440. min2, max2
  441. );
  442. xi = data[i++];
  443. yi = data[i++];
  444. break;
  445. case CMD.A:
  446. const cx = data[i++];
  447. const cy = data[i++];
  448. const rx = data[i++];
  449. const ry = data[i++];
  450. const startAngle = data[i++];
  451. const endAngle = data[i++] + startAngle;
  452. // TODO Arc 旋转
  453. i += 1;
  454. const anticlockwise = !data[i++];
  455. if (isFirst) {
  456. // 直接使用 arc 命令
  457. // 第一个命令起点还未定义
  458. x0 = mathCos(startAngle) * rx + cx;
  459. y0 = mathSin(startAngle) * ry + cy;
  460. }
  461. fromArc(
  462. cx, cy, rx, ry, startAngle, endAngle,
  463. anticlockwise, min2, max2
  464. );
  465. xi = mathCos(endAngle) * rx + cx;
  466. yi = mathSin(endAngle) * ry + cy;
  467. break;
  468. case CMD.R:
  469. x0 = xi = data[i++];
  470. y0 = yi = data[i++];
  471. const width = data[i++];
  472. const height = data[i++];
  473. // Use fromLine
  474. fromLine(x0, y0, x0 + width, y0 + height, min2, max2);
  475. break;
  476. case CMD.Z:
  477. xi = x0;
  478. yi = y0;
  479. break;
  480. }
  481. // Union
  482. vec2.min(min, min, min2);
  483. vec2.max(max, max, max2);
  484. }
  485. // No data
  486. if (i === 0) {
  487. min[0] = min[1] = max[0] = max[1] = 0;
  488. }
  489. return new BoundingRect(
  490. min[0], min[1], max[0] - min[0], max[1] - min[1]
  491. );
  492. }
  493. private _calculateLength(): number {
  494. const data = this.data;
  495. const len = this._len;
  496. const ux = this._ux;
  497. const uy = this._uy;
  498. let xi = 0;
  499. let yi = 0;
  500. let x0 = 0;
  501. let y0 = 0;
  502. if (!this._pathSegLen) {
  503. this._pathSegLen = [];
  504. }
  505. const pathSegLen = this._pathSegLen;
  506. let pathTotalLen = 0;
  507. let segCount = 0;
  508. for (let i = 0; i < len;) {
  509. const cmd = data[i++] as number;
  510. const isFirst = i === 1;
  511. if (isFirst) {
  512. // 如果第一个命令是 L, C, Q
  513. // 则 previous point 同绘制命令的第一个 point
  514. // 第一个命令为 Arc 的情况下会在后面特殊处理
  515. xi = data[i];
  516. yi = data[i + 1];
  517. x0 = xi;
  518. y0 = yi;
  519. }
  520. let l = -1;
  521. switch (cmd) {
  522. case CMD.M:
  523. // moveTo 命令重新创建一个新的 subpath, 并且更新新的起点
  524. // 在 closePath 的时候使用
  525. xi = x0 = data[i++];
  526. yi = y0 = data[i++];
  527. break;
  528. case CMD.L: {
  529. const x2 = data[i++];
  530. const y2 = data[i++];
  531. const dx = x2 - xi;
  532. const dy = y2 - yi;
  533. if (mathAbs(dx) > ux || mathAbs(dy) > uy || i === len - 1) {
  534. l = Math.sqrt(dx * dx + dy * dy);
  535. xi = x2;
  536. yi = y2;
  537. }
  538. break;
  539. }
  540. case CMD.C: {
  541. const x1 = data[i++];
  542. const y1 = data[i++];
  543. const x2 = data[i++];
  544. const y2 = data[i++];
  545. const x3 = data[i++];
  546. const y3 = data[i++];
  547. // TODO adaptive iteration
  548. l = cubicLength(xi, yi, x1, y1, x2, y2, x3, y3, 10);
  549. xi = x3;
  550. yi = y3;
  551. break;
  552. }
  553. case CMD.Q: {
  554. const x1 = data[i++];
  555. const y1 = data[i++];
  556. const x2 = data[i++];
  557. const y2 = data[i++];
  558. l = quadraticLength(xi, yi, x1, y1, x2, y2, 10);
  559. xi = x2;
  560. yi = y2;
  561. break;
  562. }
  563. case CMD.A:
  564. // TODO Arc 判断的开销比较大
  565. const cx = data[i++];
  566. const cy = data[i++];
  567. const rx = data[i++];
  568. const ry = data[i++];
  569. const startAngle = data[i++];
  570. let delta = data[i++];
  571. const endAngle = delta + startAngle;
  572. // TODO Arc 旋转
  573. i += 1;
  574. const anticlockwise = !data[i++];
  575. if (isFirst) {
  576. // 直接使用 arc 命令
  577. // 第一个命令起点还未定义
  578. x0 = mathCos(startAngle) * rx + cx;
  579. y0 = mathSin(startAngle) * ry + cy;
  580. }
  581. // TODO Ellipse
  582. l = mathMax(rx, ry) * mathMin(PI2, Math.abs(delta));
  583. xi = mathCos(endAngle) * rx + cx;
  584. yi = mathSin(endAngle) * ry + cy;
  585. break;
  586. case CMD.R: {
  587. x0 = xi = data[i++];
  588. y0 = yi = data[i++];
  589. const width = data[i++];
  590. const height = data[i++];
  591. l = width * 2 + height * 2;
  592. break;
  593. }
  594. case CMD.Z: {
  595. const dx = x0 - xi;
  596. const dy = y0 - yi;
  597. l = Math.sqrt(dx * dx + dy * dy);
  598. xi = x0;
  599. yi = y0;
  600. break;
  601. }
  602. }
  603. if (l >= 0) {
  604. pathSegLen[segCount++] = l;
  605. pathTotalLen += l;
  606. }
  607. }
  608. // TODO Optimize memory cost.
  609. this._pathLen = pathTotalLen;
  610. return pathTotalLen;
  611. }
  612. /**
  613. * Rebuild path from current data
  614. * Rebuild path will not consider javascript implemented line dash.
  615. * @param {CanvasRenderingContext2D} ctx
  616. */
  617. rebuildPath(ctx: PathRebuilder, percent: number) {
  618. const d = this.data;
  619. const ux = this._ux;
  620. const uy = this._uy;
  621. const len = this._len;
  622. let x0;
  623. let y0;
  624. let xi;
  625. let yi;
  626. let x;
  627. let y;
  628. const drawPart = percent < 1;
  629. let pathSegLen;
  630. let pathTotalLen;
  631. let accumLength = 0;
  632. let segCount = 0;
  633. let displayedLength;
  634. let pendingPtDist = 0;
  635. let pendingPtX: number;
  636. let pendingPtY: number;
  637. if (drawPart) {
  638. if (!this._pathSegLen) {
  639. this._calculateLength();
  640. }
  641. pathSegLen = this._pathSegLen;
  642. pathTotalLen = this._pathLen;
  643. displayedLength = percent * pathTotalLen;
  644. if (!displayedLength) {
  645. return;
  646. }
  647. }
  648. lo: for (let i = 0; i < len;) {
  649. const cmd = d[i++];
  650. const isFirst = i === 1;
  651. if (isFirst) {
  652. // 如果第一个命令是 L, C, Q
  653. // 则 previous point 同绘制命令的第一个 point
  654. // 第一个命令为 Arc 的情况下会在后面特殊处理
  655. xi = d[i];
  656. yi = d[i + 1];
  657. x0 = xi;
  658. y0 = yi;
  659. }
  660. // Only lineTo support ignoring small segments.
  661. // Otherwise if the pending point should always been flushed.
  662. if (cmd !== CMD.L && pendingPtDist > 0) {
  663. ctx.lineTo(pendingPtX, pendingPtY);
  664. pendingPtDist = 0;
  665. }
  666. switch (cmd) {
  667. case CMD.M:
  668. x0 = xi = d[i++];
  669. y0 = yi = d[i++];
  670. ctx.moveTo(xi, yi);
  671. break;
  672. case CMD.L: {
  673. x = d[i++];
  674. y = d[i++];
  675. const dx = mathAbs(x - xi);
  676. const dy = mathAbs(y - yi);
  677. // Not draw too small seg between
  678. if (dx > ux || dy > uy) {
  679. if (drawPart) {
  680. const l = pathSegLen[segCount++];
  681. if (accumLength + l > displayedLength) {
  682. const t = (displayedLength - accumLength) / l;
  683. ctx.lineTo(xi * (1 - t) + x * t, yi * (1 - t) + y * t);
  684. break lo;
  685. }
  686. accumLength += l;
  687. }
  688. ctx.lineTo(x, y);
  689. xi = x;
  690. yi = y;
  691. pendingPtDist = 0;
  692. }
  693. else {
  694. const d2 = dx * dx + dy * dy;
  695. // Only use the farthest pending point.
  696. if (d2 > pendingPtDist) {
  697. pendingPtX = x;
  698. pendingPtY = y;
  699. pendingPtDist = d2;
  700. }
  701. }
  702. break;
  703. }
  704. case CMD.C: {
  705. const x1 = d[i++];
  706. const y1 = d[i++];
  707. const x2 = d[i++];
  708. const y2 = d[i++];
  709. const x3 = d[i++];
  710. const y3 = d[i++];
  711. if (drawPart) {
  712. const l = pathSegLen[segCount++];
  713. if (accumLength + l > displayedLength) {
  714. const t = (displayedLength - accumLength) / l;
  715. cubicSubdivide(xi, x1, x2, x3, t, tmpOutX);
  716. cubicSubdivide(yi, y1, y2, y3, t, tmpOutY);
  717. ctx.bezierCurveTo(tmpOutX[1], tmpOutY[1], tmpOutX[2], tmpOutY[2], tmpOutX[3], tmpOutY[3]);
  718. break lo;
  719. }
  720. accumLength += l;
  721. }
  722. ctx.bezierCurveTo(x1, y1, x2, y2, x3, y3);
  723. xi = x3;
  724. yi = y3;
  725. break;
  726. }
  727. case CMD.Q: {
  728. const x1 = d[i++];
  729. const y1 = d[i++];
  730. const x2 = d[i++];
  731. const y2 = d[i++];
  732. if (drawPart) {
  733. const l = pathSegLen[segCount++];
  734. if (accumLength + l > displayedLength) {
  735. const t = (displayedLength - accumLength) / l;
  736. quadraticSubdivide(xi, x1, x2, t, tmpOutX);
  737. quadraticSubdivide(yi, y1, y2, t, tmpOutY);
  738. ctx.quadraticCurveTo(tmpOutX[1], tmpOutY[1], tmpOutX[2], tmpOutY[2]);
  739. break lo;
  740. }
  741. accumLength += l;
  742. }
  743. ctx.quadraticCurveTo(x1, y1, x2, y2);
  744. xi = x2;
  745. yi = y2;
  746. break;
  747. }
  748. case CMD.A:
  749. const cx = d[i++];
  750. const cy = d[i++];
  751. const rx = d[i++];
  752. const ry = d[i++];
  753. let startAngle = d[i++];
  754. let delta = d[i++];
  755. const psi = d[i++];
  756. const anticlockwise = !d[i++];
  757. const r = (rx > ry) ? rx : ry;
  758. // const scaleX = (rx > ry) ? 1 : rx / ry;
  759. // const scaleY = (rx > ry) ? ry / rx : 1;
  760. const isEllipse = mathAbs(rx - ry) > 1e-3;
  761. let endAngle = startAngle + delta;
  762. let breakBuild = false;
  763. if (drawPart) {
  764. const l = pathSegLen[segCount++];
  765. if (accumLength + l > displayedLength) {
  766. endAngle = startAngle + delta * (displayedLength - accumLength) / l;
  767. breakBuild = true;
  768. }
  769. accumLength += l;
  770. }
  771. if (isEllipse && ctx.ellipse) {
  772. ctx.ellipse(cx, cy, rx, ry, psi, startAngle, endAngle, anticlockwise);
  773. }
  774. else {
  775. ctx.arc(cx, cy, r, startAngle, endAngle, anticlockwise);
  776. }
  777. if (breakBuild) {
  778. break lo;
  779. }
  780. if (isFirst) {
  781. // 直接使用 arc 命令
  782. // 第一个命令起点还未定义
  783. x0 = mathCos(startAngle) * rx + cx;
  784. y0 = mathSin(startAngle) * ry + cy;
  785. }
  786. xi = mathCos(endAngle) * rx + cx;
  787. yi = mathSin(endAngle) * ry + cy;
  788. break;
  789. case CMD.R:
  790. x0 = xi = d[i];
  791. y0 = yi = d[i + 1];
  792. x = d[i++];
  793. y = d[i++];
  794. const width = d[i++];
  795. const height = d[i++];
  796. if (drawPart) {
  797. const l = pathSegLen[segCount++];
  798. if (accumLength + l > displayedLength) {
  799. let d = displayedLength - accumLength;
  800. ctx.moveTo(x, y);
  801. ctx.lineTo(x + mathMin(d, width), y);
  802. d -= width;
  803. if (d > 0) {
  804. ctx.lineTo(x + width, y + mathMin(d, height));
  805. }
  806. d -= height;
  807. if (d > 0) {
  808. ctx.lineTo(x + mathMax(width - d, 0), y + height);
  809. }
  810. d -= width;
  811. if (d > 0) {
  812. ctx.lineTo(x, y + mathMax(height - d, 0));
  813. }
  814. break lo;
  815. }
  816. accumLength += l;
  817. }
  818. ctx.rect(x, y, width, height);
  819. break;
  820. case CMD.Z:
  821. if (drawPart) {
  822. const l = pathSegLen[segCount++];
  823. if (accumLength + l > displayedLength) {
  824. const t = (displayedLength - accumLength) / l;
  825. ctx.lineTo(xi * (1 - t) + x0 * t, yi * (1 - t) + y0 * t);
  826. break lo;
  827. }
  828. accumLength += l;
  829. }
  830. ctx.closePath();
  831. xi = x0;
  832. yi = y0;
  833. }
  834. }
  835. }
  836. clone() {
  837. const newProxy = new PathProxy();
  838. const data = this.data;
  839. newProxy.data = data.slice ? data.slice()
  840. : Array.prototype.slice.call(data);
  841. newProxy._len = this._len;
  842. return newProxy;
  843. }
  844. private static initDefaultProps = (function () {
  845. const proto = PathProxy.prototype;
  846. proto._saveData = true;
  847. proto._ux = 0;
  848. proto._uy = 0;
  849. proto._pendingPtDist = 0;
  850. proto._version = 0;
  851. })()
  852. }
  853. export interface PathRebuilder {
  854. moveTo(x: number, y: number): void
  855. lineTo(x: number, y: number): void
  856. bezierCurveTo(x: number, y: number, x2: number, y2: number, x3: number, y3: number): void
  857. quadraticCurveTo(x: number, y: number, x2: number, y2: number): void
  858. arc(cx: number, cy: number, r: number, startAngle: number, endAngle: number, anticlockwise: boolean): void
  859. // eslint-disable-next-line max-len
  860. ellipse(cx: number, cy: number, radiusX: number, radiusY: number, rotation: number, startAngle: number, endAngle: number, anticlockwise: boolean): void
  861. rect(x: number, y: number, width: number, height: number): void
  862. closePath(): void
  863. }