import PathProxy from '../core/PathProxy'; 
 | 
import * as line from './line'; 
 | 
import * as cubic from './cubic'; 
 | 
import * as quadratic from './quadratic'; 
 | 
import * as arc from './arc'; 
 | 
import * as curve from '../core/curve'; 
 | 
import windingLine from './windingLine'; 
 | 
  
 | 
const CMD = PathProxy.CMD; 
 | 
const PI2 = Math.PI * 2; 
 | 
  
 | 
const EPSILON = 1e-4; 
 | 
  
 | 
function isAroundEqual(a: number, b: number) { 
 | 
    return Math.abs(a - b) < EPSILON; 
 | 
} 
 | 
  
 | 
// 临时数组 
 | 
const roots = [-1, -1, -1]; 
 | 
const extrema = [-1, -1]; 
 | 
  
 | 
function swapExtrema() { 
 | 
    const tmp = extrema[0]; 
 | 
    extrema[0] = extrema[1]; 
 | 
    extrema[1] = tmp; 
 | 
} 
 | 
  
 | 
function windingCubic( 
 | 
    x0: number, y0: number, x1: number, y1: number, x2: number, y2: number, x3: number, y3: number, 
 | 
    x: number, y: number 
 | 
): number { 
 | 
    // Quick reject 
 | 
    if ( 
 | 
        (y > y0 && y > y1 && y > y2 && y > y3) 
 | 
        || (y < y0 && y < y1 && y < y2 && y < y3) 
 | 
    ) { 
 | 
        return 0; 
 | 
    } 
 | 
    const nRoots = curve.cubicRootAt(y0, y1, y2, y3, y, roots); 
 | 
    if (nRoots === 0) { 
 | 
        return 0; 
 | 
    } 
 | 
    else { 
 | 
        let w = 0; 
 | 
        let nExtrema = -1; 
 | 
        let y0_; 
 | 
        let y1_; 
 | 
        for (let i = 0; i < nRoots; i++) { 
 | 
            let t = roots[i]; 
 | 
  
 | 
            // Avoid winding error when intersection point is the connect point of two line of polygon 
 | 
            let unit = (t === 0 || t === 1) ? 0.5 : 1; 
 | 
  
 | 
            let x_ = curve.cubicAt(x0, x1, x2, x3, t); 
 | 
            if (x_ < x) { // Quick reject 
 | 
                continue; 
 | 
            } 
 | 
            if (nExtrema < 0) { 
 | 
                nExtrema = curve.cubicExtrema(y0, y1, y2, y3, extrema); 
 | 
                if (extrema[1] < extrema[0] && nExtrema > 1) { 
 | 
                    swapExtrema(); 
 | 
                } 
 | 
                y0_ = curve.cubicAt(y0, y1, y2, y3, extrema[0]); 
 | 
                if (nExtrema > 1) { 
 | 
                    y1_ = curve.cubicAt(y0, y1, y2, y3, extrema[1]); 
 | 
                } 
 | 
            } 
 | 
            if (nExtrema === 2) { 
 | 
                // 分成三段单调函数 
 | 
                if (t < extrema[0]) { 
 | 
                    w += y0_ < y0 ? unit : -unit; 
 | 
                } 
 | 
                else if (t < extrema[1]) { 
 | 
                    w += y1_ < y0_ ? unit : -unit; 
 | 
                } 
 | 
                else { 
 | 
                    w += y3 < y1_ ? unit : -unit; 
 | 
                } 
 | 
            } 
 | 
            else { 
 | 
                // 分成两段单调函数 
 | 
                if (t < extrema[0]) { 
 | 
                    w += y0_ < y0 ? unit : -unit; 
 | 
                } 
 | 
                else { 
 | 
                    w += y3 < y0_ ? unit : -unit; 
 | 
                } 
 | 
            } 
 | 
        } 
 | 
        return w; 
 | 
    } 
 | 
} 
 | 
  
 | 
function windingQuadratic( 
 | 
    x0: number, y0: number, x1: number, y1: number, x2: number, y2: number, 
 | 
    x: number, y: number 
 | 
): number { 
 | 
    // Quick reject 
 | 
    if ( 
 | 
        (y > y0 && y > y1 && y > y2) 
 | 
        || (y < y0 && y < y1 && y < y2) 
 | 
    ) { 
 | 
        return 0; 
 | 
    } 
 | 
    const nRoots = curve.quadraticRootAt(y0, y1, y2, y, roots); 
 | 
    if (nRoots === 0) { 
 | 
        return 0; 
 | 
    } 
 | 
    else { 
 | 
        const t = curve.quadraticExtremum(y0, y1, y2); 
 | 
        if (t >= 0 && t <= 1) { 
 | 
            let w = 0; 
 | 
            let y_ = curve.quadraticAt(y0, y1, y2, t); 
 | 
            for (let i = 0; i < nRoots; i++) { 
 | 
                // Remove one endpoint. 
 | 
                let unit = (roots[i] === 0 || roots[i] === 1) ? 0.5 : 1; 
 | 
  
 | 
                let x_ = curve.quadraticAt(x0, x1, x2, roots[i]); 
 | 
                if (x_ < x) {   // Quick reject 
 | 
                    continue; 
 | 
                } 
 | 
                if (roots[i] < t) { 
 | 
                    w += y_ < y0 ? unit : -unit; 
 | 
                } 
 | 
                else { 
 | 
                    w += y2 < y_ ? unit : -unit; 
 | 
                } 
 | 
            } 
 | 
            return w; 
 | 
        } 
 | 
        else { 
 | 
            // Remove one endpoint. 
 | 
            const unit = (roots[0] === 0 || roots[0] === 1) ? 0.5 : 1; 
 | 
  
 | 
            const x_ = curve.quadraticAt(x0, x1, x2, roots[0]); 
 | 
            if (x_ < x) {   // Quick reject 
 | 
                return 0; 
 | 
            } 
 | 
            return y2 < y0 ? unit : -unit; 
 | 
        } 
 | 
    } 
 | 
} 
 | 
// TODO 
 | 
// Arc 旋转 
 | 
// startAngle, endAngle has been normalized by normalizeArcAngles 
 | 
function windingArc( 
 | 
    cx: number, cy: number, r: number, startAngle: number, endAngle: number, anticlockwise: boolean, 
 | 
    x: number, y: number 
 | 
) { 
 | 
    y -= cy; 
 | 
    if (y > r || y < -r) { 
 | 
        return 0; 
 | 
    } 
 | 
    const tmp = Math.sqrt(r * r - y * y); 
 | 
    roots[0] = -tmp; 
 | 
    roots[1] = tmp; 
 | 
  
 | 
    const dTheta = Math.abs(startAngle - endAngle); 
 | 
    if (dTheta < 1e-4) { 
 | 
        return 0; 
 | 
    } 
 | 
    if (dTheta >= PI2 - 1e-4) { 
 | 
        // Is a circle 
 | 
        startAngle = 0; 
 | 
        endAngle = PI2; 
 | 
        const dir = anticlockwise ? 1 : -1; 
 | 
        if (x >= roots[0] + cx && x <= roots[1] + cx) { 
 | 
            return dir; 
 | 
        } 
 | 
        else { 
 | 
            return 0; 
 | 
        } 
 | 
    } 
 | 
  
 | 
    if (startAngle > endAngle) { 
 | 
        // Swap, make sure startAngle is smaller than endAngle. 
 | 
        const tmp = startAngle; 
 | 
        startAngle = endAngle; 
 | 
        endAngle = tmp; 
 | 
    } 
 | 
    // endAngle - startAngle is normalized to 0 - 2*PI. 
 | 
    // So following will normalize them to 0 - 4*PI 
 | 
    if (startAngle < 0) { 
 | 
        startAngle += PI2; 
 | 
        endAngle += PI2; 
 | 
    } 
 | 
  
 | 
    let w = 0; 
 | 
    for (let i = 0; i < 2; i++) { 
 | 
        const x_ = roots[i]; 
 | 
        if (x_ + cx > x) { 
 | 
            let angle = Math.atan2(y, x_); 
 | 
            let dir = anticlockwise ? 1 : -1; 
 | 
            if (angle < 0) { 
 | 
                angle = PI2 + angle; 
 | 
            } 
 | 
            if ( 
 | 
                (angle >= startAngle && angle <= endAngle) 
 | 
                || (angle + PI2 >= startAngle && angle + PI2 <= endAngle) 
 | 
            ) { 
 | 
                if (angle > Math.PI / 2 && angle < Math.PI * 1.5) { 
 | 
                    dir = -dir; 
 | 
                } 
 | 
                w += dir; 
 | 
            } 
 | 
        } 
 | 
    } 
 | 
    return w; 
 | 
} 
 | 
  
 | 
  
 | 
function containPath( 
 | 
    path: PathProxy, lineWidth: number, isStroke: boolean, x: number, y: number 
 | 
): boolean { 
 | 
    const data = path.data; 
 | 
    const len = path.len(); 
 | 
    let w = 0; 
 | 
    let xi = 0; 
 | 
    let yi = 0; 
 | 
    let x0 = 0; 
 | 
    let y0 = 0; 
 | 
    let x1; 
 | 
    let y1; 
 | 
  
 | 
    for (let i = 0; i < len;) { 
 | 
        const cmd = data[i++]; 
 | 
        const isFirst = i === 1; 
 | 
        // Begin a new subpath 
 | 
        if (cmd === CMD.M && i > 1) { 
 | 
            // Close previous subpath 
 | 
            if (!isStroke) { 
 | 
                w += windingLine(xi, yi, x0, y0, x, y); 
 | 
            } 
 | 
            // 如果被任何一个 subpath 包含 
 | 
            // if (w !== 0) { 
 | 
            //     return true; 
 | 
            // } 
 | 
        } 
 | 
  
 | 
        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 的时候使用 
 | 
                x0 = data[i++]; 
 | 
                y0 = data[i++]; 
 | 
                xi = x0; 
 | 
                yi = y0; 
 | 
                break; 
 | 
            case CMD.L: 
 | 
                if (isStroke) { 
 | 
                    if (line.containStroke(xi, yi, data[i], data[i + 1], lineWidth, x, y)) { 
 | 
                        return true; 
 | 
                    } 
 | 
                } 
 | 
                else { 
 | 
                    // NOTE 在第一个命令为 L, C, Q 的时候会计算出 NaN 
 | 
                    w += windingLine(xi, yi, data[i], data[i + 1], x, y) || 0; 
 | 
                } 
 | 
                xi = data[i++]; 
 | 
                yi = data[i++]; 
 | 
                break; 
 | 
            case CMD.C: 
 | 
                if (isStroke) { 
 | 
                    if (cubic.containStroke(xi, yi, 
 | 
                        data[i++], data[i++], data[i++], data[i++], data[i], data[i + 1], 
 | 
                        lineWidth, x, y 
 | 
                    )) { 
 | 
                        return true; 
 | 
                    } 
 | 
                } 
 | 
                else { 
 | 
                    w += windingCubic( 
 | 
                        xi, yi, 
 | 
                        data[i++], data[i++], data[i++], data[i++], data[i], data[i + 1], 
 | 
                        x, y 
 | 
                    ) || 0; 
 | 
                } 
 | 
                xi = data[i++]; 
 | 
                yi = data[i++]; 
 | 
                break; 
 | 
            case CMD.Q: 
 | 
                if (isStroke) { 
 | 
                    if (quadratic.containStroke(xi, yi, 
 | 
                        data[i++], data[i++], data[i], data[i + 1], 
 | 
                        lineWidth, x, y 
 | 
                    )) { 
 | 
                        return true; 
 | 
                    } 
 | 
                } 
 | 
                else { 
 | 
                    w += windingQuadratic( 
 | 
                        xi, yi, 
 | 
                        data[i++], data[i++], data[i], data[i + 1], 
 | 
                        x, y 
 | 
                    ) || 0; 
 | 
                } 
 | 
                xi = data[i++]; 
 | 
                yi = data[i++]; 
 | 
                break; 
 | 
            case CMD.A: 
 | 
                // TODO Arc 判断的开销比较大 
 | 
                const cx = data[i++]; 
 | 
                const cy = data[i++]; 
 | 
                const rx = data[i++]; 
 | 
                const ry = data[i++]; 
 | 
                const theta = data[i++]; 
 | 
                const dTheta = data[i++]; 
 | 
                // TODO Arc 旋转 
 | 
                i += 1; 
 | 
                const anticlockwise = !!(1 - data[i++]); 
 | 
                x1 = Math.cos(theta) * rx + cx; 
 | 
                y1 = Math.sin(theta) * ry + cy; 
 | 
                // 不是直接使用 arc 命令 
 | 
                if (!isFirst) { 
 | 
                    w += windingLine(xi, yi, x1, y1, x, y); 
 | 
                } 
 | 
                else { 
 | 
                    // 第一个命令起点还未定义 
 | 
                    x0 = x1; 
 | 
                    y0 = y1; 
 | 
                } 
 | 
                // zr 使用scale来模拟椭圆, 这里也对x做一定的缩放 
 | 
                const _x = (x - cx) * ry / rx + cx; 
 | 
                if (isStroke) { 
 | 
                    if (arc.containStroke( 
 | 
                        cx, cy, ry, theta, theta + dTheta, anticlockwise, 
 | 
                        lineWidth, _x, y 
 | 
                    )) { 
 | 
                        return true; 
 | 
                    } 
 | 
                } 
 | 
                else { 
 | 
                    w += windingArc( 
 | 
                        cx, cy, ry, theta, theta + dTheta, anticlockwise, 
 | 
                        _x, y 
 | 
                    ); 
 | 
                } 
 | 
                xi = Math.cos(theta + dTheta) * rx + cx; 
 | 
                yi = Math.sin(theta + dTheta) * ry + cy; 
 | 
                break; 
 | 
            case CMD.R: 
 | 
                x0 = xi = data[i++]; 
 | 
                y0 = yi = data[i++]; 
 | 
                const width = data[i++]; 
 | 
                const height = data[i++]; 
 | 
                x1 = x0 + width; 
 | 
                y1 = y0 + height; 
 | 
                if (isStroke) { 
 | 
                    if (line.containStroke(x0, y0, x1, y0, lineWidth, x, y) 
 | 
                        || line.containStroke(x1, y0, x1, y1, lineWidth, x, y) 
 | 
                        || line.containStroke(x1, y1, x0, y1, lineWidth, x, y) 
 | 
                        || line.containStroke(x0, y1, x0, y0, lineWidth, x, y) 
 | 
                    ) { 
 | 
                        return true; 
 | 
                    } 
 | 
                } 
 | 
                else { 
 | 
                    // FIXME Clockwise ? 
 | 
                    w += windingLine(x1, y0, x1, y1, x, y); 
 | 
                    w += windingLine(x0, y1, x0, y0, x, y); 
 | 
                } 
 | 
                break; 
 | 
            case CMD.Z: 
 | 
                if (isStroke) { 
 | 
                    if (line.containStroke( 
 | 
                        xi, yi, x0, y0, lineWidth, x, y 
 | 
                    )) { 
 | 
                        return true; 
 | 
                    } 
 | 
                } 
 | 
                else { 
 | 
                    // Close a subpath 
 | 
                    w += windingLine(xi, yi, x0, y0, x, y); 
 | 
                    // 如果被任何一个 subpath 包含 
 | 
                    // FIXME subpaths may overlap 
 | 
                    // if (w !== 0) { 
 | 
                    //     return true; 
 | 
                    // } 
 | 
                } 
 | 
                xi = x0; 
 | 
                yi = y0; 
 | 
                break; 
 | 
        } 
 | 
    } 
 | 
    if (!isStroke && !isAroundEqual(yi, y0)) { 
 | 
        w += windingLine(xi, yi, x0, y0, x, y) || 0; 
 | 
    } 
 | 
    return w !== 0; 
 | 
} 
 | 
  
 | 
export function contain(pathProxy: PathProxy, x: number, y: number): boolean { 
 | 
    return containPath(pathProxy, 0, false, x, y); 
 | 
} 
 | 
  
 | 
export function containStroke(pathProxy: PathProxy, lineWidth: number, x: number, y: number): boolean { 
 | 
    return containPath(pathProxy, lineWidth, true, x, y); 
 | 
} 
 |