Added roundRect support
This commit is contained in:
parent
9d7c7bae3e
commit
130fe48f8e
@ -675,6 +675,13 @@ export default (function () {
|
||||
this.__currentDefaultPath.rect(x, y, width, height);
|
||||
};
|
||||
|
||||
Context.prototype.roundRect = function (x, y, width, height, radii) {
|
||||
if (!this.__currentDefaultPath) {
|
||||
this.beginPath();
|
||||
}
|
||||
this.__currentDefaultPath.roundRect(x, y, width, height, radii);
|
||||
};
|
||||
|
||||
Context.prototype.bezierCurveTo = function (cp1x, cp1y, cp2x, cp2y, x, y) {
|
||||
if (!this.__currentDefaultPath) {
|
||||
this.beginPath();
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@aha-app/svgcanvas",
|
||||
"version": "2.5.0-a13",
|
||||
"version": "2.5.0-a14",
|
||||
"description": "svgcanvas",
|
||||
"main": "dist/svgcanvas.js",
|
||||
"scripts": {
|
||||
|
16
path2d.js
16
path2d.js
@ -1,4 +1,5 @@
|
||||
import { format } from "./utils";
|
||||
import roundRectPolyfill from "./roundRect";
|
||||
|
||||
export default (function () {
|
||||
"use strict";
|
||||
@ -90,6 +91,11 @@ export default (function () {
|
||||
this.lineTo(x, y);
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a rounded rectangle to the path.
|
||||
*/
|
||||
Path2D.prototype.roundRect = roundRectPolyfill;
|
||||
|
||||
/**
|
||||
* Add a bezier command
|
||||
*/
|
||||
@ -287,9 +293,9 @@ export default (function () {
|
||||
ea = 2 * Math.PI - ea;
|
||||
}
|
||||
if (sa > ea && sa - ea < Math.PI)
|
||||
anticlockwise = true;
|
||||
anticlockwise = true;
|
||||
if (sa < ea && ea - sa > Math.PI)
|
||||
anticlockwise = true;
|
||||
anticlockwise = true;
|
||||
|
||||
this.lineTo(t_p1p0[0], t_p1p0[1])
|
||||
this.arc(p[0], p[1], radius, sa, ea, anticlockwise)
|
||||
@ -328,9 +334,9 @@ export default (function () {
|
||||
(2 * Math.PI);
|
||||
}
|
||||
var endX =
|
||||
x +
|
||||
Math.cos(-rotation) * radiusX * Math.cos(endAngle) +
|
||||
Math.sin(-rotation) * radiusY * Math.sin(endAngle),
|
||||
x +
|
||||
Math.cos(-rotation) * radiusX * Math.cos(endAngle) +
|
||||
Math.sin(-rotation) * radiusY * Math.sin(endAngle),
|
||||
endY =
|
||||
y -
|
||||
Math.sin(-rotation) * radiusX * Math.cos(endAngle) +
|
||||
|
232
roundRect.js
Normal file
232
roundRect.js
Normal file
@ -0,0 +1,232 @@
|
||||
/*
|
||||
* Implements the .roundRect() method of the CanvasPath mixin
|
||||
* as introduced by https://github.com/whatwg/html/pull/6765
|
||||
*
|
||||
* From https://github.com/Kaiido/roundRect
|
||||
*/
|
||||
export default function roundRect(x, y, w, h, radii) {
|
||||
if (!([x, y, w, h].every((input) => Number.isFinite(input)))) {
|
||||
return;
|
||||
}
|
||||
|
||||
radii = parseRadiiArgument(radii);
|
||||
|
||||
let upperLeft, upperRight, lowerRight, lowerLeft;
|
||||
|
||||
if (radii.length === 4) {
|
||||
|
||||
upperLeft = toCornerPoint(radii[0]);
|
||||
upperRight = toCornerPoint(radii[1]);
|
||||
lowerRight = toCornerPoint(radii[2]);
|
||||
lowerLeft = toCornerPoint(radii[3]);
|
||||
|
||||
} else if (radii.length === 3) {
|
||||
|
||||
upperLeft = toCornerPoint(radii[0]);
|
||||
upperRight = toCornerPoint(radii[1]);
|
||||
lowerLeft = toCornerPoint(radii[1]);
|
||||
lowerRight = toCornerPoint(radii[2]);
|
||||
|
||||
} else if (radii.length === 2) {
|
||||
|
||||
upperLeft = toCornerPoint(radii[0]);
|
||||
lowerRight = toCornerPoint(radii[0]);
|
||||
upperRight = toCornerPoint(radii[1]);
|
||||
lowerLeft = toCornerPoint(radii[1]);
|
||||
|
||||
} else if (radii.length === 1) {
|
||||
|
||||
upperLeft = toCornerPoint(radii[0]);
|
||||
upperRight = toCornerPoint(radii[0]);
|
||||
lowerRight = toCornerPoint(radii[0]);
|
||||
lowerLeft = toCornerPoint(radii[0]);
|
||||
|
||||
} else {
|
||||
|
||||
throw new RangeError(`${getErrorMessageHeader(this)} ${radii.length} is not a valid size for radii sequence.`);
|
||||
|
||||
}
|
||||
|
||||
const corners = [upperLeft, upperRight, lowerRight, lowerLeft];
|
||||
const negativeCorner = corners.find(({ x, y }) => x < 0 || y < 0);
|
||||
const negativeValue = negativeCorner?.x < 0 ? negativeCorner.x : negativeCorner?.y
|
||||
|
||||
if (corners.some(({ x, y }) => !Number.isFinite(x) || !Number.isFinite(y))) {
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
if (negativeCorner) {
|
||||
|
||||
throw new RangeError(`${getErrorMessageHeader(this)} Radius value ${negativeCorner} is negative.`);
|
||||
|
||||
}
|
||||
|
||||
fixOverlappingCorners(corners);
|
||||
|
||||
if (w < 0 && h < 0) {
|
||||
|
||||
this.moveTo(x - upperLeft.x, y);
|
||||
this.ellipse(x + w + upperRight.x, y - upperRight.y, upperRight.x, upperRight.y, 0, -Math.PI * 1.5, -Math.PI);
|
||||
this.ellipse(x + w + lowerRight.x, y + h + lowerRight.y, lowerRight.x, lowerRight.y, 0, -Math.PI, -Math.PI / 2);
|
||||
this.ellipse(x - lowerLeft.x, y + h + lowerLeft.y, lowerLeft.x, lowerLeft.y, 0, -Math.PI / 2, 0);
|
||||
this.ellipse(x - upperLeft.x, y - upperLeft.y, upperLeft.x, upperLeft.y, 0, 0, -Math.PI / 2);
|
||||
|
||||
} else if (w < 0) {
|
||||
|
||||
this.moveTo(x - upperLeft.x, y);
|
||||
this.ellipse(x + w + upperRight.x, y + upperRight.y, upperRight.x, upperRight.y, 0, -Math.PI / 2, -Math.PI, 1);
|
||||
this.ellipse(x + w + lowerRight.x, y + h - lowerRight.y, lowerRight.x, lowerRight.y, 0, -Math.PI, -Math.PI * 1.5, 1);
|
||||
this.ellipse(x - lowerLeft.x, y + h - lowerLeft.y, lowerLeft.x, lowerLeft.y, 0, Math.PI / 2, 0, 1);
|
||||
this.ellipse(x - upperLeft.x, y + upperLeft.y, upperLeft.x, upperLeft.y, 0, 0, -Math.PI / 2, 1);
|
||||
|
||||
} else if (h < 0) {
|
||||
|
||||
this.moveTo(x + upperLeft.x, y);
|
||||
this.ellipse(x + w - upperRight.x, y - upperRight.y, upperRight.x, upperRight.y, 0, Math.PI / 2, 0, 1);
|
||||
this.ellipse(x + w - lowerRight.x, y + h + lowerRight.y, lowerRight.x, lowerRight.y, 0, 0, -Math.PI / 2, 1);
|
||||
this.ellipse(x + lowerLeft.x, y + h + lowerLeft.y, lowerLeft.x, lowerLeft.y, 0, -Math.PI / 2, -Math.PI, 1);
|
||||
this.ellipse(x + upperLeft.x, y - upperLeft.y, upperLeft.x, upperLeft.y, 0, -Math.PI, -Math.PI * 1.5, 1);
|
||||
|
||||
} else {
|
||||
|
||||
this.moveTo(x + upperLeft.x, y);
|
||||
this.ellipse(x + w - upperRight.x, y + upperRight.y, upperRight.x, upperRight.y, 0, -Math.PI / 2, 0);
|
||||
this.ellipse(x + w - lowerRight.x, y + h - lowerRight.y, lowerRight.x, lowerRight.y, 0, 0, Math.PI / 2);
|
||||
this.ellipse(x + lowerLeft.x, y + h - lowerLeft.y, lowerLeft.x, lowerLeft.y, 0, Math.PI / 2, Math.PI);
|
||||
this.ellipse(x + upperLeft.x, y + upperLeft.y, upperLeft.x, upperLeft.y, 0, Math.PI, Math.PI * 1.5);
|
||||
|
||||
}
|
||||
|
||||
this.closePath();
|
||||
this.moveTo(x, y);
|
||||
|
||||
function toDOMPointInit(value) {
|
||||
|
||||
const { x, y, z, w } = value;
|
||||
return { x, y, z, w };
|
||||
|
||||
}
|
||||
|
||||
function parseRadiiArgument(value) {
|
||||
|
||||
// https://webidl.spec.whatwg.org/#es-union
|
||||
// with 'optional (unrestricted double or DOMPointInit
|
||||
// or sequence<(unrestricted double or DOMPointInit)>) radii = 0'
|
||||
const type = typeof value;
|
||||
|
||||
if (type === "undefined" || value === null) {
|
||||
|
||||
return [0];
|
||||
|
||||
}
|
||||
if (type === "function") {
|
||||
|
||||
return [NaN];
|
||||
|
||||
}
|
||||
if (type === "object") {
|
||||
|
||||
if (typeof value[Symbol.iterator] === "function") {
|
||||
|
||||
return [...value].map((elem) => {
|
||||
// https://webidl.spec.whatwg.org/#es-union
|
||||
// with '(unrestricted double or DOMPointInit)'
|
||||
const elemType = typeof elem;
|
||||
if (elemType === "undefined" || elem === null) {
|
||||
return 0;
|
||||
}
|
||||
if (elemType === "function") {
|
||||
return NaN;
|
||||
}
|
||||
if (elemType === "object") {
|
||||
return toDOMPointInit(elem);
|
||||
}
|
||||
return toUnrestrictedNumber(elem);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
return [toDOMPointInit(value)];
|
||||
|
||||
}
|
||||
|
||||
return [toUnrestrictedNumber(value)];
|
||||
|
||||
}
|
||||
|
||||
function toUnrestrictedNumber(value) {
|
||||
|
||||
return +value;
|
||||
|
||||
}
|
||||
|
||||
function toCornerPoint(value) {
|
||||
|
||||
const asNumber = toUnrestrictedNumber(value);
|
||||
if (Number.isFinite(asNumber)) {
|
||||
|
||||
return {
|
||||
x: asNumber,
|
||||
y: asNumber
|
||||
};
|
||||
|
||||
}
|
||||
if (Object(value) === value) {
|
||||
|
||||
return {
|
||||
x: toUnrestrictedNumber(value.x ?? 0),
|
||||
y: toUnrestrictedNumber(value.y ?? 0)
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
return {
|
||||
x: NaN,
|
||||
y: NaN
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
function fixOverlappingCorners(corners) {
|
||||
|
||||
const [upperLeft, upperRight, lowerRight, lowerLeft] = corners;
|
||||
const factors = [
|
||||
Math.abs(w) / (upperLeft.x + upperRight.x),
|
||||
Math.abs(h) / (upperRight.y + lowerRight.y),
|
||||
Math.abs(w) / (lowerRight.x + lowerLeft.x),
|
||||
Math.abs(h) / (upperLeft.y + lowerLeft.y)
|
||||
];
|
||||
const minFactor = Math.min(...factors);
|
||||
if (minFactor <= 1) {
|
||||
|
||||
for (const radii of corners) {
|
||||
|
||||
radii.x *= minFactor;
|
||||
radii.y *= minFactor;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function getErrorMessageHeader(instance) {
|
||||
|
||||
return `Failed to execute 'roundRect' on '${getConstructorName(instance)}':`;
|
||||
|
||||
}
|
||||
|
||||
function getConstructorName(instance) {
|
||||
|
||||
return Object(instance) === instance &&
|
||||
instance instanceof Path2D ? "Path2D" :
|
||||
instance instanceof globalThis?.CanvasRenderingContext2D ? "CanvasRenderingContext2D" :
|
||||
instance instanceof globalThis?.OffscreenCanvasRenderingContext2D ? "OffscreenCanvasRenderingContext2D" :
|
||||
instance?.constructor.name ||
|
||||
instance;
|
||||
|
||||
}
|
@ -23,6 +23,7 @@ import transform from "./tests/transform";
|
||||
import pattern from "./tests/pattern";
|
||||
import path2D from "./tests/path2D";
|
||||
import clip from "./tests/clip";
|
||||
import roundRect from "./tests/roundRect";
|
||||
|
||||
const tests = [
|
||||
tiger,
|
||||
@ -49,6 +50,7 @@ const tests = [
|
||||
transform,
|
||||
pattern,
|
||||
path2D,
|
||||
roundRect,
|
||||
];
|
||||
|
||||
for (let fn of tests) {
|
||||
|
@ -22,6 +22,7 @@ import tiger from './tests/tiger'
|
||||
import transform from './tests/transform'
|
||||
import pattern from "./tests/pattern";
|
||||
import path2D from './tests/path2D';
|
||||
import roundRect from './tests/roundRect'
|
||||
|
||||
const tests = {
|
||||
tiger,
|
||||
@ -45,7 +46,8 @@ const tests = {
|
||||
text,
|
||||
transform,
|
||||
pattern,
|
||||
path2D
|
||||
path2D,
|
||||
roundRect,
|
||||
};
|
||||
|
||||
const config = {
|
||||
|
29
test/tests/roundRect.js
Normal file
29
test/tests/roundRect.js
Normal file
@ -0,0 +1,29 @@
|
||||
export default function roundRect(ctx) {
|
||||
ctx.beginPath();
|
||||
ctx.roundRect(150, 20, 100, 50, 10);
|
||||
ctx.stroke();
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.roundRect(150, 150, 100, 50, 50);
|
||||
ctx.stroke();
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.roundRect(150, 300, 50, 50, 50);
|
||||
ctx.stroke();
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.roundRect(300, 20, 100, 50, 10);
|
||||
ctx.fill();
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.roundRect(300, 150, 100, 50, [10, 20, 30, 50]);
|
||||
ctx.stroke();
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.roundRect(300, 300, 50, 50, [10, 30]);
|
||||
ctx.stroke();
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.roundRect(300, 400, 50, 50, [30]);
|
||||
ctx.stroke();
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user