docs: refresh cache formats and web server workflows (#2233)
## Summary * **What is the goal of this PR?** Update project documentation to match the current master implementation for cache formats, i18n, file transfer/web server workflows, SD-card fonts, and root user-facing docs. * **What changes are included?** Refreshes `book.bin`/`section.bin` docs for v6/v25, updates File Transfer/Calibre/WebDAV/API docs, documents 24 UI languages and JSON language persistence, updates root README/USER_GUIDE cache and network details, and syncs the tracked CLAUDE skill doc cache-version notes. ## Additional Context * Docs-only change. Verified with `git diff --check origin/master..HEAD` and stale-reference greps for old cache versions, removed i18n APIs, old WiFi screen wording, and raw `Serial.printf` examples. No firmware build was run. --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**NO**_
This commit is contained in:
+152
-87
@@ -1,22 +1,26 @@
|
||||
# File Formats
|
||||
|
||||
These formats describe the SD-card cache files under `/.crosspoint/epub_<hash>/`.
|
||||
All POD fields are written in the ESP32 little-endian representation used by
|
||||
`Serialization.h`; strings are length-prefixed UTF-8.
|
||||
|
||||
## `book.bin`
|
||||
|
||||
### Version 5
|
||||
### Version 7
|
||||
|
||||
ImHex Pattern:
|
||||
`book.bin` stores EPUB metadata plus lookup tables for spine and TOC entries.
|
||||
The current firmware writes this version from `BookMetadataCache`.
|
||||
|
||||
ImHex pattern:
|
||||
|
||||
```c++
|
||||
import std.mem;
|
||||
import std.string;
|
||||
import std.core;
|
||||
|
||||
// === Configuration ===
|
||||
#define EXPECTED_VERSION 5
|
||||
#define EXPECTED_VERSION 7
|
||||
#define MAX_STRING_LENGTH 65535
|
||||
|
||||
// === String Structure ===
|
||||
|
||||
struct String {
|
||||
u32 length [[hidden, comment("String byte length")]];
|
||||
if (length > MAX_STRING_LENGTH) {
|
||||
@@ -29,75 +33,56 @@ fn format_string(String s) {
|
||||
return s.data;
|
||||
};
|
||||
|
||||
// === Metadata Structure ===
|
||||
|
||||
struct Metadata {
|
||||
String title [[comment("Book title")]];
|
||||
String author [[comment("Book author")]];
|
||||
String language [[comment("Book language code")]];
|
||||
String coverItemHref [[comment("Path to cover image")]];
|
||||
String textReferenceHref [[comment("Path to guided first text reference")]];
|
||||
} [[comment("Book metadata information")]];
|
||||
|
||||
// === Spine Entry Structure ===
|
||||
};
|
||||
|
||||
struct SpineEntry {
|
||||
String href [[comment("Resource path")]];
|
||||
u32 cumulativeSize [[comment("Cumulative size in bytes"), color("FF6B6B")]];
|
||||
s16 tocIndex [[comment("Index into TOC (-1 if none)"), color("4ECDC4")]];
|
||||
} [[comment("Spine entry defining reading order")]];
|
||||
|
||||
// === TOC Entry Structure ===
|
||||
u32 cumulativeSize [[comment("Cumulative uncompressed spine size through this entry")]];
|
||||
s16 tocIndex [[comment("Index into TOC, or inherited/previous TOC index when no direct entry exists")]];
|
||||
};
|
||||
|
||||
struct TocEntry {
|
||||
String title [[comment("Chapter/section title")]];
|
||||
String href [[comment("Resource path")]];
|
||||
String anchor [[comment("Fragment identifier")]];
|
||||
u8 level [[comment("Nesting level (0-255)"), color("95E1D3")]];
|
||||
s16 spineIndex [[comment("Index into spine (-1 if none)"), color("F38181")]];
|
||||
} [[comment("Table of contents entry")]];
|
||||
|
||||
// === Book Bin Structure ===
|
||||
u8 level [[comment("Nesting level")]];
|
||||
s16 spineIndex [[comment("Index into spine (-1 if none)")]];
|
||||
};
|
||||
|
||||
struct BookBin {
|
||||
// Header
|
||||
u8 version [[comment("Format version"), color("FFD93D")]];
|
||||
|
||||
// Version validation
|
||||
u8 version;
|
||||
if (version != EXPECTED_VERSION) {
|
||||
std::error(std::format("Unsupported version: {} (expected {})", version, EXPECTED_VERSION));
|
||||
}
|
||||
|
||||
u32 lutOffset [[comment("Offset to lookup tables"), color("6BCB77")]];
|
||||
u16 spineCount [[comment("Number of spine entries"), color("4D96FF")]];
|
||||
u16 tocCount [[comment("Number of TOC entries"), color("FF6B9D")]];
|
||||
u32 lutOffset [[comment("Offset to lookup tables")]];
|
||||
u16 spineCount;
|
||||
u16 tocCount;
|
||||
|
||||
// Metadata section
|
||||
Metadata metadata [[comment("Book metadata")]];
|
||||
Metadata metadata;
|
||||
|
||||
// Validate LUT offset alignment
|
||||
u32 currentOffset = $;
|
||||
if (currentOffset != lutOffset) {
|
||||
std::warning(std::format("LUT offset mismatch: expected 0x{:X}, got 0x{:X}", lutOffset, currentOffset));
|
||||
}
|
||||
|
||||
// Lookup Tables
|
||||
u32 spineLut[spineCount] [[comment("Spine entry offsets"), color("4D96FF")]];
|
||||
u32 tocLut[tocCount] [[comment("TOC entry offsets"), color("FF6B9D")]];
|
||||
u32 spineLut[spineCount] [[comment("Spine entry offsets")]];
|
||||
u32 tocLut[tocCount] [[comment("TOC entry offsets")]];
|
||||
|
||||
// Data Entries
|
||||
SpineEntry spines[spineCount] [[comment("Spine entries (reading order)")]];
|
||||
TocEntry toc[tocCount] [[comment("Table of contents entries")]];
|
||||
SpineEntry spines[spineCount];
|
||||
TocEntry toc[tocCount];
|
||||
};
|
||||
|
||||
// === File Parsing ===
|
||||
|
||||
BookBin book @ 0x00;
|
||||
|
||||
// Validate we've consumed the entire file
|
||||
u32 fileSize = std::mem::size();
|
||||
u32 parsedSize = $;
|
||||
|
||||
if (parsedSize != fileSize) {
|
||||
std::warning(std::format("Unparsed data detected: {} bytes remaining at offset 0x{:X}", fileSize - parsedSize, parsedSize));
|
||||
}
|
||||
@@ -105,20 +90,33 @@ if (parsedSize != fileSize) {
|
||||
|
||||
## `section.bin`
|
||||
|
||||
### Version 24
|
||||
### Version 25
|
||||
|
||||
ImHex Pattern:
|
||||
Each file in `sections/*.bin` stores one laid-out spine section. The header is
|
||||
also the cache-busting key: if any layout-affecting setting differs from the
|
||||
current reader settings, the section is discarded and rebuilt.
|
||||
|
||||
Version 25 includes:
|
||||
|
||||
- cache-busting fields for paragraph alignment, hyphenation, embedded CSS,
|
||||
image rendering mode, and Focus Reading
|
||||
- page offset LUT
|
||||
- anchor-to-page map for fragment and footnote navigation
|
||||
- paragraph and list-item LUTs used by KOReader sync page refinement
|
||||
- optional per-word Focus Reading split metadata
|
||||
- per-page footnote entries
|
||||
|
||||
ImHex pattern:
|
||||
|
||||
```c++
|
||||
import std.mem;
|
||||
import std.string;
|
||||
import std.core;
|
||||
|
||||
// === Configuration ===
|
||||
#define EXPECTED_VERSION 24
|
||||
#define EXPECTED_VERSION 25
|
||||
#define MAX_STRING_LENGTH 65535
|
||||
|
||||
// === String Structure ===
|
||||
#define FOOTNOTE_NUMBER_LEN 32
|
||||
#define FOOTNOTE_HREF_LEN 96
|
||||
|
||||
struct String {
|
||||
u32 length [[hidden, comment("String byte length")]];
|
||||
@@ -132,44 +130,79 @@ fn format_string(String s) {
|
||||
return s.data;
|
||||
};
|
||||
|
||||
// === Page Structure ===
|
||||
|
||||
enum PageElementTag : u8 {
|
||||
PageLine = 1,
|
||||
PageImage = 2,
|
||||
PageHorizontalRule = 3
|
||||
TAG_PageLine = 1,
|
||||
TAG_PageImage = 2,
|
||||
TAG_PageHorizontalRule = 3
|
||||
};
|
||||
|
||||
enum WordStyle : u8 {
|
||||
REGULAR = 0,
|
||||
BOLD = 1,
|
||||
ITALIC = 2,
|
||||
BOLD_ITALIC = 3
|
||||
BOLD_ITALIC = 3,
|
||||
UNDERLINE = 4,
|
||||
STRIKETHROUGH = 8,
|
||||
SUP = 16,
|
||||
SUB = 32
|
||||
};
|
||||
|
||||
enum BlockStyle : u8 {
|
||||
enum TextAlign : u8 {
|
||||
JUSTIFIED = 0,
|
||||
LEFT_ALIGN = 1,
|
||||
CENTER_ALIGN = 2,
|
||||
RIGHT_ALIGN = 3,
|
||||
NONE = 4
|
||||
};
|
||||
|
||||
struct BlockStyle {
|
||||
TextAlign alignment;
|
||||
bool textAlignDefined;
|
||||
s16 marginTop;
|
||||
s16 marginBottom;
|
||||
s16 marginLeft;
|
||||
s16 marginRight;
|
||||
s16 paddingTop;
|
||||
s16 paddingBottom;
|
||||
s16 paddingLeft;
|
||||
s16 paddingRight;
|
||||
s16 textIndent;
|
||||
bool textIndentDefined;
|
||||
bool isRtl;
|
||||
bool directionDefined;
|
||||
};
|
||||
|
||||
struct TextBlock {
|
||||
u16 wordCount;
|
||||
String words[wordCount];
|
||||
s16 wordXPos[wordCount];
|
||||
WordStyle wordStyle[wordCount];
|
||||
|
||||
u8 hasFocus;
|
||||
if (hasFocus != 0) {
|
||||
u8 wordFocusBoundary[wordCount] [[comment("UTF-8 byte boundary between bold prefix and suffix")]];
|
||||
u16 wordFocusSuffixX[wordCount] [[comment("Suffix x offset from word start")]];
|
||||
}
|
||||
|
||||
BlockStyle blockStyle;
|
||||
};
|
||||
|
||||
struct ImageBlock {
|
||||
String imagePath;
|
||||
s16 width;
|
||||
s16 height;
|
||||
};
|
||||
|
||||
struct PageLine {
|
||||
s16 xPos;
|
||||
s16 yPos;
|
||||
u16 wordCount;
|
||||
String words[wordCount];
|
||||
u16 wordXPos[wordCount];
|
||||
WordStyle wordStyle[wordCount];
|
||||
BlockStyle blockStyle;
|
||||
s16 xPos;
|
||||
s16 yPos;
|
||||
TextBlock block;
|
||||
};
|
||||
|
||||
struct PageImage {
|
||||
s16 xPos;
|
||||
s16 yPos;
|
||||
String imagePath;
|
||||
s16 width;
|
||||
s16 height;
|
||||
ImageBlock image;
|
||||
};
|
||||
|
||||
struct PageHorizontalRule {
|
||||
@@ -180,63 +213,95 @@ struct PageHorizontalRule {
|
||||
};
|
||||
|
||||
struct PageElement {
|
||||
u8 pageElementType;
|
||||
if (pageElementType == 1) {
|
||||
PageElementTag pageElementType;
|
||||
if (pageElementType == TAG_PageLine) {
|
||||
PageLine pageLine [[inline]];
|
||||
} else if (pageElementType == 2) {
|
||||
} else if (pageElementType == TAG_PageImage) {
|
||||
PageImage pageImage [[inline]];
|
||||
} else if (pageElementType == 3) {
|
||||
} else if (pageElementType == TAG_PageHorizontalRule) {
|
||||
PageHorizontalRule horizontalRule [[inline]];
|
||||
} else {
|
||||
std::error(std::format("Unknown page element type: {}", pageElementType));
|
||||
}
|
||||
};
|
||||
|
||||
struct FootnoteEntry {
|
||||
char number[FOOTNOTE_NUMBER_LEN];
|
||||
char href[FOOTNOTE_HREF_LEN];
|
||||
};
|
||||
|
||||
struct Page {
|
||||
u16 elementCount;
|
||||
PageElement elements[elementCount] [[inline]];
|
||||
|
||||
u16 footnoteCount;
|
||||
FootnoteEntry footnotes[footnoteCount];
|
||||
};
|
||||
|
||||
// === Section Bin Structure ===
|
||||
struct AnchorEntry {
|
||||
String anchor;
|
||||
u16 page;
|
||||
};
|
||||
|
||||
struct AnchorMap {
|
||||
u16 count;
|
||||
AnchorEntry entries[count];
|
||||
};
|
||||
|
||||
struct ParagraphLut {
|
||||
u16 count;
|
||||
u16 paragraphIndex[count];
|
||||
};
|
||||
|
||||
struct SectionBin {
|
||||
// Header
|
||||
u8 version [[comment("Format version"), color("FFD93D")]];
|
||||
|
||||
// Version validation
|
||||
u8 version;
|
||||
if (version != EXPECTED_VERSION) {
|
||||
std::error(std::format("Unsupported version: {} (expected {})", version, EXPECTED_VERSION));
|
||||
}
|
||||
|
||||
// Cache busting parameters
|
||||
s32 fontId;
|
||||
float lineCompression;
|
||||
bool extraParagraphSpacing;
|
||||
u8 paragraphAlignment;
|
||||
u16 viewportWidth;
|
||||
u16 vieportHeight;
|
||||
u16 viewportHeight;
|
||||
bool hyphenationEnabled;
|
||||
bool embeddedStyle;
|
||||
u8 imageRendering;
|
||||
bool focusReadingEnabled;
|
||||
|
||||
u16 pageCount;
|
||||
u32 lutOffset;
|
||||
u32 pageLutOffset;
|
||||
u32 anchorMapOffset;
|
||||
u32 paragraphLutOffset;
|
||||
u32 listItemLutOffset;
|
||||
|
||||
Page page[pageCount];
|
||||
Page pages[pageCount];
|
||||
|
||||
// Validate LUT offset alignment
|
||||
u32 currentOffset = $;
|
||||
if (currentOffset != lutOffset) {
|
||||
std::warning(std::format("LUT offset mismatch: expected 0x{:X}, got 0x{:X}", lutOffset, currentOffset));
|
||||
if (currentOffset != pageLutOffset) {
|
||||
std::warning(std::format("Page LUT offset mismatch: expected 0x{:X}, got 0x{:X}", pageLutOffset, currentOffset));
|
||||
}
|
||||
|
||||
// Lookup Tables
|
||||
u32 lut[pageCount];
|
||||
u32 pageLut[pageCount] [[comment("Page data offsets")]];
|
||||
|
||||
if (anchorMapOffset != 0) {
|
||||
AnchorMap anchorMap @ anchorMapOffset;
|
||||
}
|
||||
|
||||
if (paragraphLutOffset != 0) {
|
||||
ParagraphLut paragraphLut @ paragraphLutOffset;
|
||||
}
|
||||
|
||||
if (listItemLutOffset != 0 && paragraphLutOffset != 0) {
|
||||
u16 listItemIndex[paragraphLut.count] @ listItemLutOffset;
|
||||
}
|
||||
};
|
||||
|
||||
// === File Parsing ===
|
||||
SectionBin section @ 0x00;
|
||||
|
||||
SectionBin book @ 0x00;
|
||||
|
||||
// Validate we've consumed the entire file
|
||||
u32 fileSize = std::mem::size();
|
||||
u32 parsedSize = $;
|
||||
|
||||
if (parsedSize != fileSize) {
|
||||
std::warning(std::format("Unparsed data detected: {} bytes remaining at offset 0x{:X}", fileSize - parsedSize, parsedSize));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user