A book's journey
A book is downloaded once and prepared for the panel, kept in a folder on the reader, drawn page by page into memory, pushed to the e-ink screen, and its progress goes back to the server.
Before looking at any one part of Inkshelf, follow a single book all the way. Each stop on the way is a chapter of this site; this page is the map that ties them together.
Think of a train line. The book is the passenger; each station is a part of the code that does one job and hands the book on. Two kinds of passenger take slightly different tracks: comics (pictures of pages) and EPUBs (text that must be laid out).
The whole trip
Two kinds of book, two roads
The big difference between a comic and an EPUB is when the hard work happens.
- A comic is a stack of pictures. Turning a big color picture into the panel's 16 grays16 grays: The InkPad 3 shows 16 levels from black to white, 17 apart on the 0–255 scale: 0, 17, 34, … 255. is slow on the reader's processor, so Inkshelf does it once, at download time, and saves each page as a small .pg4.pg4: Inkshelf’s page image file: a small header, then two pixels per byte (4 bits each), ready to draw. file. Turning a page later only means reading that file back.
- An EPUB is ReflowableReflowable: A book whose text is laid out again for the screen and font size, like a web page, as opposed to fixed layout.: its text has no pages until you choose a font size.
So Inkshelf keeps the EPUB as it is, notes where each chapter starts (
index.json), and lays out pages only when you read them.
Recipes from Mealie take the comic road: the app lays a recipe out into page pictures, which are then prepared like a comic's. A Fixed layoutFixed layout: A book whose pages have a set size and position for everything, like a PDF; Inkshelf draws each page whole. EPUB does too: its pages are drawn whole at download time.
Why so much happens at download time
Three constraints shape the trip, and you will meet them again in every part:
- The reader is slow. A page turn must feel instant, so anything that can be done in advance is: page images are prepared while downloading, and the pages around the one you read are prepared in the background.
- The panel is slow too. An e-ink refresh takes a visible moment, and fast refreshes leave GhostingGhosting: A faint trace of the previous image left after a partial refresh. A full (flashing) refresh clears it.. So Inkshelf refreshes only the area that changed, and every few pages flashes the whole screen clean.
- Wi-Fi comes and goes. A download can stop halfway, so files are written whole or not at all
(Atomic renameAtomic rename: Write a file under a temporary name, then rename it: other code sees the old file or the whole new one, never half.), and
book.jsoncomes last. Progress waits on the reader until the server can take it.
Going deeper: what the shelf folder looks like
Each book has a folder named after its id, under inkshelf/servers/<server name>/books/ on the
reader:
| A book of pages (comic, recipe, fixed-layout EPUB) | A reflowable EPUB |
|---|---|
0001.pg4, 0002.pg4, … one per page | book.epub, the file as the server sent it |
index.json, its chapters and table of contents | |
cover.pg4, its cover, prepared for the panel | |
book.json, written last | book.json, written last |
A CBZ is first downloaded whole, its pages are prepared into .pg4 files, then the archive is
deleted: only the pages take space.
The EPUB's page count in book.json is a Virtual pageVirtual page: 1500 characters of an EPUB. A stable “page number” for progress and the server, whatever the font size. count: 1500 characters a page
(CharsPerPage), since the real pages depend on the font size.
Where it lives in the code
internal/shelf/download.go · Store.DownloadFetches a book and prepares it, book.json last.internal/graphics/pageimage/prepare.go · PrepareDecode, fit, tone, dither: a page image becomes a .pg4 file.internal/shelf/prepare_epub.go · downloadReflowableFetches an EPUB, builds index.json and cover.pg4.internal/app/openbook/openbook.go · OpenLocked, TurnPageOpens a downloaded book; turns a page and schedules the sync.internal/reading/renderer.go · pagesLockedLays out a chapter of an EPUB into pages (layout.Paginate).internal/app/screens/reader/reader.go · DrawAndRefreshLockedDraws the page into the canvas and refreshes the panel.internal/device/pocketbook/framebuffer_linux_arm.go · flushCopies the canvas to the framebuffer and sends the refresh ioctl.internal/progress/syncer.go · SyncerSends the reading progress to the server, a few seconds after the last turn.
The full story is spread over Architecture, The image pipeline and Files on the reader.
Next: the stations of this trip are parts of the code. The layer cake shows how they are stacked.