Step 4 - painter
2 min read
Create a pure utility module (board.util.ts) - no Angular imports - that owns all geometry and painting. The board component will delegate to it.
-
drawElement(ctx, el, helpers) rendering every BoardElement type:
- path: smoothed polyline from points[].
- note: sticky note, default 180x180, filled rounded rect with a subtle shadow, wrapped centered label.
- text: multi-line text block honoring fontSize/bold/italic/align.
- shape: all 10 kinds (rect, roundRect, ellipse, triangle, diamond, hexagon, star, bubble, line, arrow); fillable "area shapes" vs stroke-only lines; arrowheads on 'arrow'.
- frame: large back-layer container with a title label at the top-left, drawn as a light outline panel.
- connection: resolved later (prompt 16) - stub it.
- image: resolved later (prompt 15) - stub it.
-
orderForDraw(els): stable paint order in three bands - frames at the back, then connections, then everything else. Within a band sort by optional z (missing = 0), ties broken by createdAt. All rendering, hit-testing and exports must consume this order.
-
Hit-testing: hitTest(els, x, y) returning the topmost element (iterate the draw order backwards); point-in-shape per kind, distance-to-segment for paths and lines with a tolerance scaled by 1/viewport.scale; boundsOf(el) for marquee intersection.
-
Note label fitting: fitNoteFontSize(ctx, text, w, h) - binary-search the largest font size whose wrapped text fits the note with a 0.12 padding ratio, line-height ratio 1.3, floor at 7px. Memoize results in a Map capped at 2000 entries (clear wholesale when exceeded) - this cache is what keeps repainting hundreds of notes cheap.
-
Palettes: 8 pastel NOTE_COLORS, a stroke color set, background presets including one dark.
Write vitest specs for fitNoteFontSize and hitTest edge cases (rotated nothing yet - just bands, tolerance, empty text).
Acceptance: a hardcoded demo array of one of each element type renders correctly in draw-order bands, and clicking reports the correct topmost element id.
Discussion
0 commentsNo comments yet. Start the discussion.