Files
crosspoint-reader-mod/mod/prs/MERGED.md
cottongin 4cf395aee9 port: upstream PR #1342 - Book Info screen, richer metadata, safer controls
Ports upstream PR #1342 (feat: Add Book Info screen, richer metadata,
and safer file-browser controls) with mod-specific adaptations:

- Parse and cache series, seriesIndex, description from EPUB OPF
- Bump book.bin cache version to 6 for new metadata fields
- Add BookInfoActivity (new screen) accessible via Right button in FileBrowser
- Add ManageBook menu via Left button in FileBrowser (replaces upstream hidden delete)
- Guard all delete/archive actions with ConfirmationActivity (10 call sites)
- Add inputArmed gating to ConfirmationActivity to prevent accidental confirmation
- Safe deserialization: readString now returns bool with MAX_STRING_LENGTH guard
- Add series field to RecentBooksStore with JSON and binary serialization
- Add i18n keys: STR_BOOK_INFO, STR_AUTHOR, STR_SERIES, STR_FILE_SIZE, etc.

Made-with: Cursor
2026-03-09 00:39:32 -04:00

37 KiB
Raw Blame History

Merged Upstream PRs

Tracking document for upstream PRs ported into this mod.

  • PR #1038 — Replace std::list with std::vector in text layout (znelson)
  • PR #1045 — Shorten "Forget Wifi" button labels (lukestein)
  • PR #1037 — Fix hyphenation and rendering of decomposed characters (jpirnay)
  • PR #1019 — Display file extensions in File Browser (CaptainFrito)
  • PR #1055 — Byte-level framebuffer writes for fillRect and drawLine (jpirnay)
  • PR #1027 — Reduce ParsedText layout time 79% via word-width cache and hyphenation early exit (jpirnay)
  • PR #1068 — Correct hyphenation of URLs (Uri-Tauber)
  • PR #1329 — Refactor shared reader utilities (upstream)
  • PR #1143 + #1172 — TOC fragment navigation + multi-spine TOC (upstream)
  • PR #1311 — Fix inter-word spacing rounding error (znelson)
  • PR #1320 — RAII JPEG resource cleanup (upstream)
  • PR #1322 — Early exit on fillUncompressedSizes (jpirnay)
  • PR #1325 — Dynamic settings tab label (upstream)
  • PR #1342 — Book Info screen, richer EPUB metadata, serialization safety (upstream, adapted)

PR #1038: Replace std::list with std::vector in text layout

Context

The core list-to-vector conversion was already performed in a prior mod sync. This port only brings in two targeted fixes from the PR that were missing locally.

Changes applied

  1. Erase consumed words in layoutAndExtractLines (lib/Epub/Epub/ParsedText.cpp): Added .erase() calls after the line extraction loop to remove consumed words from words, wordStyles, wordContinues, and forceBreakAfter vectors. Without this, the 750-word early flush threshold fires on every subsequent addWord call instead of only ~3 times per large paragraph, causing ~1,670 redundant flushes.
  2. Fix wordContinues flag in hyphenateWordAtIndex (lib/Epub/Epub/ParsedText.cpp): Corrected the attach-to-previous flag handling when splitting a word at a hyphenation point. The prefix now keeps its original flag and the remainder gets false, instead of the previous behavior that cleared the prefix's flag and transferred it to the remainder. This fix was identified by coderabbit review and accepted in commit 9bbe994.

Differences from upstream PR

  • The forceBreakAfter vector erase was added (mod-only vector not present in upstream) alongside the three upstream vectors.
  • The upstream PR's full list-to-vector conversion (TextBlock.h/.cpp, ParsedText.h/.cpp) was already done in a prior mod sync. Only the two fixes above were new.

Notable PR discussion

  • znelson posted detailed benchmarks comparing list vs vector on ESP32-C3 with "Intermezzo" by Sally Rooney (chapter 14, 2,420-word paragraph): 11% faster chapter parse time, 89% more heap headroom (~50KB saved), 10-14% faster layout for medium paragraphs.
  • The erase cost is ~90-133 microseconds per flush (0.1% of total flush time).

PR #1045: Shorten "Forget Wifi" button labels to fit on button

Changes applied

Updated STR_FORGET_BUTTON in all 9 translation yaml files under lib/I18n/translations/:

Language Before After
Czech "Zapomenout na síť" "Zapomenout"
English "Forget network" "Forget"
French "Oublier le réseau" "Oublier"
German "WLAN entfernen" "Entfernen"
Portuguese "Esquecer rede" "Esquecer"
Romanian "Uitaţi reţeaua" "Uitaţi"
Russian "Забыть сеть" "Забыть"
Spanish "Olvidar la red" "Olvidar"
Swedish "Glöm nätverk" "Glöm"

Prerequisite

Romanian translation file (romanian.yaml) was pulled from upstream/master (merged PR #987 by ariel-lindemann) as it was missing locally.

Differences from upstream PR

None. Changes are identical to the upstream PR.

Notable PR discussion

  • ariel-lindemann requested the Romanian shortening be included (was added in a follow-up commit by lukestein).
  • Translations were verified via Google Translate (lukestein is not a native speaker of non-English languages).

PR #1037: Fix hyphenation and rendering of decomposed characters

Changes applied

  1. **lib/Utf8/Utf8.h**: Added utf8IsCombiningMark(uint32_t) inline function that identifies Unicode combining diacritical marks (ranges: 0x0300-0x036F, 0x1DC0-0x1DFF, 0x20D0-0x20FF, 0xFE20-0xFE2F).
  2. **lib/EpdFont/EpdFont.cpp**: Added combining mark positioning in getTextBounds. Tracks last base glyph state (lastBaseX, lastBaseAdvance, lastBaseTop, hasBaseGlyph). Combining marks are centered over the base glyph's advance width and raised with a minimum 1px gap. Cursor does not advance for combining marks.
  3. **lib/Epub/Epub/hyphenation/HyphenationCommon.cpp**: Added NFC-like precomposition in collectCodepoints(). When a combining diacritic follows a base character and a precomposed Latin-1/Latin-Extended scalar exists (grave, acute, circumflex, tilde, diaeresis, cedilla), the base is replaced with the precomposed form and the combining mark is skipped. This allows Liang hyphenation patterns to match decomposed text correctly.
  4. **lib/GfxRenderer/GfxRenderer.cpp**: Added combining mark handling to drawText, drawTextRotated90CW, drawTextRotated90CCW, and getTextAdvanceX. Each text drawing function tracks base glyph state and renders combining marks at adjusted (raised, centered) positions without advancing the cursor. getTextAdvanceX skips combining marks entirely.

Differences from upstream PR

  • **drawTextRotated90CCW**: This is a mod-only function (not present in upstream). Combining mark handling was added here following the same pattern as the CW rotation, with coordinate adjustments inverted for CCW direction (+raiseBy on X, +lastBaseAdvance/2 on Y instead of negatives).
  • The upstream PR initially had a duplicate local isCombiningMark function in EpdFont.cpp (anonymous namespace) which was flagged by coderabbit and replaced with the shared utf8IsCombiningMark from Utf8.h. Our port uses the shared function directly throughout.

Notable PR discussion

  • jpirnay noted this is not a 100% bullet-proof implementation -- it maps common base+combining sequences rather than implementing full Unicode NFC normalization.
  • The render fix is more universal: it properly x-centers the compound glyph over the previous one and uses at least 1pt visual distance in Y.
  • lukestein specifically expressed interest in the fix for hyphenation of already-hyphenated words (e.g., "US-Satellitensystem" was exclusively broken at the existing hyphen).
  • osteotek approved the approach of breaking already-hyphenated words at additional Liang pattern points.

PR #1019: Display file extensions in File Browser

Changes applied

In src/activities/home/MyLibraryActivity.cpp:

  1. Added getFileExtension(std::string) helper function near the existing getFileName. Returns the file extension including the dot (e.g., ".epub"), or empty string for directories or files without extensions.
  2. Updated the GUI.drawList call to pass the extension lambda as the rowValue parameter and false for highlightValue. The drawList signature already supported these parameters with defaults.

Differences from upstream PR

None. The implementation is functionally identical.

Notable PR discussion

  • CaptainFrito questioned whether the multiple lambda approach (each independently indexing into the files array) is optimal, suggesting a single function returning a struct might be better.
  • Eloren1 asked how long filenames look with extensions displayed. This remains an open question in the upstream PR. See mod enhancement below.
  • The PR was force-pushed to squash commits.

Mod enhancement: Expandable selected row for long filenames

Addresses Eloren1's concern about long filenames. When the selected row's filename overflows the available text width, the row expands to 2 lines with smart text wrapping. The file extension moves to the bottom-right of the expanded area, baseline-aligned with the last text line. Non-selected rows retain single-line truncation. If the filename still overflows after 2 lines, the second line is truncated with "...".

Files modified:

  • src/components/themes/BaseTheme.cpp: Added wrapTextToLines utility function with 3-tier break logic and truncateWithEllipsis helper. Modified drawList to detect selected-row overflow, draw expanded selection highlight at 2x row height, render wrapped title with row-height line spacing, and position the file extension on line 2 (right-aligned).
  • src/components/themes/lyra/LyraTheme.cpp: Same helpers and analogous drawList modifications with Lyra-specific styling (rounded-rect selection highlight, icon aligned with line 1, scroll bar awareness).

Design decisions:

  • Only the selected/highlighted row expands; other rows with long filenames continue to truncate normally.
  • Expansion triggers when the title overflows the value-reduced text width (i.e., when it would be truncated in single-line mode). For titles that overflow the value area but still fit the full width, the expanded row shows the full title on line 1 with the extension on line 2 below.
  • 3-tier text wrapping for natural line breaks:
    1. Preferred delimiters: breaks at " -- ", " - ", en-dash, or em-dash separators (common "Title - Author" naming convention). Uses last occurrence to maximize line 1 content.
    2. Word boundaries: breaks at last space or hyphen that fits on line 1.
    3. Character-level fallback: for long unbroken tokens without spaces or hyphens.
  • Each wrapped line is placed at the same Y position as a normal list row (row-height spacing between lines), giving natural visual separation that matches the list's vertical rhythm.
  • File extension is always positioned on line 2 of the expanded area (right-aligned), regardless of how many text lines are produced by wrapping.
  • Icons in LyraTheme are aligned with line 1 (not centered in the expanded area).
  • Pagination uses effectivePageItems (pageItems - 1 when expanding) for totalPages, scroll indicators, and page boundaries. This ensures hidden items appear on the next page with proper scroll indicators. To prevent items from the previous page "leaking" into view, the page start is clamped to never go before the original (non-expanded) page boundary. Edge case: when the selected item is the last on the original page, the start shifts forward minimally to keep it visible.
  • Boundary item duplication: when navigating to a "real" page boundary (non-expanding path), if the item just before the page start would need expansion when selected, it is included at the top of the current page (the page start is decremented by 1). This prevents the "bumped" item from vanishing when the user navigates from it to the next page. The guard selectedIndex < pageStartIndex + pageItems - 1 ensures the selected item isn't pushed off the page by this adjustment.

PR #1055: Byte-level framebuffer writes for fillRect and axis-aligned drawLine

Context

Eliminates per-pixel drawPixel calls for solid fills, axis-aligned lines, and dithered fills by writing directly to the framebuffer at byte granularity. Exploits the fact that one logical dimension always maps to a contiguous physical row in the framebuffer, allowing entire spans to be written with byte masking and memset instead of individual read-modify-write cycles per pixel. Measured 232470x speedups on ESP32-C3 hardware for full-screen operations.

Changes applied

  1. lib/GfxRenderer/GfxRenderer.h: Added two new private methods: fillPhysicalHSpanByte(int phyY, int phyX_start, int phyX_end, uint8_t patternByte) for writing a patterned horizontal span with byte-level edge blending, and fillPhysicalHSpan(int phyY, int phyX_start, int phyX_end, bool state) as a thin solid-fill wrapper.
  2. lib/GfxRenderer/GfxRenderer.cpp: Added #include <cstring> for memset. Implemented fillPhysicalHSpanByte (bounds clamping, MSB-first bit packing, partial-byte masking at edges, memset for aligned middle) and fillPhysicalHSpan wrapper.
  3. drawLine (axis-aligned cases): Logical vertical lines in Portrait/PortraitInverted now route through fillPhysicalHSpan instead of per-pixel loops. Logical horizontal lines in Landscape orientations similarly use fillPhysicalHSpan. Bresenham diagonal path is unchanged.
  4. fillRect: Replaced the per-row drawLine loop with orientation-specific fast paths. Each orientation iterates over the logical dimension that maps to a constant physical row, writing the perpendicular extent as a single fillPhysicalHSpan call.
  5. fillRectDither: Replaced per-pixel drawPixelDither loops for DarkGray and LightGray with orientation-aware fillPhysicalHSpanByte calls using pre-computed byte patterns. DarkGray uses checkerboard 0xAA/0x55 alternating by physical row parity. LightGray uses 1-in-4 pattern with row-skipping optimization for all-white rows.

Differences from upstream PR

  • fillPolygon landscape optimization (coderabbit nitpick): The upstream PR's coderabbit review noted that fillPolygon's horizontal scanline inner loop could benefit from fillPhysicalHSpan for Landscape orientations. This was noted as a "future optimization opportunity" in the review and not implemented in the PR. We applied it here: for LandscapeCounterClockwise and LandscapeClockwise, the scanline fill now uses fillPhysicalHSpan with appropriate coordinate transforms, falling back to per-pixel for Portrait orientations (where horizontal logical lines map to vertical physical lines).

Notable PR discussion

  • jpirnay posted hardware benchmarks: fillRect(480×800) went from 125,577 µs to 519 µs (242× speedup), vertical drawLine(800px) from 261 µs to 3 µs (87×), fillRectDither DarkGray 234× speedup, LightGray 470× speedup.
  • coderabbit review approved all core changes with 8 LGTM comments; the only nitpick was the fillPolygon optimization opportunity (applied in this port).
  • The fillRectDither LightGray change has a subtle semantic difference from upstream: the original drawPixelDither<LightGray> only wrote dark pixels (leaving white untouched), while the new span-based approach explicitly writes the full pattern. For fillRectDither (which fills a complete rectangle) this is semantically equivalent.

PR #1027: Reduce ParsedText layout time 79% via word-width cache and hyphenation early exit

Context

Reduces CPU time spent in ParsedText::layoutAndExtractLines, the hot path for every page render on the ESP32. Two independent optimizations contribute: a direct-mapped word-width cache and an early exit in the hyphenation breakpoint loop. Benchmarked on device (50 iterations, 474 px justified viewport) showing 59% improvement depending on corpus and layout mode.

Changes applied

  1. Word-width cache (lib/Epub/Epub/ParsedText.cpp): Added a 128-entry, 4 KB static array (BSS, not heap) that caches getTextAdvanceX results keyed by (word, fontId, style). Lookup is O(1) via FNV-1a hash + bitmask slot selection. Words >= 24 bytes bypass the cache (uncommon, unlikely to repeat). Hyphen-fragment measurements (never repeated) skip the cache entirely. calculateWordWidths now calls cachedMeasureWordWidth instead of measureWordWidth.
  2. Hyphenation early exit (lib/Epub/Epub/ParsedText.cpp): In hyphenateWordAtIndex, when a candidate prefix is wider than the available space, the loop now breaks instead of continueing. Hyphenator::breakOffsets returns candidates in ascending byte-offset order, so prefix widths are non-decreasing -- all subsequent candidates will also be too wide. A reusable std::string prefix buffer replaces per-iteration substr allocations.
  3. Reserve hint (lib/Epub/Epub/ParsedText.cpp): Added lineBreakIndices.reserve(totalWordCount / 8 + 1) in computeLineBreaks to avoid repeated reallocation.

Differences from upstream PR

  • List-specific optimizations not applicable: The upstream PR includes std::list splice optimizations in extractLine and iterator changes (std::advance to std::next) throughout. Our mod already uses std::vector (from PR #1038), so these changes don't apply -- vector index access and move iterators are already in place.
  • continuesVec sync removed: The upstream PR updates a separate continuesVec pointer in hyphenateWordAtIndex. Our mod modifies wordContinues directly (it's already a vector), so this indirection is unnecessary.
  • Benchmark infrastructure excluded: The PR's final commit removed ParsedTextLegacy.h/.cpp, ParsedTextBenchmark.h/.cpp, and main.cpp benchmark hooks. These were development-only files not part of the deliverable.

Notable PR discussion

  • osteotek noted he had previously tried a word-width cache with "almost zero on-device improvements" and requested separate benchmarks for each optimization.
  • jpirnay posted individual contribution data: the cache dominates (78% for DP layout, 34% for greedy) while the early exit contributes 12%. The cache saves a getTextAdvanceX call on every word in calculateWordWidths (5661 calls/iteration), whereas the early exit only fires on the handful of words per paragraph that trigger hyphenation.
  • jpirnay's benchmark table (50 iterations, 474 px justified viewport):
Scenario Early exit only Cache only (derived) Both combined
German DP 1% ~7% 8%
English DP 1% ~8% 9%
Combined DP 1% ~8% 9%
German greedy 2% ~3% 5%
English greedy 2% ~4% 6%
Combined greedy 2% ~3% 5%

PR #1068: Correct hyphenation of URLs

Context

Long URLs in EPUBs could not be line-wrapped because buildExplicitBreakInfos required alphabetic characters on both sides of an explicit hyphen marker. URLs contain /, digits, dots, and other non-alphabetic characters, so path separators were never recognized as break points.

Changes applied

  1. lib/Epub/Epub/hyphenation/HyphenationCommon.cpp: Added case '/': to isExplicitHyphen, treating the URL path separator as an explicit hyphen delimiter alongside existing hyphen/dash characters.
  2. lib/Epub/Epub/hyphenation/Hyphenator.cpp: Split the single combined filter in buildExplicitBreakInfos into a two-stage check. The isExplicitHyphen test is applied first. Then, for / and - specifically, the strict requirement that both adjacent codepoints must be alphabetic is relaxed — these separators can break next to digits, dots, or other URL characters. All other explicit hyphens retain the original TeX-style alphabetic-surround rule.

Differences from upstream PR

  • Repeated-separator guard (mod enhancement): Added a guard from the coderabbit review nitpick (not yet addressed in the upstream PR) that skips break points between consecutive identical separators. This prevents a line break from being inserted between the two slashes in http:// or between double hyphens like --.

Notable PR discussion

  • coderabbit approved the isExplicitHyphen change and flagged the repeated-separator edge case as a nitpick. The PR author has not yet addressed it.
  • The PR resolves the URL portion of issue #1066, where MIT Press EPUBs with long URLs caused rendering problems.

PR #1329: Refactor reader utils

Context

Both EpubReaderActivity and TxtReaderActivity contained duplicated logic for orientation switching, page-turn detection, refresh cycling, and grayscale anti-aliasing. This PR extracts shared reader utilities into a new ReaderUtils.h header.

Changes applied

  1. src/activities/reader/ReaderUtils.h (new): Created namespace ReaderUtils containing GO_HOME_MS, applyOrientation(), PageTurnResult + detectPageTurn(), displayWithRefreshCycle(), and renderAntiAliased() (template to avoid std::function overhead). Includes storeBwBuffer() null-check from CodeRabbit review.
  2. src/activities/reader/EpubReaderActivity.cpp: Replaced local applyReaderOrientation(), goHomeMs, manual page-turn detection, and refresh cycling with ReaderUtils:: equivalents.
  3. src/activities/reader/TxtReaderActivity.cpp: Same delegation to ReaderUtils::, removing duplicated orientation switch, page-turn detection, and anti-aliasing code.

Differences from upstream PR

  • CodeRabbit fix applied: Added storeBwBuffer() return value check in renderAntiAliased() that upstream's draft doesn't yet include.
  • Mod's applyOrientation preserved: The mod's EpubReaderActivity::applyOrientation() method (which handles settings persistence and section reset) was kept and internally calls ReaderUtils::applyOrientation() for the renderer orientation change.

PR #1143 + #1172: TOC fragment navigation + multi-spine TOC

Context

Many EPUBs have spine files containing multiple TOC chapters (e.g., short story collections, academic texts). The status bar showed incorrect chapter titles, chapter skip jumped entire spine items instead of TOC entries, and cross-spine chapter transitions required full re-indexing. These PRs add TOC-aware boundary tracking and multi-spine section caching.

Changes applied

  1. lib/Epub/Epub/Section.h: Added TocBoundary struct, tocBoundaries vector, buildTocBoundaries(), getTocIndexForPage(), getPageForTocIndex(), getPageRangeForTocIndex(), readAnchorMap(), readCachedPageCount().
  2. lib/Epub/Epub/Section.cpp: Implemented all new TOC boundary methods. Incremented SECTION_FILE_VERSION from 18 to 19. Integrated buildTocBoundaries() into both loadSectionFile and createSectionFile. Added static readAnchorMap() for lightweight section probing.
  3. lib/Epub/Epub/parsers/ChapterHtmlSlimParser.h/.cpp: Added tocAnchors set and tocAnchorPageMap. Constructor accepts tocAnchors parameter. Forced page breaks at TOC anchor boundaries in startNewTextBlock so chapters start on fresh pages.
  4. src/activities/ActivityResult.h: Added tocIndex to ChapterResult.
  5. src/activities/reader/EpubReaderActivity.h/.cpp: Added pendingTocIndex for deferred cross-spine TOC navigation. Chapter skip (long-press) now walks TOC entries. Status bar uses section->getTocIndexForPage() for accurate subchapter title. Added cacheMultiSpineChapter() for proactive indexing of all spines in the same TOC chapter.
  6. src/activities/reader/EpubReaderChapterSelectionActivity.h/.cpp: Added currentTocIndex parameter for precise pre-positioning. Returns tocIndex alongside spineIndex in ChapterResult.

Differences from upstream PRs

  • Mod's footnote support preserved: Upstream removed footnote navigation; the mod's getPageForAnchor(), footnote depth tracking, and EpubReaderFootnotesActivity are all retained.
  • Mod's image rendering preserved: Upstream removed image rendering options; the mod's imageRendering parameter chain is preserved throughout Section, parser, and settings.
  • Activity base class retained: Upstream adopted ActivityWithSubactivity; the mod keeps Activity as the base class for reader activities.
  • No STR_INDEXING_PROGRESS key: Instead of adding a new translation key, the progress popup reuses tr(STR_INDEXING) with snprintf to append the numeric (x/y) progress, avoiding changes to 17+ YAML files.
  • Section file version: Mod increments from v18 to v19 (upstream from v13 to v14). The mod's higher version reflects additional fields from previous mod-exclusive changes.

PR #1320: JPEG resource cleanup

Context

JpegToBmpConverter::convert() had scattered free()/delete calls across multiple early-return paths, making it prone to resource leaks on error.

Changes applied

  1. lib/JpegToBmpConverter/JpegToBmpConverter.cpp: Introduced ScopedCleanup RAII struct that manages rowBuffer, mcuRowBuffer, atkinsonDitherer, fsDitherer, atkinson1BitDitherer, rowAccum, and rowCount. All pointers are initialized to nullptr and freed in the destructor. Removed scattered manual free()/delete calls. Changed rowCount from uint16_t* to uint32_t* to prevent overflow for wide images.

Differences from upstream PR

  • None -- clean port. The mod's JpegToBmpConverter.cpp had the same structure as upstream's pre-PR version.

PR #1325: Settings tab label

Context

The settings screen's "Confirm" button showed a hardcoded "Toggle" label even when the tab bar was focused, where it should indicate the next category name.

Changes applied

  1. src/activities/settings/SettingsActivity.cpp: When selectedSettingIndex == 0 (tab bar focused), the confirm button now shows the next category's name using I18N.get(categoryNames[(selectedCategoryIndex + 1) % categoryCount]). Otherwise, it shows the standard tr(STR_TOGGLE) label.

Differences from upstream PR

  • None -- clean port.

PR #1311: Fix inter-word spacing rounding error

Context

Inter-word gap widths were computed as two separately-snapped integers: fp4::toPixel(spaceAdvance) + fp4::toPixel(kernSum). Because fp4::toPixel(a) + fp4::toPixel(b) can differ from fp4::toPixel(a + b) by +/-1 pixel when fractional parts straddle a rounding boundary, each inter-word space could be one pixel wider or narrower than the correct value, affecting line-break width decisions and word-position accumulation.

Changes applied

  1. lib/GfxRenderer/GfxRenderer.h: Replaced getSpaceKernAdjust() declaration with getSpaceAdvance(), which returns the full inter-word space advance (space glyph advance + both flanking kern values) combined in fixed-point before a single pixel snap.
  2. lib/GfxRenderer/GfxRenderer.cpp: Replaced getSpaceKernAdjust() implementation with getSpaceAdvance() that retrieves the space glyph advance (12.4 FP), adds kern(leftCp, ' ') and kern(' ', rightCp) (4.4 FP), then snaps the combined sum once via fp4::toPixel(). getSpaceWidth() is retained for the single-space-word case in measureWordWidth.
  3. lib/Epub/Epub/ParsedText.h: Removed spaceWidth parameter from computeLineBreaks, computeHyphenatedLineBreaks, and extractLine.
  4. lib/Epub/Epub/ParsedText.cpp: Updated all four call sites (computeLineBreaks, computeHyphenatedLineBreaks, and both gap-calculation loops in extractLine) to use renderer.getSpaceAdvance() instead of spaceWidth + renderer.getSpaceKernAdjust(). Removed spaceWidth pre-computation from layoutAndExtractLines.

Differences from upstream PR

  • Mod's vector-based ParsedText: The mod's ParsedText.cpp uses std::vector (from #1038 port) and has additional vectors (forceBreakAfter, wordContinues). The spacing replacement is structurally identical at each call site.
  • Word-width cache preserved: The mod's word-width cache (from #1027 port) is unaffected by this change since it caches getTextAdvanceX results, not space widths.

Notable PR discussion

  • jdk2pq tested on device and confirmed working.
  • The single-snap pattern matches what getTextAdvanceX already uses for intra-word glyph advances, making inter-word spacing consistent with word-width measurement.

PR #1322: Early exit on fillUncompressedSizes

Context

fillUncompressedSizes scanned the entire ZIP central directory even when all requested target entries had already been matched, wasting time on large EPUB files with many entries.

Changes applied

  1. lib/ZipFile/ZipFile.cpp: Added targetCount variable before the scan loop and a break after the inner match loop when matched >= targetCount, terminating the central-directory scan early.

Differences from upstream PR

  • None -- identical to upstream commit e60ba76.

Notable PR discussion

  • CodeRabbit flagged a theoretical duplicate-match overcount issue, but znelson and ngxson approved as-is since duplicate central-directory filenames are not expected in valid EPUB/ZIP files.

PR #1342: Book Info, metadata, serialization safety

Context

Upstream PR #1342 adds a Book Info screen, richer EPUB metadata (series, seriesIndex, description), bounded serialization reads, and file browser controls for accessing the new info screen. This port adapts the feature with two significant mod-specific changes: a different file browser button mapping and mandatory confirmation guards on all destructive book actions.

Changes applied

  1. EPUB metadata expansion (lib/Epub/Epub/parsers/ContentOpfParser.h/.cpp, lib/Epub/Epub/BookMetadataCache.h/.cpp, lib/Epub/Epub.h/.cpp): Added series, seriesIndex, description fields. Parses dc:description, calibre:series/calibre:series_index (OPF2), and EPUB3 belongs-to-collection/group-position. Bumped BOOK_CACHE_VERSION from 5 to 6. Added stripHtml() and trim() helpers for description text. Capped description at 2048 chars.
  2. Serialization safety (lib/Serialization/Serialization.h): Changed both readString overloads from void to bool. Added MAX_STRING_LENGTH = 4096 guard. Updated all 37 call sites across 10 files to check the return value.
  3. RecentBooksStore series field (src/RecentBooksStore.h/.cpp, src/JsonSettingsIO.cpp): Added series field to RecentBook struct. Updated addBook()/updateBook() signatures. Updated JSON serialization and binary migration. Updated all reader call sites to pass series data.
  4. BookInfoActivity (src/activities/home/BookInfoActivity.h/.cpp): New ActivityManager-compliant activity displaying title, author, series, language, file size, and description with scrollable content. Loads metadata synchronously in onEnter(), no background tasks.
  5. File browser controls (src/activities/home/FileBrowserActivity.h/.cpp): See mod adaptation below.
  6. ConfirmationActivity input gating (src/activities/util/ConfirmationActivity.h/.cpp): Added inputArmed mechanism — input is ignored until all buttons are released after dialog opens, preventing accidental confirm from the press that opened the dialog.
  7. Confirmation guards (HomeActivity, RecentBooksActivity, EpubReaderActivity, FileBrowserActivity): All BookManager::deleteBook() and BookManager::archiveBook() calls now chain through ConfirmationActivity before executing. See mod adaptation below.
  8. i18n keys (lib/I18n/translations/english.yaml): Added STR_BOOK_INFO, STR_AUTHOR, STR_SERIES, STR_FILE_SIZE, STR_DESCRIPTION, STR_MANAGE, STR_INFO.
  9. Recent books subtitle (src/activities/home/RecentBooksActivity.cpp): List subtitle now shows "Author • Series" when series is available.

Differences from upstream PR

  • File browser button mapping (mod-adapted):
Button Upstream Mod
Back short parent dir / home parent dir / home
Back long home home
Confirm short open open
Confirm long no-op no-op
Left short (unused) open ManageBook menu
Left long delete (hidden) (no action)
Right short Book Info Book Info

The upstream PR hid the delete action behind a long-press of the Left button. The mod instead exposes the full ManageBook menu (archive, delete, reindex, etc.) via a short-press of Left, consistent with the ManageBook menu available from the Home screen and reader.

  • Confirmation guards (mod-specific addition): The upstream PR does not add confirmation dialogs for destructive actions. The mod wraps all 10 delete/archive call sites across HomeActivity, RecentBooksActivity, EpubReaderActivity, and FileBrowserActivity in ConfirmationActivity chains using ActivityManager's safe startActivityForResult chaining pattern.

  • No upstream file deletion in browser: Upstream's file browser had a direct Storage.remove() delete path. The mod replaces this entirely with the ManageBook menu which routes through BookManager (proper cache cleanup, recents removal, etc.).

Notable upstream PR discussion

  • The upstream PR went through multiple review rounds with CodeRabbit, addressing bounded string appending in description parsing, first-description guards, proper HTML tag heuristics, and inputArmed timing.