feat: canvas overlay renderer
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f22a0c16df
commit
96221a217d
2 changed files with 67 additions and 0 deletions
39
src/render/renderer.test.ts
Normal file
39
src/render/renderer.test.ts
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import { describe, it, expect } from 'vitest';
|
||||
import { drawOverlay } from './renderer';
|
||||
import type { Segment, RenderStyle } from '../types';
|
||||
|
||||
class FakeCtx {
|
||||
calls: string[] = [];
|
||||
strokeStyle = '';
|
||||
lineWidth = 0;
|
||||
beginPath() { this.calls.push('begin'); }
|
||||
setLineDash(d: number[]) { this.calls.push(`dash:${d.length}`); }
|
||||
moveTo(x: number, y: number) { this.calls.push(`move:${x},${y}`); }
|
||||
lineTo(x: number, y: number) { this.calls.push(`line:${x},${y}`); }
|
||||
stroke() { this.calls.push(`stroke:${this.strokeStyle}`); }
|
||||
}
|
||||
|
||||
const STYLE: RenderStyle = { cutColor: '#cut', rapidColor: '#rapid', lineWidth: 2 };
|
||||
|
||||
describe('drawOverlay', () => {
|
||||
it('strokes a cut as a solid polyline in the cut colour', () => {
|
||||
const ctx = new FakeCtx();
|
||||
const segs: Segment[] = [{ kind: 'cut', points: [[0, 0], [5, 5], [10, 0]] }];
|
||||
drawOverlay(ctx as unknown as CanvasRenderingContext2D, segs, STYLE);
|
||||
expect(ctx.calls).toEqual(['begin', 'dash:0', 'move:0,0', 'line:5,5', 'line:10,0', 'stroke:#cut']);
|
||||
});
|
||||
|
||||
it('strokes a rapid dashed in the rapid colour', () => {
|
||||
const ctx = new FakeCtx();
|
||||
const segs: Segment[] = [{ kind: 'rapid', points: [[0, 0], [5, 0]] }];
|
||||
drawOverlay(ctx as unknown as CanvasRenderingContext2D, segs, STYLE);
|
||||
expect(ctx.calls).toContain('dash:2');
|
||||
expect(ctx.calls).toContain('stroke:#rapid');
|
||||
});
|
||||
|
||||
it('skips degenerate segments', () => {
|
||||
const ctx = new FakeCtx();
|
||||
drawOverlay(ctx as unknown as CanvasRenderingContext2D, [{ kind: 'cut', points: [[1, 1]] }], STYLE);
|
||||
expect(ctx.calls).toEqual([]);
|
||||
});
|
||||
});
|
||||
28
src/render/renderer.ts
Normal file
28
src/render/renderer.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import type { Segment, RenderStyle } from '../types';
|
||||
|
||||
/** Draw already-projected (image-pixel) segments onto a 2D context. */
|
||||
export function drawOverlay(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
projected: Segment[],
|
||||
style: RenderStyle,
|
||||
): void {
|
||||
for (const seg of projected) {
|
||||
if (seg.points.length < 2) continue;
|
||||
ctx.beginPath();
|
||||
ctx.setLineDash(seg.kind === 'rapid' ? [6, 4] : []);
|
||||
ctx.strokeStyle = seg.kind === 'rapid' ? style.rapidColor : style.cutColor;
|
||||
ctx.lineWidth = style.lineWidth;
|
||||
const first = seg.points[0]!;
|
||||
ctx.moveTo(first[0], first[1]);
|
||||
for (let i = 1; i < seg.points.length; i++) {
|
||||
ctx.lineTo(seg.points[i]![0], seg.points[i]![1]);
|
||||
}
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
/** Clear the whole canvas. */
|
||||
export function clearCanvas(ctx: CanvasRenderingContext2D, width: number, height: number): void {
|
||||
ctx.setLineDash([]);
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue