feat(pdf): typed-text tool (Windows-Ink friendly)
Some checks failed
CI / Windows build (push) Has been cancelled

Add a text-annotation tool to the PDF editor. With the text tool a
pen-tap, or a mouse double-click, drops a text box at that normalized
page point and focuses a real Flutter TextField — so the OS IME and the
Windows-Ink handwriting panel feed it (device-validated). Tapping an
existing box re-opens it; clearing it deletes it.

- SidecarText {nx, ny, text, fontSize (page-relative), color} per page,
  glued under zoom; stored in the sidecar `texts` field (back-compat
  missing -> none), saved via scheduleTextsSave and loaded on open.
- Rendered in pageOverlaysBuilder at the scaled position.

PDF editor only for now (note text later). analyze clean, 397 tests.
This commit is contained in:
2026-06-25 00:11:45 +08:00
parent 1d5ba05bb8
commit 20add27a30
12 changed files with 627 additions and 3 deletions

View File

@@ -137,6 +137,9 @@ class SidecarRepository {
/// Page index → highlight rects loaded from the sidecar.
Map<int, List<SidecarHighlight>> get loadedHighlights => _sidecar.highlights;
/// Page index → typed-text annotations loaded from the sidecar.
Map<int, List<SidecarText>> get loadedTexts => _sidecar.texts;
/// Scratch-link anchors loaded from the sidecar.
List<SidecarScratchLink> get loadedScratchLinks => _sidecar.scratchLinks;
@@ -202,6 +205,17 @@ class SidecarRepository {
_replace(highlights: next);
}
/// Replace the typed-text annotations for [pageIndex] and schedule a save.
void scheduleTextsSave(int pageIndex, List<SidecarText> texts) {
final next = Map<int, List<SidecarText>>.from(_sidecar.texts);
if (texts.isEmpty) {
next.remove(pageIndex);
} else {
next[pageIndex] = List<SidecarText>.of(texts);
}
_replace(texts: next);
}
/// Add (or update) a scratch-link anchor, preserving any existing scratchpad,
/// and schedule a save.
void scheduleScratchLinkUpsert(ScratchLink link) {
@@ -302,6 +316,7 @@ class SidecarRepository {
String? title,
Map<int, List<EditorStroke>>? strokes,
Map<int, List<SidecarHighlight>>? highlights,
Map<int, List<SidecarText>>? texts,
List<Bookmark>? bookmarks,
List<SidecarScratchLink>? scratchLinks,
String? ocrText,
@@ -320,6 +335,7 @@ class SidecarRepository {
updatedAt: DateTime.now().toUtc(),
strokes: strokes ?? _sidecar.strokes,
highlights: highlights ?? _sidecar.highlights,
texts: texts ?? _sidecar.texts,
bookmarks: bookmarks ?? _sidecar.bookmarks,
scratchLinks: scratchLinks ?? _sidecar.scratchLinks,
ocrText: clearOcrText ? null : (ocrText ?? _sidecar.ocrText),