Renderer layer: satinRenderer.ts + fillRenderer.ts consume the optional gradient field (#350 renderer layer) #394

Closed
opened 2026-10-05 11:09:32 +00:00 by Mark · 1 comment
Owner

Parent-Issue: #350

Depends on #393 (type layer — StitchPath.gradient?: { startColor, endColor }). Do this issue only after #393 lands.

Scope (renderer layer only — no UI changes)

Make src/lib/canvas/satinRenderer.ts and src/lib/canvas/fillRenderer.ts consume the new optional gradient field on the service stitch object. When gradient is present, the renderer draws with a CanvasGradient from startColor to endColor along the path's bounding-box axis (or along the satin direction for satin columns). When gradient is absent, behavior is unchanged — solid threadColor.

Concrete change — sat

Both files currently have a line near the top of the render function:

const color = obj.threadColor ?? "#000000";

(satinRenderer.ts:56, fillRenderer.ts:62). Replace with:

const solid = obj.threadColor ?? "#000000";
const startColor = obj.gradient?.startColor ?? solid;
const endColor = obj.gradient?.endColor ?? solid;

Then in the draw loop, build a createLinearGradient(x0, y0, x1, y1) once per render, add both color stops, and use the gradient as the fill/stroke when startColor !== endColor. When startColor === endColor (or both fall back to solid), use solid directly to skip the gradient machinery (no perf regression on the common no-gradient path).

For satin (satinRenderer.ts:117):

stroke: obj.threadColor ?? "#000000",

becomes:

stroke: gradientReady
  ? ctx.createLinearGradient(...)  // filled with start→end stops
  : (obj.threadColor ?? "#000000"),

For fill (fillRenderer.ts:62): same shape, but fillStyle instead of stroke.

Gradient axis decision: along the path's bounding-box diagonal (top-left → bottom-right is the simplest visually-correct default; matches the hatomUI convention in the same canvas). If the renderer already has a bbox or transform context handy, use that — don't introduce a new bbox calc per render.

Files to touch

  • src/lib/canvas/satinRenderer.ts — gradient branch + stop array
  • src/lib/canvas/fillRenderer.ts — same
  • src/lib/canvas/satinRenderer.test.ts — new test: a path with gradient: { startColor: '#ff0000', endColor: '#0000ff' } renders without throwing and produces a non-empty draw log; a path without gradient still uses threadColor (regression guard)
  • src/lib/canvas/fillRenderer.test.ts — same

Acceptance

  • pnpm test passes including the new gradient test cases.
  • The existing solid-color tests still pass unchanged (proves back-compat).
  • No UI change — this issue produces no visible change in the editor until #350 UI layer lands; that's correct and expected.

Reference patterns

Look at how the renderer tests already build a ServiceStitchObject with threadColor: "#..." (satinRenderer.test.ts:13, fillRenderer.test.ts:13) — copy that constructor shape and add the gradient-bearing variant alongside.

Discovery check

Confirm before starting:

grep -n 'threadColor' src/lib/canvas/satinRenderer.ts src/lib/canvas/fillRenderer.ts

should show the existing const color = obj.threadColor ?? "#000000" lines exactly as cited above. (Verified 2026-10-05.)

**Parent-Issue:** #350 Depends on #393 (type layer — `StitchPath.gradient?: { startColor, endColor }`). Do this issue only after #393 lands. ## Scope (renderer layer only — no UI changes) Make `src/lib/canvas/satinRenderer.ts` and `src/lib/canvas/fillRenderer.ts` consume the new optional `gradient` field on the service stitch object. When `gradient` is present, the renderer draws with a CanvasGradient from `startColor` to `endColor` along the path's bounding-box axis (or along the satin direction for satin columns). When `gradient` is absent, behavior is unchanged — solid `threadColor`. ## Concrete change — sat Both files currently have a line near the top of the render function: ```ts const color = obj.threadColor ?? "#000000"; ``` (satinRenderer.ts:56, fillRenderer.ts:62). Replace with: ```ts const solid = obj.threadColor ?? "#000000"; const startColor = obj.gradient?.startColor ?? solid; const endColor = obj.gradient?.endColor ?? solid; ``` Then in the draw loop, build a `createLinearGradient(x0, y0, x1, y1)` once per render, add both color stops, and use the gradient as the fill/stroke when `startColor !== endColor`. When `startColor === endColor` (or both fall back to `solid`), use `solid` directly to skip the gradient machinery (no perf regression on the common no-gradient path). For **satin** (satinRenderer.ts:117): ```ts stroke: obj.threadColor ?? "#000000", ``` becomes: ```ts stroke: gradientReady ? ctx.createLinearGradient(...) // filled with start→end stops : (obj.threadColor ?? "#000000"), ``` For **fill** (fillRenderer.ts:62): same shape, but `fillStyle` instead of `stroke`. Gradient axis decision: along the path's bounding-box diagonal (top-left → bottom-right is the simplest visually-correct default; matches the hatomUI convention in the same canvas). If the renderer already has a bbox or transform context handy, use that — don't introduce a new bbox calc per render. ## Files to touch - `src/lib/canvas/satinRenderer.ts` — gradient branch + stop array - `src/lib/canvas/fillRenderer.ts` — same - `src/lib/canvas/satinRenderer.test.ts` — new test: a path with `gradient: { startColor: '#ff0000', endColor: '#0000ff' }` renders without throwing and produces a non-empty draw log; a path without gradient still uses `threadColor` (regression guard) - `src/lib/canvas/fillRenderer.test.ts` — same ## Acceptance - `pnpm test` passes including the new gradient test cases. - The existing solid-color tests still pass unchanged (proves back-compat). - No UI change — this issue produces no visible change in the editor until #350 UI layer lands; that's correct and expected. ## Reference patterns Look at how the renderer tests already build a `ServiceStitchObject` with `threadColor: "#..."` (satinRenderer.test.ts:13, fillRenderer.test.ts:13) — copy that constructor shape and add the gradient-bearing variant alongside. ## Discovery check Confirm before starting: ``` grep -n 'threadColor' src/lib/canvas/satinRenderer.ts src/lib/canvas/fillRenderer.ts ``` should show the existing `const color = obj.threadColor ?? "#000000"` lines exactly as cited above. (Verified 2026-10-05.)
Author
Owner

Landed as commit 90900a9 on origin/main. Renderer-layer gradient consumption matches the type-layer shape from ab81a69. satinStitchObjectToFabricPath now reads obj.gradient and strokes with a Fabric.js Gradient along the path bbox diagonal when both stops are set and distinct; falls back to solid threadColor otherwise. drawFillStitches likewise reads gradient on service StitchObject (workspace StitchObject does not yet carry gradient — that wires in with the UI layer). 16 new tests across both files (satin: stitchBoundingBox/gradientStops/buildSatinLinearGradient/gradient-path, fill: gradient describe with bbox-diagonal verify). tsc/eslint/37/37 unit tests/build all clean. Full vitest suite has 20 pre-existing failures (smart guides geometry) that predate this commit.

Landed as commit 90900a9 on origin/main. Renderer-layer gradient consumption matches the type-layer shape from ab81a69. satinStitchObjectToFabricPath now reads `obj.gradient` and strokes with a Fabric.js Gradient along the path bbox diagonal when both stops are set and distinct; falls back to solid `threadColor` otherwise. drawFillStitches likewise reads gradient on service StitchObject (workspace StitchObject does not yet carry gradient — that wires in with the UI layer). 16 new tests across both files (satin: stitchBoundingBox/gradientStops/buildSatinLinearGradient/gradient-path, fill: gradient describe with bbox-diagonal verify). tsc/eslint/37/37 unit tests/build all clean. Full vitest suite has 20 pre-existing failures (smart guides geometry) that predate this commit.
Mark closed this issue 2026-10-05 21:21:03 +00:00
Sign in to join this conversation.
No description provided.