3 Commits

Author SHA1 Message Date
6c2dd71b82 feat(pen): brush opacity + highlighter multiply
Some checks failed
CI / Windows build (push) Has been cancelled
Honor each brush's opacity/blend so the brushes feel distinct
(closes TODO(brush-opacity)).

- Shared paint resolver: a stroke's color alpha is multiplied by its
  brush opacity; ballpoint/pencil opacity is tied to pressure
  (per-stroke average this increment) so a ballpoint reads lighter
  than a solid fountain pen.
- Highlighter paints with BlendMode.multiply and draws once, so
  cross-stroke overlap darkens like a real marker while self-overlap
  doesn't.
- Applied across BOTH render paths (PenCanvas static/live painters and
  the PDF _PageOverlayPainter).

Pencil paper-grain texture still deferred (TODO brush-texture); brush
kind is not yet serialized (TODO brush-persist — next). analyze clean,
tests green.
2026-06-24 23:27:11 +08:00
eca5141372 feat(p0): live canvas renders via revision-gated ui.Picture cache (step 3)
Some checks failed
CI / Windows build (push) Has been cancelled
The live PenCanvas committed-ink layer now uses the relocated render/ painters
(render.StaticInkPainter + InkPictureCache + StrokeStore) instead of the old
canvas/ink_painters versions — the P0.5 perf prerequisite. The committed layer's
ui.Picture is recorded once per StrokeStore.revision and replayed on the raster
thread, so pinch / pan / live-stroke frames no longer re-rasterize committed ink.

- pen_canvas mirrors widget.strokes (PenStroke) into a StrokeStore (EditorStroke)
  on every new-list identity (the parent already replaces the list on each
  commit/erase), bumping the revision → cache invalidates → static layer repaints.
- thinning (PenConfig.pressureSensitivity) is threaded into the render painters
  AND folded into the cache key + shouldRepaint, so a sensitivity change can't
  replay a stale Picture built at the old thinning.
- live layer converts _liveStroke→EditorStroke per frame (correct: it must
  repaint every move); eraser preview keeps the existing canvas painter.
- pen_canvas disposes the InkPictureCache.

Equivalent by construction (both paths call buildStrokeOutline with the same
thinning); device confirms final fidelity. The old canvas Static/LiveInkPainter
are now orphaned (buildStrokePath still used by tests) — P1 deletes them.

flutter analyze lib/editor clean; 90/90 tests (+6: thinning repaint/cache + live).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 02:59:30 +08:00
914951afb7 feat(engine): P0 stroke engine + persistence
Per the full-refactor plan §9 (input-independent half of P0):
- engine: canonical EditorStroke (lossless InkStroke round-trip) +
  stroke_geometry (single getStroke outline) + revision-gated StrokeStore
- render: static/live ink painters + ink_picture_cache (revision-keyed)
  + annotation_layer (RepaintBoundary)
- persistence: DB v6 (ink, notebook_pages) + editor_repository diff-write
  (UPSERT changed / DELETE removed in one txn; id-set after commit) +
  save_scheduler
- pdf_service export now FILLS the getStroke outline (R7 hairline fix)
Not yet wired into the live editor (input relocation pending pen-pressure
diagnostic). 28 new tests pass.
2026-06-21 23:41:01 +08:00