From d45f355e872bf564485555d4d23ada0c6cb37948 Mon Sep 17 00:00:00 2001 From: efenner Date: Thu, 15 Jan 2026 05:14:59 -0700 Subject: [PATCH 1/7] feat: Add EPUB table omitted placeholder (#372) ## Summary * **What is the goal of this PR?**: Fix the bug I reported in https://github.com/daveallie/crosspoint-reader/issues/292 * **What changes are included?**: Instead of silently dropping table content in EPUBs., replace with an italicized '[Table omitted]' message where tables appear. ## Additional Context * Add any other information that might be helpful for the reviewer (e.g., performance implications, potential risks, specific areas to focus on). --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**PARTIALLY **_ --------- Co-authored-by: Evan Fenner Co-authored-by: Warp --- lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp b/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp index e3e08310..e540abcc 100644 --- a/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp +++ b/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp @@ -25,7 +25,7 @@ constexpr int NUM_ITALIC_TAGS = sizeof(ITALIC_TAGS) / sizeof(ITALIC_TAGS[0]); const char* IMAGE_TAGS[] = {"img"}; constexpr int NUM_IMAGE_TAGS = sizeof(IMAGE_TAGS) / sizeof(IMAGE_TAGS[0]); -const char* SKIP_TAGS[] = {"head", "table"}; +const char* SKIP_TAGS[] = {"head"}; constexpr int NUM_SKIP_TAGS = sizeof(SKIP_TAGS) / sizeof(SKIP_TAGS[0]); bool isWhitespace(const char c) { return c == ' ' || c == '\r' || c == '\n' || c == '\t'; } @@ -63,6 +63,20 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char* return; } + // Special handling for tables - show placeholder text instead of dropping silently + if (strcmp(name, "table") == 0) { + // Add placeholder text + self->startNewTextBlock(TextBlock::CENTER_ALIGN); + if (self->currentTextBlock) { + self->currentTextBlock->addWord("[Table omitted]", EpdFontFamily::ITALIC); + } + + // Skip table contents + self->skipUntilDepth = self->depth; + self->depth += 1; + return; + } + if (matches(name, IMAGE_TAGS, NUM_IMAGE_TAGS)) { // TODO: Start processing image tags self->skipUntilDepth = self->depth; From eb84bcee7cd9c0347810c46f23b2f143c7106ad3 Mon Sep 17 00:00:00 2001 From: Dave Allie Date: Thu, 15 Jan 2026 02:22:38 +1100 Subject: [PATCH 2/7] chore: Pin links2004/WebSockets version --- platformio.ini | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/platformio.ini b/platformio.ini index cbe47fe9..ef27ffd5 100644 --- a/platformio.ini +++ b/platformio.ini @@ -45,9 +45,9 @@ lib_deps = InputManager=symlink://open-x4-sdk/libs/hardware/InputManager EInkDisplay=symlink://open-x4-sdk/libs/display/EInkDisplay SDCardManager=symlink://open-x4-sdk/libs/hardware/SDCardManager - ArduinoJson @ 7.4.2 - QRCode @ 0.0.1 - links2004/WebSockets @ ^2.4.1 + bblanchon/ArduinoJson @ 7.4.2 + ricmoo/QRCode @ 0.0.1 + links2004/WebSockets @ 2.7.3 [env:default] extends = base From c1c94c0112153ece7c96656eeea145f0419c8d60 Mon Sep 17 00:00:00 2001 From: Jonas Diemer Date: Thu, 15 Jan 2026 13:21:46 +0100 Subject: [PATCH 3/7] Feature: Show img alt text (#168) Let's start small by showing the ALT text of IMG. This is rudimentary, but avoids those otherwise completely blank chapters. I feel we will need this even when we can render images if that rendering takes >1s - I would then prefer rendering optional and showing the ALT text first. --- .../Epub/parsers/ChapterHtmlSlimParser.cpp | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp b/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp index e540abcc..acddd81d 100644 --- a/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp +++ b/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp @@ -79,9 +79,26 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char* if (matches(name, IMAGE_TAGS, NUM_IMAGE_TAGS)) { // TODO: Start processing image tags - self->skipUntilDepth = self->depth; - self->depth += 1; - return; + std::string alt; + if (atts != nullptr) { + for (int i = 0; atts[i]; i += 2) { + if (strcmp(atts[i], "alt") == 0) { + alt = "[Image: " + std::string(atts[i + 1]) + "]"; + } + } + Serial.printf("[%lu] [EHP] Image alt: %s\n", millis(), alt.c_str()); + + self->startNewTextBlock(TextBlock::CENTER_ALIGN); + self->italicUntilDepth = min(self->italicUntilDepth, self->depth); + self->depth += 1; + self->characterData(userData, alt.c_str(), alt.length()); + + } else { + // Skip for now + self->skipUntilDepth = self->depth; + self->depth += 1; + return; + } } if (matches(name, SKIP_TAGS, NUM_SKIP_TAGS)) { From c98ba142e8a7028e0d07ef52ba0201c60464c121 Mon Sep 17 00:00:00 2001 From: Maeve Andrews <37351465+maeveynot@users.noreply.github.com> Date: Thu, 15 Jan 2026 06:23:36 -0600 Subject: [PATCH 4/7] fix: draw button hints correctly if orientation is not portrait (#363) ~~Quick~~ fix for https://github.com/daveallie/crosspoint-reader/issues/362 (this just applies to the chapter selection menu:) ~~If the orientation is portrait, hints as we know them make sense to draw. If the orientation is inverted, we'd have to change the order of the labels (along with everything's position), and if it's one of the landscape choices, we'd have to render the text and buttons vertically. All those other cases will be more complicated.~~ ~~Punt on this for now by only rendering if portrait.~~ Update: this now draws the hints at the physical button position no matter what the orientation is, by temporarily changing orientation to portrait. --------- Co-authored-by: Maeve Andrews --- lib/GfxRenderer/GfxRenderer.cpp | 8 +++++++- lib/GfxRenderer/GfxRenderer.h | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/GfxRenderer/GfxRenderer.cpp b/lib/GfxRenderer/GfxRenderer.cpp index 28022e90..7072fed8 100644 --- a/lib/GfxRenderer/GfxRenderer.cpp +++ b/lib/GfxRenderer/GfxRenderer.cpp @@ -468,7 +468,10 @@ int GfxRenderer::getLineHeight(const int fontId) const { } void GfxRenderer::drawButtonHints(const int fontId, const char* btn1, const char* btn2, const char* btn3, - const char* btn4) const { + const char* btn4) { + const Orientation orig_orientation = getOrientation(); + setOrientation(Orientation::Portrait); + const int pageHeight = getScreenHeight(); constexpr int buttonWidth = 106; constexpr int buttonHeight = 40; @@ -481,12 +484,15 @@ void GfxRenderer::drawButtonHints(const int fontId, const char* btn1, const char // Only draw if the label is non-empty if (labels[i] != nullptr && labels[i][0] != '\0') { const int x = buttonPositions[i]; + fillRect(x, pageHeight - buttonY, buttonWidth, buttonHeight, false); drawRect(x, pageHeight - buttonY, buttonWidth, buttonHeight); const int textWidth = getTextWidth(fontId, labels[i]); const int textX = x + (buttonWidth - 1 - textWidth) / 2; drawText(fontId, textX, pageHeight - buttonY + textYOffset, labels[i]); } } + + setOrientation(orig_orientation); } void GfxRenderer::drawSideButtonHints(const int fontId, const char* topBtn, const char* bottomBtn) const { diff --git a/lib/GfxRenderer/GfxRenderer.h b/lib/GfxRenderer/GfxRenderer.h index 9d341bcc..b1fea69b 100644 --- a/lib/GfxRenderer/GfxRenderer.h +++ b/lib/GfxRenderer/GfxRenderer.h @@ -84,7 +84,7 @@ class GfxRenderer { EpdFontFamily::Style style = EpdFontFamily::REGULAR) const; // UI Components - void drawButtonHints(int fontId, const char* btn1, const char* btn2, const char* btn3, const char* btn4) const; + void drawButtonHints(int fontId, const char* btn1, const char* btn2, const char* btn3, const char* btn4); void drawSideButtonHints(int fontId, const char* topBtn, const char* bottomBtn) const; private: From 5a55fa1c6e600e72ca6faf6c1d1d759cab10de52 Mon Sep 17 00:00:00 2001 From: Nathan James <64075030+Nathanjms@users.noreply.github.com> Date: Thu, 15 Jan 2026 12:25:18 +0000 Subject: [PATCH 5/7] fix: also apply longPressChapterSkip setting to xtc reader (#378) ## Summary * This builds upon the helpful PR https://github.com/crosspoint-reader/crosspoint-reader/pull/341, and adds support for the setting to also apply to the XTC reader, which I believed has just been missed and was not intentionally left out. * XTC does not have chapter support yet, but it does skip 10 pages when long-pressed, and so I think this is useful. --- ### AI Usage Did you use AI tools to help write this code? No --- src/activities/reader/XtcReaderActivity.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/activities/reader/XtcReaderActivity.cpp b/src/activities/reader/XtcReaderActivity.cpp index 9cdf5c97..a211e61c 100644 --- a/src/activities/reader/XtcReaderActivity.cpp +++ b/src/activities/reader/XtcReaderActivity.cpp @@ -127,7 +127,7 @@ void XtcReaderActivity::loop() { return; } - const bool skipPages = mappedInput.getHeldTime() > skipPageMs; + const bool skipPages = SETTINGS.longPressChapterSkip && mappedInput.getHeldTime() > skipPageMs; const int skipAmount = skipPages ? 10 : 1; if (prevReleased) { From 4eef2b57937cba52cbf111cd036303a90a988c70 Mon Sep 17 00:00:00 2001 From: Luke Stein <44452336+lukestein@users.noreply.github.com> Date: Thu, 15 Jan 2026 07:26:39 -0500 Subject: [PATCH 6/7] feat: Add MAC address display to WiFi Networks screen (#381) ## Summary * Implements #380, allowing the user to see the device's MAC address in order to register on wifi networks ## Additional Context * Although @markatlnk suggested showing on the settings screen, I implemented display at the bottom of the WiFi Networks selection screen (accessed via "File Transfer" > "Join a Network") since I think it makes more sense there. * Tested on my own device ![IMG_2873](https://github.com/user-attachments/assets/b82a20dc-41a0-4b21-81f1-20876aa2c6b0) --- ### 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**_ --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> --- src/activities/network/WifiSelectionActivity.cpp | 11 +++++++++++ src/activities/network/WifiSelectionActivity.h | 3 +++ 2 files changed, 14 insertions(+) diff --git a/src/activities/network/WifiSelectionActivity.cpp b/src/activities/network/WifiSelectionActivity.cpp index a8653f43..07d44418 100644 --- a/src/activities/network/WifiSelectionActivity.cpp +++ b/src/activities/network/WifiSelectionActivity.cpp @@ -37,6 +37,14 @@ void WifiSelectionActivity::onEnter() { savePromptSelection = 0; forgetPromptSelection = 0; + // Cache MAC address for display + uint8_t mac[6]; + WiFi.macAddress(mac); + char macStr[32]; + snprintf(macStr, sizeof(macStr), "MAC address: %02x-%02x-%02x-%02x-%02x-%02x", mac[0], mac[1], mac[2], mac[3], mac[4], + mac[5]); + cachedMacAddress = std::string(macStr); + // Trigger first update to show scanning message updateRequired = true; @@ -572,6 +580,9 @@ void WifiSelectionActivity::renderNetworkList() const { renderer.drawText(SMALL_FONT_ID, 20, pageHeight - 90, countStr); } + // Show MAC address above the network count and legend + renderer.drawText(SMALL_FONT_ID, 20, pageHeight - 105, cachedMacAddress.c_str()); + // Draw help text renderer.drawText(SMALL_FONT_ID, 20, pageHeight - 75, "* = Encrypted | + = Saved"); const auto labels = mappedInput.mapLabels("« Back", "Connect", "", ""); diff --git a/src/activities/network/WifiSelectionActivity.h b/src/activities/network/WifiSelectionActivity.h index 33ea26b1..0a7e7166 100644 --- a/src/activities/network/WifiSelectionActivity.h +++ b/src/activities/network/WifiSelectionActivity.h @@ -62,6 +62,9 @@ class WifiSelectionActivity final : public ActivityWithSubactivity { // Password to potentially save (from keyboard or saved credentials) std::string enteredPassword; + // Cached MAC address string for display + std::string cachedMacAddress; + // Whether network was connected using a saved password (skip save prompt) bool usedSavedPassword = false; From 21277e03eb09a0fe7f28aae7108c600c39e219c8 Mon Sep 17 00:00:00 2001 From: Luke Stein <44452336+lukestein@users.noreply.github.com> Date: Thu, 15 Jan 2026 07:27:17 -0500 Subject: [PATCH 7/7] docs: Update User Guide to reflect release 0.14.0 (#376) --- USER_GUIDE.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/USER_GUIDE.md b/USER_GUIDE.md index b411140e..0c852691 100644 --- a/USER_GUIDE.md +++ b/USER_GUIDE.md @@ -96,6 +96,10 @@ The Settings screen allows you to configure the device's behavior. There are a f - Left, Right, Back, Confirm - Left, Back, Confirm, Right - **Side Button Layout (reader)**: Swap the order of the up and down volume buttons from Previous/Next to Next/Previous. This change is only in effect when reading. +- **Long-press Chapter Skip**: Set whether long-pressing page turn buttons skip to the next/previous chapter. + - "Chapter Skip" (default) - Long-pressing skips to next/previous chapter + - "Page Scroll" - Long-pressing scrolls a page up/down +- Swap the order of the up and down volume buttons from Previous/Next to Next/Previous. This change is only in effect when reading. - **Reader Font Family**: Choose the font used for reading: - "Bookerly" (default) - Amazon's reading font - "Noto Sans" - Google's sans-serif font @@ -144,6 +148,9 @@ If the **Short Power Button Click** setting is set to "Page Turn", you can also * **Next Chapter:** Press and **hold** the **Right** (or **Volume Down**) button briefly, then release. * **Previous Chapter:** Press and **hold** the **Left** (or **Volume Up**) button briefly, then release. +This feature can be disabled in **[Settings](#35-settings)** to help avoid changing chapters by mistake. + + ### System Navigation * **Return to Book Selection:** Press **Back** to close the book and return to the **[Book Selection](#32-book-selection)** screen. * **Return to Home:** Press and **hold** the **Back** button to close the book and return to the **[Home](#31-home-screen)** screen.