feat(note): rebuild note editor on the pen-first canvas
Some checks failed
CI / Windows build (push) Has been cancelled
Some checks failed
CI / Windows build (push) Has been cancelled
Notes now use the single performant inking engine (PenCanvas) instead of the old ink_canvas, per "all note features on the pen-first canvas". - ink_stroke_adapter: pure InkStroke<->PenStroke bridge (normalize against a logical note page; drop non-freehand shapes/text). Round-trip tested. - pen_palette_widgets: shared M3 ToolButton/PaletteDivider/RoundIconButton so PDF + note editors use identical chrome (PenEditorScreen migrated to them; its private copies deleted). - PenNoteScreen: PenCanvas over a white logical page, undo/redo, title, save -> Note.strokes (+ local OCR for search). Pressure curve, eraser size/mode and palm rejection all inherited from the shared canvas. - Route home (new/open) + search note hits -> PenNoteScreen; remove the now-redundant "Pen Canvas (beta)" spike button; delete the dead old note_editor_screen. Tests: ink_stroke_adapter (5) + pen_note_screen widget (load + commit, 2). flutter analyze: 0 issues. Full suite: 265/265.
This commit is contained in:
96
lib/editor/notebook/ink_stroke_adapter.dart
Normal file
96
lib/editor/notebook/ink_stroke_adapter.dart
Normal file
@@ -0,0 +1,96 @@
|
||||
// lib/editor/notebook/ink_stroke_adapter.dart
|
||||
//
|
||||
// Bridge between the legacy note/ppt storage model (`InkStroke`, ABSOLUTE pixel
|
||||
// coordinates, `PenTool`) and the pen-first canvas model (`PenStroke`,
|
||||
// NORMALIZED [0,1] coordinates, `PenStrokeKind`). The pen-first canvas is the
|
||||
// single performant inking engine, so notes and slides are rebuilt on top of it
|
||||
// and persisted back as `InkStroke` via this adapter.
|
||||
//
|
||||
// Coordinates are normalized against a logical page rectangle: ink absolute
|
||||
// (x,y) -> pen (x/pageW, y/pageH) and back. Stroke width is likewise expressed
|
||||
// as a fraction of the page width on the pen side and as absolute pixels on the
|
||||
// ink side. Only freehand pen/highlighter strokes round-trip; shape/text
|
||||
// `PenTool`s have no pen-canvas representation and are dropped (the pen-first
|
||||
// note is handwriting-first — see the rebuild roadmap).
|
||||
|
||||
import 'dart:ui' show Size;
|
||||
|
||||
import '../../models/ink_point.dart';
|
||||
import '../../models/ink_stroke.dart';
|
||||
import '../../models/pen_tool.dart';
|
||||
import '../canvas/pen_stroke.dart';
|
||||
|
||||
/// Logical page rectangle a blank note is inked on (portrait, ~A4 √2 ratio).
|
||||
/// Strokes are normalized against this so they stay pinned under zoom/pan.
|
||||
const Size kNoteLogicalPage = Size(1000, 1414);
|
||||
|
||||
/// True when [tool] is a freehand mark the pen canvas can render
|
||||
/// (pen/marker/highlighter). Shapes and text are not representable.
|
||||
bool isFreehandTool(PenTool tool) =>
|
||||
tool == PenTool.pen ||
|
||||
tool == PenTool.marker ||
|
||||
tool == PenTool.highlighter;
|
||||
|
||||
/// Maps an ink [PenTool] to the pen-canvas stroke kind.
|
||||
PenStrokeKind penKindFromTool(PenTool tool) =>
|
||||
tool == PenTool.highlighter ? PenStrokeKind.highlighter : PenStrokeKind.pen;
|
||||
|
||||
/// Maps a pen-canvas stroke kind back to a [PenTool].
|
||||
PenTool toolFromPenKind(PenStrokeKind kind) =>
|
||||
kind == PenStrokeKind.highlighter ? PenTool.highlighter : PenTool.pen;
|
||||
|
||||
/// Convert a stored [InkStroke] (absolute px on [page]) to a [PenStroke]
|
||||
/// (normalized). Returns null for non-freehand strokes (shapes/text), which the
|
||||
/// pen canvas cannot draw.
|
||||
PenStroke? penStrokeFromInk(InkStroke s, Size page) {
|
||||
if (!isFreehandTool(s.tool)) return null;
|
||||
if (s.points.isEmpty) return null;
|
||||
final w = page.width <= 0 ? 1.0 : page.width;
|
||||
final h = page.height <= 0 ? 1.0 : page.height;
|
||||
return PenStroke(
|
||||
points: [
|
||||
for (final p in s.points)
|
||||
PenPoint(p.x / w, p.y / h, p.pressure, tilt: p.tilt),
|
||||
],
|
||||
color: s.color,
|
||||
width: s.strokeWidth / w,
|
||||
kind: penKindFromTool(s.tool),
|
||||
);
|
||||
}
|
||||
|
||||
/// Convert a freshly drawn [PenStroke] (normalized) back to an [InkStroke]
|
||||
/// (absolute px on [page]) for persistence. [id] and [createdAt] come from the
|
||||
/// caller (uuid + clock) so this stays pure/deterministic.
|
||||
InkStroke inkStrokeFromPen(
|
||||
PenStroke s,
|
||||
Size page, {
|
||||
required String id,
|
||||
required DateTime createdAt,
|
||||
}) {
|
||||
final w = page.width <= 0 ? 1.0 : page.width;
|
||||
final h = page.height <= 0 ? 1.0 : page.height;
|
||||
return InkStroke(
|
||||
id: id,
|
||||
points: [
|
||||
for (final p in s.points)
|
||||
InkPoint(
|
||||
x: p.x * w,
|
||||
y: p.y * h,
|
||||
pressure: p.pressure ?? 0.5,
|
||||
tilt: p.tilt ?? 0.0,
|
||||
timestamp: 0,
|
||||
),
|
||||
],
|
||||
tool: toolFromPenKind(s.kind),
|
||||
color: s.color,
|
||||
strokeWidth: s.width * w,
|
||||
createdAt: createdAt,
|
||||
);
|
||||
}
|
||||
|
||||
/// Convert a list of stored ink strokes to pen strokes, dropping the ones the
|
||||
/// canvas cannot represent (shapes/text). Order is preserved.
|
||||
List<PenStroke> penStrokesFromInk(Iterable<InkStroke> strokes, Size page) =>
|
||||
[for (final s in strokes) penStrokeFromInk(s, page)]
|
||||
.whereType<PenStroke>()
|
||||
.toList();
|
||||
Reference in New Issue
Block a user