import type { AnnotationShape } from "@ltt/shared";
/**
* Vector annotation rendering over a screenshot canvas, shared by the
* AnnotationEditor and the ScreenshotViewer lightbox. Shapes use
* screenshot-fraction coordinates.
*/
export function drawShape(c: CanvasRenderingContext2D, shape: AnnotationShape, w: number, h: number) {
c.strokeStyle = shape.color;
c.fillStyle = shape.color;
c.lineWidth = shape.strokeWidth;
c.lineCap = "round";
c.lineJoin = "round";
const px = (x: number) => x * w;
const py = (y: number) => y * h;
switch (shape.tool) {
case "pen": {
const pts = shape.points ?? [];
if (pts.length < 2) break;
c.beginPath();
c.moveTo(px(pts[0][0]), py(pts[0][1]));
for (const [x, y] of pts.slice(1)) c.lineTo(px(x), py(y));
c.stroke();
break;
}
case "arrow": {
const pts = shape.points ?? [];
if (pts.length < 2) break;
const [x1, y1] = pts[0];
const [x2, y2] = pts[pts.length - 1];
const ax = px(x1), ay = py(y1), bx = px(x2), by = py(y2);
c.beginPath();
c.moveTo(ax, ay);
c.lineTo(bx, by);
c.stroke();
const angle = Math.atan2(by - ay, bx - ax);
const head = Math.max(shape.strokeWidth * 4, 10);
c.beginPath();
c.moveTo(bx, by);
c.lineTo(bx - head * Math.cos(angle - Math.PI / 7), by - head * Math.sin(angle - Math.PI / 7));
c.lineTo(bx - head * Math.cos(angle + Math.PI / 7), by - head * Math.sin(angle + Math.PI / 7));
c.closePath();
c.fill();
break;
}
case "rect": {
const r = shape.rect;
if (!r) break;
c.strokeRect(px(r[0]), py(r[1]), px(r[2]), py(r[3]));
break;
}
case "text": {
if (!shape.text || !shape.pos) break;
const fontSize = Math.max(shape.strokeWidth * 5, 14);
c.font = `bold ${fontSize}px "IBM Plex Mono", monospace`;
c.save();
c.shadowColor = "rgba(0,0,0,0.8)";
c.shadowBlur = 3;
c.fillText(shape.text, px(shape.pos[0]), py(shape.pos[1]));
c.restore();
break;
}
}
}
export function drawShapes(c: CanvasRenderingContext2D, shapes: AnnotationShape[], w: number, h: number) {
for (const shape of shapes) drawShape(c, shape, w, h);
}