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(); }