feat(pdf): paragraph-precise bookmarks
Some checks failed
CI / Windows build (push) Has been cancelled
Some checks failed
CI / Windows build (push) Has been cancelled
Add a bookmark tool to the PDF editor. A bookmark anchors to a precise location: when text is selected it captures the selection's normalized rect + the start char index in the page text (the true paragraph anchor); with no selection it falls back to the tapped page + point. - Bookmark model gains optional normalized anchor rect + charIndex + label (all absent from JSON when null, so old sidecars still load). - Bookmarks persist in the sidecar (scheduleBookmarkUpsert) and load on open; a bookmarks panel lists them and tapping one jumps to its page. Delete is persisted. Scoped to the PDF editor (note bookmarks later); scroll-to-anchor is page-level for now. analyze clean, 391 tests green.
This commit is contained in:
@@ -219,6 +219,65 @@ void main() {
|
||||
expect(reparsed.version, kBadnoteSidecarVersion);
|
||||
});
|
||||
|
||||
test('paragraph-anchored bookmark round-trips its anchor + char index', () {
|
||||
final original = BadnoteSidecar(bookmarks: [
|
||||
Bookmark(
|
||||
id: 'bm-anchor',
|
||||
documentId: 'doc1',
|
||||
pageNumber: 3,
|
||||
label: 'A bookmarked paragraph',
|
||||
color: 0xFF2196F3,
|
||||
createdAt: DateTime.utc(2026, 6, 24, 11, 0, 0),
|
||||
anchorLeft: 0.12,
|
||||
anchorTop: 0.20,
|
||||
anchorRight: 0.88,
|
||||
anchorBottom: 0.235,
|
||||
charIndex: 1234,
|
||||
),
|
||||
]);
|
||||
final reparsed = _roundTrip(original);
|
||||
expect(reparsed.bookmarks, original.bookmarks);
|
||||
final bm = reparsed.bookmarks.single;
|
||||
expect(bm.pageNumber, 3);
|
||||
expect(bm.anchorLeft, 0.12);
|
||||
expect(bm.anchorTop, 0.20);
|
||||
expect(bm.anchorRight, 0.88);
|
||||
expect(bm.anchorBottom, 0.235);
|
||||
expect(bm.charIndex, 1234);
|
||||
});
|
||||
|
||||
test('legacy page-only bookmark JSON decodes (anchor fields absent)', () {
|
||||
// A bookmark authored before the anchor fields existed: only page-level.
|
||||
final legacyJson = {
|
||||
'badnoteSidecarVersion': 1,
|
||||
'bookmarks': [
|
||||
{
|
||||
'id': 'legacy1',
|
||||
'documentId': 'doc1',
|
||||
'pageNumber': 7,
|
||||
'label': 'Old bookmark',
|
||||
'color': 0xFF2196F3,
|
||||
'createdAt': '2026-06-01T00:00:00.000Z',
|
||||
}
|
||||
],
|
||||
};
|
||||
final decoded = BadnoteSidecar.fromJson(legacyJson);
|
||||
final bm = decoded.bookmarks.single;
|
||||
expect(bm.pageNumber, 7);
|
||||
expect(bm.anchorLeft, isNull);
|
||||
expect(bm.anchorTop, isNull);
|
||||
expect(bm.anchorRight, isNull);
|
||||
expect(bm.anchorBottom, isNull);
|
||||
expect(bm.charIndex, isNull);
|
||||
// Re-encoding preserves the page-only data; any anchor keys are null.
|
||||
final reJson = bm.toJson();
|
||||
expect(reJson['anchorLeft'], isNull);
|
||||
expect(reJson['charIndex'], isNull);
|
||||
expect(reJson['pageNumber'], 7);
|
||||
// And it decodes back to an equal page-only bookmark.
|
||||
expect(Bookmark.fromJson(reJson), bm);
|
||||
});
|
||||
|
||||
test('unknown fields are ignored (forward-compat)', () {
|
||||
final encoded = jsonEncode(BadnoteSidecar(
|
||||
strokes: {0: [_editorStroke('s0', EditorTool.pen)]},
|
||||
|
||||
Reference in New Issue
Block a user