303 lines
6.1 KiB
TypeScript
303 lines
6.1 KiB
TypeScript
|
|
import type { ShapeSettings } from '@/types/shape';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Draw a rectangle with optional rounded corners
|
||
|
|
*/
|
||
|
|
export function drawRectangle(
|
||
|
|
ctx: CanvasRenderingContext2D,
|
||
|
|
x: number,
|
||
|
|
y: number,
|
||
|
|
width: number,
|
||
|
|
height: number,
|
||
|
|
settings: ShapeSettings
|
||
|
|
): void {
|
||
|
|
ctx.save();
|
||
|
|
|
||
|
|
if (settings.cornerRadius > 0) {
|
||
|
|
// Rounded rectangle
|
||
|
|
const radius = Math.min(
|
||
|
|
settings.cornerRadius,
|
||
|
|
Math.abs(width) / 2,
|
||
|
|
Math.abs(height) / 2
|
||
|
|
);
|
||
|
|
|
||
|
|
ctx.beginPath();
|
||
|
|
const x1 = Math.min(x, x + width);
|
||
|
|
const y1 = Math.min(y, y + height);
|
||
|
|
const w = Math.abs(width);
|
||
|
|
const h = Math.abs(height);
|
||
|
|
|
||
|
|
ctx.moveTo(x1 + radius, y1);
|
||
|
|
ctx.lineTo(x1 + w - radius, y1);
|
||
|
|
ctx.quadraticCurveTo(x1 + w, y1, x1 + w, y1 + radius);
|
||
|
|
ctx.lineTo(x1 + w, y1 + h - radius);
|
||
|
|
ctx.quadraticCurveTo(x1 + w, y1 + h, x1 + w - radius, y1 + h);
|
||
|
|
ctx.lineTo(x1 + radius, y1 + h);
|
||
|
|
ctx.quadraticCurveTo(x1, y1 + h, x1, y1 + h - radius);
|
||
|
|
ctx.lineTo(x1, y1 + radius);
|
||
|
|
ctx.quadraticCurveTo(x1, y1, x1 + radius, y1);
|
||
|
|
ctx.closePath();
|
||
|
|
} else {
|
||
|
|
// Regular rectangle
|
||
|
|
ctx.beginPath();
|
||
|
|
ctx.rect(x, y, width, height);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (settings.fill) {
|
||
|
|
ctx.fillStyle = settings.fillColor;
|
||
|
|
ctx.fill();
|
||
|
|
}
|
||
|
|
|
||
|
|
if (settings.stroke) {
|
||
|
|
ctx.strokeStyle = settings.strokeColor;
|
||
|
|
ctx.lineWidth = settings.strokeWidth;
|
||
|
|
ctx.stroke();
|
||
|
|
}
|
||
|
|
|
||
|
|
ctx.restore();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Draw an ellipse
|
||
|
|
*/
|
||
|
|
export function drawEllipse(
|
||
|
|
ctx: CanvasRenderingContext2D,
|
||
|
|
cx: number,
|
||
|
|
cy: number,
|
||
|
|
rx: number,
|
||
|
|
ry: number,
|
||
|
|
settings: ShapeSettings
|
||
|
|
): void {
|
||
|
|
ctx.save();
|
||
|
|
ctx.beginPath();
|
||
|
|
ctx.ellipse(cx, cy, Math.abs(rx), Math.abs(ry), 0, 0, Math.PI * 2);
|
||
|
|
|
||
|
|
if (settings.fill) {
|
||
|
|
ctx.fillStyle = settings.fillColor;
|
||
|
|
ctx.fill();
|
||
|
|
}
|
||
|
|
|
||
|
|
if (settings.stroke) {
|
||
|
|
ctx.strokeStyle = settings.strokeColor;
|
||
|
|
ctx.lineWidth = settings.strokeWidth;
|
||
|
|
ctx.stroke();
|
||
|
|
}
|
||
|
|
|
||
|
|
ctx.restore();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Draw a line
|
||
|
|
*/
|
||
|
|
export function drawLine(
|
||
|
|
ctx: CanvasRenderingContext2D,
|
||
|
|
x1: number,
|
||
|
|
y1: number,
|
||
|
|
x2: number,
|
||
|
|
y2: number,
|
||
|
|
settings: ShapeSettings
|
||
|
|
): void {
|
||
|
|
ctx.save();
|
||
|
|
ctx.beginPath();
|
||
|
|
ctx.moveTo(x1, y1);
|
||
|
|
ctx.lineTo(x2, y2);
|
||
|
|
|
||
|
|
ctx.strokeStyle = settings.strokeColor;
|
||
|
|
ctx.lineWidth = settings.strokeWidth;
|
||
|
|
ctx.lineCap = 'round';
|
||
|
|
ctx.stroke();
|
||
|
|
|
||
|
|
ctx.restore();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Draw an arrow
|
||
|
|
*/
|
||
|
|
export function drawArrow(
|
||
|
|
ctx: CanvasRenderingContext2D,
|
||
|
|
x1: number,
|
||
|
|
y1: number,
|
||
|
|
x2: number,
|
||
|
|
y2: number,
|
||
|
|
settings: ShapeSettings
|
||
|
|
): void {
|
||
|
|
ctx.save();
|
||
|
|
|
||
|
|
// Draw line
|
||
|
|
ctx.beginPath();
|
||
|
|
ctx.moveTo(x1, y1);
|
||
|
|
ctx.lineTo(x2, y2);
|
||
|
|
ctx.strokeStyle = settings.strokeColor;
|
||
|
|
ctx.lineWidth = settings.strokeWidth;
|
||
|
|
ctx.lineCap = 'round';
|
||
|
|
ctx.stroke();
|
||
|
|
|
||
|
|
// Calculate arrow head
|
||
|
|
const angle = Math.atan2(y2 - y1, x2 - x1);
|
||
|
|
const headSize = settings.arrowHeadSize;
|
||
|
|
const headAngle = (settings.arrowHeadAngle * Math.PI) / 180;
|
||
|
|
|
||
|
|
const leftAngle = angle + Math.PI - headAngle;
|
||
|
|
const rightAngle = angle + Math.PI + headAngle;
|
||
|
|
|
||
|
|
const leftX = x2 + headSize * Math.cos(leftAngle);
|
||
|
|
const leftY = y2 + headSize * Math.sin(leftAngle);
|
||
|
|
const rightX = x2 + headSize * Math.cos(rightAngle);
|
||
|
|
const rightY = y2 + headSize * Math.sin(rightAngle);
|
||
|
|
|
||
|
|
// Draw arrow head
|
||
|
|
ctx.beginPath();
|
||
|
|
ctx.moveTo(x2, y2);
|
||
|
|
ctx.lineTo(leftX, leftY);
|
||
|
|
ctx.lineTo(rightX, rightY);
|
||
|
|
ctx.closePath();
|
||
|
|
|
||
|
|
if (settings.fill) {
|
||
|
|
ctx.fillStyle = settings.fillColor;
|
||
|
|
ctx.fill();
|
||
|
|
}
|
||
|
|
|
||
|
|
ctx.strokeStyle = settings.strokeColor;
|
||
|
|
ctx.lineWidth = settings.strokeWidth;
|
||
|
|
ctx.lineJoin = 'round';
|
||
|
|
ctx.stroke();
|
||
|
|
|
||
|
|
ctx.restore();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Draw a regular polygon
|
||
|
|
*/
|
||
|
|
export function drawPolygon(
|
||
|
|
ctx: CanvasRenderingContext2D,
|
||
|
|
cx: number,
|
||
|
|
cy: number,
|
||
|
|
radius: number,
|
||
|
|
sides: number,
|
||
|
|
settings: ShapeSettings
|
||
|
|
): void {
|
||
|
|
ctx.save();
|
||
|
|
ctx.beginPath();
|
||
|
|
|
||
|
|
const angleStep = (Math.PI * 2) / sides;
|
||
|
|
const startAngle = -Math.PI / 2; // Start at top
|
||
|
|
|
||
|
|
for (let i = 0; i <= sides; i++) {
|
||
|
|
const angle = startAngle + i * angleStep;
|
||
|
|
const x = cx + radius * Math.cos(angle);
|
||
|
|
const y = cy + radius * Math.sin(angle);
|
||
|
|
|
||
|
|
if (i === 0) {
|
||
|
|
ctx.moveTo(x, y);
|
||
|
|
} else {
|
||
|
|
ctx.lineTo(x, y);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
ctx.closePath();
|
||
|
|
|
||
|
|
if (settings.fill) {
|
||
|
|
ctx.fillStyle = settings.fillColor;
|
||
|
|
ctx.fill();
|
||
|
|
}
|
||
|
|
|
||
|
|
if (settings.stroke) {
|
||
|
|
ctx.strokeStyle = settings.strokeColor;
|
||
|
|
ctx.lineWidth = settings.strokeWidth;
|
||
|
|
ctx.lineJoin = 'round';
|
||
|
|
ctx.stroke();
|
||
|
|
}
|
||
|
|
|
||
|
|
ctx.restore();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Draw a star
|
||
|
|
*/
|
||
|
|
export function drawStar(
|
||
|
|
ctx: CanvasRenderingContext2D,
|
||
|
|
cx: number,
|
||
|
|
cy: number,
|
||
|
|
outerRadius: number,
|
||
|
|
points: number,
|
||
|
|
innerRadiusRatio: number,
|
||
|
|
settings: ShapeSettings
|
||
|
|
): void {
|
||
|
|
ctx.save();
|
||
|
|
ctx.beginPath();
|
||
|
|
|
||
|
|
const innerRadius = outerRadius * innerRadiusRatio;
|
||
|
|
const angleStep = Math.PI / points;
|
||
|
|
const startAngle = -Math.PI / 2;
|
||
|
|
|
||
|
|
for (let i = 0; i < points * 2; i++) {
|
||
|
|
const angle = startAngle + i * angleStep;
|
||
|
|
const radius = i % 2 === 0 ? outerRadius : innerRadius;
|
||
|
|
const x = cx + radius * Math.cos(angle);
|
||
|
|
const y = cy + radius * Math.sin(angle);
|
||
|
|
|
||
|
|
if (i === 0) {
|
||
|
|
ctx.moveTo(x, y);
|
||
|
|
} else {
|
||
|
|
ctx.lineTo(x, y);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
ctx.closePath();
|
||
|
|
|
||
|
|
if (settings.fill) {
|
||
|
|
ctx.fillStyle = settings.fillColor;
|
||
|
|
ctx.fill();
|
||
|
|
}
|
||
|
|
|
||
|
|
if (settings.stroke) {
|
||
|
|
ctx.strokeStyle = settings.strokeColor;
|
||
|
|
ctx.lineWidth = settings.strokeWidth;
|
||
|
|
ctx.lineJoin = 'round';
|
||
|
|
ctx.stroke();
|
||
|
|
}
|
||
|
|
|
||
|
|
ctx.restore();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Draw a triangle
|
||
|
|
*/
|
||
|
|
export function drawTriangle(
|
||
|
|
ctx: CanvasRenderingContext2D,
|
||
|
|
x: number,
|
||
|
|
y: number,
|
||
|
|
width: number,
|
||
|
|
height: number,
|
||
|
|
settings: ShapeSettings
|
||
|
|
): void {
|
||
|
|
ctx.save();
|
||
|
|
ctx.beginPath();
|
||
|
|
|
||
|
|
// Equilateral triangle
|
||
|
|
const centerX = x + width / 2;
|
||
|
|
const topY = y;
|
||
|
|
const bottomY = y + height;
|
||
|
|
const leftX = x;
|
||
|
|
const rightX = x + width;
|
||
|
|
|
||
|
|
ctx.moveTo(centerX, topY);
|
||
|
|
ctx.lineTo(rightX, bottomY);
|
||
|
|
ctx.lineTo(leftX, bottomY);
|
||
|
|
ctx.closePath();
|
||
|
|
|
||
|
|
if (settings.fill) {
|
||
|
|
ctx.fillStyle = settings.fillColor;
|
||
|
|
ctx.fill();
|
||
|
|
}
|
||
|
|
|
||
|
|
if (settings.stroke) {
|
||
|
|
ctx.strokeStyle = settings.strokeColor;
|
||
|
|
ctx.lineWidth = settings.strokeWidth;
|
||
|
|
ctx.lineJoin = 'round';
|
||
|
|
ctx.stroke();
|
||
|
|
}
|
||
|
|
|
||
|
|
ctx.restore();
|
||
|
|
}
|