123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994 |
- /**
- * Path 代理,可以在`buildPath`中用于替代`ctx`, 会保存每个path操作的命令到pathCommands属性中
- * 可以用于 isInsidePath 判断以及获取boundingRect
- */
- // TODO getTotalLength, getPointAtLength, arcTo
- /* global Float32Array */
- import * as vec2 from './vector';
- import BoundingRect from './BoundingRect';
- import {devicePixelRatio as dpr} from '../config';
- import { fromLine, fromCubic, fromQuadratic, fromArc } from './bbox';
- import { cubicLength, cubicSubdivide, quadraticLength, quadraticSubdivide } from './curve';
- const CMD = {
- M: 1,
- L: 2,
- C: 3,
- Q: 4,
- A: 5,
- Z: 6,
- // Rect
- R: 7
- };
- // const CMD_MEM_SIZE = {
- // M: 3,
- // L: 3,
- // C: 7,
- // Q: 5,
- // A: 9,
- // R: 5,
- // Z: 1
- // };
- interface ExtendedCanvasRenderingContext2D extends CanvasRenderingContext2D {
- dpr?: number
- }
- const tmpOutX: number[] = [];
- const tmpOutY: number[] = [];
- const min: number[] = [];
- const max: number[] = [];
- const min2: number[] = [];
- const max2: number[] = [];
- const mathMin = Math.min;
- const mathMax = Math.max;
- const mathCos = Math.cos;
- const mathSin = Math.sin;
- const mathAbs = Math.abs;
- const PI = Math.PI;
- const PI2 = PI * 2;
- const hasTypedArray = typeof Float32Array !== 'undefined';
- const tmpAngles: number[] = [];
- function modPI2(radian: number) {
- // It's much more stable to mod N instedof PI
- const n = Math.round(radian / PI * 1e8) / 1e8;
- return (n % 2) * PI;
- }
- /**
- * Normalize start and end angles.
- * startAngle will be normalized to 0 ~ PI*2
- * sweepAngle(endAngle - startAngle) will be normalized to 0 ~ PI*2 if clockwise.
- * -PI*2 ~ 0 if anticlockwise.
- */
- export function normalizeArcAngles(angles: number[], anticlockwise: boolean): void {
- let newStartAngle = modPI2(angles[0]);
- if (newStartAngle < 0) {
- // Normlize to 0 - PI2
- newStartAngle += PI2;
- }
- let delta = newStartAngle - angles[0];
- let newEndAngle = angles[1];
- newEndAngle += delta;
- // https://github.com/chromium/chromium/blob/c20d681c9c067c4e15bb1408f17114b9e8cba294/third_party/blink/renderer/modules/canvas/canvas2d/canvas_path.cc#L184
- // Is circle
- if (!anticlockwise && newEndAngle - newStartAngle >= PI2) {
- newEndAngle = newStartAngle + PI2;
- }
- else if (anticlockwise && newStartAngle - newEndAngle >= PI2) {
- newEndAngle = newStartAngle - PI2;
- }
- // Make startAngle < endAngle when clockwise, otherwise endAngle < startAngle.
- // The sweep angle can never been larger than P2.
- else if (!anticlockwise && newStartAngle > newEndAngle) {
- newEndAngle = newStartAngle + (PI2 - modPI2(newStartAngle - newEndAngle));
- }
- else if (anticlockwise && newStartAngle < newEndAngle) {
- newEndAngle = newStartAngle - (PI2 - modPI2(newEndAngle - newStartAngle));
- }
- angles[0] = newStartAngle;
- angles[1] = newEndAngle;
- }
- export default class PathProxy {
- dpr = 1
- data: number[] | Float32Array
- /**
- * Version is for tracking if the path has been changed.
- */
- private _version: number
- /**
- * If save path data.
- */
- private _saveData: boolean
- /**
- * If the line segment is too small to draw. It will be added to the pending pt.
- * It will be added if the subpath needs to be finished before stroke, fill, or starting a new subpath.
- */
- private _pendingPtX: number;
- private _pendingPtY: number;
- // Distance of pending pt to previous point.
- // 0 if there is no pending point.
- // Only update the pending pt when distance is larger.
- private _pendingPtDist: number;
- private _ctx: ExtendedCanvasRenderingContext2D
- private _xi = 0
- private _yi = 0
- private _x0 = 0
- private _y0 = 0
- private _len = 0
- // Calculating path len and seg len.
- private _pathSegLen: number[]
- private _pathLen: number
- // Unit x, Unit y. Provide for avoiding drawing that too short line segment
- private _ux: number
- private _uy: number
- static CMD = CMD
- constructor(notSaveData?: boolean) {
- if (notSaveData) {
- this._saveData = false;
- }
- if (this._saveData) {
- this.data = [];
- }
- }
- increaseVersion() {
- this._version++;
- }
- /**
- * Version can be used outside for compare if the path is changed.
- * For example to determine if need to update svg d str in svg renderer.
- */
- getVersion() {
- return this._version;
- }
- /**
- * @readOnly
- */
- setScale(sx: number, sy: number, segmentIgnoreThreshold?: number) {
- // Compat. Previously there is no segmentIgnoreThreshold.
- segmentIgnoreThreshold = segmentIgnoreThreshold || 0;
- if (segmentIgnoreThreshold > 0) {
- this._ux = mathAbs(segmentIgnoreThreshold / dpr / sx) || 0;
- this._uy = mathAbs(segmentIgnoreThreshold / dpr / sy) || 0;
- }
- }
- setDPR(dpr: number) {
- this.dpr = dpr;
- }
- setContext(ctx: ExtendedCanvasRenderingContext2D) {
- this._ctx = ctx;
- }
- getContext(): ExtendedCanvasRenderingContext2D {
- return this._ctx;
- }
- beginPath() {
- this._ctx && this._ctx.beginPath();
- this.reset();
- return this;
- }
- /**
- * Reset path data.
- */
- reset() {
- // Reset
- if (this._saveData) {
- this._len = 0;
- }
- if (this._pathSegLen) {
- this._pathSegLen = null;
- this._pathLen = 0;
- }
- // Update version
- this._version++;
- }
- moveTo(x: number, y: number) {
- // Add pending point for previous path.
- this._drawPendingPt();
- this.addData(CMD.M, x, y);
- this._ctx && this._ctx.moveTo(x, y);
- // x0, y0, xi, yi 是记录在 _dashedXXXXTo 方法中使用
- // xi, yi 记录当前点, x0, y0 在 closePath 的时候回到起始点。
- // 有可能在 beginPath 之后直接调用 lineTo,这时候 x0, y0 需要
- // 在 lineTo 方法中记录,这里先不考虑这种情况,dashed line 也只在 IE10- 中不支持
- this._x0 = x;
- this._y0 = y;
- this._xi = x;
- this._yi = y;
- return this;
- }
- lineTo(x: number, y: number) {
- const dx = mathAbs(x - this._xi);
- const dy = mathAbs(y - this._yi);
- const exceedUnit = dx > this._ux || dy > this._uy;
- this.addData(CMD.L, x, y);
- if (this._ctx && exceedUnit) {
- this._ctx.lineTo(x, y);
- }
- if (exceedUnit) {
- this._xi = x;
- this._yi = y;
- this._pendingPtDist = 0;
- }
- else {
- const d2 = dx * dx + dy * dy;
- // Only use the farthest pending point.
- if (d2 > this._pendingPtDist) {
- this._pendingPtX = x;
- this._pendingPtY = y;
- this._pendingPtDist = d2;
- }
- }
- return this;
- }
- bezierCurveTo(x1: number, y1: number, x2: number, y2: number, x3: number, y3: number) {
- this._drawPendingPt();
- this.addData(CMD.C, x1, y1, x2, y2, x3, y3);
- if (this._ctx) {
- this._ctx.bezierCurveTo(x1, y1, x2, y2, x3, y3);
- }
- this._xi = x3;
- this._yi = y3;
- return this;
- }
- quadraticCurveTo(x1: number, y1: number, x2: number, y2: number) {
- this._drawPendingPt();
- this.addData(CMD.Q, x1, y1, x2, y2);
- if (this._ctx) {
- this._ctx.quadraticCurveTo(x1, y1, x2, y2);
- }
- this._xi = x2;
- this._yi = y2;
- return this;
- }
- arc(cx: number, cy: number, r: number, startAngle: number, endAngle: number, anticlockwise?: boolean) {
- this._drawPendingPt();
- tmpAngles[0] = startAngle;
- tmpAngles[1] = endAngle;
- normalizeArcAngles(tmpAngles, anticlockwise);
- startAngle = tmpAngles[0];
- endAngle = tmpAngles[1];
- let delta = endAngle - startAngle;
- this.addData(
- CMD.A, cx, cy, r, r, startAngle, delta, 0, anticlockwise ? 0 : 1
- );
- this._ctx && this._ctx.arc(cx, cy, r, startAngle, endAngle, anticlockwise);
- this._xi = mathCos(endAngle) * r + cx;
- this._yi = mathSin(endAngle) * r + cy;
- return this;
- }
- // TODO
- arcTo(x1: number, y1: number, x2: number, y2: number, radius: number) {
- this._drawPendingPt();
- if (this._ctx) {
- this._ctx.arcTo(x1, y1, x2, y2, radius);
- }
- return this;
- }
- // TODO
- rect(x: number, y: number, w: number, h: number) {
- this._drawPendingPt();
- this._ctx && this._ctx.rect(x, y, w, h);
- this.addData(CMD.R, x, y, w, h);
- return this;
- }
- closePath() {
- // Add pending point for previous path.
- this._drawPendingPt();
- this.addData(CMD.Z);
- const ctx = this._ctx;
- const x0 = this._x0;
- const y0 = this._y0;
- if (ctx) {
- ctx.closePath();
- }
- this._xi = x0;
- this._yi = y0;
- return this;
- }
- fill(ctx: CanvasRenderingContext2D) {
- ctx && ctx.fill();
- this.toStatic();
- }
- stroke(ctx: CanvasRenderingContext2D) {
- ctx && ctx.stroke();
- this.toStatic();
- }
- len() {
- return this._len;
- }
- setData(data: Float32Array | number[]) {
- const len = data.length;
- if (!(this.data && this.data.length === len) && hasTypedArray) {
- this.data = new Float32Array(len);
- }
- for (let i = 0; i < len; i++) {
- this.data[i] = data[i];
- }
- this._len = len;
- }
- appendPath(path: PathProxy | PathProxy[]) {
- if (!(path instanceof Array)) {
- path = [path];
- }
- const len = path.length;
- let appendSize = 0;
- let offset = this._len;
- for (let i = 0; i < len; i++) {
- appendSize += path[i].len();
- }
- if (hasTypedArray && (this.data instanceof Float32Array)) {
- this.data = new Float32Array(offset + appendSize);
- }
- for (let i = 0; i < len; i++) {
- const appendPathData = path[i].data;
- for (let k = 0; k < appendPathData.length; k++) {
- this.data[offset++] = appendPathData[k];
- }
- }
- this._len = offset;
- }
- /**
- * 填充 Path 数据。
- * 尽量复用而不申明新的数组。大部分图形重绘的指令数据长度都是不变的。
- */
- addData(
- cmd: number,
- a?: number,
- b?: number,
- c?: number,
- d?: number,
- e?: number,
- f?: number,
- g?: number,
- h?: number
- ) {
- if (!this._saveData) {
- return;
- }
- let data = this.data;
- if (this._len + arguments.length > data.length) {
- // 因为之前的数组已经转换成静态的 Float32Array
- // 所以不够用时需要扩展一个新的动态数组
- this._expandData();
- data = this.data;
- }
- for (let i = 0; i < arguments.length; i++) {
- data[this._len++] = arguments[i];
- }
- }
- private _drawPendingPt() {
- if (this._pendingPtDist > 0) {
- this._ctx && this._ctx.lineTo(this._pendingPtX, this._pendingPtY);
- this._pendingPtDist = 0;
- }
- }
- private _expandData() {
- // Only if data is Float32Array
- if (!(this.data instanceof Array)) {
- const newData = [];
- for (let i = 0; i < this._len; i++) {
- newData[i] = this.data[i];
- }
- this.data = newData;
- }
- }
- /**
- * Convert dynamic array to static Float32Array
- *
- * It will still use a normal array if command buffer length is less than 10
- * Because Float32Array itself may take more memory than a normal array.
- *
- * 10 length will make sure at least one M command and one A(arc) command.
- */
- toStatic() {
- if (!this._saveData) {
- return;
- }
- this._drawPendingPt();
- const data = this.data;
- if (data instanceof Array) {
- data.length = this._len;
- if (hasTypedArray && this._len > 11) {
- this.data = new Float32Array(data);
- }
- }
- }
- getBoundingRect() {
- min[0] = min[1] = min2[0] = min2[1] = Number.MAX_VALUE;
- max[0] = max[1] = max2[0] = max2[1] = -Number.MAX_VALUE;
- const data = this.data;
- let xi = 0;
- let yi = 0;
- let x0 = 0;
- let y0 = 0;
- let i;
- for (i = 0; i < this._len;) {
- const cmd = data[i++] as number;
- const isFirst = i === 1;
- if (isFirst) {
- // 如果第一个命令是 L, C, Q
- // 则 previous point 同绘制命令的第一个 point
- // 第一个命令为 Arc 的情况下会在后面特殊处理
- xi = data[i];
- yi = data[i + 1];
- x0 = xi;
- y0 = yi;
- }
- switch (cmd) {
- case CMD.M:
- // moveTo 命令重新创建一个新的 subpath, 并且更新新的起点
- // 在 closePath 的时候使用
- xi = x0 = data[i++];
- yi = y0 = data[i++];
- min2[0] = x0;
- min2[1] = y0;
- max2[0] = x0;
- max2[1] = y0;
- break;
- case CMD.L:
- fromLine(xi, yi, data[i], data[i + 1], min2, max2);
- xi = data[i++];
- yi = data[i++];
- break;
- case CMD.C:
- fromCubic(
- xi, yi, data[i++], data[i++], data[i++], data[i++], data[i], data[i + 1],
- min2, max2
- );
- xi = data[i++];
- yi = data[i++];
- break;
- case CMD.Q:
- fromQuadratic(
- xi, yi, data[i++], data[i++], data[i], data[i + 1],
- min2, max2
- );
- xi = data[i++];
- yi = data[i++];
- break;
- case CMD.A:
- const cx = data[i++];
- const cy = data[i++];
- const rx = data[i++];
- const ry = data[i++];
- const startAngle = data[i++];
- const endAngle = data[i++] + startAngle;
- // TODO Arc 旋转
- i += 1;
- const anticlockwise = !data[i++];
- if (isFirst) {
- // 直接使用 arc 命令
- // 第一个命令起点还未定义
- x0 = mathCos(startAngle) * rx + cx;
- y0 = mathSin(startAngle) * ry + cy;
- }
- fromArc(
- cx, cy, rx, ry, startAngle, endAngle,
- anticlockwise, min2, max2
- );
- xi = mathCos(endAngle) * rx + cx;
- yi = mathSin(endAngle) * ry + cy;
- break;
- case CMD.R:
- x0 = xi = data[i++];
- y0 = yi = data[i++];
- const width = data[i++];
- const height = data[i++];
- // Use fromLine
- fromLine(x0, y0, x0 + width, y0 + height, min2, max2);
- break;
- case CMD.Z:
- xi = x0;
- yi = y0;
- break;
- }
- // Union
- vec2.min(min, min, min2);
- vec2.max(max, max, max2);
- }
- // No data
- if (i === 0) {
- min[0] = min[1] = max[0] = max[1] = 0;
- }
- return new BoundingRect(
- min[0], min[1], max[0] - min[0], max[1] - min[1]
- );
- }
- private _calculateLength(): number {
- const data = this.data;
- const len = this._len;
- const ux = this._ux;
- const uy = this._uy;
- let xi = 0;
- let yi = 0;
- let x0 = 0;
- let y0 = 0;
- if (!this._pathSegLen) {
- this._pathSegLen = [];
- }
- const pathSegLen = this._pathSegLen;
- let pathTotalLen = 0;
- let segCount = 0;
- for (let i = 0; i < len;) {
- const cmd = data[i++] as number;
- const isFirst = i === 1;
- if (isFirst) {
- // 如果第一个命令是 L, C, Q
- // 则 previous point 同绘制命令的第一个 point
- // 第一个命令为 Arc 的情况下会在后面特殊处理
- xi = data[i];
- yi = data[i + 1];
- x0 = xi;
- y0 = yi;
- }
- let l = -1;
- switch (cmd) {
- case CMD.M:
- // moveTo 命令重新创建一个新的 subpath, 并且更新新的起点
- // 在 closePath 的时候使用
- xi = x0 = data[i++];
- yi = y0 = data[i++];
- break;
- case CMD.L: {
- const x2 = data[i++];
- const y2 = data[i++];
- const dx = x2 - xi;
- const dy = y2 - yi;
- if (mathAbs(dx) > ux || mathAbs(dy) > uy || i === len - 1) {
- l = Math.sqrt(dx * dx + dy * dy);
- xi = x2;
- yi = y2;
- }
- break;
- }
- case CMD.C: {
- const x1 = data[i++];
- const y1 = data[i++];
- const x2 = data[i++];
- const y2 = data[i++];
- const x3 = data[i++];
- const y3 = data[i++];
- // TODO adaptive iteration
- l = cubicLength(xi, yi, x1, y1, x2, y2, x3, y3, 10);
- xi = x3;
- yi = y3;
- break;
- }
- case CMD.Q: {
- const x1 = data[i++];
- const y1 = data[i++];
- const x2 = data[i++];
- const y2 = data[i++];
- l = quadraticLength(xi, yi, x1, y1, x2, y2, 10);
- xi = x2;
- yi = y2;
- break;
- }
- case CMD.A:
- // TODO Arc 判断的开销比较大
- const cx = data[i++];
- const cy = data[i++];
- const rx = data[i++];
- const ry = data[i++];
- const startAngle = data[i++];
- let delta = data[i++];
- const endAngle = delta + startAngle;
- // TODO Arc 旋转
- i += 1;
- if (isFirst) {
- // 直接使用 arc 命令
- // 第一个命令起点还未定义
- x0 = mathCos(startAngle) * rx + cx;
- y0 = mathSin(startAngle) * ry + cy;
- }
- // TODO Ellipse
- l = mathMax(rx, ry) * mathMin(PI2, Math.abs(delta));
- xi = mathCos(endAngle) * rx + cx;
- yi = mathSin(endAngle) * ry + cy;
- break;
- case CMD.R: {
- x0 = xi = data[i++];
- y0 = yi = data[i++];
- const width = data[i++];
- const height = data[i++];
- l = width * 2 + height * 2;
- break;
- }
- case CMD.Z: {
- const dx = x0 - xi;
- const dy = y0 - yi;
- l = Math.sqrt(dx * dx + dy * dy);
- xi = x0;
- yi = y0;
- break;
- }
- }
- if (l >= 0) {
- pathSegLen[segCount++] = l;
- pathTotalLen += l;
- }
- }
- // TODO Optimize memory cost.
- this._pathLen = pathTotalLen;
- return pathTotalLen;
- }
- /**
- * Rebuild path from current data
- * Rebuild path will not consider javascript implemented line dash.
- * @param {CanvasRenderingContext2D} ctx
- */
- rebuildPath(ctx: PathRebuilder, percent: number) {
- const d = this.data;
- const ux = this._ux;
- const uy = this._uy;
- const len = this._len;
- let x0;
- let y0;
- let xi;
- let yi;
- let x;
- let y;
- const drawPart = percent < 1;
- let pathSegLen;
- let pathTotalLen;
- let accumLength = 0;
- let segCount = 0;
- let displayedLength;
- let pendingPtDist = 0;
- let pendingPtX: number;
- let pendingPtY: number;
- if (drawPart) {
- if (!this._pathSegLen) {
- this._calculateLength();
- }
- pathSegLen = this._pathSegLen;
- pathTotalLen = this._pathLen;
- displayedLength = percent * pathTotalLen;
- if (!displayedLength) {
- return;
- }
- }
- lo: for (let i = 0; i < len;) {
- const cmd = d[i++];
- const isFirst = i === 1;
- if (isFirst) {
- // 如果第一个命令是 L, C, Q
- // 则 previous point 同绘制命令的第一个 point
- // 第一个命令为 Arc 的情况下会在后面特殊处理
- xi = d[i];
- yi = d[i + 1];
- x0 = xi;
- y0 = yi;
- }
- // Only lineTo support ignoring small segments.
- // Otherwise if the pending point should always been flushed.
- if (cmd !== CMD.L && pendingPtDist > 0) {
- ctx.lineTo(pendingPtX, pendingPtY);
- pendingPtDist = 0;
- }
- switch (cmd) {
- case CMD.M:
- x0 = xi = d[i++];
- y0 = yi = d[i++];
- ctx.moveTo(xi, yi);
- break;
- case CMD.L: {
- x = d[i++];
- y = d[i++];
- const dx = mathAbs(x - xi);
- const dy = mathAbs(y - yi);
- // Not draw too small seg between
- if (dx > ux || dy > uy) {
- if (drawPart) {
- const l = pathSegLen[segCount++];
- if (accumLength + l > displayedLength) {
- const t = (displayedLength - accumLength) / l;
- ctx.lineTo(xi * (1 - t) + x * t, yi * (1 - t) + y * t);
- break lo;
- }
- accumLength += l;
- }
- ctx.lineTo(x, y);
- xi = x;
- yi = y;
- pendingPtDist = 0;
- }
- else {
- const d2 = dx * dx + dy * dy;
- // Only use the farthest pending point.
- if (d2 > pendingPtDist) {
- pendingPtX = x;
- pendingPtY = y;
- pendingPtDist = d2;
- }
- }
- break;
- }
- case CMD.C: {
- const x1 = d[i++];
- const y1 = d[i++];
- const x2 = d[i++];
- const y2 = d[i++];
- const x3 = d[i++];
- const y3 = d[i++];
- if (drawPart) {
- const l = pathSegLen[segCount++];
- if (accumLength + l > displayedLength) {
- const t = (displayedLength - accumLength) / l;
- cubicSubdivide(xi, x1, x2, x3, t, tmpOutX);
- cubicSubdivide(yi, y1, y2, y3, t, tmpOutY);
- ctx.bezierCurveTo(tmpOutX[1], tmpOutY[1], tmpOutX[2], tmpOutY[2], tmpOutX[3], tmpOutY[3]);
- break lo;
- }
- accumLength += l;
- }
- ctx.bezierCurveTo(x1, y1, x2, y2, x3, y3);
- xi = x3;
- yi = y3;
- break;
- }
- case CMD.Q: {
- const x1 = d[i++];
- const y1 = d[i++];
- const x2 = d[i++];
- const y2 = d[i++];
- if (drawPart) {
- const l = pathSegLen[segCount++];
- if (accumLength + l > displayedLength) {
- const t = (displayedLength - accumLength) / l;
- quadraticSubdivide(xi, x1, x2, t, tmpOutX);
- quadraticSubdivide(yi, y1, y2, t, tmpOutY);
- ctx.quadraticCurveTo(tmpOutX[1], tmpOutY[1], tmpOutX[2], tmpOutY[2]);
- break lo;
- }
- accumLength += l;
- }
- ctx.quadraticCurveTo(x1, y1, x2, y2);
- xi = x2;
- yi = y2;
- break;
- }
- case CMD.A:
- const cx = d[i++];
- const cy = d[i++];
- const rx = d[i++];
- const ry = d[i++];
- let startAngle = d[i++];
- let delta = d[i++];
- const psi = d[i++];
- const anticlockwise = !d[i++];
- const r = (rx > ry) ? rx : ry;
- // const scaleX = (rx > ry) ? 1 : rx / ry;
- // const scaleY = (rx > ry) ? ry / rx : 1;
- const isEllipse = mathAbs(rx - ry) > 1e-3;
- let endAngle = startAngle + delta;
- let breakBuild = false;
- if (drawPart) {
- const l = pathSegLen[segCount++];
- if (accumLength + l > displayedLength) {
- endAngle = startAngle + delta * (displayedLength - accumLength) / l;
- breakBuild = true;
- }
- accumLength += l;
- }
- if (isEllipse && ctx.ellipse) {
- ctx.ellipse(cx, cy, rx, ry, psi, startAngle, endAngle, anticlockwise);
- }
- else {
- ctx.arc(cx, cy, r, startAngle, endAngle, anticlockwise);
- }
- if (breakBuild) {
- break lo;
- }
- if (isFirst) {
- // 直接使用 arc 命令
- // 第一个命令起点还未定义
- x0 = mathCos(startAngle) * rx + cx;
- y0 = mathSin(startAngle) * ry + cy;
- }
- xi = mathCos(endAngle) * rx + cx;
- yi = mathSin(endAngle) * ry + cy;
- break;
- case CMD.R:
- x0 = xi = d[i];
- y0 = yi = d[i + 1];
- x = d[i++];
- y = d[i++];
- const width = d[i++];
- const height = d[i++];
- if (drawPart) {
- const l = pathSegLen[segCount++];
- if (accumLength + l > displayedLength) {
- let d = displayedLength - accumLength;
- ctx.moveTo(x, y);
- ctx.lineTo(x + mathMin(d, width), y);
- d -= width;
- if (d > 0) {
- ctx.lineTo(x + width, y + mathMin(d, height));
- }
- d -= height;
- if (d > 0) {
- ctx.lineTo(x + mathMax(width - d, 0), y + height);
- }
- d -= width;
- if (d > 0) {
- ctx.lineTo(x, y + mathMax(height - d, 0));
- }
- break lo;
- }
- accumLength += l;
- }
- ctx.rect(x, y, width, height);
- break;
- case CMD.Z:
- if (drawPart) {
- const l = pathSegLen[segCount++];
- if (accumLength + l > displayedLength) {
- const t = (displayedLength - accumLength) / l;
- ctx.lineTo(xi * (1 - t) + x0 * t, yi * (1 - t) + y0 * t);
- break lo;
- }
- accumLength += l;
- }
- ctx.closePath();
- xi = x0;
- yi = y0;
- }
- }
- }
- clone() {
- const newProxy = new PathProxy();
- const data = this.data;
- newProxy.data = data.slice ? data.slice()
- : Array.prototype.slice.call(data);
- newProxy._len = this._len;
- return newProxy;
- }
- private static initDefaultProps = (function () {
- const proto = PathProxy.prototype;
- proto._saveData = true;
- proto._ux = 0;
- proto._uy = 0;
- proto._pendingPtDist = 0;
- proto._version = 0;
- })()
- }
- export interface PathRebuilder {
- moveTo(x: number, y: number): void
- lineTo(x: number, y: number): void
- bezierCurveTo(x: number, y: number, x2: number, y2: number, x3: number, y3: number): void
- quadraticCurveTo(x: number, y: number, x2: number, y2: number): void
- arc(cx: number, cy: number, r: number, startAngle: number, endAngle: number, anticlockwise: boolean): void
- // eslint-disable-next-line max-len
- ellipse(cx: number, cy: number, radiusX: number, radiusY: number, rotation: number, startAngle: number, endAngle: number, anticlockwise: boolean): void
- rect(x: number, y: number, width: number, height: number): void
- closePath(): void
- }
|