From 20fc5262c8213a5ec10d1dd4111aba50f2c080dc Mon Sep 17 00:00:00 2001 From: Arthur Tazhitdinov Date: Mon, 2 Mar 2026 13:00:53 +0300 Subject: [PATCH 01/11] refactor: rename MyLibrary to FileBrowser (#1260) ## Summary * Renames MyLibrary component to FileBrowser, as it better reflects what it is, in my opinion ## Additional Context * Frees the Library name for possible future library component that can cache metadata, provide other ways of browsing than filesystem structure, etc --- ### AI Usage Did you use AI tools to help write this code? _**< YES >**_ --- src/activities/ActivityManager.cpp | 6 ++--- src/activities/ActivityManager.h | 2 +- ...ryActivity.cpp => FileBrowserActivity.cpp} | 26 +++++++++---------- ...ibraryActivity.h => FileBrowserActivity.h} | 6 ++--- src/activities/home/HomeActivity.cpp | 12 ++++----- src/activities/home/HomeActivity.h | 4 +-- src/activities/reader/EpubReaderActivity.cpp | 2 +- src/activities/reader/ReaderActivity.cpp | 2 +- src/activities/reader/ReaderActivity.h | 2 +- src/activities/reader/TxtReaderActivity.cpp | 2 +- src/activities/reader/XtcReaderActivity.cpp | 2 +- 11 files changed, 33 insertions(+), 33 deletions(-) rename src/activities/home/{MyLibraryActivity.cpp => FileBrowserActivity.cpp} (91%) rename src/activities/home/{MyLibraryActivity.h => FileBrowserActivity.h} (67%) diff --git a/src/activities/ActivityManager.cpp b/src/activities/ActivityManager.cpp index cb8336fa..3cf115b1 100644 --- a/src/activities/ActivityManager.cpp +++ b/src/activities/ActivityManager.cpp @@ -5,8 +5,8 @@ #include "boot_sleep/BootActivity.h" #include "boot_sleep/SleepActivity.h" #include "browser/OpdsBookBrowserActivity.h" +#include "home/FileBrowserActivity.h" #include "home/HomeActivity.h" -#include "home/MyLibraryActivity.h" #include "home/RecentBooksActivity.h" #include "network/CrossPointWebServerActivity.h" #include "reader/ReaderActivity.h" @@ -169,8 +169,8 @@ void ActivityManager::goToFileTransfer() { void ActivityManager::goToSettings() { replaceActivity(std::make_unique(renderer, mappedInput)); } -void ActivityManager::goToMyLibrary(std::string path) { - replaceActivity(std::make_unique(renderer, mappedInput, std::move(path))); +void ActivityManager::goToFileBrowser(std::string path) { + replaceActivity(std::make_unique(renderer, mappedInput, std::move(path))); } void ActivityManager::goToRecentBooks() { diff --git a/src/activities/ActivityManager.h b/src/activities/ActivityManager.h index 047276f0..bc24c42e 100644 --- a/src/activities/ActivityManager.h +++ b/src/activities/ActivityManager.h @@ -79,7 +79,7 @@ class ActivityManager { // goTo... functions are convenient wrapper for replaceActivity() void goToFileTransfer(); void goToSettings(); - void goToMyLibrary(std::string path = {}); + void goToFileBrowser(std::string path = {}); void goToRecentBooks(); void goToBrowser(); void goToReader(std::string path); diff --git a/src/activities/home/MyLibraryActivity.cpp b/src/activities/home/FileBrowserActivity.cpp similarity index 91% rename from src/activities/home/MyLibraryActivity.cpp rename to src/activities/home/FileBrowserActivity.cpp index eb919c6d..1e394d14 100644 --- a/src/activities/home/MyLibraryActivity.cpp +++ b/src/activities/home/FileBrowserActivity.cpp @@ -1,4 +1,4 @@ -#include "MyLibraryActivity.h" +#include "FileBrowserActivity.h" #include #include @@ -69,7 +69,7 @@ void sortFileList(std::vector& strs) { }); } -void MyLibraryActivity::loadFiles() { +void FileBrowserActivity::loadFiles() { files.clear(); auto root = Storage.open(basepath.c_str()); @@ -104,7 +104,7 @@ void MyLibraryActivity::loadFiles() { sortFileList(files); } -void MyLibraryActivity::onEnter() { +void FileBrowserActivity::onEnter() { Activity::onEnter(); loadFiles(); @@ -113,20 +113,20 @@ void MyLibraryActivity::onEnter() { requestUpdate(); } -void MyLibraryActivity::onExit() { +void FileBrowserActivity::onExit() { Activity::onExit(); files.clear(); } -void MyLibraryActivity::clearFileMetadata(const std::string& fullPath) { +void FileBrowserActivity::clearFileMetadata(const std::string& fullPath) { // Only clear cache for .epub files if (StringUtils::checkFileExtension(fullPath, ".epub")) { Epub(fullPath, "/.crosspoint").clearCache(); - LOG_DBG("MyLibrary", "Cleared metadata cache for: %s", fullPath.c_str()); + LOG_DBG("FileBrowser", "Cleared metadata cache for: %s", fullPath.c_str()); } } -void MyLibraryActivity::loop() { +void FileBrowserActivity::loop() { // Long press BACK (1s+) goes to root folder if (mappedInput.isPressed(MappedInputManager::Button::Back) && mappedInput.getHeldTime() >= GO_HOME_MS && basepath != "/") { @@ -152,10 +152,10 @@ void MyLibraryActivity::loop() { auto handler = [this, fullPath](const ActivityResult& res) { if (!res.isCancelled) { - LOG_DBG("MyLibrary", "Attempting to delete: %s", fullPath.c_str()); + LOG_DBG("FileBrowser", "Attempting to delete: %s", fullPath.c_str()); clearFileMetadata(fullPath); if (Storage.remove(fullPath.c_str())) { - LOG_DBG("MyLibrary", "Deleted successfully"); + LOG_DBG("FileBrowser", "Deleted successfully"); loadFiles(); if (files.empty()) { selectorIndex = 0; @@ -166,10 +166,10 @@ void MyLibraryActivity::loop() { requestUpdate(true); } else { - LOG_ERR("MyLibrary", "Failed to delete file: %s", fullPath.c_str()); + LOG_ERR("FileBrowser", "Failed to delete file: %s", fullPath.c_str()); } } else { - LOG_DBG("MyLibrary", "Delete cancelled by user"); + LOG_DBG("FileBrowser", "Delete cancelled by user"); } }; @@ -244,7 +244,7 @@ std::string getFileName(std::string filename) { return filename.substr(0, pos); } -void MyLibraryActivity::render(RenderLock&&) { +void FileBrowserActivity::render(RenderLock&&) { renderer.clearScreen(); const auto pageWidth = renderer.getScreenWidth(); @@ -274,7 +274,7 @@ void MyLibraryActivity::render(RenderLock&&) { renderer.displayBuffer(); } -size_t MyLibraryActivity::findEntry(const std::string& name) const { +size_t FileBrowserActivity::findEntry(const std::string& name) const { for (size_t i = 0; i < files.size(); i++) if (files[i] == name) return i; return 0; diff --git a/src/activities/home/MyLibraryActivity.h b/src/activities/home/FileBrowserActivity.h similarity index 67% rename from src/activities/home/MyLibraryActivity.h rename to src/activities/home/FileBrowserActivity.h index 3e7e8841..c4f359dd 100644 --- a/src/activities/home/MyLibraryActivity.h +++ b/src/activities/home/FileBrowserActivity.h @@ -8,7 +8,7 @@ #include "RecentBooksStore.h" #include "util/ButtonNavigator.h" -class MyLibraryActivity final : public Activity { +class FileBrowserActivity final : public Activity { private: // Deletion void clearFileMetadata(const std::string& fullPath); @@ -26,8 +26,8 @@ class MyLibraryActivity final : public Activity { size_t findEntry(const std::string& name) const; public: - explicit MyLibraryActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, std::string initialPath = "/") - : Activity("MyLibrary", renderer, mappedInput), basepath(initialPath.empty() ? "/" : std::move(initialPath)) {} + explicit FileBrowserActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, std::string initialPath = "/") + : Activity("FileBrowser", renderer, mappedInput), basepath(initialPath.empty() ? "/" : std::move(initialPath)) {} void onEnter() override; void onExit() override; void loop() override; diff --git a/src/activities/home/HomeActivity.cpp b/src/activities/home/HomeActivity.cpp index 6ee6d74d..bfab4874 100644 --- a/src/activities/home/HomeActivity.cpp +++ b/src/activities/home/HomeActivity.cpp @@ -20,7 +20,7 @@ #include "util/StringUtils.h" int HomeActivity::getMenuItemCount() const { - int count = 4; // My Library, Recents, File transfer, Settings + int count = 4; // File Browser, Recents, File transfer, Settings if (!recentBooks.empty()) { count += recentBooks.size(); } @@ -189,7 +189,7 @@ void HomeActivity::loop() { // Calculate dynamic indices based on which options are available int idx = 0; int menuSelectedIndex = selectorIndex - static_cast(recentBooks.size()); - const int myLibraryIdx = idx++; + const int fileBrowserIdx = idx++; const int recentsIdx = idx++; const int opdsLibraryIdx = hasOpdsUrl ? idx++ : -1; const int fileTransferIdx = idx++; @@ -197,8 +197,8 @@ void HomeActivity::loop() { if (selectorIndex < recentBooks.size()) { onSelectBook(recentBooks[selectorIndex].path); - } else if (menuSelectedIndex == myLibraryIdx) { - onMyLibraryOpen(); + } else if (menuSelectedIndex == fileBrowserIdx) { + onFileBrowserOpen(); } else if (menuSelectedIndex == recentsIdx) { onRecentsOpen(); } else if (menuSelectedIndex == opdsLibraryIdx) { @@ -231,7 +231,7 @@ void HomeActivity::render(RenderLock&&) { std::vector menuIcons = {Folder, Recent, Transfer, Settings}; if (hasOpdsUrl) { - // Insert OPDS Browser after My Library + // Insert OPDS Browser after File Browser menuItems.insert(menuItems.begin() + 2, tr(STR_OPDS_BROWSER)); menuIcons.insert(menuIcons.begin() + 2, Library); } @@ -261,7 +261,7 @@ void HomeActivity::render(RenderLock&&) { void HomeActivity::onSelectBook(const std::string& path) { activityManager.goToReader(path); } -void HomeActivity::onMyLibraryOpen() { activityManager.goToMyLibrary(); } +void HomeActivity::onFileBrowserOpen() { activityManager.goToFileBrowser(); } void HomeActivity::onRecentsOpen() { activityManager.goToRecentBooks(); } diff --git a/src/activities/home/HomeActivity.h b/src/activities/home/HomeActivity.h index 6b69d516..e2b24bea 100644 --- a/src/activities/home/HomeActivity.h +++ b/src/activities/home/HomeActivity.h @@ -3,7 +3,7 @@ #include #include "../Activity.h" -#include "./MyLibraryActivity.h" +#include "./FileBrowserActivity.h" #include "util/ButtonNavigator.h" struct RecentBook; @@ -21,7 +21,7 @@ class HomeActivity final : public Activity { uint8_t* coverBuffer = nullptr; // HomeActivity's own buffer for cover image std::vector recentBooks; void onSelectBook(const std::string& path); - void onMyLibraryOpen(); + void onFileBrowserOpen(); void onRecentsOpen(); void onSettingsOpen(); void onFileTransferOpen(); diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index b99cd07e..d889df5f 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -180,7 +180,7 @@ void EpubReaderActivity::loop() { // Long press BACK (1s+) goes to file selection if (mappedInput.isPressed(MappedInputManager::Button::Back) && mappedInput.getHeldTime() >= goHomeMs) { - activityManager.goToMyLibrary(epub ? epub->getPath() : ""); + activityManager.goToFileBrowser(epub ? epub->getPath() : ""); return; } diff --git a/src/activities/reader/ReaderActivity.cpp b/src/activities/reader/ReaderActivity.cpp index 82a1e23f..a7842a9e 100644 --- a/src/activities/reader/ReaderActivity.cpp +++ b/src/activities/reader/ReaderActivity.cpp @@ -80,7 +80,7 @@ std::unique_ptr ReaderActivity::loadTxt(const std::string& path) { void ReaderActivity::goToLibrary(const std::string& fromBookPath) { // If coming from a book, start in that book's folder; otherwise start from root auto initialPath = fromBookPath.empty() ? "/" : extractFolderPath(fromBookPath); - activityManager.goToMyLibrary(std::move(initialPath)); + activityManager.goToFileBrowser(std::move(initialPath)); } void ReaderActivity::onGoToEpubReader(std::unique_ptr epub) { diff --git a/src/activities/reader/ReaderActivity.h b/src/activities/reader/ReaderActivity.h index 601a35f7..6a3756db 100644 --- a/src/activities/reader/ReaderActivity.h +++ b/src/activities/reader/ReaderActivity.h @@ -2,7 +2,7 @@ #include #include "../Activity.h" -#include "activities/home/MyLibraryActivity.h" +#include "activities/home/FileBrowserActivity.h" class Epub; class Xtc; diff --git a/src/activities/reader/TxtReaderActivity.cpp b/src/activities/reader/TxtReaderActivity.cpp index 994492ee..756b7749 100644 --- a/src/activities/reader/TxtReaderActivity.cpp +++ b/src/activities/reader/TxtReaderActivity.cpp @@ -76,7 +76,7 @@ void TxtReaderActivity::onExit() { void TxtReaderActivity::loop() { // Long press BACK (1s+) goes to file selection if (mappedInput.isPressed(MappedInputManager::Button::Back) && mappedInput.getHeldTime() >= goHomeMs) { - activityManager.goToMyLibrary(txt ? txt->getPath() : ""); + activityManager.goToFileBrowser(txt ? txt->getPath() : ""); return; } diff --git a/src/activities/reader/XtcReaderActivity.cpp b/src/activities/reader/XtcReaderActivity.cpp index a10d1025..84cc51da 100644 --- a/src/activities/reader/XtcReaderActivity.cpp +++ b/src/activities/reader/XtcReaderActivity.cpp @@ -70,7 +70,7 @@ void XtcReaderActivity::loop() { // Long press BACK (1s+) goes to file selection if (mappedInput.isPressed(MappedInputManager::Button::Back) && mappedInput.getHeldTime() >= goHomeMs) { - activityManager.goToMyLibrary(xtc ? xtc->getPath() : ""); + activityManager.goToFileBrowser(xtc ? xtc->getPath() : ""); return; } From 7884bc566e42175ca279fa1b6b895e8360f1a5e7 Mon Sep 17 00:00:00 2001 From: jpirnay Date: Mon, 2 Mar 2026 12:02:09 +0100 Subject: [PATCH 02/11] fix: Hanging indent (negative text-indent) and em-unit sizing (#1229) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary * **What is the goal of this PR?** Fixing two independent CSS rendering bugs combined to make hanging-indent list styles (e.g. margin-left:3em; text-indent:-1em) render incorrectly: * **What changes are included?** 1. Negative text-indent was silently ignored Three guards in ParsedText.cpp (computeLineBreaks, computeHyphenatedLineBreaks, extractLine) conditioned firstLineIndent on blockStyle.textIndent > 0, so any negative value collapsed to zero. Additionally, wordXpos was uint16_t, which cannot represent negative offsets — a cast of e.g. −18 would wrap to 65518 and render the word far off-screen. 2. extraParagraphSpacing suppressed hanging indents Even after removing the > 0 guard, the existing !extraParagraphSpacing condition would still suppress all text-indent when that setting is on (its default). Positive text-indent is a decorative paragraph indent that the user can reasonably replace with vertical spacing — negative text-indent is structural (it positions the list marker) and must always apply. 3. em unit was calibrated against line height, not font size emSize was computed as getLineHeight() * lineCompression (the full line advance). CSS em units are defined relative to the font-size, which corresponds to the ascender height — not the line height. Using line height makes every em-based margin/indent ~20–30% wider than a browser would render it, and is especially noticeable for CSS that uses font-size: small (which we do not implement). ## Additional Context Test case ``` .lsl1 { margin-left: 3em; text-indent: -1em; }
• First list item that wraps across lines
• Short item
``` Before: all lines of all items started at 3 em from the left edge (indent ignored). After: the bullet marker hangs at 2 em; continuation lines align at 3 em. before after --- ### 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? _**YES**_ --- lib/Epub/Epub/ParsedText.cpp | 28 +++++++++++++------ lib/Epub/Epub/Section.cpp | 2 +- lib/Epub/Epub/blocks/TextBlock.cpp | 2 +- lib/Epub/Epub/blocks/TextBlock.h | 4 +-- .../Epub/parsers/ChapterHtmlSlimParser.cpp | 5 ++-- 5 files changed, 25 insertions(+), 16 deletions(-) diff --git a/lib/Epub/Epub/ParsedText.cpp b/lib/Epub/Epub/ParsedText.cpp index 584d080b..3c26fb29 100644 --- a/lib/Epub/Epub/ParsedText.cpp +++ b/lib/Epub/Epub/ParsedText.cpp @@ -144,9 +144,12 @@ std::vector ParsedText::computeLineBreaks(const GfxRenderer& renderer, c return {}; } - // Calculate first line indent (only for left/justified text without extra paragraph spacing) + // Calculate first line indent (only for left/justified text). + // Positive text-indent (paragraph indent) is suppressed when extraParagraphSpacing is on. + // Negative text-indent (hanging indent, e.g. margin-left:3em; text-indent:-1em) always applies — + // it is structural (positions the bullet/marker), not decorative. const int firstLineIndent = - blockStyle.textIndent > 0 && !extraParagraphSpacing && + blockStyle.textIndentDefined && (blockStyle.textIndent < 0 || !extraParagraphSpacing) && (blockStyle.alignment == CssTextAlign::Justify || blockStyle.alignment == CssTextAlign::Left) ? blockStyle.textIndent : 0; @@ -275,9 +278,12 @@ std::vector ParsedText::computeHyphenatedLineBreaks(const GfxRenderer& r const int pageWidth, const int spaceWidth, std::vector& wordWidths, std::vector& continuesVec) { - // Calculate first line indent (only for left/justified text without extra paragraph spacing) + // Calculate first line indent (only for left/justified text). + // Positive text-indent (paragraph indent) is suppressed when extraParagraphSpacing is on. + // Negative text-indent (hanging indent, e.g. margin-left:3em; text-indent:-1em) always applies — + // it is structural (positions the bullet/marker), not decorative. const int firstLineIndent = - blockStyle.textIndent > 0 && !extraParagraphSpacing && + blockStyle.textIndentDefined && (blockStyle.textIndent < 0 || !extraParagraphSpacing) && (blockStyle.alignment == CssTextAlign::Justify || blockStyle.alignment == CssTextAlign::Left) ? blockStyle.textIndent : 0; @@ -443,10 +449,13 @@ void ParsedText::extractLine(const size_t breakIndex, const int pageWidth, const const size_t lastBreakAt = breakIndex > 0 ? lineBreakIndices[breakIndex - 1] : 0; const size_t lineWordCount = lineBreak - lastBreakAt; - // Calculate first line indent (only for left/justified text without extra paragraph spacing) + // Calculate first line indent (only for left/justified text). + // Positive text-indent (paragraph indent) is suppressed when extraParagraphSpacing is on. + // Negative text-indent (hanging indent, e.g. margin-left:3em; text-indent:-1em) always applies — + // it is structural (positions the bullet/marker), not decorative. const bool isFirstLine = breakIndex == 0; const int firstLineIndent = - isFirstLine && blockStyle.textIndent > 0 && !extraParagraphSpacing && + isFirstLine && blockStyle.textIndentDefined && (blockStyle.textIndent < 0 || !extraParagraphSpacing) && (blockStyle.alignment == CssTextAlign::Justify || blockStyle.alignment == CssTextAlign::Left) ? blockStyle.textIndent : 0; @@ -485,8 +494,9 @@ void ParsedText::extractLine(const size_t breakIndex, const int pageWidth, const ? spareSpace / static_cast(actualGapCount) : 0; - // Calculate initial x position (first line starts at indent for left/justified text) - auto xpos = static_cast(firstLineIndent); + // Calculate initial x position (first line starts at indent for left/justified text; + // may be negative for hanging indents, e.g. margin-left:3em; text-indent:-1em). + auto xpos = static_cast(firstLineIndent); if (blockStyle.alignment == CssTextAlign::Right) { xpos = effectivePageWidth - lineWordWidthSum - totalNaturalGaps; } else if (blockStyle.alignment == CssTextAlign::Center) { @@ -495,7 +505,7 @@ void ParsedText::extractLine(const size_t breakIndex, const int pageWidth, const // Pre-calculate X positions for words // Continuation words attach to the previous word with no space before them - std::vector lineXPos; + std::vector lineXPos; lineXPos.reserve(lineWordCount); for (size_t wordIdx = 0; wordIdx < lineWordCount; wordIdx++) { diff --git a/lib/Epub/Epub/Section.cpp b/lib/Epub/Epub/Section.cpp index 814f5029..6e5aadf9 100644 --- a/lib/Epub/Epub/Section.cpp +++ b/lib/Epub/Epub/Section.cpp @@ -10,7 +10,7 @@ #include "parsers/ChapterHtmlSlimParser.h" namespace { -constexpr uint8_t SECTION_FILE_VERSION = 14; +constexpr uint8_t SECTION_FILE_VERSION = 16; constexpr uint32_t HEADER_SIZE = sizeof(uint8_t) + sizeof(int) + sizeof(float) + sizeof(bool) + sizeof(uint8_t) + sizeof(uint16_t) + sizeof(uint16_t) + sizeof(uint16_t) + sizeof(bool) + sizeof(bool) + sizeof(uint32_t); diff --git a/lib/Epub/Epub/blocks/TextBlock.cpp b/lib/Epub/Epub/blocks/TextBlock.cpp index 1671e59a..e3e35d42 100644 --- a/lib/Epub/Epub/blocks/TextBlock.cpp +++ b/lib/Epub/Epub/blocks/TextBlock.cpp @@ -74,7 +74,7 @@ bool TextBlock::serialize(FsFile& file) const { std::unique_ptr TextBlock::deserialize(FsFile& file) { uint16_t wc; std::vector words; - std::vector wordXpos; + std::vector wordXpos; std::vector wordStyles; BlockStyle blockStyle; diff --git a/lib/Epub/Epub/blocks/TextBlock.h b/lib/Epub/Epub/blocks/TextBlock.h index b654dcf1..85fdd55a 100644 --- a/lib/Epub/Epub/blocks/TextBlock.h +++ b/lib/Epub/Epub/blocks/TextBlock.h @@ -13,12 +13,12 @@ class TextBlock final : public Block { private: std::vector words; - std::vector wordXpos; + std::vector wordXpos; std::vector wordStyles; BlockStyle blockStyle; public: - explicit TextBlock(std::vector words, std::vector word_xpos, + explicit TextBlock(std::vector words, std::vector word_xpos, std::vector word_styles, const BlockStyle& blockStyle = BlockStyle()) : words(std::move(words)), wordXpos(std::move(word_xpos)), diff --git a/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp b/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp index e732b60a..2753f928 100644 --- a/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp +++ b/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp @@ -278,8 +278,7 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char* int displayWidth = 0; int displayHeight = 0; - const float emSize = - static_cast(self->renderer.getLineHeight(self->fontId)) * self->lineCompression; + const float emSize = static_cast(self->renderer.getFontAscenderSize(self->fontId)); CssStyle imgStyle = self->cssParser ? self->cssParser->resolveStyle("img", classAttr) : CssStyle{}; // Merge inline style (e.g. style="height: 2em") so it overrides stylesheet rules if (!styleAttr.empty()) { @@ -505,7 +504,7 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char* } } - const float emSize = static_cast(self->renderer.getLineHeight(self->fontId)) * self->lineCompression; + const float emSize = static_cast(self->renderer.getFontAscenderSize(self->fontId)); const auto userAlignmentBlockStyle = BlockStyle::fromCssStyle( cssStyle, emSize, static_cast(self->paragraphAlignment), self->viewportWidth); From add08bc6c55fffc2654e00484f2ade596acb5eb5 Mon Sep 17 00:00:00 2001 From: jpirnay Date: Mon, 2 Mar 2026 13:28:14 +0100 Subject: [PATCH 03/11] feat: Prefer ".sleep" over "sleep" for custom image directory (#948) ## Summary * Custom sleep screen images now load from /.sleep directory (preferred), falling back to /sleep for backwards compatibility. The dot-prefix keeps the directory hidden from the file browser. * Rewrote User Guide section 3.6 to document all six sleep screen modes, cover settings, and the updated custom image setup. ## Additional Context * The sleep directoy entry while browsing files was distracting. --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _NO_ --- USER_GUIDE.md | 27 +++++++++++++++++---- src/activities/boot_sleep/SleepActivity.cpp | 19 ++++++++++++--- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/USER_GUIDE.md b/USER_GUIDE.md index 0f438fbf..8552cb22 100644 --- a/USER_GUIDE.md +++ b/USER_GUIDE.md @@ -308,13 +308,30 @@ If you use the HTTPS listener, use `https://:7200` (`curl -k` only fo ### 3.7 Sleep Screen -You can customize the sleep screen by placing custom images in specific locations on the SD card: +The **Sleep Screen** setting controls what is displayed when the device goes to sleep: -- **Single Image:** Place a file named `sleep.bmp` in the root directory. -- **Multiple Images:** Create a `sleep` directory in the root of the SD card and place any number of `.bmp` images inside. If images are found in this directory, they will take priority over the `sleep.bmp` file, and one will be randomly selected each time the device sleeps. +| Mode | Behavior | +|------|----------| +| **Dark** (default) | The CrossPoint logo on a dark background. | +| **Light** | The CrossPoint logo on a white background. | +| **Custom** | A custom image from the SD card (see below). Falls back to **Dark** if no custom image is found. | +| **Cover** | The cover of the currently open book. Falls back to **Dark** if no book is open. | +| **Cover + Custom** | The cover of the currently open book. Falls back to **Custom** behavior if no book is open. | +| **None** | A blank screen. | -> [!NOTE] -> You'll need to set the **Sleep Screen** setting to **Custom** in order to use these images. +#### Cover settings + +When using **Cover** or **Cover + Custom**, two additional settings apply: + +- **Sleep Screen Cover Mode**: **Fit** (scale to fit, white borders) or **Crop** (scale and crop to fill the screen). +- **Sleep Screen Cover Filter**: **None** (grayscale), **Contrast** (black & white), or **Inverted** (inverted black & white). + +#### Custom images + +To use custom sleep images, set the sleep screen mode to **Custom** or **Cover + Custom**, then place images on the SD card: + +- **Multiple Images (recommended):** Create a `.sleep` directory in the root of the SD card and place any number of `.bmp` images inside. One will be randomly selected each time the device sleeps. (A directory named `sleep` is also accepted as a fallback.) +- **Single Image:** Place a file named `sleep.bmp` in the root directory. This is used as a fallback if no valid images are found in the `.sleep`/`sleep` directory. > [!TIP] > For best results: diff --git a/src/activities/boot_sleep/SleepActivity.cpp b/src/activities/boot_sleep/SleepActivity.cpp index cd6de5fc..35a0ee50 100644 --- a/src/activities/boot_sleep/SleepActivity.cpp +++ b/src/activities/boot_sleep/SleepActivity.cpp @@ -32,9 +32,20 @@ void SleepActivity::onEnter() { } void SleepActivity::renderCustomSleepScreen() const { - // Check if we have a /sleep directory - auto dir = Storage.open("/sleep"); + // Check if we have a /.sleep (preferred) or /sleep directory + const char* sleepDir = nullptr; + auto dir = Storage.open("/.sleep"); if (dir && dir.isDirectory()) { + sleepDir = "/.sleep"; + } else { + if (dir) dir.close(); + dir = Storage.open("/sleep"); + if (dir && dir.isDirectory()) { + sleepDir = "/sleep"; + } + } + + if (sleepDir) { std::vector files; char name[500]; // collect all valid BMP files @@ -74,10 +85,10 @@ void SleepActivity::renderCustomSleepScreen() const { } APP_STATE.lastSleepImage = randomFileIndex; APP_STATE.saveToFile(); - const auto filename = "/sleep/" + files[randomFileIndex]; + const auto filename = std::string(sleepDir) + "/" + files[randomFileIndex]; FsFile file; if (Storage.openFileForRead("SLP", filename, file)) { - LOG_DBG("SLP", "Randomly loading: /sleep/%s", files[randomFileIndex].c_str()); + LOG_DBG("SLP", "Randomly loading: %s/%s", sleepDir, files[randomFileIndex].c_str()); delay(100); Bitmap bitmap(file, true); if (bitmap.parseHeaders() == BmpReaderError::Ok) { From 54322796e54c43d34389bc1ff65b09287de3bb05 Mon Sep 17 00:00:00 2001 From: Dani Poveda Date: Mon, 2 Mar 2026 13:29:01 +0100 Subject: [PATCH 04/11] fix: improve and add Spanish translations (#1254) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary * **What is the goal of this PR?** - Improve and add the latest missing Spanish translations * **What changes are included?** - Add missing spaces and remove extra unneeded ones (spaces at the end of certain strings and others, i.e. the one introduced in the string `Smart Device`; actually, `SmartDevice` is the correct Calibre plugin name) - Normalise the use of caps in certain strings - Adapting the translation to the one found in related third-party software (i.e. Spanish translation for the word `plugin` in Calibre is `complemento`) - Shortening some translations to make them smaller and fit better in screen - Rewording ambiguous translations (i.e. `Volver a inicio` could mean to go back to Home, but also to go back to the first page of the current book, so I changed it for a more specific action, `Volver al menú Inicio`) ## Additional Context * **Missing spaces caused a lack of clarity** - My main motivation for this PR was the following:
Screenshots: In English: ![English.bmp](https://github.com/user-attachments/files/25650211/English.bmp) In Spanish: ![Spanish.bmp](https://github.com/user-attachments/files/25650225/Spanish.bmp)
--- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**NO**_ --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- docs/translators.md | 1 + lib/I18n/translations/spanish.yaml | 122 +++++++++++++++++------------ 2 files changed, 74 insertions(+), 49 deletions(-) diff --git a/docs/translators.md b/docs/translators.md index 704090e5..eadf5560 100644 --- a/docs/translators.md +++ b/docs/translators.md @@ -35,6 +35,7 @@ If you'd like to add your name to this list, please open a PR adding yourself an - [yeyeto2788](https://github.com/yeyeto2788) - [Skrzakk](https://github.com/Skrzakk) - [pablohc](https://github.com/pablohc) +- [DaniPhii](https://github.com/DaniPhii) ## Swedish - [dawiik](https://github.com/dawiik) diff --git a/lib/I18n/translations/spanish.yaml b/lib/I18n/translations/spanish.yaml index b40af505..cd139b43 100644 --- a/lib/I18n/translations/spanish.yaml +++ b/lib/I18n/translations/spanish.yaml @@ -6,7 +6,7 @@ STR_CROSSPOINT: "CrossPoint" STR_BOOTING: "Iniciando..." STR_SLEEPING: "Suspendido" STR_ENTERING_SLEEP: "Entrando en suspensión" -STR_BROWSE_FILES: "Explorador de Archivos" +STR_BROWSE_FILES: "Explorador de archivos" STR_FILE_TRANSFER: "Transferir archivos" STR_SETTINGS_TITLE: "Ajustes" STR_CALIBRE_LIBRARY: "Biblioteca de Calibre" @@ -46,11 +46,11 @@ STR_PRESS_ANY_CONTINUE: "Pulse cualquier botón para continuar" STR_SELECT_HINT: "Izq./Der.: Seleccionar | OK: Confirmar" STR_HOW_CONNECT: "¿Cómo desea conectarse?" STR_JOIN_NETWORK: "Unirse a una red" -STR_CREATE_HOTSPOT: "Crear Punto de Acceso" +STR_CREATE_HOTSPOT: "Crear punto de acceso" STR_JOIN_DESC: "Conectarse a una red Wi-Fi existente" STR_HOTSPOT_DESC: "Conectarse a este dispositivo" -STR_STARTING_HOTSPOT: "Iniciando Punto de Acceso..." -STR_HOTSPOT_MODE: "Modo Punto de Acceso" +STR_STARTING_HOTSPOT: "Iniciando punto de acceso..." +STR_HOTSPOT_MODE: "Modo punto de acceso" STR_CONNECT_WIFI_HINT: "Conecte su dispositivo a esta red Wi-Fi" STR_OPEN_URL_HINT: "Abra esta dirección en su navegador" STR_OR_HTTP_PREFIX: "o http://" @@ -65,22 +65,22 @@ STR_ENTER_WIFI_PASSWORD: "Introduzca la contraseña del Wi-Fi" STR_ENTER_TEXT: "Introduzca el texto" STR_TO_PREFIX: "a " STR_CALIBRE_DISCOVERING: "Buscando Calibre..." -STR_CALIBRE_CONNECTING_TO: "Conectándose a" +STR_CALIBRE_CONNECTING_TO: "Conectándose a " STR_CALIBRE_CONNECTED_TO: "Conectado a " STR_CALIBRE_WAITING_COMMANDS: "Esperando comandos..." STR_CONNECTION_FAILED_RETRYING: "(Error de conexión, reintentando...)" STR_CALIBRE_DISCONNECTED: "Calibre desconectado" STR_CALIBRE_WAITING_TRANSFER: "Esperando transferencia..." -STR_CALIBRE_TRANSFER_HINT: "Si la transferencia falla, active \\n'Ignorar espacio libre' en la configuración del \\nPlugin Smart Device de Calibre." +STR_CALIBRE_TRANSFER_HINT: "Si la transferencia falla, active\\n\"Ignorar espacio libre\" en la configuración del\\ncomplemento SmartDevice de Calibre." STR_CALIBRE_RECEIVING: "Recibiendo: " STR_CALIBRE_RECEIVED: "Recibido: " STR_CALIBRE_WAITING_MORE: "Esperando más..." STR_CALIBRE_FAILED_CREATE_FILE: "Error al crear el archivo" STR_CALIBRE_PASSWORD_REQUIRED: "Contraseña requerida" STR_CALIBRE_TRANSFER_INTERRUPTED: "Transferencia interrumpida" -STR_CALIBRE_INSTRUCTION_1: "1) Instale el Plugin CrossPoint Reader" +STR_CALIBRE_INSTRUCTION_1: "1) Instale el complemento CrossPoint Reader" STR_CALIBRE_INSTRUCTION_2: "2) Conéctese a la misma red Wi-Fi" -STR_CALIBRE_INSTRUCTION_3: "3) Desde Calibre seleccione: \"Enviar a dispositivo\"" +STR_CALIBRE_INSTRUCTION_3: "3) Desde Calibre, seleccione \"Enviar a dispositivo\"" STR_CALIBRE_INSTRUCTION_4: "\"Permanezca en esta pantalla mientras se envía\"" STR_CAT_DISPLAY: "Pantalla" STR_CAT_READER: "Lector" @@ -92,15 +92,15 @@ STR_STATUS_BAR: "Barra de estado" STR_HIDE_BATTERY: "Ocultar % de batería" STR_EXTRA_SPACING: "Espaciado entre párrafos" STR_TEXT_AA: "Suavizado de texto" -STR_SHORT_PWR_BTN: "Función especial botón Power" +STR_SHORT_PWR_BTN: "Toque corto botón encendido" STR_ORIENTATION: "Orientación" STR_FRONT_BTN_LAYOUT: "Diseño de los botones frontales" -STR_SIDE_BTN_LAYOUT: "Función botones laterales (Lector)" +STR_SIDE_BTN_LAYOUT: "Función botones laterales (lector)" STR_LONG_PRESS_SKIP: "Saltar capítulo (pulsación larga)" STR_FONT_FAMILY: "Tipografía" STR_EXT_READER_FONT: "Tipografía externa" STR_EXT_CHINESE_FONT: "Tipografía" -STR_EXT_UI_FONT: "Tipografía (Pantalla)" +STR_EXT_UI_FONT: "Tipografía (interfaz)" STR_FONT_SIZE: "Tamaño" STR_LINE_SPACING: "Interlineado" STR_ASCII_LETTER_SPACING: "Espaciado entre letras ASCII" @@ -126,7 +126,7 @@ STR_DOCUMENT_MATCHING: "Coincidencia de doc." STR_AUTHENTICATE: "Autenticar" STR_KOREADER_USERNAME: "Usuario de KOReader" STR_KOREADER_PASSWORD: "Contraseña de KOReader" -STR_FILENAME: "Nombre del archivo" +STR_FILENAME: "Nombre de archivo" STR_BINARY: "Binario" STR_SET_CREDENTIALS_FIRST: "Configurar credenciales" STR_WIFI_CONN_FAILED: "Fallo de conexión Wi-Fi" @@ -142,8 +142,8 @@ STR_CLEAR_CACHE_WARNING_3: "Los libros deberán ser reindexados" STR_CLEAR_CACHE_WARNING_4: "cuando se vuelvan a abrir." STR_CLEARING_CACHE: "Borrando caché..." STR_CACHE_CLEARED: "Caché borrada" -STR_ITEMS_REMOVED: "Elementos eliminados" -STR_FAILED_LOWER: "Error" +STR_ITEMS_REMOVED: "elementos eliminados" +STR_FAILED_LOWER: "falló" STR_CLEAR_CACHE_FAILED: "No se pudo borrar la caché" STR_CHECK_SERIAL_OUTPUT: "Consulte los registros del puerto serie" STR_DARK: "Oscuro" @@ -184,16 +184,16 @@ STR_JUSTIFY: "Justificado" STR_ALIGN_LEFT: "Izquierda" STR_CENTER: "Centro" STR_ALIGN_RIGHT: "Derecha" -STR_MIN_1: "1 Minuto" -STR_MIN_5: "5 Minutos" -STR_MIN_10: "10 Minutos" -STR_MIN_15: "15 Minutos" -STR_MIN_30: "30 Minutos" -STR_PAGES_1: "1 Página" -STR_PAGES_5: "5 Páginas" -STR_PAGES_10: "10 Páginas" -STR_PAGES_15: "15 Páginas" -STR_PAGES_30: "30 Páginas" +STR_MIN_1: "1 min." +STR_MIN_5: "5 min." +STR_MIN_10: "10 min." +STR_MIN_15: "15 min." +STR_MIN_30: "30 min." +STR_PAGES_1: "1 pág." +STR_PAGES_5: "5 pág." +STR_PAGES_10: "10 pág." +STR_PAGES_15: "15 pág." +STR_PAGES_30: "30 pág." STR_UPDATE: "Actualizar" STR_CHECKING_UPDATE: "Verificando actualización..." STR_NEW_UPDATE: "¡Nueva actualización disponible!" @@ -203,20 +203,20 @@ STR_UPDATING: "Actualizando..." STR_NO_UPDATE: "No hay actualizaciones disponibles" STR_UPDATE_FAILED: "Fallo de actualización" STR_UPDATE_COMPLETE: "Actualización completada" -STR_POWER_ON_HINT: "Pulse y mantenga presionado el botón de encendido para volver a encender" +STR_POWER_ON_HINT: "Reinicie manteniendo pulsado botón de encendido" STR_EXTERNAL_FONT: "Fuente externa" -STR_BUILTIN_DISABLED: "Incorporado (Desactivado)" +STR_BUILTIN_DISABLED: "Incorporado (desactivado)" STR_NO_ENTRIES: "No se encontraron elementos" STR_DOWNLOADING: "Descargando..." STR_DOWNLOAD_FAILED: "Fallo de descarga" -STR_ERROR_MSG: "Error" +STR_ERROR_MSG: "Error:" STR_UNNAMED: "Sin nombre" -STR_NO_SERVER_URL: "No se ha configurado la URL del servidor" +STR_NO_SERVER_URL: "No se configuró URL de servidor" STR_FETCH_FEED_FAILED: "Fallo al obtener el feed" STR_PARSE_FEED_FAILED: "Fallo al procesar el feed" STR_NETWORK_PREFIX: "Red: " STR_IP_ADDRESS_PREFIX: "IP: " -STR_SCAN_QR_WIFI_HINT: "O escanee el código QR con su teléfono para conectarse a Wi-Fi." +STR_SCAN_QR_WIFI_HINT: "o lea el QR con su tfno. para conectarse al Wi-Fi." STR_ERROR_GENERAL_FAILURE: "Error: Fallo general" STR_ERROR_NETWORK_NOT_FOUND: "Error: Red no encontrada" STR_ERROR_CONNECTION_TIMEOUT: "Error: Tiempo de conexión agotado" @@ -225,7 +225,8 @@ STR_BACK: "« Atrás" STR_EXIT: "« Salir" STR_HOME: "« Inicio" STR_SAVE: "« Guardar" -STR_SELECT: "Elegir" +STR_SELECT: "Selec." +STR_SELECTED: "Seleccionado" STR_TOGGLE: "Cambiar" STR_CONFIRM: "Confirmar" STR_CANCEL: "Cancelar" @@ -235,6 +236,8 @@ STR_DOWNLOAD: "Descargar" STR_RETRY: "Reintentar" STR_YES: "Sí" STR_NO: "No" +STR_SHOW: "Mostrar" +STR_HIDE: "Ocultar" STR_STATE_ON: "Activado" STR_STATE_OFF: "Desactivado" STR_NOT_SET: "No configurado" @@ -247,6 +250,21 @@ STR_CAPS_OFF: "minúsculas" STR_OK_BUTTON: "OK" STR_SLEEP_COVER_FILTER: "Filtro de pantalla de suspensión" STR_FILTER_CONTRAST: "Contraste" +STR_CUSTOMISE_STATUS_BAR: "Personalizar barra de estado" +STR_CHAPTER_PAGE_COUNT: "Contador pág. cap." +STR_BOOK_PROGRESS_PERCENTAGE: "Porcentaje progreso libro" +STR_PROGRESS_BAR: "Barra de progreso" +STR_PROGRESS_BAR_THICKNESS: "Grosor de barra de progreso" +STR_PROGRESS_BAR_THIN: "Fino" +STR_PROGRESS_BAR_MEDIUM: "Medio" +STR_PROGRESS_BAR_THICK: "Ancho" +STR_BOOK: "Libro" +STR_CHAPTER: "Capítulo" +STR_EXAMPLE_CHAPTER: "Capítulo 21" +STR_EXAMPLE_BOOK: "Título del libro" +STR_PREVIEW: "Previsualización" +STR_TITLE: "Título" +STR_BATTERY: "Batería" STR_UI_THEME: "Interfaz" STR_THEME_CLASSIC: "Clásico" STR_THEME_LYRA: "Lyra" @@ -269,37 +287,38 @@ STR_DEFAULT_VALUE: "Predeterminado" STR_REMAP_PROMPT: "Pulse un botón frontal para cada función" STR_UNASSIGNED: "Sin asignar" STR_ALREADY_ASSIGNED: "Ya asignado" -STR_REMAP_RESET_HINT: "Botón lateral arriba: Restablecer configuración" -STR_REMAP_CANCEL_HINT: "Botón lateral abajo: Anular reconfiguración" -STR_HW_BACK_LABEL: "Atrás (Primer botón)" -STR_HW_CONFIRM_LABEL: "Confirmar (Segundo botón)" -STR_HW_LEFT_LABEL: "Izq. (Tercer botón)" -STR_HW_RIGHT_LABEL: "Der. (Cuarto botón)" +STR_REMAP_RESET_HINT: "Botón lateral arriba: restablecer configuración" +STR_REMAP_CANCEL_HINT: "Botón lateral abajo: anular reconfiguración" +STR_HW_BACK_LABEL: "Atrás (primer botón)" +STR_HW_CONFIRM_LABEL: "Confirmar (segundo botón)" +STR_HW_LEFT_LABEL: "Izq. (tercer botón)" +STR_HW_RIGHT_LABEL: "Der. (cuarto botón)" STR_GO_TO_PERCENT: "Ir a %" -STR_GO_HOME_BUTTON: "Volver a inicio" -STR_DELETE: "Borrar" +STR_GO_HOME_BUTTON: "Volver al menú Inicio" STR_SYNC_PROGRESS: "Sincronizar progreso de lectura" STR_DELETE_CACHE: "Borrar caché del libro" -STR_CHAPTER_PREFIX: "Cap.:" -STR_PAGES_SEPARATOR: " Páginas |" -STR_BOOK_PREFIX: "Libro:" -STR_KBD_SHIFT: "shift" -STR_KBD_SHIFT_CAPS: "SHIFT" +STR_DELETE: "Borrar" +STR_DISPLAY_QR: "Mostrar página como QR" +STR_CHAPTER_PREFIX: "Cap.: " +STR_PAGES_SEPARATOR: " Págs. | " +STR_BOOK_PREFIX: "Libro: " +STR_KBD_SHIFT: "minús." +STR_KBD_SHIFT_CAPS: "MAYÚS." STR_KBD_LOCK: "BLOQUEAR" STR_CALIBRE_URL_HINT: "Para Calibre, agregue /opds a su URL" STR_PERCENT_STEP_HINT: "Izq./Der.: 1% | Subir/Bajar: 10%" STR_SYNCING_TIME: "Tiempo de sincronización..." -STR_CALC_HASH: "Calculando HASH del documento..." -STR_HASH_FAILED: "No se pudo calcular el HASH del documento" +STR_CALC_HASH: "Calculando hash del documento..." +STR_HASH_FAILED: "No se pudo calcular el hash del documento" STR_FETCH_PROGRESS: "Recuperando progreso remoto..." STR_UPLOAD_PROGRESS: "Subiendo progreso..." STR_NO_CREDENTIALS_MSG: "No se han configurado credenciales" STR_KOREADER_SETUP_HINT: "Configure una cuenta de KOReader en la configuración" STR_PROGRESS_FOUND: "¡Progreso encontrado!" -STR_REMOTE_LABEL: "Remoto" -STR_LOCAL_LABEL: "Local" -STR_PAGE_OVERALL_FORMAT: "Página %d, %.2f%% Completada" -STR_PAGE_TOTAL_OVERALL_FORMAT: "Página %d / %d, %.2f%% Completada" +STR_REMOTE_LABEL: "Remoto:" +STR_LOCAL_LABEL: "Local:" +STR_PAGE_OVERALL_FORMAT: "Página %d, %.2f%% completado" +STR_PAGE_TOTAL_OVERALL_FORMAT: "Página %d / %d, %.2f%% completado" STR_DEVICE_FROM_FORMAT: " De: %s" STR_APPLY_REMOTE: "Aplicar progreso remoto" STR_UPLOAD_LOCAL: "Subir progreso local" @@ -312,4 +331,9 @@ STR_UPLOAD: "Subir" STR_BOOK_S_STYLE: "Estilo del libro" STR_EMBEDDED_STYLE: "Estilo integrado" STR_OPDS_SERVER_URL: "URL del servidor OPDS" +STR_FOOTNOTES: "Pie de página" +STR_NO_FOOTNOTES: "No hay notas al pie de esta página" +STR_LINK: "[enlace]" STR_SCREENSHOT_BUTTON: "Tomar captura de pantalla" +STR_AUTO_TURN_ENABLED: "Paso pág. automático: " +STR_AUTO_TURN_PAGES_PER_MIN: "Páginas por minuto" From e96f4d8d15a5153772ac658914ce9ac2e4e26d66 Mon Sep 17 00:00:00 2001 From: Xuan-Son Nguyen Date: Mon, 2 Mar 2026 13:30:55 +0100 Subject: [PATCH 05/11] chore: remove rendundant xTaskCreate (#1264) ## Summary Ref discussion: https://github.com/crosspoint-reader/crosspoint-reader/pull/1222#discussion_r2865402110 Important note that this is a bug-for-bug fix. In reality, this branch `WiFi.status() == WL_CONNECTED` is pretty much a dead code because the entry point of these 2 activities don't use wifi. It is better to refactor the management of network though, but it's better to be a dedicated PR. --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? **NO** --- .../reader/KOReaderSyncActivity.cpp | 20 +------------------ .../settings/KOReaderAuthActivity.cpp | 16 +-------------- 2 files changed, 2 insertions(+), 34 deletions(-) diff --git a/src/activities/reader/KOReaderSyncActivity.cpp b/src/activities/reader/KOReaderSyncActivity.cpp index 251bd716..4df71e93 100644 --- a/src/activities/reader/KOReaderSyncActivity.cpp +++ b/src/activities/reader/KOReaderSyncActivity.cpp @@ -204,25 +204,7 @@ void KOReaderSyncActivity::onEnter() { // Check if already connected (e.g. from settings page auth) if (WiFi.status() == WL_CONNECTED) { LOG_DBG("KOSync", "Already connected to WiFi"); - state = SYNCING; - statusMessage = tr(STR_SYNCING_TIME); - requestUpdate(true); - - // Perform sync directly (will be handled in loop) - xTaskCreate( - [](void* param) { - auto* self = static_cast(param); - // Sync time first - syncTimeWithNTP(); - { - RenderLock lock(*self); - self->statusMessage = tr(STR_CALC_HASH); - } - self->requestUpdate(true); - self->performSync(); - vTaskDelete(nullptr); - }, - "SyncTask", 4096, this, 1, nullptr); + onWifiSelectionComplete(true); return; } diff --git a/src/activities/settings/KOReaderAuthActivity.cpp b/src/activities/settings/KOReaderAuthActivity.cpp index 138369bc..2240ac6e 100644 --- a/src/activities/settings/KOReaderAuthActivity.cpp +++ b/src/activities/settings/KOReaderAuthActivity.cpp @@ -51,23 +51,9 @@ void KOReaderAuthActivity::performAuthentication() { void KOReaderAuthActivity::onEnter() { Activity::onEnter(); - // Turn on WiFi - WiFi.mode(WIFI_STA); - // Check if already connected if (WiFi.status() == WL_CONNECTED) { - state = AUTHENTICATING; - statusMessage = tr(STR_AUTHENTICATING); - requestUpdate(); - - // Perform authentication in a separate task - xTaskCreate( - [](void* param) { - auto* self = static_cast(param); - self->performAuthentication(); - vTaskDelete(nullptr); - }, - "AuthTask", 4096, this, 1, nullptr); + onWifiSelectionComplete(true); return; } From 1a1c2316b74cd2bc76ed446c3649739406a135c7 Mon Sep 17 00:00:00 2001 From: Mirus Date: Tue, 3 Mar 2026 08:01:47 +0200 Subject: [PATCH 06/11] chore: new Ukrainian localization strings (#1270) ## Summary * **What changes are included?** New Ukrainian localization strings ## Additional Context auto turn functionality https://github.com/crosspoint-reader/crosspoint-reader/pull/1219 --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**NO **_ --- lib/I18n/translations/ukrainian.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/I18n/translations/ukrainian.yaml b/lib/I18n/translations/ukrainian.yaml index 4bcb097f..88d96259 100644 --- a/lib/I18n/translations/ukrainian.yaml +++ b/lib/I18n/translations/ukrainian.yaml @@ -297,6 +297,7 @@ STR_GO_TO_PERCENT: "Перейти до %" STR_GO_HOME_BUTTON: "На головну" STR_SYNC_PROGRESS: "Прогрес синхронізації" STR_DELETE_CACHE: "Видалити кеш книги" +STR_DELETE: "Видалити" STR_DISPLAY_QR: "Показати сторінку як QR-код" STR_CHAPTER_PREFIX: "Розділ: " STR_PAGES_SEPARATOR: " сторінок | " @@ -334,3 +335,5 @@ STR_FOOTNOTES: "Зноски" STR_NO_FOOTNOTES: "На цій сторінці немає зносок" STR_LINK: "[посилання]" STR_SCREENSHOT_BUTTON: "Знімок екрана" +STR_AUTO_TURN_ENABLED: "Автоперегортання увімк: " +STR_AUTO_TURN_PAGES_PER_MIN: "Автоперегортання (ст/хв)" \ No newline at end of file From 2d0c99671abca8e3997a126f588cec0930ddd1f6 Mon Sep 17 00:00:00 2001 From: Dave Allie Date: Wed, 4 Mar 2026 01:41:02 +1100 Subject: [PATCH 07/11] fix: Enable DESTRUCTOR_CLOSES_FILE flag (#1075) ## Summary * Enable `DESTRUCTOR_CLOSES_FILE` flag * We're never intending to not close files, so if we accidentally leave them open as they're destructured, this will help close them. ## Additional Context * As spotted in https://github.com/crosspoint-reader/crosspoint-reader/pull/869, there are cases where we were accidentally not closing files Looks to use about 5K of flash. ``` RAM: [=== ] 31.5% (used 103100 bytes from 327680 bytes) Flash: [======= ] 68.9% (used 4513220 bytes from 6553600 bytes) ``` ``` RAM: [=== ] 31.5% (used 103100 bytes from 327680 bytes) Flash: [======= ] 68.9% (used 4518498 bytes from 6553600 bytes) ``` --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? No --- platformio.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/platformio.ini b/platformio.ini index fc6bd0f6..691f1570 100644 --- a/platformio.ini +++ b/platformio.ini @@ -24,6 +24,7 @@ build_flags = -DARDUINO_USB_CDC_ON_BOOT=1 -DEINK_DISPLAY_SINGLE_BUFFER_MODE=1 -DDISABLE_FS_H_WARNING=1 + -DDESTRUCTOR_CLOSES_FILE=1 # https://libexpat.github.io/doc/api/latest/#XML_GE -DXML_GE=0 -DXML_CONTEXT_BYTES=1024 From 857f5badf6320d60194af886557638a3416413c9 Mon Sep 17 00:00:00 2001 From: Uri Tauber <142022451+Uri-Tauber@users.noreply.github.com> Date: Tue, 3 Mar 2026 17:57:57 +0200 Subject: [PATCH 08/11] fix: add Technically Unsupported section to SCOPE.md (#1295) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary **Goal of this PR** Add an **"In-scope — technically not supported"** section to `SCOPE.md`. This clarifies hardware/UX limitations (e.g., clock support) and is intended to reduce recurring feature requests on topics already discussed. Based on discussions in #287 and #626. --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**< NO >**_ --- SCOPE.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/SCOPE.md b/SCOPE.md index 68099787..3559ec19 100644 --- a/SCOPE.md +++ b/SCOPE.md @@ -39,6 +39,14 @@ usability over "swiss-army-knife" functionality. * **Complex Annotation:** No typed out notes. These features are better suited for devices with better input capabilities and more powerful chips. +### In-scope — Technically Unsupported + +*These features align with CrossPoint's goals but are impractical on the current hardware or produce poor UX.* + +* **Clock Display:** The ESP32-C3's RTC drifts significantly during deep sleep; making the clock untrustworthy after any sleep cycle. NTP sync could help, but CrossPoint doesn't connect to the internet on every boot. + +* **PDF Rendering:** PDFs are fixed-layout documents, so rendering them requires displaying pages as images rather than reflowable text — resulting in constant panning and zooming that makes for a poor reading experience on e-ink. + ## 3. Idea Evaluation While I appreciate the desire to add new and exciting features to CrossPoint Reader, CrossPoint Reader is designed to be From c8cbbb3ffa956dab1a6b51f628df8d28b3a9eada Mon Sep 17 00:00:00 2001 From: jpirnay Date: Tue, 3 Mar 2026 16:59:06 +0100 Subject: [PATCH 09/11] feat: User setting for image display (#1291) ## Summary **What is the goal of this PR?** Add a user setting to decide image support: display, show placeholder instead, supress fully Fixes #1289 --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**< NO >**_ --- lib/Epub/Epub/Section.cpp | 23 +++++++++++-------- lib/Epub/Epub/Section.h | 7 +++--- .../Epub/parsers/ChapterHtmlSlimParser.cpp | 9 +++++++- lib/Epub/Epub/parsers/ChapterHtmlSlimParser.h | 6 +++-- lib/I18n/translations/english.yaml | 4 ++++ src/CrossPointSettings.h | 7 ++++++ src/SettingsList.h | 3 +++ src/activities/reader/EpubReaderActivity.cpp | 6 +++-- 8 files changed, 48 insertions(+), 17 deletions(-) diff --git a/lib/Epub/Epub/Section.cpp b/lib/Epub/Epub/Section.cpp index 6e5aadf9..bc04d475 100644 --- a/lib/Epub/Epub/Section.cpp +++ b/lib/Epub/Epub/Section.cpp @@ -10,10 +10,10 @@ #include "parsers/ChapterHtmlSlimParser.h" namespace { -constexpr uint8_t SECTION_FILE_VERSION = 16; +constexpr uint8_t SECTION_FILE_VERSION = 17; constexpr uint32_t HEADER_SIZE = sizeof(uint8_t) + sizeof(int) + sizeof(float) + sizeof(bool) + sizeof(uint8_t) + sizeof(uint16_t) + sizeof(uint16_t) + sizeof(uint16_t) + sizeof(bool) + sizeof(bool) + - sizeof(uint32_t); + sizeof(uint8_t) + sizeof(uint32_t); } // namespace uint32_t Section::onPageComplete(std::unique_ptr page) { @@ -36,7 +36,7 @@ uint32_t Section::onPageComplete(std::unique_ptr page) { void Section::writeSectionFileHeader(const int fontId, const float lineCompression, const bool extraParagraphSpacing, const uint8_t paragraphAlignment, const uint16_t viewportWidth, const uint16_t viewportHeight, const bool hyphenationEnabled, - const bool embeddedStyle) { + const bool embeddedStyle, const uint8_t imageRendering) { if (!file) { LOG_DBG("SCT", "File not open for writing header"); return; @@ -44,7 +44,7 @@ void Section::writeSectionFileHeader(const int fontId, const float lineCompressi static_assert(HEADER_SIZE == sizeof(SECTION_FILE_VERSION) + sizeof(fontId) + sizeof(lineCompression) + sizeof(extraParagraphSpacing) + sizeof(paragraphAlignment) + sizeof(viewportWidth) + sizeof(viewportHeight) + sizeof(pageCount) + sizeof(hyphenationEnabled) + - sizeof(embeddedStyle) + sizeof(uint32_t), + sizeof(embeddedStyle) + sizeof(imageRendering) + sizeof(uint32_t), "Header size mismatch"); serialization::writePod(file, SECTION_FILE_VERSION); serialization::writePod(file, fontId); @@ -55,13 +55,15 @@ void Section::writeSectionFileHeader(const int fontId, const float lineCompressi serialization::writePod(file, viewportHeight); serialization::writePod(file, hyphenationEnabled); serialization::writePod(file, embeddedStyle); + serialization::writePod(file, imageRendering); serialization::writePod(file, pageCount); // Placeholder for page count (will be initially 0 when written) serialization::writePod(file, static_cast(0)); // Placeholder for LUT offset } bool Section::loadSectionFile(const int fontId, const float lineCompression, const bool extraParagraphSpacing, const uint8_t paragraphAlignment, const uint16_t viewportWidth, - const uint16_t viewportHeight, const bool hyphenationEnabled, const bool embeddedStyle) { + const uint16_t viewportHeight, const bool hyphenationEnabled, const bool embeddedStyle, + const uint8_t imageRendering) { if (!Storage.openFileForRead("SCT", filePath, file)) { return false; } @@ -84,6 +86,7 @@ bool Section::loadSectionFile(const int fontId, const float lineCompression, con uint8_t fileParagraphAlignment; bool fileHyphenationEnabled; bool fileEmbeddedStyle; + uint8_t fileImageRendering; serialization::readPod(file, fileFontId); serialization::readPod(file, fileLineCompression); serialization::readPod(file, fileExtraParagraphSpacing); @@ -92,11 +95,13 @@ bool Section::loadSectionFile(const int fontId, const float lineCompression, con serialization::readPod(file, fileViewportHeight); serialization::readPod(file, fileHyphenationEnabled); serialization::readPod(file, fileEmbeddedStyle); + serialization::readPod(file, fileImageRendering); if (fontId != fileFontId || lineCompression != fileLineCompression || extraParagraphSpacing != fileExtraParagraphSpacing || paragraphAlignment != fileParagraphAlignment || viewportWidth != fileViewportWidth || viewportHeight != fileViewportHeight || - hyphenationEnabled != fileHyphenationEnabled || embeddedStyle != fileEmbeddedStyle) { + hyphenationEnabled != fileHyphenationEnabled || embeddedStyle != fileEmbeddedStyle || + imageRendering != fileImageRendering) { file.close(); LOG_ERR("SCT", "Deserialization failed: Parameters do not match"); clearCache(); @@ -129,7 +134,7 @@ bool Section::clearCache() const { bool Section::createSectionFile(const int fontId, const float lineCompression, const bool extraParagraphSpacing, const uint8_t paragraphAlignment, const uint16_t viewportWidth, const uint16_t viewportHeight, const bool hyphenationEnabled, const bool embeddedStyle, - const std::function& popupFn) { + const uint8_t imageRendering, const std::function& popupFn) { const auto localPath = epub->getSpineItem(spineIndex).href; const auto tmpHtmlPath = epub->getCachePath() + "/.tmp_" + std::to_string(spineIndex) + ".html"; @@ -179,7 +184,7 @@ bool Section::createSectionFile(const int fontId, const float lineCompression, c return false; } writeSectionFileHeader(fontId, lineCompression, extraParagraphSpacing, paragraphAlignment, viewportWidth, - viewportHeight, hyphenationEnabled, embeddedStyle); + viewportHeight, hyphenationEnabled, embeddedStyle, imageRendering); std::vector lut = {}; // Derive the content base directory and image cache path prefix for the parser @@ -201,7 +206,7 @@ bool Section::createSectionFile(const int fontId, const float lineCompression, c epub, tmpHtmlPath, renderer, fontId, lineCompression, extraParagraphSpacing, paragraphAlignment, viewportWidth, viewportHeight, hyphenationEnabled, [this, &lut](std::unique_ptr page) { lut.emplace_back(this->onPageComplete(std::move(page))); }, - embeddedStyle, contentBase, imageBasePath, popupFn, cssParser); + embeddedStyle, contentBase, imageBasePath, imageRendering, popupFn, cssParser); Hyphenator::setPreferredLanguage(epub->getLanguage()); success = visitor.parseAndBuildPages(); diff --git a/lib/Epub/Epub/Section.h b/lib/Epub/Epub/Section.h index 42a6d993..70fd0fe5 100644 --- a/lib/Epub/Epub/Section.h +++ b/lib/Epub/Epub/Section.h @@ -16,7 +16,7 @@ class Section { void writeSectionFileHeader(int fontId, float lineCompression, bool extraParagraphSpacing, uint8_t paragraphAlignment, uint16_t viewportWidth, uint16_t viewportHeight, bool hyphenationEnabled, - bool embeddedStyle); + bool embeddedStyle, uint8_t imageRendering); uint32_t onPageComplete(std::unique_ptr page); public: @@ -30,10 +30,11 @@ class Section { filePath(epub->getCachePath() + "/sections/" + std::to_string(spineIndex) + ".bin") {} ~Section() = default; bool loadSectionFile(int fontId, float lineCompression, bool extraParagraphSpacing, uint8_t paragraphAlignment, - uint16_t viewportWidth, uint16_t viewportHeight, bool hyphenationEnabled, bool embeddedStyle); + uint16_t viewportWidth, uint16_t viewportHeight, bool hyphenationEnabled, bool embeddedStyle, + uint8_t imageRendering); bool clearCache() const; bool createSectionFile(int fontId, float lineCompression, bool extraParagraphSpacing, uint8_t paragraphAlignment, uint16_t viewportWidth, uint16_t viewportHeight, bool hyphenationEnabled, bool embeddedStyle, - const std::function& popupFn = nullptr); + uint8_t imageRendering, const std::function& popupFn = nullptr); std::unique_ptr loadPageFromSectionFile(); }; diff --git a/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp b/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp index 2753f928..6a0636bb 100644 --- a/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp +++ b/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp @@ -243,7 +243,14 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char* } } - if (!src.empty()) { + // imageRendering: 0=display, 1=placeholder (alt text only), 2=suppress entirely + if (self->imageRendering == 2) { + self->skipUntilDepth = self->depth; + self->depth += 1; + return; + } + + if (!src.empty() && self->imageRendering != 1) { LOG_DBG("EHP", "Found image: src=%s", src.c_str()); { diff --git a/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.h b/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.h index 663ce860..ce530797 100644 --- a/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.h +++ b/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.h @@ -48,6 +48,7 @@ class ChapterHtmlSlimParser { bool hyphenationEnabled; const CssParser* cssParser; bool embeddedStyle; + uint8_t imageRendering; std::string contentBase; std::string imageBasePath; int imageCounter = 0; @@ -94,8 +95,8 @@ class ChapterHtmlSlimParser { const uint16_t viewportHeight, const bool hyphenationEnabled, const std::function)>& completePageFn, const bool embeddedStyle, const std::string& contentBase, - const std::string& imageBasePath, const std::function& popupFn = nullptr, - const CssParser* cssParser = nullptr) + const std::string& imageBasePath, const uint8_t imageRendering = 0, + const std::function& popupFn = nullptr, const CssParser* cssParser = nullptr) : epub(epub), filepath(filepath), @@ -111,6 +112,7 @@ class ChapterHtmlSlimParser { popupFn(popupFn), cssParser(cssParser), embeddedStyle(embeddedStyle), + imageRendering(imageRendering), contentBase(contentBase), imageBasePath(imageBasePath) {} diff --git a/lib/I18n/translations/english.yaml b/lib/I18n/translations/english.yaml index f5b80b8e..38192cc9 100644 --- a/lib/I18n/translations/english.yaml +++ b/lib/I18n/translations/english.yaml @@ -92,6 +92,10 @@ STR_STATUS_BAR: "Status Bar" STR_HIDE_BATTERY: "Hide Battery %" STR_EXTRA_SPACING: "Extra Paragraph Spacing" STR_TEXT_AA: "Text Anti-Aliasing" +STR_IMAGES: "Images" +STR_IMAGES_DISPLAY: "Display" +STR_IMAGES_PLACEHOLDER: "Placeholder" +STR_IMAGES_SUPPRESS: "Suppress" STR_SHORT_PWR_BTN: "Short Power Button Click" STR_ORIENTATION: "Reading Orientation" STR_FRONT_BTN_LAYOUT: "Front Button Layout" diff --git a/src/CrossPointSettings.h b/src/CrossPointSettings.h index 5ba7bde2..9a0e298b 100644 --- a/src/CrossPointSettings.h +++ b/src/CrossPointSettings.h @@ -134,6 +134,9 @@ class CrossPointSettings { // UI Theme enum UI_THEME { CLASSIC = 0, LYRA = 1, LYRA_3_COVERS = 2 }; + // Image rendering in EPUB reader + enum IMAGE_RENDERING { IMAGES_DISPLAY = 0, IMAGES_PLACEHOLDER = 1, IMAGES_SUPPRESS = 2, IMAGE_RENDERING_COUNT }; + // Sleep screen settings uint8_t sleepScreen = DARK; // Sleep screen cover mode settings @@ -192,6 +195,10 @@ class CrossPointSettings { uint8_t fadingFix = 0; // Use book's embedded CSS styles for EPUB rendering (1 = enabled, 0 = disabled) uint8_t embeddedStyle = 1; + // Show hidden files/directories (starting with '.') in the file browser (0 = hidden, 1 = show) + uint8_t showHiddenFiles = 0; + // Image rendering mode in EPUB reader + uint8_t imageRendering = IMAGES_DISPLAY; ~CrossPointSettings() = default; diff --git a/src/SettingsList.h b/src/SettingsList.h index c10d58c6..f69c9e93 100644 --- a/src/SettingsList.h +++ b/src/SettingsList.h @@ -62,6 +62,9 @@ inline const std::vector& getSettingsList() { StrId::STR_CAT_READER), SettingInfo::Toggle(StrId::STR_TEXT_AA, &CrossPointSettings::textAntiAliasing, "textAntiAliasing", StrId::STR_CAT_READER), + SettingInfo::Enum(StrId::STR_IMAGES, &CrossPointSettings::imageRendering, + {StrId::STR_IMAGES_DISPLAY, StrId::STR_IMAGES_PLACEHOLDER, StrId::STR_IMAGES_SUPPRESS}, + "imageRendering", StrId::STR_CAT_READER), // --- Controls --- SettingInfo::Enum(StrId::STR_SIDE_BTN_LAYOUT, &CrossPointSettings::sideButtonLayout, {StrId::STR_PREV_NEXT, StrId::STR_NEXT_PREV}, "sideButtonLayout", StrId::STR_CAT_CONTROLS), diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index d889df5f..059d3ea1 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -571,14 +571,16 @@ void EpubReaderActivity::render(RenderLock&& lock) { if (!section->loadSectionFile(SETTINGS.getReaderFontId(), SETTINGS.getReaderLineCompression(), SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth, - viewportHeight, SETTINGS.hyphenationEnabled, SETTINGS.embeddedStyle)) { + viewportHeight, SETTINGS.hyphenationEnabled, SETTINGS.embeddedStyle, + SETTINGS.imageRendering)) { LOG_DBG("ERS", "Cache not found, building..."); const auto popupFn = [this]() { GUI.drawPopup(renderer, tr(STR_INDEXING)); }; if (!section->createSectionFile(SETTINGS.getReaderFontId(), SETTINGS.getReaderLineCompression(), SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth, - viewportHeight, SETTINGS.hyphenationEnabled, SETTINGS.embeddedStyle, popupFn)) { + viewportHeight, SETTINGS.hyphenationEnabled, SETTINGS.embeddedStyle, + SETTINGS.imageRendering, popupFn)) { LOG_ERR("ERS", "Failed to persist page data to SD"); section.reset(); return; From af4d87b7cc85d95a2cc97625fc12ac59b3d6cbc6 Mon Sep 17 00:00:00 2001 From: jpirnay Date: Tue, 3 Mar 2026 17:02:04 +0100 Subject: [PATCH 10/11] fix: Extend missing / amend existing German translations (#1226) ## Summary * **What is the goal of this PR?** Extend missing / amend existing German translations * **What changes are included?** German.yaml ## Additional Context --- --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**NO**_ Co-authored-by: Arthur Tazhitdinov --- lib/I18n/translations/german.yaml | 35 +++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/lib/I18n/translations/german.yaml b/lib/I18n/translations/german.yaml index 863c0bcb..aebff633 100644 --- a/lib/I18n/translations/german.yaml +++ b/lib/I18n/translations/german.yaml @@ -63,7 +63,7 @@ STR_MAC_ADDRESS: "MAC-Adresse:" STR_CHECKING_WIFI: "WLAN prüfen…" STR_ENTER_WIFI_PASSWORD: "WLAN-Passwort eingeben" STR_ENTER_TEXT: "Text eingeben" -STR_TO_PREFIX: "bis" +STR_TO_PREFIX: "mit " STR_CALIBRE_DISCOVERING: "Calibre finden..." STR_CALIBRE_CONNECTING_TO: "Verbinden mit" STR_CALIBRE_CONNECTED_TO: "Verbunden mit" @@ -120,7 +120,7 @@ STR_SELECT_WALLPAPER: "Bildauswahl Standby" STR_CLEAR_READING_CACHE: "Lese-Cache leeren" STR_CALIBRE: "Calibre" STR_USERNAME: "Benutzername" -STR_PASSWORD: "Passwort nötig" +STR_PASSWORD: "Passwort" STR_SYNC_SERVER_URL: "Sync-Server-URL" STR_DOCUMENT_MATCHING: "Dateizuordnung" STR_AUTHENTICATE: "Authentifizieren" @@ -169,7 +169,7 @@ STR_FRONT_LAYOUT_BCLR: "Zurück, Bst, L, R" STR_FRONT_LAYOUT_LRBC: "L, R, Zurück, Bst" STR_FRONT_LAYOUT_LBCR: "L, Zurück, Bst, R" STR_PREV_NEXT: "Zurück/Weiter" -STR_NEXT_PREV: "Weiter/Zuürck" +STR_NEXT_PREV: "Weiter/Zurück" STR_BOOKERLY: "Bookerly" STR_NOTO_SANS: "Noto Sans" STR_OPEN_DYSLEXIC: "Open Dyslexic" @@ -236,6 +236,8 @@ STR_DOWNLOAD: "Herunterladen" STR_RETRY: "Wiederh." STR_YES: "Ja" STR_NO: "Nein" +STR_SHOW: "Zeigen" +STR_HIDE: "Ausblenden" STR_STATE_ON: "An" STR_STATE_OFF: "Aus" STR_NOT_SET: "Leer" @@ -248,6 +250,21 @@ STR_CAPS_OFF: "umsch" STR_OK_BUTTON: "OK" STR_SLEEP_COVER_FILTER: "Standby-Coverfilter" STR_FILTER_CONTRAST: "Kontrast" +STR_CUSTOMISE_STATUS_BAR: "Statusleiste anpassen" +STR_CHAPTER_PAGE_COUNT: "Kapitel-Seitenanzahl" +STR_BOOK_PROGRESS_PERCENTAGE: "Buchfortschritt in %" +STR_PROGRESS_BAR: "Fortschrittsbalken" +STR_PROGRESS_BAR_THICKNESS: "Balkenstärke" +STR_PROGRESS_BAR_THIN: "Dünn" +STR_PROGRESS_BAR_MEDIUM: "Mittel" +STR_PROGRESS_BAR_THICK: "Dick" +STR_BOOK: "Buch" +STR_CHAPTER: "Kapitel" +STR_EXAMPLE_CHAPTER: "Kapitel 21" +STR_EXAMPLE_BOOK: "Buchtitel" +STR_PREVIEW: "Vorschau" +STR_TITLE: "Titel" +STR_BATTERY: "Batterie" STR_UI_THEME: "System-Design" STR_THEME_CLASSIC: "Klassisch" STR_THEME_LYRA: "Lyra" @@ -280,6 +297,7 @@ STR_GO_TO_PERCENT: "Gehe zu %" STR_GO_HOME_BUTTON: "Zum Anfang" STR_SYNC_PROGRESS: "Fortschritt synchronisieren" STR_DELETE_CACHE: "Buch-Cache leeren" +STR_DISPLAY_QR: "Seite als QR anzeigen" STR_DELETE: "Löschen" STR_CHAPTER_PREFIX: "Kapitel:" STR_PAGES_SEPARATOR: " Seiten | " @@ -289,7 +307,7 @@ STR_KBD_SHIFT_CAPS: "UMSCH" STR_KBD_LOCK: "FESTST" STR_CALIBRE_URL_HINT: "Calibre: URL um /opds ergänzen" STR_PERCENT_STEP_HINT: "links/rechts: 1% hoch/runter: 10%" -STR_SYNCING_TIME: "Zeit synchonisieren…" +STR_SYNCING_TIME: "Zeit synchronisieren…" STR_CALC_HASH: "Dokument-Hash berechnen…" STR_HASH_FAILED: "Dokument-Hash fehlgeschlagen" STR_FETCH_PROGRESS: "Externen Fortschritt abrufen..." @@ -302,8 +320,8 @@ STR_LOCAL_LABEL: "Lokal:" STR_PAGE_OVERALL_FORMAT: " Seite %d, %.2f%% insgesamt" STR_PAGE_TOTAL_OVERALL_FORMAT: " Seite %d/%d, %.2f%% insgesamt" STR_DEVICE_FROM_FORMAT: " Von: %s" -STR_APPLY_REMOTE: "Ext. Fortschritt übern." -STR_UPLOAD_LOCAL: "Lokalen Fortschritt hochl." +STR_APPLY_REMOTE: "Externen Fortschritt übernehmen" +STR_UPLOAD_LOCAL: "Lokalen Fortschritt hochladen" STR_NO_REMOTE_MSG: "Kein externer Fortschritt" STR_UPLOAD_PROMPT: "Aktuelle Position hochladen?" STR_UPLOAD_SUCCESS: "Hochgeladen!" @@ -314,3 +332,8 @@ STR_BOOK_S_STYLE: "Buch-Stil" STR_EMBEDDED_STYLE: "Eingebetteter Stil" STR_OPDS_SERVER_URL: "OPDS-Server-URL" STR_SCREENSHOT_BUTTON: "Screenshot aufnehmen" +STR_FOOTNOTES: "Fußnoten" +STR_NO_FOOTNOTES: "Keine Fußnoten auf dieser Seite" +STR_LINK: "[Link]" +STR_AUTO_TURN_ENABLED: "Auto-Umblättern aktiv: " +STR_AUTO_TURN_PAGES_PER_MIN: "Auto-Umblättern (Seiten/Min.)" From 902fd9d2f33b20b769540952a8a31770da1f28db Mon Sep 17 00:00:00 2001 From: Zach Nelson Date: Wed, 4 Mar 2026 19:48:02 -0600 Subject: [PATCH 11/11] refactor: Avoid rebuilding cache path strings (#1300) ## Summary **What is the goal of this PR?** Avoid building cache path strings twice, once to check existence of the file and a second time to delete the file. --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**NO**_ --- lib/Epub/Epub/BookMetadataCache.cpp | 10 ++++++---- lib/Epub/Epub/parsers/ContentOpfParser.cpp | 8 +++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/Epub/Epub/BookMetadataCache.cpp b/lib/Epub/Epub/BookMetadataCache.cpp index 236c6b17..3cdee0b0 100644 --- a/lib/Epub/Epub/BookMetadataCache.cpp +++ b/lib/Epub/Epub/BookMetadataCache.cpp @@ -274,11 +274,13 @@ bool BookMetadataCache::buildBookBin(const std::string& epubPath, const BookMeta } bool BookMetadataCache::cleanupTmpFiles() const { - if (Storage.exists((cachePath + tmpSpineBinFile).c_str())) { - Storage.remove((cachePath + tmpSpineBinFile).c_str()); + const auto spineBinFile = cachePath + tmpSpineBinFile; + if (Storage.exists(spineBinFile.c_str())) { + Storage.remove(spineBinFile.c_str()); } - if (Storage.exists((cachePath + tmpTocBinFile).c_str())) { - Storage.remove((cachePath + tmpTocBinFile).c_str()); + const auto tocBinFile = cachePath + tmpTocBinFile; + if (Storage.exists(tocBinFile.c_str())) { + Storage.remove(tocBinFile.c_str()); } return true; } diff --git a/lib/Epub/Epub/parsers/ContentOpfParser.cpp b/lib/Epub/Epub/parsers/ContentOpfParser.cpp index 1cf7e7e0..33518bc6 100644 --- a/lib/Epub/Epub/parsers/ContentOpfParser.cpp +++ b/lib/Epub/Epub/parsers/ContentOpfParser.cpp @@ -36,12 +36,10 @@ ContentOpfParser::~ContentOpfParser() { if (tempItemStore) { tempItemStore.close(); } - if (Storage.exists((cachePath + itemCacheFile).c_str())) { - Storage.remove((cachePath + itemCacheFile).c_str()); + const auto itemCachePath = cachePath + itemCacheFile; + if (Storage.exists(itemCachePath.c_str())) { + Storage.remove(itemCachePath.c_str()); } - itemIndex.clear(); - itemIndex.shrink_to_fit(); - useItemIndex = false; } size_t ContentOpfParser::write(const uint8_t data) { return write(&data, 1); }