/** 
 | 
 * 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 
 | 
} 
 |