Skip to content

refactor: Removed redundant FsFile close() calls#1434

Merged
znelson merged 5 commits into
crosspoint-reader:masterfrom
znelson:file-raii
Apr 14, 2026
Merged

refactor: Removed redundant FsFile close() calls#1434
znelson merged 5 commits into
crosspoint-reader:masterfrom
znelson:file-raii

Conversation

@znelson
Copy link
Copy Markdown
Member

@znelson znelson commented Mar 20, 2026

Summary

What is the goal of this PR? (e.g., Implements the new feature for file uploading.)

DESTRUCTOR_CLOSES_FILE=1 is set in platformio.ini, which makes SdFat's FsBaseFile destructor call close() automatically when a file goes out of scope.

Three categories of file close calls remain untouched:

  1. Close before Storage.remove() on the same path: ScreenshotUtil.cpp closes the file before deleting it on write error. The remove might fail if the file is still open.
  2. Close before reopening the same variable: Epub.cpp writes a temp NCX/nav file, closes it, then reopens it for reading. The RecentBooksStore.cpp close before saveToFile() is the same pattern, it rewrites the same file.
  3. Close on member variables: BookMetadataCache.cpp (bookFile, spineFile, tocFile), Section.cpp (file), XtcParser.cpp (m_file), ZipFile.cpp (file). These persist beyond any single function scope, so the destructor timing doesn't match the intended close point.

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? PARTIALLY

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 20, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1427bc02-e281-4e43-8ccd-44cde6b6a3c0

📥 Commits

Reviewing files that changed from the base of the PR and between 7b288d7 and 1369655.

📒 Files selected for processing (14)
  • lib/Epub/Epub/BookMetadataCache.cpp
  • lib/Epub/Epub/Section.cpp
  • lib/Epub/Epub/blocks/ImageBlock.cpp
  • lib/Epub/Epub/css/CssParser.cpp
  • lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp
  • lib/Xtc/Xtc.cpp
  • lib/ZipFile/ZipFile.cpp
  • src/activities/boot_sleep/SleepActivity.cpp
  • src/activities/home/FileBrowserActivity.cpp
  • src/activities/reader/EpubReaderActivity.cpp
  • src/activities/reader/TxtReaderActivity.cpp
  • src/components/themes/BaseTheme.cpp
  • src/network/CrossPointWebServer.cpp
  • src/util/ScreenshotUtil.cpp
💤 Files with no reviewable changes (8)
  • lib/Epub/Epub/blocks/ImageBlock.cpp
  • lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp
  • src/components/themes/BaseTheme.cpp
  • src/activities/reader/TxtReaderActivity.cpp
  • lib/Xtc/Xtc.cpp
  • src/activities/reader/EpubReaderActivity.cpp
  • src/activities/home/FileBrowserActivity.cpp
  • src/activities/boot_sleep/SleepActivity.cpp
✅ Files skipped from review due to trivial changes (2)
  • lib/ZipFile/ZipFile.cpp
  • lib/Epub/Epub/BookMetadataCache.cpp
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/util/ScreenshotUtil.cpp
  • lib/Epub/Epub/css/CssParser.cpp
  • lib/Epub/Epub/Section.cpp
📜 Recent review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: cppcheck
  • GitHub Check: build
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: znelson
Repo: crosspoint-reader/crosspoint-reader PR: 1418
File: lib/hal/HalStorage.cpp:99-101
Timestamp: 2026-03-20T20:25:05.965Z
Learning: In crosspoint-reader/crosspoint-reader (`lib/hal/HalStorage.cpp`), `HalStorage::openFileForRead` and `HalStorage::openFileForWrite` take a `HalFile&` output parameter and return `bool`. On failure paths (e.g., `!ok` or OOM), the functions return `false` and intentionally leave `file` unchanged. This is correct: typical callers default-construct `file` (already empty/invalid), and silently resetting a previously-valid handle would leak a file descriptor the caller still owns. All callers check the `bool` return before using `file`. Do not flag the unchanged `file` on failure paths as a stale-handle bug in future reviews.
Learnt from: jpirnay
Repo: crosspoint-reader/crosspoint-reader PR: 1342
File: lib/Serialization/Serialization.h:50-57
Timestamp: 2026-03-08T10:24:33.238Z
Learning: In crosspoint-reader/crosspoint-reader, every call site of `serialization::readString` (lib/Serialization/Serialization.h) checks the bool return value and immediately closes the file and returns false on failure. There is therefore no need to advance the file pointer (e.g. via seekCur) when rejecting an oversized string, because no subsequent read ever occurs after a failure. Additionally, calling seekCur with a potentially UINT32_MAX-sized offset on the ESP32 SdFat implementation is unsafe and should be avoided.
Learnt from: pablohc
Repo: crosspoint-reader/crosspoint-reader PR: 0
File: :0-0
Timestamp: 2026-03-28T20:57:09.493Z
Learning: In crosspoint-reader/crosspoint-reader (src/activities/home/HomeActivity.cpp, HomeActivity::onEnter), `coverRendered = false` and `coverBufferStored = false` ARE correctly reset in `onEnter()`. This was fixed in PR `#1488`. Do not flag their absence as a bug in future reviews.
Learnt from: znelson
Repo: crosspoint-reader/crosspoint-reader PR: 1418
File: lib/hal/HalStorage.cpp:182-186
Timestamp: 2026-03-20T20:25:15.070Z
Learning: In crosspoint-reader/crosspoint-reader, `HalFile::openNextFile()` (lib/hal/HalStorage.cpp) intentionally returns an empty `HalFile()` on both EOF and OOM. This is by design: callers (directory listing loops in ClearCacheActivity, SleepActivity, FileBrowserActivity) stop iteration on an empty `HalFile`, which is the correct behavior for both EOF and OOM. OOM is already surfaced via `LOG_ERR("HAL", "OOM HalFile::openNextFile")` for diagnostics. Do not flag the EOF/OOM conflation in `openNextFile()` as a bug or suggest an API split in future reviews.
Learnt from: alkk
Repo: crosspoint-reader/crosspoint-reader PR: 1617
File: src/network/CrossPointWebServer.cpp:876-880
Timestamp: 2026-04-08T15:27:22.890Z
Learning: In crosspoint-reader/crosspoint-reader (`src/network/CrossPointWebServer.cpp`), `clearEpubCacheIfNeeded()` and `clearEpubCachesInDirectory()` are intentionally best-effort/fire-and-forget helpers. Their return values (or `Epub::clearCache()` success) are never checked, and callers (rename, move, upload, WebSocket upload) always return 200 on a successful filesystem operation regardless of whether cache clearing succeeded. Stale caches are self-healing on next open. Do not flag the unchecked cache-clear return as a bug or suggest making rename/move fail when cache clearing fails in future reviews.
📚 Learning: 2026-03-09T00:59:12.412Z
Learnt from: laird
Repo: crosspoint-reader/crosspoint-reader PR: 1331
File: src/activities/network/CrossPointWebServerActivity.cpp:0-0
Timestamp: 2026-03-09T00:59:12.412Z
Learning: In the crosspoint-reader codebase (targeting ESP32-C3), WebSocketsServer is polled synchronously via wsServer->loop() on the single main-core loop. There is no preemptive threading, so websocket event handlers and accessors like CrossPointWebServer::getWsUploadStatus() always execute sequentially on the same core. No mutex or lock is needed to protect shared upload state globals (wsUploadFileName, wsLastCompleteName, etc.) from concurrent access. getWsUploadStatus() returns a value-copy struct with std::string fields (not raw char pointers), making the snapshot safe without synchronization.

Applied to files:

  • src/network/CrossPointWebServer.cpp
📚 Learning: 2026-04-08T15:27:22.890Z
Learnt from: alkk
Repo: crosspoint-reader/crosspoint-reader PR: 1617
File: src/network/CrossPointWebServer.cpp:876-880
Timestamp: 2026-04-08T15:27:22.890Z
Learning: In crosspoint-reader/crosspoint-reader (`src/network/CrossPointWebServer.cpp`), `clearEpubCacheIfNeeded()` and `clearEpubCachesInDirectory()` are intentionally best-effort/fire-and-forget helpers. Their return values (or `Epub::clearCache()` success) are never checked, and callers (rename, move, upload, WebSocket upload) always return 200 on a successful filesystem operation regardless of whether cache clearing succeeded. Stale caches are self-healing on next open. Do not flag the unchecked cache-clear return as a bug or suggest making rename/move fail when cache clearing fails in future reviews.

Applied to files:

  • src/network/CrossPointWebServer.cpp
📚 Learning: 2026-02-27T22:49:59.600Z
Learnt from: ngxson
Repo: crosspoint-reader/crosspoint-reader PR: 1218
File: src/activities/ActivityManager.cpp:254-265
Timestamp: 2026-02-27T22:49:59.600Z
Learning: In this codebase, assertions are always enabled (no NDEBUG). Use assert() to crash on programmer errors and surface logic bugs during development and in production builds. Do not rely on asserts for runtime error handling; they should enforce invariants that must always hold. Keep asserts side-effect free and inexpensive, and avoid relying on them for user-visible failures. Include <cassert> where appropriate and document the invariant being tested.

Applied to files:

  • src/network/CrossPointWebServer.cpp
📚 Learning: 2026-03-02T10:14:16.036Z
Learnt from: Uri-Tauber
Repo: crosspoint-reader/crosspoint-reader PR: 1245
File: lib/Epub/Epub/Section.cpp:277-308
Timestamp: 2026-03-02T10:14:16.036Z
Learning: Guideline: Strengthen serialization::readString to defend against unbounded growth when reading from disk data. Implement and enforce a maximum allowed length (e.g., a configured or reasonable constant) and validate the incoming length before resizing or allocating. Audit all call sites (e.g., BookMetadataCache, TextBlock, KOReaderCredentialStore, Section cache readers) to ensure they do not rely on unbounded len-based resizing. If the readString API must remain, add internal safeguards (bounds checks, length validation, and error handling) so per-call-site validations are not required. Ensure Section cache files remain versioned (SECTION_FILE_VERSION) and parameter mismatches invalidate caches, but do not rely on unsafe allocations; prefer safe, bounded reads with explicit errors on invalid data.

Applied to files:

  • src/network/CrossPointWebServer.cpp
📚 Learning: 2026-03-28T11:06:29.611Z
Learnt from: pablohc
Repo: crosspoint-reader/crosspoint-reader PR: 1488
File: src/activities/home/HomeActivity.cpp:92-95
Timestamp: 2026-03-28T11:06:29.611Z
Learning: When reviewing crosspoint-reader code, avoid flagging a missing `renderer.displayBuffer()` call immediately after `GUI.drawPopup()` / `BaseTheme::drawPopup()`: `BaseTheme::drawPopup()` already calls `renderer.displayBuffer()` before returning, so the popup is guaranteed to be flushed to the e-ink panel before subsequent blocking work begins. Conversely, do not require a `renderer.displayBuffer()` call after `fillPopupProgress()`; it intentionally does not flush, so intermediate progress-bar updates may not appear unless the update granularity warrants an explicit flush elsewhere.

Applied to files:

  • src/network/CrossPointWebServer.cpp
📚 Learning: 2026-04-12T12:28:33.205Z
Learnt from: Uri-Tauber
Repo: crosspoint-reader/crosspoint-reader PR: 1629
File: src/activities/home/HomeActivity.cpp:119-120
Timestamp: 2026-04-12T12:28:33.205Z
Learning: When reviewing code in this repository (C/C++ sources), set review comment severity according to this policy:
- Use **Major** (🟠) only for defects with realistic risk of crash, out-of-memory (OOM), invalid pointer dereference, data corruption, or other severe issues that are unlikely to be caught in casual/manual device testing.
- Use **Minor** or informational for UX gaps, logic edge-cases, style issues, or missing feature completeness that the author can verify (or has verified) through normal device use.
- Do **not** escalate severity to Major based on behavioral/UX observations alone; assume the author has already tested the feature on their own device and only treat issues as Major if they match the high-risk defect categories above.

Applied to files:

  • src/network/CrossPointWebServer.cpp
🔇 Additional comments (3)
src/network/CrossPointWebServer.cpp (3)

195-209: Correct close-before-delete handling in abort flow.

Explicitly closing wsUploadFile before Storage.remove() is the right choice for this persistent upload handle.


1344-1356: Zero-byte completion path cleanup looks correct.

Closing the file immediately in the zero-byte branch avoids keeping a stale open handle past logical completion.


1400-1425: Upload completion cleanup ordering is solid.

Closing wsUploadFile before clearing upload state keeps resource lifecycle explicit and consistent.


📝 Walkthrough

Walkthrough

This PR standardizes file/dir resource management: it removes many explicit FsFile/FsDir close() calls and adds targeted explicit close() calls before reopen/remove in other paths, changing where cleanup occurs (RAII vs. manual) across multiple modules; no public APIs were altered.

Changes

Cohort / File(s) Summary
EPUB section & page file handling
lib/Epub/Epub/Section.cpp
Added explicit file.close() on several failure and success paths during section file deserialization/creation and page reading.
EPUB core, parsing & assets
lib/Epub/Epub.cpp, lib/Epub/Epub/css/CssParser.cpp, lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp, lib/Epub/Epub/blocks/ImageBlock.cpp
Mixed edits: removed many explicit FsFile::close() calls relying on RAII in several flows; added explicit closes in temp file flows before reopen/remove and on some error paths.
EPUB cache / metadata
lib/Epub/Epub/BookMetadataCache.cpp
Added explicit closes for persistent FsFile members (bookFile, spineFile, tocFile) on multiple error and completion paths.
Zip / temp file handling
lib/ZipFile/ZipFile.cpp
Minor inline comment before existing file.close() in ZipFile::close() (no behavior change).
XTC parsing & BMP generation
lib/Xtc/Xtc/XtcParser.cpp, lib/Xtc/Xtc.cpp
Added m_file.close() on multiple XtcParser::open() failure paths; removed some explicit closes in BMP generation paths and adjusted close-before-reopen/remove behavior.
Text & cover generation utilities
lib/Txt/Txt.cpp, src/util/ScreenshotUtil.cpp
Removed explicit closes in several text/cover flows; added explicit file.close() before Storage.remove() in BMP save error paths and after row writes.
KOReader sync & doc-id
lib/KOReaderSync/KOReaderCredentialStore.cpp, lib/KOReaderSync/KOReaderDocumentId.cpp
Removed explicit file.close() calls in credential load and document-id calculation paths (rely on RAII).
Settings, state & recent books
lib/I18n/I18n.cpp, src/CrossPointSettings.cpp, src/CrossPointState.cpp, src/RecentBooksStore.cpp
Removed many manual inputFile.close()/file.close() calls on early-exit and success; RecentBooksStore retains one explicit close in a specific branch.
Wi‑Fi & credential stores
src/WifiCredentialStore.cpp
Removed explicit file.close() in version-check and normal completion paths.
Reader activities & caches
src/activities/reader/EpubReaderActivity.cpp, src/activities/reader/TxtReaderActivity.cpp
Removed explicit FsFile::close() calls in progress and page-index cache load/save paths (rely on scope/RAII).
Filesystem traversal & UI
src/activities/boot_sleep/SleepActivity.cpp, src/activities/home/FileBrowserActivity.cpp
Removed many per-entry FsDir::close()/FsFile::close() calls during directory iteration and selection; loop/skip branches no longer close per-file handles.
Themes & rendering
src/components/themes/BaseTheme.cpp
Removed redundant file.close() calls when reading cover BMP data.
Web server upload handling
src/network/CrossPointWebServer.cpp
Added explicit wsUploadFile.close() in multiple upload abort/completion/zero-byte branches to ensure upload file is closed before cleanup/removal.

Sequence Diagram(s)

(omitted — changes are resource-management adjustments without new multi-component control flow that warrants a sequence diagram)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • ngxson
  • daveallie
  • osteotek
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'refactor: Removed redundant FsFile close() calls' clearly and concisely summarizes the main change—removing unnecessary file closing calls due to RAII/automatic cleanup.
Description check ✅ Passed The description explains the goal, context (DESTRUCTOR_CLOSES_FILE=1), categorizes the three types of close() calls that remain, and notes partial AI usage, all of which directly relate to the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@znelson znelson marked this pull request as ready for review March 20, 2026 04:34
znelson added a commit to znelson/crosspoint-reader that referenced this pull request Mar 20, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
lib/Epub/Epub.cpp (2)

154-160: ⚠️ Potential issue | 🟠 Major

Stop on failed TOC temp-file extraction.

Both paths ignore readItemContentsToStream(...), so a failed or partial copy still falls through to reopen and parse the temp file. That can turn the zip read error into parsing truncated TOC data instead of failing fast. Mirror the CSS path here: close, delete the temp file, and return false before the reopen when extraction fails.

💡 Suggested fix
-  readItemContentsToStream(tocNcxItem, tempNcxFile, 1024);
+  if (!readItemContentsToStream(tocNcxItem, tempNcxFile, 1024)) {
+    tempNcxFile.close();
+    Storage.remove(tmpNcxPath.c_str());
+    return false;
+  }
   tempNcxFile.close();
   if (!Storage.openFileForRead("EBP", tmpNcxPath, tempNcxFile)) {
+    Storage.remove(tmpNcxPath.c_str());
     return false;
   }
-  readItemContentsToStream(tocNavItem, tempNavFile, 1024);
+  if (!readItemContentsToStream(tocNavItem, tempNavFile, 1024)) {
+    tempNavFile.close();
+    Storage.remove(tmpNavPath.c_str());
+    return false;
+  }
   tempNavFile.close();
   if (!Storage.openFileForRead("EBP", tmpNavPath, tempNavFile)) {
+    Storage.remove(tmpNavPath.c_str());
     return false;
   }

Also applies to: 210-216

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/Epub/Epub.cpp` around lines 154 - 160, The TOC temp-file extraction calls
readItemContentsToStream(tocNcxItem, tempNcxFile, 1024) but doesn’t check its
result, so failures can lead to reopening a truncated file; update the block
around Storage.openFileForWrite("EBP", tmpNcxPath, tempNcxFile) to capture the
return value of readItemContentsToStream, and if it indicates failure close
tempNcxFile, delete the temp file (same mechanism used in the CSS extraction
path), and return false before attempting Storage.openFileForRead; apply the
same fix to the second occurrence of this pattern (the block referenced in the
comment as lines 210-216).

549-556: ⚠️ Potential issue | 🟠 Major

Abort image conversion when the temp extraction fails.

These four call sites have the same gap as the TOC paths: readItemContentsToStream(...) is ignored, then the temp file is reopened and handed to the decoder. On a failed or partial unzip, that can feed corrupt input into cover/thumb generation. Return early after closing and removing the temp file instead.

💡 Suggested fix pattern
-  readItemContentsToStream(coverImageHref, coverJpg, 1024);
+  if (!readItemContentsToStream(coverImageHref, coverJpg, 1024)) {
+    coverJpg.close();
+    Storage.remove(coverJpgTempPath.c_str());
+    return false;
+  }
   coverJpg.close();

   if (!Storage.openFileForRead("EBP", coverJpgTempPath, coverJpg)) {
+    Storage.remove(coverJpgTempPath.c_str());
     return false;
   }

Apply the same guard to the PNG and thumbnail variants.

Also applies to: 583-590, 638-645, 675-682

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/Epub/Epub.cpp` around lines 549 - 556, The code currently ignores the
return value of readItemContentsToStream before closing and reopening the temp
files (e.g., in the cover flow: Storage.openFileForWrite("EBP",
coverJpgTempPath, coverJpg); readItemContentsToStream(coverImageHref, coverJpg,
1024); coverJpg.close(); Storage.openFileForRead(...)), so if extraction fails a
corrupt file is passed to the decoder; update each site (cover JPG, cover PNG,
thumbnail variants) to check the boolean result of readItemContentsToStream, and
on failure call coverJpg.close(), remove the temp file (coverJpgTempPath), and
return false immediately instead of proceeding to Storage.openFileForRead; apply
the same guard/pattern to the other occurrences at the PNG and thumbnail blocks
referenced in the comment.
🧹 Nitpick comments (2)
src/util/ScreenshotUtil.cpp (1)

72-77: Consider extracting a small cleanup helper to avoid repetition.

close()+remove()+return false is repeated in multiple branches; a tiny local helper/lambda would reduce drift risk if this flow changes later.

♻️ Optional refactor sketch
+  auto closeAndRemove = [&]() {
+    file.close();
+    Storage.remove(filename);
+    return false;
+  };
+
   if (write_error) {
-    file.close();
-    Storage.remove(filename);
-    return false;
+    return closeAndRemove();
   }
...
   if (rowSizePadded > kMaxRowSize) {
     LOG_ERR("SCR", "Row size %u exceeds buffer capacity", rowSizePadded);
-    file.close();
-    Storage.remove(filename);
-    return false;
+    return closeAndRemove();
   }

Also applies to: 82-87, 111-116

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/util/ScreenshotUtil.cpp` around lines 72 - 77, Extract the repeated
cleanup sequence (file.close(); Storage.remove(filename); return false;) into a
small local helper (e.g., a lambda named cleanupAndFail or a static inline
function) and replace each occurrence (the branches checking write_error and the
other repeated blocks around symbols file.close(), Storage.remove(filename),
write_error, filename) with a single call to that helper; ensure the helper
captures or accepts filename and file (or references them appropriately) so
behavior and early return semantics remain identical.
lib/Xtc/Xtc/XtcParser.cpp (1)

44-48: Consolidate to one idempotent cleanup path.

These early-return branches duplicate close logic because close() only closes when m_isOpen is true. Consider making close() always perform full cleanup (including file close), then call close() in each error branch to avoid future misses.

Proposed refactor
 XtcError XtcParser::open(const char* filepath) {
@@
   m_lastError = readHeader();
   if (m_lastError != XtcError::OK) {
     LOG_DBG("XTC", "Failed to read header: %s", errorToString(m_lastError));
-    // Explicit close() required: member variable persists beyond function scope
-    m_file.close();
+    close();
     return m_lastError;
   }
@@
 void XtcParser::close() {
-  if (m_isOpen) {
-    // Explicit close() required: member variable persists beyond function scope
-    m_file.close();
-    m_isOpen = false;
-  }
+  m_file.close();
+  m_isOpen = false;
   m_pageTable.clear();
   m_chapters.clear();
   m_title.clear();
+  m_author.clear();
+  m_defaultWidth = DISPLAY_WIDTH;
+  m_defaultHeight = DISPLAY_HEIGHT;
+  m_bitDepth = 1;
   m_hasChapters = false;
   memset(&m_header, 0, sizeof(m_header));
 }

Also applies to: 54-58, 61-65, 71-75, 80-84, 92-97

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/Xtc/Xtc/XtcParser.cpp` around lines 44 - 48, The duplicated
m_file.close() in early-return branches should be replaced by calling the class
cleanup method (make XtcParser::close() fully idempotent and responsible for
closing m_file and resetting m_isOpen), so update close() to always perform full
cleanup (close m_file if open, clear/reset state, and be safe to call multiple
times) and replace the explicit m_file.close() calls in functions (the branches
that currently check m_lastError and call m_file.close()) with a call to
close(); ensure callers use close() for all error returns so there is a single,
centralized cleanup path.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@lib/Epub/Epub.cpp`:
- Around line 154-160: The TOC temp-file extraction calls
readItemContentsToStream(tocNcxItem, tempNcxFile, 1024) but doesn’t check its
result, so failures can lead to reopening a truncated file; update the block
around Storage.openFileForWrite("EBP", tmpNcxPath, tempNcxFile) to capture the
return value of readItemContentsToStream, and if it indicates failure close
tempNcxFile, delete the temp file (same mechanism used in the CSS extraction
path), and return false before attempting Storage.openFileForRead; apply the
same fix to the second occurrence of this pattern (the block referenced in the
comment as lines 210-216).
- Around line 549-556: The code currently ignores the return value of
readItemContentsToStream before closing and reopening the temp files (e.g., in
the cover flow: Storage.openFileForWrite("EBP", coverJpgTempPath, coverJpg);
readItemContentsToStream(coverImageHref, coverJpg, 1024); coverJpg.close();
Storage.openFileForRead(...)), so if extraction fails a corrupt file is passed
to the decoder; update each site (cover JPG, cover PNG, thumbnail variants) to
check the boolean result of readItemContentsToStream, and on failure call
coverJpg.close(), remove the temp file (coverJpgTempPath), and return false
immediately instead of proceeding to Storage.openFileForRead; apply the same
guard/pattern to the other occurrences at the PNG and thumbnail blocks
referenced in the comment.

---

Nitpick comments:
In `@lib/Xtc/Xtc/XtcParser.cpp`:
- Around line 44-48: The duplicated m_file.close() in early-return branches
should be replaced by calling the class cleanup method (make XtcParser::close()
fully idempotent and responsible for closing m_file and resetting m_isOpen), so
update close() to always perform full cleanup (close m_file if open, clear/reset
state, and be safe to call multiple times) and replace the explicit
m_file.close() calls in functions (the branches that currently check m_lastError
and call m_file.close()) with a call to close(); ensure callers use close() for
all error returns so there is a single, centralized cleanup path.

In `@src/util/ScreenshotUtil.cpp`:
- Around line 72-77: Extract the repeated cleanup sequence (file.close();
Storage.remove(filename); return false;) into a small local helper (e.g., a
lambda named cleanupAndFail or a static inline function) and replace each
occurrence (the branches checking write_error and the other repeated blocks
around symbols file.close(), Storage.remove(filename), write_error, filename)
with a single call to that helper; ensure the helper captures or accepts
filename and file (or references them appropriately) so behavior and early
return semantics remain identical.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ccade0bb-547d-4170-a075-5bf67afa0ba2

📥 Commits

Reviewing files that changed from the base of the PR and between 7c1f48d and 7b288d7.

📒 Files selected for processing (7)
  • lib/Epub/Epub.cpp
  • lib/Epub/Epub/BookMetadataCache.cpp
  • lib/Epub/Epub/Section.cpp
  • lib/Xtc/Xtc/XtcParser.cpp
  • lib/ZipFile/ZipFile.cpp
  • src/RecentBooksStore.cpp
  • src/util/ScreenshotUtil.cpp
✅ Files skipped from review due to trivial changes (1)
  • lib/ZipFile/ZipFile.cpp
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/RecentBooksStore.cpp
  • lib/Epub/Epub/Section.cpp
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build
  • GitHub Check: cppcheck
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: jpirnay
Repo: crosspoint-reader/crosspoint-reader PR: 1342
File: lib/Serialization/Serialization.h:50-57
Timestamp: 2026-03-08T10:24:33.238Z
Learning: In crosspoint-reader/crosspoint-reader, every call site of `serialization::readString` (lib/Serialization/Serialization.h) checks the bool return value and immediately closes the file and returns false on failure. There is therefore no need to advance the file pointer (e.g. via seekCur) when rejecting an oversized string, because no subsequent read ever occurs after a failure. Additionally, calling seekCur with a potentially UINT32_MAX-sized offset on the ESP32 SdFat implementation is unsafe and should be avoided.
📚 Learning: 2026-02-27T22:49:59.600Z
Learnt from: ngxson
Repo: crosspoint-reader/crosspoint-reader PR: 1218
File: src/activities/ActivityManager.cpp:254-265
Timestamp: 2026-02-27T22:49:59.600Z
Learning: In this codebase, assertions are always enabled (no NDEBUG). Use assert() to crash on programmer errors and surface logic bugs during development and in production builds. Do not rely on asserts for runtime error handling; they should enforce invariants that must always hold. Keep asserts side-effect free and inexpensive, and avoid relying on them for user-visible failures. Include <cassert> where appropriate and document the invariant being tested.

Applied to files:

  • src/util/ScreenshotUtil.cpp
  • lib/Xtc/Xtc/XtcParser.cpp
  • lib/Epub/Epub/BookMetadataCache.cpp
  • lib/Epub/Epub.cpp
📚 Learning: 2026-03-02T10:14:16.036Z
Learnt from: Uri-Tauber
Repo: crosspoint-reader/crosspoint-reader PR: 1245
File: lib/Epub/Epub/Section.cpp:277-308
Timestamp: 2026-03-02T10:14:16.036Z
Learning: Guideline: Strengthen serialization::readString to defend against unbounded growth when reading from disk data. Implement and enforce a maximum allowed length (e.g., a configured or reasonable constant) and validate the incoming length before resizing or allocating. Audit all call sites (e.g., BookMetadataCache, TextBlock, KOReaderCredentialStore, Section cache readers) to ensure they do not rely on unbounded len-based resizing. If the readString API must remain, add internal safeguards (bounds checks, length validation, and error handling) so per-call-site validations are not required. Ensure Section cache files remain versioned (SECTION_FILE_VERSION) and parameter mismatches invalidate caches, but do not rely on unsafe allocations; prefer safe, bounded reads with explicit errors on invalid data.

Applied to files:

  • src/util/ScreenshotUtil.cpp
  • lib/Xtc/Xtc/XtcParser.cpp
  • lib/Epub/Epub/BookMetadataCache.cpp
  • lib/Epub/Epub.cpp
🔇 Additional comments (5)
src/util/ScreenshotUtil.cpp (1)

72-77: Good close-before-delete sequencing in error paths.

These updates correctly enforce close() before Storage.remove(filename) and keep cleanup ordering explicit where handle state matters.

Also applies to: 82-87, 111-116

lib/Epub/Epub/BookMetadataCache.cpp (1)

36-38: LGTM! Proper explicit close() handling for member variables.

The added explicit close() calls and clarifying comments are correct. Since bookFile, spineFile, and tocFile are class member variables, their destructors only run when the BookMetadataCache object is destroyed—not when these functions return. The explicit closes ensure:

  1. File handles are released promptly on both success and error paths
  2. Resources aren't held unnecessarily between operations (e.g., after buildBookBin completes)
  3. Error paths properly clean up all previously-opened files

The consistent commenting pattern clearly documents the rationale for reviewers and maintainers.

Also applies to: 47-51, 79-82, 108-119, 174-180, 274-277, 383-385

lib/Xtc/Xtc/XtcParser.cpp (1)

46-47: Nice fix on early-failure handle cleanup.

Explicitly closing m_file before returning from open() failure paths is correct here because the member outlives function scope.

Also applies to: 56-57, 63-64, 73-74, 82-83

lib/Epub/Epub.cpp (2)

305-324: The CSS temp-file flow looks solid.

This path checks the extraction result and keeps explicit close() only where the same temp path is reopened or deleted in the same scope.


191-193: Keeping the explicit closes before Storage.remove() here is the right exception to RAII.

These deletes happen before scope exit, so relying on the destructor would close the handle too late.

Also applies to: 249-251, 565-568, 599-602, 659-662, 694-697

ngxson
ngxson previously approved these changes Mar 21, 2026
lukestein pushed a commit to lukestein/crosspoint-reader that referenced this pull request Apr 7, 2026
@znelson znelson merged commit 23aad21 into crosspoint-reader:master Apr 14, 2026
6 checks passed
@znelson znelson deleted the file-raii branch April 14, 2026 21:56
trilwu pushed a commit to trilwu/crosspet that referenced this pull request Apr 23, 2026
)

**What is the goal of this PR?** (e.g., Implements the new feature for
file uploading.)

`DESTRUCTOR_CLOSES_FILE=1` is set in platformio.ini, which makes SdFat's
FsBaseFile destructor call close() automatically when a file goes out of
scope.

Three categories of file close calls remain untouched:
1. Close before Storage.remove() on the same path: ScreenshotUtil.cpp
closes the file before deleting it on write error. The remove might fail
if the file is still open.
2. Close before reopening the same variable: Epub.cpp writes a temp
NCX/nav file, closes it, then reopens it for reading. The
RecentBooksStore.cpp close before saveToFile() is the same pattern, it
rewrites the same file.
3. Close on member variables: BookMetadataCache.cpp (bookFile,
spineFile, tocFile), Section.cpp (file), XtcParser.cpp (m_file),
ZipFile.cpp (file). These persist beyond any single function scope, so
the destructor timing doesn't match the intended close point.

---

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? _**PARTIALLY**_
aBER0724 pushed a commit to aBER0724/crosspoint-reader-cjk that referenced this pull request May 7, 2026
)

## Summary

**What is the goal of this PR?** (e.g., Implements the new feature for
file uploading.)

`DESTRUCTOR_CLOSES_FILE=1` is set in platformio.ini, which makes SdFat's
FsBaseFile destructor call close() automatically when a file goes out of
scope.

Three categories of file close calls remain untouched:
1. Close before Storage.remove() on the same path: ScreenshotUtil.cpp
closes the file before deleting it on write error. The remove might fail
if the file is still open.
2. Close before reopening the same variable: Epub.cpp writes a temp
NCX/nav file, closes it, then reopens it for reading. The
RecentBooksStore.cpp close before saveToFile() is the same pattern, it
rewrites the same file.
3. Close on member variables: BookMetadataCache.cpp (bookFile,
spineFile, tocFile), Section.cpp (file), XtcParser.cpp (m_file),
ZipFile.cpp (file). These persist beyond any single function scope, so
the destructor timing doesn't match the intended close point.

---

### 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? _**PARTIALLY**_
HenriqueMorato added a commit to HenriqueMorato/crosspoint-reader that referenced this pull request May 8, 2026
- DESTRUCTOR_CLOSES_FILE=1 is project policy (crosspoint-reader#1075, crosspoint-reader#1434, a48ad3c) so HalFile destructors release the SdFat handle at scope exit; the explicit close()s in load() at the bounded-read overflow guard and post-loop were dead code
- persist() needs the close to fire BEFORE Storage.rename() against the same path - SdFat behaviour against an open handle is implementation-defined - so the write is now wrapped in its own block and the destructor closes deterministically before the rename
- Saves 16 bytes flash; logic identical
znelson added a commit that referenced this pull request May 15, 2026
## Summary

This release adds SD card fonts — the most-requested feature since
launch — brings the X3 to first-class status, redesigns the on-screen
keyboard, overhauls OPDS, and ships SD-card firmware updates. 144
changes from 53 contributors, 32 of whom are new to the project.

**🔠 SD Card Fonts**
Custom fonts are here. A complete font subsystem lets you install and
use fonts beyond the three built-in families. A new `.cpfont` binary
format packs multiple styles (regular, bold, italic, bold-italic) into a
single file per size, with on-demand glyph loading from the SD card. A
two-pass prewarm renderer bulk-reads glyphs per page, achieving
near-flash performance for Latin text and viable CJK rendering. Fonts
can be downloaded over WiFi directly from the device, uploaded via the
web interface, or copied manually to the SD card. The build pipeline
ships a 17-family font library (serif, sans, mono, accessibility) with
CI distribution via a dedicated crosspoint-fonts repository. As a bonus,
CJK characters no longer get spurious hyphens at line breaks, and an
advance-table cache eliminates 30+ second stalls during CJK section
indexing.

**📱 X3 Comes of Age**
The X3 graduates from initial bring-up to a proper target. Grayscale
antialiasing is sharper, EPUB images render correctly, OTA updates work,
and sleep screen dimensions are dialed in. The headline addition:
gyroscope-based tilt page turning via the QMI8658 IMU — tilt the device
to turn pages hands-free. SD-card firmware update support and X3
bootloader compatibility mean users can update without a USB connection.

**⌨️ Redesigned On-Screen Keyboard**
The keyboard has been completely redesigned with improved layout, better
key feedback, and a fix for the space key barely moving the cursor. Text
entry across WiFi setup, OPDS search, and KOSync login is noticeably
smoother.

**👁️ Focus Reading**
A new reading mode bolds the initial characters of each word (similar to
Bionic Reading) to create artificial fixation points, helping improve
reading speed and focus. The bolding ratio is 45%, with a minimum of 1
character and a maximum of 9, applied dynamically during indexing.

**📚 OPDS Overhaul**
OPDS gains in-catalog search with next/prev page navigation, support for
multiple servers, correct handling of relative paths and query
parameters (fixing CopyParty compatibility), and KOReader-compatible
download filenames.

**🔤 Text Rendering Refinements**
Combining marks (diacritics) now use font metrics for positioning
instead of heuristics, proportional numeral spacing is supported, and
differential rounding eliminates uneven inter-glyph gaps. Hyphenation
now recognizes ISO 639-2 language codes, nested block-level CSS styles
are tracked correctly, and horizontal CSS insets are capped at 2em to
prevent runaway margins. Bookerly has been replaced with Noto Serif for
licensing reasons.

**🎨 New Theme: RoundedRaff**
A new rounded theme joins the theme picker, with fixes for sleep cover
crop grid artifacts.

**🔋 Battery & Power**
Battery percentage smoothing on the X4 eliminates jittery readings. A
short press on the power button can be set to trigger a manual screen
refresh — handy for clearing ghosting.

**📶 WiFi & Networking**
WiFi connections now self-heal from transient drops without manual
intervention, and a dBm signal strength indicator appears during web
server sessions. WiFi networks can be edited directly from the web UI.

**🔄 KOSync**
Reading position sync is significantly more accurate. The old
character-offset approach frequently landed on the wrong paragraph after
syncing between devices — the new xpath-based mapping syncs at the
paragraph level, matching KOReader's own behavior. A separate fix
switches the HTTP layer to `esp_http_client`, and the reader now
releases ~65KB of EPUB heap before the TLS handshake — together these
eliminate the out-of-memory crashes that plagued KOSync on large books.

**🛡️ Stability**
Two memory leaks patched, a wild pointer crash in JPEGDEC MCU_SKIP
handling fixed, boot loops with large XTC files eliminated, legacy XTC
headers supported, the OTA updater now streams GitHub release JSON
instead of buffering it in RAM, and a JPEG downscaler y-axis scale
factor bug is corrected.

**🌐 Languages**
Slovenian is new. Russian, Ukrainian, Swedish, Italian, and Spanish
translations received significant updates.

---

Also in this release: **SD-card firmware updates without USB**, **file
extensions in the file browser**, **full path bar navigation**,
**end-of-book navigation improvements**, **XTC status bar**, **smarter
"Cover + Custom" sleep screens**, **set sleep cover from the BMP
viewer**, **orientation-aware popups**, **page turn buttons that follow
orientation**, **long-press delete for directories**, **context-aware
screenshot filenames with book title**, **crash reason displayed on
boot**, **empty line rendering in the TXT reader**, **wallpaper recency
buffer to prevent clustering**, **font family deletion from the
device**, **next/prev labels in the BMP viewer**, **non-breaking space
justification fix**, **README guidance for USB-locked third-party Xteink
units**, and a long tail of web UI polish, i18n memory optimizations,
and code quality improvements.

## What's Changed

### Features

* feat: add SD card font support with on-device download and web
management by @adriancaruana, @znelson, @itsthisjustin, @jpirnay, and
@mcrosson
* feat: Initial support for the x3 by @itsthisjustin in
#875
* feat: X3 grayscale antialiasing improvements by @juicecultus in
#1607
* feat: X3 gyroscope-based tilt page turning via QMI8658 IMU by
@juicecultus in
#1636
* feat(update): SD-card firmware update + X3 bootloader compatibility by
@eunchurn in
#1786
* feat: self-heal from transient WiFi loss, add dBm indicator during
WebServerActivity by @jeremydk in
#1780
* feat: edit wifi networks in webui by @osteotek in
#1743
* feat: add OPDS search support & next/prev page navigation by @rxmmah
in #1462
* feat: Support for multiple OPDS servers by @osteotek in
#1209
* feat: Adjust Navigation at End of Book by @nscheung in
#1425
* feat: Display file extensions in File Browser by @CaptainFrito in
#1019
* feat: show full path bar in file browser by @zgredex in
#1411
* feat: enable manual screen refresh on power button short press by
@bdeshi in
#1626
* feat: Rework "Cover + Custom" sleep screens to show covers only when
currently reading by @iandchasse in
#1256
* feat: Set sleep cover from BMP viewer by @el in
#1104
* feat: show crash reason on boot by @ngxson in
#1453
* feat: Support for proportional numeral spacing by @znelson in
#1414
* feat: add orientation-aware popups for reader activities by @mrtnvgr
in #1428
* feat: smooth battery percentage for x4 by @jonvex in
#1635
* feat: context-aware screenshot filenames with book title by
@jonstieglitz in
#1589
* feat(theme): add roundedraff theme and fix sleep cover crop grid
artifacts by @bunsoootchi in
#918
* feat: Page turn button orientation change by @mchuck in
#1069
* feat: Status bar for XTC files by @leecming82 in
#1849
* feat: enhance long press action to delete both files and directories
by @WuTofu in
#1803
* feat: Added Slovenian translation by @thehijacker in
#1551
* feat: focus reading by @vjapolitzer in
#1670
* feat: add next / prev labels to bmp viewer by @Telemaniaka in
#1852
* feat: add font family deletion functionality by @WuTofu in
#1919
* feat: separate into "Download All" and "Update All" in font manager by
@WuTofu in
#1955
* feat: verify CRC32 checksum for font files by @WuTofu in
#1904
* feat: increase default weight of Bitter font for improved rendering by
@uxjulia in
#1922
* feat: allow unnamed intervals by @steka in
#1903

### Fixes

* fix: epub images not rendering correctly on x3 by @itsthisjustin in
#1572
* fix: OTA update on x3 and progress bar on x4 and x3 by @itsthisjustin
in #1805
* fix: boot looping when opening large XTC files by @itsthisjustin in
#1648
* fix: Wild pointer crash in JPEGDEC MCU_SKIP handling by @itsthisjustin
in #1627
* fix: two small memory leaks by @Uri-Tauber in
#1628
* fix: use esp_http_client for KOSync to prevent TLS OOM on ESP32-C3 by
@trilwu in
#1381
* fix: Read GH release JSON as stream in OTA updater by @znelson in
#1810
* fix: support legacy XTC file headers where pageTableOffset=48 by
@uxjulia in
#1816
* fix: Use font metrics for combining mark positioning by @znelson in
#1310
* fix: Use differential rounding for consistent inter-glyph spacing by
@znelson in
#1413
* fix: Support hyphenation for EPUBs using ISO 639-2 language codes by
@znelson in
#1461
* fix: Track block style stack for nested styles by @daveallie in
#1582
* fix: cap per-side horizontal CSS inset at 2em by @rhoopr in
#1694
* fix: increase loadable epub size by @CSCMe in
#1638
* fix: Switch to xpath map for paragraph level syncing in KOSync by
@itsthisjustin in
#1686
* fix: free Epub RAM and simplify KOSync navigation via ActivityManager
by @wylanswets in
#1860
* fix: improve KOSync bidirectional position matching accuracy by
@wylanswets in
#1897
* fix: Fix failing very first wifi connection attempt by @jpirnay in
#1521
* fix: avoid skipping chapter after screenshot by @Mraulio in
#1625
* fix: back navigation from BMPViewer by @Telemaniaka in
#1597
* fix: Fix ghosting on exit of BMPViewer by @jpirnay in
#1432
* fix: make footnotes consider orientation for gutters by @Telemaniaka
in #1665
* fix: footnote link text by @steka in
#1666
* fix: Erroneous navigation with long filenames in footnote links by
@CSCMe in
#1723
* fix: prevent wallpaper clustering with 16-entry recency buffer by
@zgredex in
#1606
* fix: webserver /delete API backward compatibility by @DianaNites in
#1475
* fix: relative opds paths and query param with copyparty by @philips in
#1535
* fix: use same file name as KOReader for OPDS downloads by @spfenwick
in #1286
* fix: pressing space barely moves input cursor (#1729) by @pablohc in
#1733
* fix: keyboard feedback #1644 by @pablohc in
#1697
* fix: pluralize folder/file counts correctly in file list summary by
@fain182 in
#1701
* fix: rendering bug of scrollbar in RoundedRaff theme by @Uri-Tauber in
#1814
* fix: two roundedraff bugs by @Uri-Tauber in
#1851
* fix: overlap in download font list layout by @pablohc in
#1900
* fix: remove duplicate 'Download Fonts' menu entry and improve
navigation by @zgredex in
#1893
* fix: Add common ligatures to SD font conversion ranges by @znelson
* fix: capture instantiateVariableFont return value by @jpirnay in
#1911
* fix: Roundraff theme home menu offset with no recent books by @znelson
in #1845
* fix: Missing navigation button labels in Roundedraff theme by
@Uri-Tauber in
#1905
* fix: gracefully resolve fonts missing variants by @Uri-Tauber in
#1921
* fix: distribute justifyExtra to non-breaking space tokens by
@prawnwhoyawns in
#1783
* fix: remove percent rendering from activities by @mcrosson in
#1901
* fix: Restore performance in fontconvert_sdcard.py by @znelson in
#1924
* fix: Prepare SD card font caches from txt reader by @znelson in
#1973
* fix: make script help paths lightweight by @sabraman in
#1937
* fix: Replaced Bookerly with Noto Serif for licensing reasons by
@znelson in
#1736
* fix: incorrect y-axis scale factor in jpeg nearest-neighbor downscaler
by @WuTofu in
#1807
* fix: display empty lines in txt reader by @Uri-Tauber in
#1841
* fix: short-press power action triggered after screenshot combo release
by @pablohc in
#1853
* fix: correct Russian auto-turn translations by @a-ignatev in
#1566
* fix: Update Ukrainian translations for footnotes (issue 1409) by
@mirus-ua in
#1585
* fix: missing swedish translations by @steka in
#1667
* fix: Add swedish keyboard translations by @steka in
#1726
* fix: swedish translations by @steka in
#1762
* fix: swedish translation by @steka in
#1829
* fix: swedish translation by @steka in
#1888
* fix: Polish translation by @th0m4sek in
#1909
* fix: Ukrainian-translation by @KymAndriy in
#1946
* fix: Ukrainian translation by @KymAndriy in
#1939
* fix: python requirements files by @steka in
#1768
* fix: missing requirement by @steka in
#1896
* fix: Use LOG_ macros in loc functions by @znelson in
#1794

### Internal

* refactor: redesign on-screen keyboard by @pablohc in
#1644
* refactor: replace picojpeg with JPEGDEC for cover art conversion by
@jpirnay in
#1517
* refactor: Refactor drawArc / fillArc for faster execution by @jpirnay
in #1540
* perf: replace i18n pointer tables with offset tables, strip unused
strings by @jpirnay in
#1408
* refactor: Store only unique localization strings in offset buffers by
@znelson in
#1802
* refactor: Move language setting into JSON settings by @znelson in
#1796
* refactor: Use C++20 'requires' in ActivityResult constructor by
@znelson in
#1420
* refactor: Use default member initializers for JpegContext and
PngContext by @znelson in
#1435
* refactor: logPrintf and predefined log level strings by @CSCMe in
#1546
* refactor: RAII scoped open/close for ZipFile by @znelson in
#1433
* refactor: Deduplicated BMP header writing in Xtc by @znelson in
#1439
* refactor: Added shared XML parser teardown helper by @znelson in
#1438
* refactor: Removed redundant FsFile close() calls by @znelson in
#1434
* refactor: Deduplicate battery drawing code and fix Lyra charging
indicator by @znelson in
#1437
* refactor: Deduplicate Roundraff battery drawing by @znelson in
#1847
* refactor: Simplify sort in GfxRenderer::fillPolygon by @znelson in
#1817
* refactor: Avoid vector for page turn rates list by @znelson in
#1818
* refactor: Use std::size instead of sizeof/sizeof by @znelson in
#1819
* refactor: Use fixed-size integers for BookMetadataCache data by
@znelson in
#1844
* refactor: Simplify isReaderActivity bookkeeping by @znelson in
#1838
* refactor: Simplify XtcReaderActivity with detectPageTurn by @znelson
in #1837
* refactor: change ukrainian translation to adaptation and add missing
lines by @KymAndriy in
#1828
* chore: drop JPEGDEC patch in favour of upstream fix by @martinbrook in
#1465
* chore: clang-format.fix.ps1 script: Add .venv to list of path
exclusions by @jpirnay in
#1515
* chore: Updating sleep screen dimensions for X3 by @jensechu in
#1688
* chore: Clarify X3 RTC in SCOPE.md by @znelson in
#1687
* chore: Improved Italian translations by @znelson in
#1685
* chore: change ukrainian translation to adaptation by @KymAndriy in
#1684
* chore: Update spanish.yaml by @mvidelatraduc in
#1717
* chore: One Italian translation tweak by @znelson in
#1718
* chore: git pre-commit hook for format fix by @osteotek in
#1730
* chore: Update SDK to fork in CrossPoint org by @znelson in
#1836
* chore: Added RAM to firmware_size_history.py script by @znelson in
#1830
* chore: Updated docs to reflect DESTRUCTOR_CLOSES_FILE=1 by @znelson in
#1878
* feat: cap compressed group size at 64 KB by @jpirnay in
#1913
* fix: build-script bug fixes for fontconvert{,_sdcard}.py by @jpirnay
in #1910
* feat: include short SHA in CROSSPOINT_VERSION by @osteotek in
#1728
* feat: show long branch names by @steka in
#1727
* feat: enable pio build cache by @Uri-Tauber in
#1769
* style: put page name first in browser titles by @fain182 in
#1703
* style: unify page headers across web UI by @fain182 in
#1702
* style: move file type badges into Type column by @fain182 in
#1793
* style: align action buttons vertically with page title by @fain182 in
#1795
* docs: Update README with firmware flashing instructions by @ryneches
in #1654
* docs: fix typos by @kianmeng in
#1705
* docs: update README.md to reflect the current state of crosspoint by
@Uri-Tauber in
#1812
* docs: Add documentation for USB-locked Xteink devices by
@itsthisjustin in
#1990
* docs: expand first use of OPDS acronym and provide a wikipedia link by
@sizezero in
#1824
* docs: fix KOReader sync guide link by @sabraman in
#1930
* docs: fix hyphenation updater script name by @sabraman in
#1931
* fix: sd font download urls in docs by @mcrosson in
#1945
* fix: sd font folder paths in documentation by @mcrosson in
#1944
* chore: Add verbose mode to build-sd-fonts.py by @znelson in
#1923

## New Contributors
* @a-ignatev made their first contribution in
#1566
* @CSCMe made their first contribution in
#1546
* @thehijacker made their first contribution in
#1551
* @Telemaniaka made their first contribution in
#1597
* @Mraulio made their first contribution in
#1625
* @rxmmah made their first contribution in
#1462
* @bdeshi made their first contribution in
#1626
* @DianaNites made their first contribution in
#1475
* @ryneches made their first contribution in
#1654
* @zgredex made their first contribution in
#1411
* @jonvex made their first contribution in
#1635
* @KymAndriy made their first contribution in
#1684
* @jensechu made their first contribution in
#1688
* @kianmeng made their first contribution in
#1705
* @philips made their first contribution in
#1535
* @fain182 made their first contribution in
#1701
* @mvidelatraduc made their first contribution in
#1717
* @bunsoootchi made their first contribution in
#918
* @rhoopr made their first contribution in
#1694
* @spfenwick made their first contribution in
#1286
* @trilwu made their first contribution in
#1381
* @jonstieglitz made their first contribution in
#1589
* @uxjulia made their first contribution in
#1816
* @mchuck made their first contribution in
#1069
* @sizezero made their first contribution in
#1824
* @leecming82 made their first contribution in
#1849
* @jeremydk made their first contribution in
#1780
* @WuTofu made their first contribution in
#1803
* @wylanswets made their first contribution in
#1860
* @sabraman made their first contribution in
#1930
* @prawnwhoyawns made their first contribution in
#1783
* @mcrosson made their first contribution as co-author on SD card font
support

**Full Changelog**:
1.2.0...release/1.3.0

---------

Co-authored-by: Justin Mitchell <justin@jmitch.com>
Co-authored-by: Chun Ming Lee <95391408+leecming82@users.noreply.github.com>
Co-authored-by: Uri Tauber <uritaube@gmail.com>
sabraman added a commit to marginalia-os/marginalia-firmware that referenced this pull request May 15, 2026
* fix: Prepare SD card font caches from txt reader (crosspoint-reader#1973)

## Summary

SD card font fixes:
- `TxtReaderActivity` needs to call `renderer.ensureSdCardFontReady` to
build the advance lookup table to support rendering with SD card fonts.
This revealed that `TxtReaderActivity` was inconsistently performing
layout with `getTextWidth`, when the renderer actually uses
`getTextAdvanceX`, which can lead to minor inconsistencies in alignment.
- Avoid allocating one big `allText` string in
`ParsedText::layoutAndExtractLines`. Instead, pass the vector of word
strings directly to `SdCardFont::buildAdvanceTable`, where the algorithm
just needs to iterate codepoints anyway.

---

### 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? _**PARTIALLY**_

---------

Co-authored-by: Justin Mitchell <justin@jmitch.com>

* fix: Add documentation for USB-locked Xteink devices (crosspoint-reader#1990)

Document the Xteink Unlocker tool requirement for third-party purchased
xteink units that ship with USB flashing locked. Include warnings about
bricking risks when flashing unsupported firmwares (e.g. Papyrix) on
locked devices, as they may permanently lock the device with no recovery
path.

* fix: update README.md to reflect the current state of crosspoint (crosspoint-reader#1812)

## Summary

As noted in
[crosspoint-reader#1680](crosspoint-reader#1680 (comment)),
the README hasn't been updated in a while and has fallen behind the
actual firmware. This PR brings it up to date.

Beyond the feature list, I added a section acknowledging community forks
worth knowing about. I also took some deliberate editorial choices
around how CrossPoint is framed — I think it has the potential to be
more than just "an alternative Xteink firmware", and the wording
reflects that.

A note on process: I wrote the bulk of the text myself, but used AI
tools to scan the codebase and catch features I might have missed, and
to clean up my English (I'm fluent but not a native speaker). If any
line reads as unnatural or AI-sounding, please flag it — I'd rather fix
it than leave it.

---

One thing outside the scope of this PR: I think the cover photo could
use a refresh, ideally replaced with a small gallery showing different
CrossPoint screens. If you have a professional camera and an Xteink
device and want to help with that, let me know.

---------

Co-authored-by: Zach Nelson <zach@zdnelson.com>

* chore: Update version to 1.3.0 (crosspoint-reader#1827)

## Summary

This release adds SD card fonts — the most-requested feature since
launch — brings the X3 to first-class status, redesigns the on-screen
keyboard, overhauls OPDS, and ships SD-card firmware updates. 144
changes from 53 contributors, 32 of whom are new to the project.

**🔠 SD Card Fonts**
Custom fonts are here. A complete font subsystem lets you install and
use fonts beyond the three built-in families. A new `.cpfont` binary
format packs multiple styles (regular, bold, italic, bold-italic) into a
single file per size, with on-demand glyph loading from the SD card. A
two-pass prewarm renderer bulk-reads glyphs per page, achieving
near-flash performance for Latin text and viable CJK rendering. Fonts
can be downloaded over WiFi directly from the device, uploaded via the
web interface, or copied manually to the SD card. The build pipeline
ships a 17-family font library (serif, sans, mono, accessibility) with
CI distribution via a dedicated crosspoint-fonts repository. As a bonus,
CJK characters no longer get spurious hyphens at line breaks, and an
advance-table cache eliminates 30+ second stalls during CJK section
indexing.

**📱 X3 Comes of Age**
The X3 graduates from initial bring-up to a proper target. Grayscale
antialiasing is sharper, EPUB images render correctly, OTA updates work,
and sleep screen dimensions are dialed in. The headline addition:
gyroscope-based tilt page turning via the QMI8658 IMU — tilt the device
to turn pages hands-free. SD-card firmware update support and X3
bootloader compatibility mean users can update without a USB connection.

**⌨️ Redesigned On-Screen Keyboard**
The keyboard has been completely redesigned with improved layout, better
key feedback, and a fix for the space key barely moving the cursor. Text
entry across WiFi setup, OPDS search, and KOSync login is noticeably
smoother.

**👁️ Focus Reading**
A new reading mode bolds the initial characters of each word (similar to
Bionic Reading) to create artificial fixation points, helping improve
reading speed and focus. The bolding ratio is 45%, with a minimum of 1
character and a maximum of 9, applied dynamically during indexing.

**📚 OPDS Overhaul**
OPDS gains in-catalog search with next/prev page navigation, support for
multiple servers, correct handling of relative paths and query
parameters (fixing CopyParty compatibility), and KOReader-compatible
download filenames.

**🔤 Text Rendering Refinements**
Combining marks (diacritics) now use font metrics for positioning
instead of heuristics, proportional numeral spacing is supported, and
differential rounding eliminates uneven inter-glyph gaps. Hyphenation
now recognizes ISO 639-2 language codes, nested block-level CSS styles
are tracked correctly, and horizontal CSS insets are capped at 2em to
prevent runaway margins. Bookerly has been replaced with Noto Serif for
licensing reasons.

**🎨 New Theme: RoundedRaff**
A new rounded theme joins the theme picker, with fixes for sleep cover
crop grid artifacts.

**🔋 Battery & Power**
Battery percentage smoothing on the X4 eliminates jittery readings. A
short press on the power button can be set to trigger a manual screen
refresh — handy for clearing ghosting.

**📶 WiFi & Networking**
WiFi connections now self-heal from transient drops without manual
intervention, and a dBm signal strength indicator appears during web
server sessions. WiFi networks can be edited directly from the web UI.

**🔄 KOSync**
Reading position sync is significantly more accurate. The old
character-offset approach frequently landed on the wrong paragraph after
syncing between devices — the new xpath-based mapping syncs at the
paragraph level, matching KOReader's own behavior. A separate fix
switches the HTTP layer to `esp_http_client`, and the reader now
releases ~65KB of EPUB heap before the TLS handshake — together these
eliminate the out-of-memory crashes that plagued KOSync on large books.

**🛡️ Stability**
Two memory leaks patched, a wild pointer crash in JPEGDEC MCU_SKIP
handling fixed, boot loops with large XTC files eliminated, legacy XTC
headers supported, the OTA updater now streams GitHub release JSON
instead of buffering it in RAM, and a JPEG downscaler y-axis scale
factor bug is corrected.

**🌐 Languages**
Slovenian is new. Russian, Ukrainian, Swedish, Italian, and Spanish
translations received significant updates.

---

Also in this release: **SD-card firmware updates without USB**, **file
extensions in the file browser**, **full path bar navigation**,
**end-of-book navigation improvements**, **XTC status bar**, **smarter
"Cover + Custom" sleep screens**, **set sleep cover from the BMP
viewer**, **orientation-aware popups**, **page turn buttons that follow
orientation**, **long-press delete for directories**, **context-aware
screenshot filenames with book title**, **crash reason displayed on
boot**, **empty line rendering in the TXT reader**, **wallpaper recency
buffer to prevent clustering**, **font family deletion from the
device**, **next/prev labels in the BMP viewer**, **non-breaking space
justification fix**, **README guidance for USB-locked third-party Xteink
units**, and a long tail of web UI polish, i18n memory optimizations,
and code quality improvements.

## What's Changed

### Features

* feat: add SD card font support with on-device download and web
management by @adriancaruana, @znelson, @itsthisjustin, @jpirnay, and
@mcrosson
* feat: Initial support for the x3 by @itsthisjustin in
crosspoint-reader#875
* feat: X3 grayscale antialiasing improvements by @juicecultus in
crosspoint-reader#1607
* feat: X3 gyroscope-based tilt page turning via QMI8658 IMU by
@juicecultus in
crosspoint-reader#1636
* feat(update): SD-card firmware update + X3 bootloader compatibility by
@eunchurn in
crosspoint-reader#1786
* feat: self-heal from transient WiFi loss, add dBm indicator during
WebServerActivity by @jeremydk in
crosspoint-reader#1780
* feat: edit wifi networks in webui by @osteotek in
crosspoint-reader#1743
* feat: add OPDS search support & next/prev page navigation by @rxmmah
in crosspoint-reader#1462
* feat: Support for multiple OPDS servers by @osteotek in
crosspoint-reader#1209
* feat: Adjust Navigation at End of Book by @nscheung in
crosspoint-reader#1425
* feat: Display file extensions in File Browser by @CaptainFrito in
crosspoint-reader#1019
* feat: show full path bar in file browser by @zgredex in
crosspoint-reader#1411
* feat: enable manual screen refresh on power button short press by
@bdeshi in
crosspoint-reader#1626
* feat: Rework "Cover + Custom" sleep screens to show covers only when
currently reading by @iandchasse in
crosspoint-reader#1256
* feat: Set sleep cover from BMP viewer by @el in
crosspoint-reader#1104
* feat: show crash reason on boot by @ngxson in
crosspoint-reader#1453
* feat: Support for proportional numeral spacing by @znelson in
crosspoint-reader#1414
* feat: add orientation-aware popups for reader activities by @mrtnvgr
in crosspoint-reader#1428
* feat: smooth battery percentage for x4 by @jonvex in
crosspoint-reader#1635
* feat: context-aware screenshot filenames with book title by
@jonstieglitz in
crosspoint-reader#1589
* feat(theme): add roundedraff theme and fix sleep cover crop grid
artifacts by @bunsoootchi in
crosspoint-reader#918
* feat: Page turn button orientation change by @mchuck in
crosspoint-reader#1069
* feat: Status bar for XTC files by @leecming82 in
crosspoint-reader#1849
* feat: enhance long press action to delete both files and directories
by @WuTofu in
crosspoint-reader#1803
* feat: Added Slovenian translation by @thehijacker in
crosspoint-reader#1551
* feat: focus reading by @vjapolitzer in
crosspoint-reader#1670
* feat: add next / prev labels to bmp viewer by @Telemaniaka in
crosspoint-reader#1852
* feat: add font family deletion functionality by @WuTofu in
crosspoint-reader#1919
* feat: separate into "Download All" and "Update All" in font manager by
@WuTofu in
crosspoint-reader#1955
* feat: verify CRC32 checksum for font files by @WuTofu in
crosspoint-reader#1904
* feat: increase default weight of Bitter font for improved rendering by
@uxjulia in
crosspoint-reader#1922
* feat: allow unnamed intervals by @steka in
crosspoint-reader#1903

### Fixes

* fix: epub images not rendering correctly on x3 by @itsthisjustin in
crosspoint-reader#1572
* fix: OTA update on x3 and progress bar on x4 and x3 by @itsthisjustin
in crosspoint-reader#1805
* fix: boot looping when opening large XTC files by @itsthisjustin in
crosspoint-reader#1648
* fix: Wild pointer crash in JPEGDEC MCU_SKIP handling by @itsthisjustin
in crosspoint-reader#1627
* fix: two small memory leaks by @Uri-Tauber in
crosspoint-reader#1628
* fix: use esp_http_client for KOSync to prevent TLS OOM on ESP32-C3 by
@trilwu in
crosspoint-reader#1381
* fix: Read GH release JSON as stream in OTA updater by @znelson in
crosspoint-reader#1810
* fix: support legacy XTC file headers where pageTableOffset=48 by
@uxjulia in
crosspoint-reader#1816
* fix: Use font metrics for combining mark positioning by @znelson in
crosspoint-reader#1310
* fix: Use differential rounding for consistent inter-glyph spacing by
@znelson in
crosspoint-reader#1413
* fix: Support hyphenation for EPUBs using ISO 639-2 language codes by
@znelson in
crosspoint-reader#1461
* fix: Track block style stack for nested styles by @daveallie in
crosspoint-reader#1582
* fix: cap per-side horizontal CSS inset at 2em by @rhoopr in
crosspoint-reader#1694
* fix: increase loadable epub size by @CSCMe in
crosspoint-reader#1638
* fix: Switch to xpath map for paragraph level syncing in KOSync by
@itsthisjustin in
crosspoint-reader#1686
* fix: free Epub RAM and simplify KOSync navigation via ActivityManager
by @wylanswets in
crosspoint-reader#1860
* fix: improve KOSync bidirectional position matching accuracy by
@wylanswets in
crosspoint-reader#1897
* fix: Fix failing very first wifi connection attempt by @jpirnay in
crosspoint-reader#1521
* fix: avoid skipping chapter after screenshot by @Mraulio in
crosspoint-reader#1625
* fix: back navigation from BMPViewer by @Telemaniaka in
crosspoint-reader#1597
* fix: Fix ghosting on exit of BMPViewer by @jpirnay in
crosspoint-reader#1432
* fix: make footnotes consider orientation for gutters by @Telemaniaka
in crosspoint-reader#1665
* fix: footnote link text by @steka in
crosspoint-reader#1666
* fix: Erroneous navigation with long filenames in footnote links by
@CSCMe in
crosspoint-reader#1723
* fix: prevent wallpaper clustering with 16-entry recency buffer by
@zgredex in
crosspoint-reader#1606
* fix: webserver /delete API backward compatibility by @DianaNites in
crosspoint-reader#1475
* fix: relative opds paths and query param with copyparty by @philips in
crosspoint-reader#1535
* fix: use same file name as KOReader for OPDS downloads by @spfenwick
in crosspoint-reader#1286
* fix: pressing space barely moves input cursor (crosspoint-reader#1729) by @pablohc in
crosspoint-reader#1733
* fix: keyboard feedback crosspoint-reader#1644 by @pablohc in
crosspoint-reader#1697
* fix: pluralize folder/file counts correctly in file list summary by
@fain182 in
crosspoint-reader#1701
* fix: rendering bug of scrollbar in RoundedRaff theme by @Uri-Tauber in
crosspoint-reader#1814
* fix: two roundedraff bugs by @Uri-Tauber in
crosspoint-reader#1851
* fix: overlap in download font list layout by @pablohc in
crosspoint-reader#1900
* fix: remove duplicate 'Download Fonts' menu entry and improve
navigation by @zgredex in
crosspoint-reader#1893
* fix: Add common ligatures to SD font conversion ranges by @znelson
* fix: capture instantiateVariableFont return value by @jpirnay in
crosspoint-reader#1911
* fix: Roundraff theme home menu offset with no recent books by @znelson
in crosspoint-reader#1845
* fix: Missing navigation button labels in Roundedraff theme by
@Uri-Tauber in
crosspoint-reader#1905
* fix: gracefully resolve fonts missing variants by @Uri-Tauber in
crosspoint-reader#1921
* fix: distribute justifyExtra to non-breaking space tokens by
@prawnwhoyawns in
crosspoint-reader#1783
* fix: remove percent rendering from activities by @mcrosson in
crosspoint-reader#1901
* fix: Restore performance in fontconvert_sdcard.py by @znelson in
crosspoint-reader#1924
* fix: Prepare SD card font caches from txt reader by @znelson in
crosspoint-reader#1973
* fix: make script help paths lightweight by @sabraman in
crosspoint-reader#1937
* fix: Replaced Bookerly with Noto Serif for licensing reasons by
@znelson in
crosspoint-reader#1736
* fix: incorrect y-axis scale factor in jpeg nearest-neighbor downscaler
by @WuTofu in
crosspoint-reader#1807
* fix: display empty lines in txt reader by @Uri-Tauber in
crosspoint-reader#1841
* fix: short-press power action triggered after screenshot combo release
by @pablohc in
crosspoint-reader#1853
* fix: correct Russian auto-turn translations by @a-ignatev in
crosspoint-reader#1566
* fix: Update Ukrainian translations for footnotes (issue 1409) by
@mirus-ua in
crosspoint-reader#1585
* fix: missing swedish translations by @steka in
crosspoint-reader#1667
* fix: Add swedish keyboard translations by @steka in
crosspoint-reader#1726
* fix: swedish translations by @steka in
crosspoint-reader#1762
* fix: swedish translation by @steka in
crosspoint-reader#1829
* fix: swedish translation by @steka in
crosspoint-reader#1888
* fix: Polish translation by @th0m4sek in
crosspoint-reader#1909
* fix: Ukrainian-translation by @KymAndriy in
crosspoint-reader#1946
* fix: Ukrainian translation by @KymAndriy in
crosspoint-reader#1939
* fix: python requirements files by @steka in
crosspoint-reader#1768
* fix: missing requirement by @steka in
crosspoint-reader#1896
* fix: Use LOG_ macros in loc functions by @znelson in
crosspoint-reader#1794

### Internal

* refactor: redesign on-screen keyboard by @pablohc in
crosspoint-reader#1644
* refactor: replace picojpeg with JPEGDEC for cover art conversion by
@jpirnay in
crosspoint-reader#1517
* refactor: Refactor drawArc / fillArc for faster execution by @jpirnay
in crosspoint-reader#1540
* perf: replace i18n pointer tables with offset tables, strip unused
strings by @jpirnay in
crosspoint-reader#1408
* refactor: Store only unique localization strings in offset buffers by
@znelson in
crosspoint-reader#1802
* refactor: Move language setting into JSON settings by @znelson in
crosspoint-reader#1796
* refactor: Use C++20 'requires' in ActivityResult constructor by
@znelson in
crosspoint-reader#1420
* refactor: Use default member initializers for JpegContext and
PngContext by @znelson in
crosspoint-reader#1435
* refactor: logPrintf and predefined log level strings by @CSCMe in
crosspoint-reader#1546
* refactor: RAII scoped open/close for ZipFile by @znelson in
crosspoint-reader#1433
* refactor: Deduplicated BMP header writing in Xtc by @znelson in
crosspoint-reader#1439
* refactor: Added shared XML parser teardown helper by @znelson in
crosspoint-reader#1438
* refactor: Removed redundant FsFile close() calls by @znelson in
crosspoint-reader#1434
* refactor: Deduplicate battery drawing code and fix Lyra charging
indicator by @znelson in
crosspoint-reader#1437
* refactor: Deduplicate Roundraff battery drawing by @znelson in
crosspoint-reader#1847
* refactor: Simplify sort in GfxRenderer::fillPolygon by @znelson in
crosspoint-reader#1817
* refactor: Avoid vector for page turn rates list by @znelson in
crosspoint-reader#1818
* refactor: Use std::size instead of sizeof/sizeof by @znelson in
crosspoint-reader#1819
* refactor: Use fixed-size integers for BookMetadataCache data by
@znelson in
crosspoint-reader#1844
* refactor: Simplify isReaderActivity bookkeeping by @znelson in
crosspoint-reader#1838
* refactor: Simplify XtcReaderActivity with detectPageTurn by @znelson
in crosspoint-reader#1837
* refactor: change ukrainian translation to adaptation and add missing
lines by @KymAndriy in
crosspoint-reader#1828
* chore: drop JPEGDEC patch in favour of upstream fix by @martinbrook in
crosspoint-reader#1465
* chore: clang-format.fix.ps1 script: Add .venv to list of path
exclusions by @jpirnay in
crosspoint-reader#1515
* chore: Updating sleep screen dimensions for X3 by @jensechu in
crosspoint-reader#1688
* chore: Clarify X3 RTC in SCOPE.md by @znelson in
crosspoint-reader#1687
* chore: Improved Italian translations by @znelson in
crosspoint-reader#1685
* chore: change ukrainian translation to adaptation by @KymAndriy in
crosspoint-reader#1684
* chore: Update spanish.yaml by @mvidelatraduc in
crosspoint-reader#1717
* chore: One Italian translation tweak by @znelson in
crosspoint-reader#1718
* chore: git pre-commit hook for format fix by @osteotek in
crosspoint-reader#1730
* chore: Update SDK to fork in CrossPoint org by @znelson in
crosspoint-reader#1836
* chore: Added RAM to firmware_size_history.py script by @znelson in
crosspoint-reader#1830
* chore: Updated docs to reflect DESTRUCTOR_CLOSES_FILE=1 by @znelson in
crosspoint-reader#1878
* feat: cap compressed group size at 64 KB by @jpirnay in
crosspoint-reader#1913
* fix: build-script bug fixes for fontconvert{,_sdcard}.py by @jpirnay
in crosspoint-reader#1910
* feat: include short SHA in CROSSPOINT_VERSION by @osteotek in
crosspoint-reader#1728
* feat: show long branch names by @steka in
crosspoint-reader#1727
* feat: enable pio build cache by @Uri-Tauber in
crosspoint-reader#1769
* style: put page name first in browser titles by @fain182 in
crosspoint-reader#1703
* style: unify page headers across web UI by @fain182 in
crosspoint-reader#1702
* style: move file type badges into Type column by @fain182 in
crosspoint-reader#1793
* style: align action buttons vertically with page title by @fain182 in
crosspoint-reader#1795
* docs: Update README with firmware flashing instructions by @ryneches
in crosspoint-reader#1654
* docs: fix typos by @kianmeng in
crosspoint-reader#1705
* docs: update README.md to reflect the current state of crosspoint by
@Uri-Tauber in
crosspoint-reader#1812
* docs: Add documentation for USB-locked Xteink devices by
@itsthisjustin in
crosspoint-reader#1990
* docs: expand first use of OPDS acronym and provide a wikipedia link by
@sizezero in
crosspoint-reader#1824
* docs: fix KOReader sync guide link by @sabraman in
crosspoint-reader#1930
* docs: fix hyphenation updater script name by @sabraman in
crosspoint-reader#1931
* fix: sd font download urls in docs by @mcrosson in
crosspoint-reader#1945
* fix: sd font folder paths in documentation by @mcrosson in
crosspoint-reader#1944
* chore: Add verbose mode to build-sd-fonts.py by @znelson in
crosspoint-reader#1923

## New Contributors
* @a-ignatev made their first contribution in
crosspoint-reader#1566
* @CSCMe made their first contribution in
crosspoint-reader#1546
* @thehijacker made their first contribution in
crosspoint-reader#1551
* @Telemaniaka made their first contribution in
crosspoint-reader#1597
* @Mraulio made their first contribution in
crosspoint-reader#1625
* @rxmmah made their first contribution in
crosspoint-reader#1462
* @bdeshi made their first contribution in
crosspoint-reader#1626
* @DianaNites made their first contribution in
crosspoint-reader#1475
* @ryneches made their first contribution in
crosspoint-reader#1654
* @zgredex made their first contribution in
crosspoint-reader#1411
* @jonvex made their first contribution in
crosspoint-reader#1635
* @KymAndriy made their first contribution in
crosspoint-reader#1684
* @jensechu made their first contribution in
crosspoint-reader#1688
* @kianmeng made their first contribution in
crosspoint-reader#1705
* @philips made their first contribution in
crosspoint-reader#1535
* @fain182 made their first contribution in
crosspoint-reader#1701
* @mvidelatraduc made their first contribution in
crosspoint-reader#1717
* @bunsoootchi made their first contribution in
crosspoint-reader#918
* @rhoopr made their first contribution in
crosspoint-reader#1694
* @spfenwick made their first contribution in
crosspoint-reader#1286
* @trilwu made their first contribution in
crosspoint-reader#1381
* @jonstieglitz made their first contribution in
crosspoint-reader#1589
* @uxjulia made their first contribution in
crosspoint-reader#1816
* @mchuck made their first contribution in
crosspoint-reader#1069
* @sizezero made their first contribution in
crosspoint-reader#1824
* @leecming82 made their first contribution in
crosspoint-reader#1849
* @jeremydk made their first contribution in
crosspoint-reader#1780
* @WuTofu made their first contribution in
crosspoint-reader#1803
* @wylanswets made their first contribution in
crosspoint-reader#1860
* @sabraman made their first contribution in
crosspoint-reader#1930
* @prawnwhoyawns made their first contribution in
crosspoint-reader#1783
* @mcrosson made their first contribution as co-author on SD card font
support

**Full Changelog**:
crosspoint-reader/crosspoint-reader@1.2.0...release/1.3.0

---------

Co-authored-by: Justin Mitchell <justin@jmitch.com>
Co-authored-by: Chun Ming Lee <95391408+leecming82@users.noreply.github.com>
Co-authored-by: Uri Tauber <uritaube@gmail.com>

* Add package settings UI

---------

Co-authored-by: Zach Nelson <zach@zdnelson.com>
Co-authored-by: Justin Mitchell <justin@jmitch.com>
Co-authored-by: Uri Tauber <uritaube@gmail.com>
Co-authored-by: Chun Ming Lee <95391408+leecming82@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants