import PathProxy from '../../core/PathProxy'; 
 | 
import { isArray } from '../../core/util'; 
 | 
  
 | 
const PI = Math.PI; 
 | 
const PI2 = PI * 2; 
 | 
const mathSin = Math.sin; 
 | 
const mathCos = Math.cos; 
 | 
const mathACos = Math.acos; 
 | 
const mathATan2 = Math.atan2; 
 | 
const mathAbs = Math.abs; 
 | 
const mathSqrt = Math.sqrt; 
 | 
const mathMax = Math.max; 
 | 
const mathMin = Math.min; 
 | 
const e = 1e-4; 
 | 
  
 | 
function intersect( 
 | 
    x0: number, y0: number, 
 | 
    x1: number, y1: number, 
 | 
    x2: number, y2: number, 
 | 
    x3: number, y3: number 
 | 
): [number, number] { 
 | 
    const dx10 = x1 - x0; 
 | 
    const dy10 = y1 - y0; 
 | 
    const dx32 = x3 - x2; 
 | 
    const dy32 = y3 - y2; 
 | 
    let t = dy32 * dx10 - dx32 * dy10; 
 | 
    if (t * t < e) { 
 | 
        return; 
 | 
    } 
 | 
    t = (dx32 * (y0 - y2) - dy32 * (x0 - x2)) / t; 
 | 
    return [x0 + t * dx10, y0 + t * dy10]; 
 | 
} 
 | 
  
 | 
// Compute perpendicular offset line of length rc. 
 | 
function computeCornerTangents( 
 | 
    x0: number, y0: number, 
 | 
    x1: number, y1: number, 
 | 
    radius: number, cr: number, 
 | 
    clockwise: boolean 
 | 
) { 
 | 
    const x01 = x0 - x1; 
 | 
    const y01 = y0 - y1; 
 | 
    const lo = (clockwise ? cr : -cr) / mathSqrt(x01 * x01 + y01 * y01); 
 | 
    const ox = lo * y01; 
 | 
    const oy = -lo * x01; 
 | 
    const x11 = x0 + ox; 
 | 
    const y11 = y0 + oy; 
 | 
    const x10 = x1 + ox; 
 | 
    const y10 = y1 + oy; 
 | 
    const x00 = (x11 + x10) / 2; 
 | 
    const y00 = (y11 + y10) / 2; 
 | 
    const dx = x10 - x11; 
 | 
    const dy = y10 - y11; 
 | 
    const d2 = dx * dx + dy * dy; 
 | 
    const r = radius - cr; 
 | 
    const s = x11 * y10 - x10 * y11; 
 | 
    const d = (dy < 0 ? -1 : 1) * mathSqrt(mathMax(0, r * r * d2 - s * s)); 
 | 
    let cx0 = (s * dy - dx * d) / d2; 
 | 
    let cy0 = (-s * dx - dy * d) / d2; 
 | 
    const cx1 = (s * dy + dx * d) / d2; 
 | 
    const cy1 = (-s * dx + dy * d) / d2; 
 | 
    const dx0 = cx0 - x00; 
 | 
    const dy0 = cy0 - y00; 
 | 
    const dx1 = cx1 - x00; 
 | 
    const dy1 = cy1 - y00; 
 | 
  
 | 
    // Pick the closer of the two intersection points 
 | 
    // TODO: Is there a faster way to determine which intersection to use? 
 | 
    if (dx0 * dx0 + dy0 * dy0 > dx1 * dx1 + dy1 * dy1) { 
 | 
        cx0 = cx1; 
 | 
        cy0 = cy1; 
 | 
    } 
 | 
  
 | 
    return { 
 | 
        cx: cx0, 
 | 
        cy: cy0, 
 | 
        x0: -ox, 
 | 
        y0: -oy, 
 | 
        x1: cx0 * (radius / r - 1), 
 | 
        y1: cy0 * (radius / r - 1) 
 | 
    }; 
 | 
} 
 | 
  
 | 
// For compatibility, don't use normalizeCssArray 
 | 
// 5 represents [5, 5, 5, 5] 
 | 
// [5] represents [5, 5, 0, 0] 
 | 
// [5, 10] represents [5, 5, 10, 10] 
 | 
// [5, 10, 15] represents [5, 10, 15, 15] 
 | 
// [5, 10, 15, 20] represents [5, 10, 15, 20] 
 | 
function normalizeCornerRadius(cr: number | number[]): number[] { 
 | 
    let arr: number[]; 
 | 
    if (isArray(cr)) { 
 | 
        const len = cr.length; 
 | 
        if (!len) { 
 | 
            return cr as number[]; 
 | 
        } 
 | 
        if (len === 1) { 
 | 
            arr = [cr[0], cr[0], 0, 0]; 
 | 
        } 
 | 
        else if (len === 2) { 
 | 
            arr = [cr[0], cr[0], cr[1], cr[1]]; 
 | 
        } 
 | 
        else if (len === 3) { 
 | 
            arr = cr.concat(cr[2]); 
 | 
        } 
 | 
        else { 
 | 
            arr = cr; 
 | 
        } 
 | 
    } 
 | 
    else { 
 | 
        arr = [cr, cr, cr, cr]; 
 | 
    } 
 | 
    return arr; 
 | 
} 
 | 
  
 | 
export function buildPath(ctx: CanvasRenderingContext2D | PathProxy, shape: { 
 | 
    cx: number 
 | 
    cy: number 
 | 
    startAngle: number 
 | 
    endAngle: number 
 | 
    clockwise?: boolean, 
 | 
    r?: number, 
 | 
    r0?: number, 
 | 
    cornerRadius?: number | number[] 
 | 
}) { 
 | 
    let radius = mathMax(shape.r, 0); 
 | 
    let innerRadius = mathMax(shape.r0 || 0, 0); 
 | 
    const hasRadius = radius > 0; 
 | 
    const hasInnerRadius = innerRadius > 0; 
 | 
  
 | 
    if (!hasRadius && !hasInnerRadius) { 
 | 
        return; 
 | 
    } 
 | 
  
 | 
    if (!hasRadius) { 
 | 
        // use innerRadius as radius if no radius 
 | 
        radius = innerRadius; 
 | 
        innerRadius = 0; 
 | 
    } 
 | 
  
 | 
    if (innerRadius > radius) { 
 | 
        // swap, ensure that radius is always larger than innerRadius 
 | 
        const tmp = radius; 
 | 
        radius = innerRadius; 
 | 
        innerRadius = tmp; 
 | 
    } 
 | 
  
 | 
    const { startAngle, endAngle } = shape; 
 | 
    if (isNaN(startAngle) || isNaN(endAngle)) { 
 | 
        return; 
 | 
    } 
 | 
  
 | 
    const { cx, cy } = shape; 
 | 
    const clockwise = !!shape.clockwise; 
 | 
  
 | 
    let arc = mathAbs(endAngle - startAngle); 
 | 
    const mod = arc > PI2 && arc % PI2; 
 | 
    mod > e && (arc = mod); 
 | 
  
 | 
    // is a point 
 | 
    if (!(radius > e)) { 
 | 
        ctx.moveTo(cx, cy); 
 | 
    } 
 | 
    // is a circle or annulus 
 | 
    else if (arc > PI2 - e) { 
 | 
        ctx.moveTo( 
 | 
            cx + radius * mathCos(startAngle), 
 | 
            cy + radius * mathSin(startAngle) 
 | 
        ); 
 | 
        ctx.arc(cx, cy, radius, startAngle, endAngle, !clockwise); 
 | 
  
 | 
        if (innerRadius > e) { 
 | 
            ctx.moveTo( 
 | 
                cx + innerRadius * mathCos(endAngle), 
 | 
                cy + innerRadius * mathSin(endAngle) 
 | 
            ); 
 | 
            ctx.arc(cx, cy, innerRadius, endAngle, startAngle, clockwise); 
 | 
        } 
 | 
    } 
 | 
    // is a circular or annular sector 
 | 
    else { 
 | 
        let icrStart; 
 | 
        let icrEnd; 
 | 
        let ocrStart; 
 | 
        let ocrEnd; 
 | 
  
 | 
        let ocrs; 
 | 
        let ocre; 
 | 
        let icrs; 
 | 
        let icre; 
 | 
  
 | 
        let ocrMax; 
 | 
        let icrMax; 
 | 
        let limitedOcrMax; 
 | 
        let limitedIcrMax; 
 | 
  
 | 
        let xre; 
 | 
        let yre; 
 | 
        let xirs; 
 | 
        let yirs; 
 | 
  
 | 
        const xrs = radius * mathCos(startAngle); 
 | 
        const yrs = radius * mathSin(startAngle); 
 | 
        const xire = innerRadius * mathCos(endAngle); 
 | 
        const yire = innerRadius * mathSin(endAngle); 
 | 
  
 | 
        const hasArc = arc > e; 
 | 
        if (hasArc) { 
 | 
            const cornerRadius = shape.cornerRadius; 
 | 
            if (cornerRadius) { 
 | 
                [icrStart, icrEnd, ocrStart, ocrEnd] = normalizeCornerRadius(cornerRadius); 
 | 
            } 
 | 
  
 | 
            const halfRd = mathAbs(radius - innerRadius) / 2; 
 | 
            ocrs = mathMin(halfRd, ocrStart); 
 | 
            ocre = mathMin(halfRd, ocrEnd); 
 | 
            icrs = mathMin(halfRd, icrStart); 
 | 
            icre = mathMin(halfRd, icrEnd); 
 | 
  
 | 
            limitedOcrMax = ocrMax = mathMax(ocrs, ocre); 
 | 
            limitedIcrMax = icrMax = mathMax(icrs, icre); 
 | 
  
 | 
            // draw corner radius 
 | 
            if (ocrMax > e || icrMax > e) { 
 | 
                xre = radius * mathCos(endAngle); 
 | 
                yre = radius * mathSin(endAngle); 
 | 
                xirs = innerRadius * mathCos(startAngle); 
 | 
                yirs = innerRadius * mathSin(startAngle); 
 | 
  
 | 
                // restrict the max value of corner radius 
 | 
                if (arc < PI) { 
 | 
                    const it = intersect(xrs, yrs, xirs, yirs, xre, yre, xire, yire); 
 | 
                    if (it) { 
 | 
                        const x0 = xrs - it[0]; 
 | 
                        const y0 = yrs - it[1]; 
 | 
                        const x1 = xre - it[0]; 
 | 
                        const y1 = yre - it[1]; 
 | 
                        const a = 1 / mathSin( 
 | 
                            // eslint-disable-next-line max-len 
 | 
                            mathACos((x0 * x1 + y0 * y1) / (mathSqrt(x0 * x0 + y0 * y0) * mathSqrt(x1 * x1 + y1 * y1))) / 2 
 | 
                        ); 
 | 
                        const b = mathSqrt(it[0] * it[0] + it[1] * it[1]); 
 | 
                        limitedOcrMax = mathMin(ocrMax, (radius - b) / (a + 1)); 
 | 
                        limitedIcrMax = mathMin(icrMax, (innerRadius - b) / (a - 1)); 
 | 
                    } 
 | 
                } 
 | 
            } 
 | 
        } 
 | 
  
 | 
        // the sector is collapsed to a line 
 | 
        if (!hasArc) { 
 | 
            ctx.moveTo(cx + xrs, cy + yrs); 
 | 
        } 
 | 
        // the outer ring has corners 
 | 
        else if (limitedOcrMax > e) { 
 | 
            const crStart = mathMin(ocrStart, limitedOcrMax); 
 | 
            const crEnd = mathMin(ocrEnd, limitedOcrMax); 
 | 
            const ct0 = computeCornerTangents(xirs, yirs, xrs, yrs, radius, crStart, clockwise); 
 | 
            const ct1 = computeCornerTangents(xre, yre, xire, yire, radius, crEnd, clockwise); 
 | 
  
 | 
            ctx.moveTo(cx + ct0.cx + ct0.x0, cy + ct0.cy + ct0.y0); 
 | 
  
 | 
            // Have the corners merged? 
 | 
            if (limitedOcrMax < ocrMax && crStart === crEnd) { 
 | 
                // eslint-disable-next-line max-len 
 | 
                ctx.arc(cx + ct0.cx, cy + ct0.cy, limitedOcrMax, mathATan2(ct0.y0, ct0.x0), mathATan2(ct1.y0, ct1.x0), !clockwise); 
 | 
            } 
 | 
            else { 
 | 
                // draw the two corners and the ring 
 | 
                // eslint-disable-next-line max-len 
 | 
                crStart > 0 && ctx.arc(cx + ct0.cx, cy + ct0.cy, crStart, mathATan2(ct0.y0, ct0.x0), mathATan2(ct0.y1, ct0.x1), !clockwise); 
 | 
                // eslint-disable-next-line max-len 
 | 
                ctx.arc(cx, cy, radius, mathATan2(ct0.cy + ct0.y1, ct0.cx + ct0.x1), mathATan2(ct1.cy + ct1.y1, ct1.cx + ct1.x1), !clockwise); 
 | 
                // eslint-disable-next-line max-len 
 | 
                crEnd > 0 && ctx.arc(cx + ct1.cx, cy + ct1.cy, crEnd, mathATan2(ct1.y1, ct1.x1), mathATan2(ct1.y0, ct1.x0), !clockwise); 
 | 
            } 
 | 
        } 
 | 
        // the outer ring is a circular arc 
 | 
        else { 
 | 
            ctx.moveTo(cx + xrs, cy + yrs); 
 | 
            ctx.arc(cx, cy, radius, startAngle, endAngle, !clockwise); 
 | 
        } 
 | 
  
 | 
        // no inner ring, is a circular sector 
 | 
        if (!(innerRadius > e) || !hasArc) { 
 | 
            ctx.lineTo(cx + xire, cy + yire); 
 | 
        } 
 | 
        // the inner ring has corners 
 | 
        else if (limitedIcrMax > e) { 
 | 
            const crStart = mathMin(icrStart, limitedIcrMax); 
 | 
            const crEnd = mathMin(icrEnd, limitedIcrMax); 
 | 
            const ct0 = computeCornerTangents(xire, yire, xre, yre, innerRadius, -crEnd, clockwise); 
 | 
            const ct1 = computeCornerTangents(xrs, yrs, xirs, yirs, innerRadius, -crStart, clockwise); 
 | 
            ctx.lineTo(cx + ct0.cx + ct0.x0, cy + ct0.cy + ct0.y0); 
 | 
  
 | 
            // Have the corners merged? 
 | 
            if (limitedIcrMax < icrMax && crStart === crEnd) { 
 | 
                // eslint-disable-next-line max-len 
 | 
                ctx.arc(cx + ct0.cx, cy + ct0.cy, limitedIcrMax, mathATan2(ct0.y0, ct0.x0), mathATan2(ct1.y0, ct1.x0), !clockwise); 
 | 
            } 
 | 
            // draw the two corners and the ring 
 | 
            else { 
 | 
                // eslint-disable-next-line max-len 
 | 
                crEnd > 0 && ctx.arc(cx + ct0.cx, cy + ct0.cy, crEnd, mathATan2(ct0.y0, ct0.x0), mathATan2(ct0.y1, ct0.x1), !clockwise); 
 | 
                // eslint-disable-next-line max-len 
 | 
                ctx.arc(cx, cy, innerRadius, mathATan2(ct0.cy + ct0.y1, ct0.cx + ct0.x1), mathATan2(ct1.cy + ct1.y1, ct1.cx + ct1.x1), clockwise); 
 | 
                // eslint-disable-next-line max-len 
 | 
                crStart > 0 && ctx.arc(cx + ct1.cx, cy + ct1.cy, crStart, mathATan2(ct1.y1, ct1.x1), mathATan2(ct1.y0, ct1.x0), !clockwise); 
 | 
            } 
 | 
        } 
 | 
        // the inner ring is just a circular arc 
 | 
        else { 
 | 
            // FIXME: if no lineTo, svg renderer will perform an abnormal drawing behavior. 
 | 
            ctx.lineTo(cx + xire, cy + yire); 
 | 
  
 | 
            ctx.arc(cx, cy, innerRadius, endAngle, startAngle, clockwise); 
 | 
        } 
 | 
    } 
 | 
  
 | 
    ctx.closePath(); 
 | 
} 
 |