grimoire 1.0.1
grimoire: ^1.0.1 copied to clipboard
Calibre-style Flutter ebook reader built on Unseal, with paginated or scrolling layouts, navigation, search, bookmarks, highlights, themes, and text selection hooks.
1.0.1 #
Fixed #
- Shortened the package description to meet pub.dev's metadata limit.
- Included the complete MIT license text so pub.dev can identify the package license.
1.0.0 #
The first Grimoire release. All features and fixes accumulated before the first publication are part of this version.
Added #
-
Fixed-layout EPUB pages: EPUB 3 spine items whose effective rendition layout is
pre-paginatedpresent as full pages instead of reflowed columns. The serving pipeline marks them (elv-fixedbody class plusdata-elv-page="W×H"from the book's declared viewports, book viewport metas dropped so they cannot fight the stage) andviewer.jssizes the page as a stage scaled withmin(vw/W, vh/H)— no pagination, no DOM wrapping, declared geometry and book selectors intact. Text selection, highlights, search marks, narration marks and fragment lookup keep working over the canonical text; pages without a declared viewport fall back to fit-width. Horizontal swipes, arrow keys, PageUp/PageDown, the wheel and tap zones turn fixed pages through the existingPageTurnEvent(RTL progression mirrored in JS); typography, margin, column, font, stylesheet and color-override settings never touch a fixed page's content; fixed items count exactly one page each (a fully fixed book needs no measurement pass).ViewerController.isFixedLayoutEpubandisFixedLayoutPage(fileIndex)let hosts branch on the mode; progress advances one page per reading-order item. V1 does not support synthetic spreads, orientation locks, or pinch, pan, and double-tap stage zoom. Depends onunseal: ^1.3.0for the fixed-layout rendition model. -
Password-protected PDFs: facsimile rendering now supports encrypted PDFs. When pdf.js needs a password (first open or a rejected attempt) the viewer reports a
PdfPasswordRequestEventandViewerController.onPdfPasswordasks the host — return the password and rendering continues, return null to leave the canvas unrendered (the parsed text layer stays usable). Passwords are never cached by the viewer; the failed-attempt count grows with each rejected answer. -
One-way annotation migration:
importAnnotationsaccepts the previous unversioned viewer persistence envelope and converts its highlights and bookmarks to Unseal's canonical versioned records without data loss.exportAnnotationsalways writes the canonical format, so applications do not need to maintain two storage formats. -
Fixed-layout page turning: comics and PDF facsimile pages now turn on a horizontal swipe (same 64px/0.35 thresholds as the text gesture layer, RTL-aware), driven by a new
PageTurnEventthrough the bridge.goToPdfPage(index)jumps PDFs to a page andpositionPageLocatorexposes the current position as aPageLocatorfor hosts; word marks are verified to apply to reflowed PDF pages. -
PDF support, both reading modes: PDFs open through the same
ViewerController(Unseal's pure-Dart pipeline extracts the text, reflows it into one HTML section per page and keeps the original bytes). Reflow mode reads like any other book — pagination, themes, search, highlights and narration over the canonical per-page text space. Facsimile mode renders the original pages pixel-faithfully with pdf.js (3.11.174 pinned, the last UMD build; lazily downloaded to a version-keyed cache, CDN on the web — nothing bundled, zero pub dependencies) behind a transparent text layer whose spans carry the same canonical text, so selection, search hits and highlights land on the identical character offsets in both modes.PdfReadingMode(reflow | facsimile) withsetPdfReadingMode— the position survives the switch because sectioniis pageiin both modes; scanned PDFs default to facsimile automatically.pdfJsEnabled/pdfJsCachePathmirror the MathJax configuration hooks. -
Search modes:
controller.search(query, {mode, caseSensitive, nearChars})exposes Calibre's contains / whole words / regex / near queries (matching lives in Unseal). The overlay gains a mode picker, case toggle and inline validation errors, marks every match on the visible page (re-marked when chapters swap), and wires the previously dormant next/previous match cycling with a position counter. -
Per-side margins and text-block caps:
ViewerMargins(left, top, right, bottom,maxTextWidth,maxTextHeight) withsetMarginsPerSide;setMargins(px)remains the uniform shorthand. In paged mode the inline-axis pair doubles as the column gap (Calibre layout parity) and the caps grow the margins instead of shrinking the page (CalibreView.set_margins). -
User stylesheet:
setUserStylesheet(css)injects a dedicated style element that theme changes never clobber, with the same body-class hooks Calibre ships (elv-paged/elv-scrolling,elv-dark/elv-light). -
Reader profiles:
captureProfile()returns a sparse diff-from-default map of every appearance setting;applyProfile(map)restores it and resets anything absent — applying an empty map resets all appearance (Calibre's__default__). Storage stays host-owned. -
RTL page progression: books declaring
page-progression-direction="rtl"get mirrored page flow — scroll math negates X at the viewport boundary (Calibreviewport.pyj), tap zones, swipes, wheel, arrow keys and mouse buttons flip, andsetReversePageTurnZonesmirrors the tap zones on demand. -
Accessibility: the reading position is announced on every page turn (TalkBack/VoiceOver live region with page, chapter and percent), the file scrollbar exposes slider semantics with increase/decrease actions, and the annotation filter chips and sheet close buttons carry semantic labels.
Fixed #
-
Single-tap image popup: tapping an image in a reflowable book now opens the fullscreen image viewer (previously only a long tap on touch or a double click on desktop did; the double-click handler is superseded — the popup opens on the first tap and a follow-up tap closes it).
-
setMargins/setColumnsPerScreenrefreshed the page-count layout signature before mutating their fields (stale cache key), andsetFontSizedid not refresh it at all. -
Narration infrastructure (used by read-aloud and media overlays):
segmentText()produces sentence segments over the same character space as highlights and positions (abbreviation, initial and decimal guards, CJK terminators, overlong-segment clamping);markNarrationRange()/clearNarrationMarks()highlight the spoken sentence and word in the page without intercepting taps, scrolling it into view;bookServerUrlFor(path)exposes loopback URLs so players can stream book media. -
BookServer HTTP Range support:
bytes=start-end,bytes=start-andbytes=-suffixreturn206 Partial Content(binary files advertiseAccept-Ranges), which audio players require for seeking into overlay clips. -
Annotation browser: the bookmarks sheet is now a full annotations browser — highlights grouped by chapter, color and decoration filters, text search over highlights and notes, sort by location or creation date, inline note editing, and one-tap export to the clipboard as Markdown, HTML or plain text.
-
Annotation export API (zero dependencies): build a
ViewerExportModelfrom the controller (filters, sorting and chapter grouping applied at build time), then render withtoMarkdown(),toHtml()ortoPlainText(). Every output part (chapter headings, highlights, notes, location links, bookmarks, front matter, CSS) can be overridden with callbacks, or hosts can walk the pure-data model and render anything themselves. Location location details support CFI, canonicalTextLocatorcoordinates or both. -
Chapter-title API:
chapterTitleFor(fileIndex)andcurrentChapterTitlemap reading-order indexes to their covering TOC section title (built once from the book's navigation, empty labels skipped). Hosts stop fuzzy-matching hrefs themselves. -
Page-change events:
controller.pageChangesfires when the location moves to another page, chapter, or whole-percent progress, carrying the position, book-wide progress and chapter title. Hosts persist progress from this stream instead of polling. -
Reading-session events:
controller.readingSessionsemits active-reading segments (startedAt/endedAt/duration) for time tracking. Segments open on the first reading event and close afterreadingIdleTimeout(default 45s, adjustable), at a five-minute segment cap, or on dispose. -
Footnote popup mode:
footnoteMode = ViewerFootnoteMode.popupkeeps the reader on the page; footnote taps resolve the target element's body from the book HTML and emitFootnoteTapEventwithhtmlandtextfields. The built-in shell shows a popup sheet; hosts with custom chrome get the content on the event. Unresolvable targets fall back to navigation. -
Back/forward history: explicit jumps (TOC, internal links, footnotes, search, highlights, CFI, shortcuts) create history;
goBack/goForwardtraverse it andcanGoBack/canGoForwardreport availability. Page turns and chapter boundary turns never create history (Calibre's back/forward semantics). -
Typography controls:
setFontFamily(null restores the book's own fonts),setJustifyandsetHyphenate, plus matching getters and constructor parameters. Changes relayout keeping position, feed the page-count layout fingerprint, and reach preloaded engines. The JS now relayouts on family/justify/hyphenation changes, which previously only restyled without re-measuring columns. -
Quick-highlight defaults:
defaultHighlightColoranddefaultHighlightDecorationset whataddHighlightFromSelection()applies when called without arguments. The built-in selection bar writes them on every tap and marks the active choice, so one-tap highlighting remembers the user's style (Calibre's default style behavior). Explicit arguments still win. -
Host chrome ownership:
ViewerController.useHostChrome(set automatically byGrimoireViewundershowShell: false). With it on, center taps and Esc still emitChromeToggleEvents for the host's own UI, butchromeVisiblestays false andtoggleChrome/showChrome/hideChromeare no-ops — hosts no longer need to fight the built-in chrome withhideChrome()on every change. -
Canonical locators: viewer positions, highlights and selections expose Unseal's
TextLocatordirectly. Host persistence useslocatorToJson/locatorFromJsoninstead of a viewer-owned wire format.
Changed #
- Grimoire uses the hosted
unseal: ^1.3.0contract. CI validates package analysis, tests, and the repository coverage floor against that dependency. - Highlight appearance uses Unseal's canonical
HighlightColorandHighlightDecorationtypes. The viewer maps palette tokens to Flutter light/dark colors while persistence stays owned by Unseal. The built-in palette matches Calibre's colors (yellow, green, blue, pink, purple). A purple wavy underline is expressible.addHighlightFromSelectiontakescolor/decoration;updateHighlightStyletakes the same as named parameters. - Highlight storage uses
decoration(wire name) andcolor({light, dark}hex pair) instead ofstyle, and now includes thecfidual-write. Colors render as inline!importantstyles instead of palette CSS classes, and the page picks the palette half from the active theme's background brightness, so existing highlights recolor live on light/dark switches (Calibrehighlight_style_as_cssparity).
Reader capabilities #
- Instant chapter transitions: dual-webview architecture with adjacent-file preloading — the spare webview loads the next file while you read, so page turns at chapter boundaries are seamless.
- Highlights (CFI-lite): persistent text highlights anchored by character offsets + context text for drift-tolerant restoration. Four styles (yellow/green/blue/underline), selection toolbar, highlight list with jump and delete. JSON serialization for host persistence.
- Bookmarks: quick named positions with the reading percentage, listed and deletable from the bookmarks sheet.
- Continuous scroll mode:
setLayoutMode(ViewerLayoutMode.scroll)switches from paged columns to flowing vertical reading. - Multi-column on tablets:
setColumnsPerScreen(2)(or 0 = auto based on screen width, Calibre-style). - External link callback:
onExternalLink/GrimoireView.onExternalLinkhands http(s) taps to the host app. - Comic mode: pinch zoom, pan and double-tap zoom on image-only
pages;
controller.isComicModedetection. - Windows and Linux:
flutter_inappwebviewengine adapter for desktop platforms (auto-selected viaViewerEngineFactory.forCurrentPlatform()). - macOS: supported through
webview_flutter(requirescom.apple.security.network.server+network.cliententitlements in the host app). - CI: GitHub Actions workflow (analyze + test).
- Engine abstraction:
ViewerEngine/ViewerEngineFactoryfor plugging alternative webview backends;FakeViewerEnginefor tests.
Core reader #
The first public release includes:
GrimoireView: Calibre-style paginated reader for Unseal books (EPUB, MOBI, AZW3, FB2, CBZ/CBR).- Loopback
BookServer(shelf) serving parsed books to the WebView, with viewer script/style injection into served HTML. viewer.jspaged mode: CSS multi-column layout with the Calibre scroll math, tap zones, keyboard paging, resize re-jump and the Android scroll-race watcher.ViewerController: page turns, TOC navigation, serialized canonical text positions, global progress, file-boundary transitions and internal link following.- Themes (light/sepia/dark/system + custom, optional book color override), font size and margin controls with position-preserving relayout.
- In-book search with Calibre-style tolerant matching (hyphenation/whitespace) and result navigation with highlight.
- Text selection stream for dictionary hooks (
onSelection/selectionEvents).