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 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/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/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/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..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 = 14; +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/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..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()); { @@ -278,8 +285,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 +511,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); 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/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); } diff --git a/lib/I18n/translations/english.yaml b/lib/I18n/translations/english.yaml index 16239f0f..8cce1b59 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/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.)" 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" 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 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 diff --git a/src/CrossPointSettings.h b/src/CrossPointSettings.h index 16c53110..83c784c7 100644 --- a/src/CrossPointSettings.h +++ b/src/CrossPointSettings.h @@ -135,6 +135,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 @@ -193,6 +196,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 10374a1e..122fbb7d 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/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/boot_sleep/SleepActivity.cpp b/src/activities/boot_sleep/SleepActivity.cpp index d3635f70..eea0211c 100644 --- a/src/activities/boot_sleep/SleepActivity.cpp +++ b/src/activities/boot_sleep/SleepActivity.cpp @@ -182,9 +182,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 @@ -224,10 +235,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) { 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 90c20500..c32fb000 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -182,7 +182,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; } @@ -573,14 +573,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; 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/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 7d7180e5..ab9d6da7 100644 --- a/src/activities/reader/TxtReaderActivity.cpp +++ b/src/activities/reader/TxtReaderActivity.cpp @@ -135,7 +135,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 9522c3af..667935bb 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; } 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; }