UNPKG

lazy-widgets

Version:

Typescript retained mode GUI for the HTML canvas API

123 lines 4.49 kB
/** * CanvasRenderingContext2D.roundRect, but safe to use in browsers without * support, like Firefox. * * @category Helper */ export function safeRoundRect(ctx, x, y, w, h, radii) { // XXX implemented by following the spec: // https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-roundrect // note that the implementation deviates slightly from the spec; no subpath // with point (x, y) is created at the end // TODO stop allocating so many objects // 1. validate x, y, w, h if (!isFinite(x) || isNaN(x) || !isFinite(y) || isNaN(y) || !isFinite(w) || isNaN(w) || !isFinite(h) || isNaN(h)) { return; } // 2. turn numerical radii into array if (typeof radii === 'number' || ('x' in radii && 'y' in radii)) { radii = [radii]; } // 3. validate radii array length const cornerCount = radii.length; if (cornerCount !== 1 && cornerCount !== 2 && cornerCount !== 3 && cornerCount !== 4) { throw new RangeError('roundRect radii expected to be either a number, or an array of numbers with a length of 1, 2, 3 or 4'); } // 4-5. validate each radius in radii array const normalizedRadii = []; for (const radius of radii) { if (typeof radius === 'number') { if (!isFinite(radius) || isNaN(radius)) { return; } if (!isFinite(radius) || isNaN(radius)) { throw new RangeError('roundRect radii expected to be zero or positive'); } normalizedRadii.push({ x: radius, y: radius }); } else { if (!isFinite(radius.x) || isNaN(radius.x) || !isFinite(radius.y) || isNaN(radius.y)) { return; } if (!isFinite(radius.x) || isNaN(radius.x) || !isFinite(radius.y) || isNaN(radius.y)) { throw new RangeError('roundRect radii expected to be zero or positive'); } normalizedRadii.push({ x: radius.x, y: radius.y }); } } // 6-10. initialize actual corner radii let upperLeft = null, upperRight = null, lowerRight = null, lowerLeft = null; if (cornerCount === 4) { [upperLeft, upperRight, lowerRight, lowerLeft] = normalizedRadii; } else if (cornerCount === 3) { upperLeft = normalizedRadii[0]; upperRight = normalizedRadii[1]; // XXX must clone, otherwise scale can be applied multiple times lowerLeft = { x: upperRight.x, y: upperRight.y }; lowerRight = normalizedRadii[2]; } else if (cornerCount === 2) { upperLeft = normalizedRadii[0]; lowerRight = { x: upperLeft.x, y: upperLeft.y }; upperRight = normalizedRadii[1]; lowerLeft = { x: upperRight.x, y: upperRight.y }; } else { upperLeft = normalizedRadii[0]; upperRight = { x: upperLeft.x, y: upperLeft.y }; lowerRight = { x: upperLeft.x, y: upperLeft.y }; lowerLeft = { x: upperLeft.x, y: upperLeft.y }; } // 11. prevent overlapping radii const top = upperLeft.x + upperRight.x; const right = upperRight.y + lowerRight.y; const bottom = lowerRight.x + lowerLeft.x; const left = upperLeft.y + lowerLeft.y; const scale = Math.min(w / top, h / right, w / bottom, h / left); if (scale < 1) { upperLeft.x *= scale; upperLeft.y *= scale; upperRight.x *= scale; upperRight.y *= scale; lowerLeft.x *= scale; lowerLeft.y *= scale; lowerRight.x *= scale; lowerRight.y *= scale; } // 12-13. create subpath const xw = x + w; const yh = y + h; // .A------B. // / \ // H C // | | // G D // \ / // `F------E' // A (begin) ctx.moveTo(x + upperLeft.x, y); // A->B ctx.lineTo(xw - upperRight.x, y); // B->C ctx.quadraticCurveTo(xw, y, xw, y + upperRight.y); // C->D ctx.lineTo(xw, yh - lowerRight.y); // D->E ctx.quadraticCurveTo(xw, yh, xw - lowerRight.x, yh); // E->F ctx.lineTo(x + lowerLeft.x, yh); // F->G ctx.quadraticCurveTo(x, yh, x, yh - lowerLeft.y); // G->H ctx.lineTo(x, y + upperLeft.y); // H->A ctx.quadraticCurveTo(x, y, x + upperLeft.x, y); // A (end) ctx.closePath(); // XXX step 14 skipped; no subpath with (x, y) is created } //# sourceMappingURL=safeRoundRect.js.map