From 3d25f67fd764883f82a7ba3698ae16a0403636d6 Mon Sep 17 00:00:00 2001 From: SamLaio Date: Sun, 24 May 2026 13:18:24 +0800 Subject: [PATCH 1/2] Stabilize reader rendering and WSL build --- .gitattributes | 1 + .gitignore | 3 +++ lib/Epub/Epub/blocks/ImageBlock.cpp | 2 +- .../converters/PngToFramebufferConverter.cpp | 4 +-- src/activities/home/HomeActivity.cpp | 17 ++++++++++-- src/activities/home/MyLibraryActivity.cpp | 23 +++++++++++++--- src/activities/home/MyLibraryActivity.h | 2 +- src/activities/home/RecentBooksActivity.cpp | 8 ++++++ src/activities/reader/EpubReaderActivity.cpp | 26 +++++++++++++++++-- .../EpubReaderChapterSelectionActivity.cpp | 10 +++++-- src/main.cpp | 16 ++++++++++++ 11 files changed, 98 insertions(+), 14 deletions(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfdb8b7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.sh text eol=lf diff --git a/.gitignore b/.gitignore index 53ca557..e3e4e75 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,6 @@ build_*.log *.original *.carousel release/ + +.history +RELEASE_NOTES.md diff --git a/lib/Epub/Epub/blocks/ImageBlock.cpp b/lib/Epub/Epub/blocks/ImageBlock.cpp index a15711b..1eecd7f 100644 --- a/lib/Epub/Epub/blocks/ImageBlock.cpp +++ b/lib/Epub/Epub/blocks/ImageBlock.cpp @@ -1,7 +1,7 @@ #include "ImageBlock.h" #include -#include +#include #include #include diff --git a/lib/Epub/Epub/converters/PngToFramebufferConverter.cpp b/lib/Epub/Epub/converters/PngToFramebufferConverter.cpp index 9b25557..0b4d25d 100644 --- a/lib/Epub/Epub/converters/PngToFramebufferConverter.cpp +++ b/lib/Epub/Epub/converters/PngToFramebufferConverter.cpp @@ -1,7 +1,7 @@ #include "PngToFramebufferConverter.h" #include -#include +#include #include #include #include @@ -431,4 +431,4 @@ bool PngToFramebufferConverter::supportsFormat(const std::string& extension) { c = tolower(c); } return (ext == ".png"); -} \ No newline at end of file +} diff --git a/src/activities/home/HomeActivity.cpp b/src/activities/home/HomeActivity.cpp index 8e66cce..0b26073 100644 --- a/src/activities/home/HomeActivity.cpp +++ b/src/activities/home/HomeActivity.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -21,6 +22,12 @@ //清理字体内存 #include "CustomEpdFont.h" +namespace { +void resetWatchdog() { + esp_task_wdt_reset(); +} +} // namespace + void HomeActivity::taskTrampoline(void* param) { auto* self = static_cast(param); self->displayTaskLoop(); @@ -66,7 +73,9 @@ void HomeActivity::loadRecentCovers(int coverHeight) { Rect popupRect; int progress = 0; + const int progressStep = recentBooks.empty() ? 0 : 90 / static_cast(recentBooks.size()); for (RecentBook& book : recentBooks) { + resetWatchdog(); if (!book.coverBmpPath.empty()) { std::string coverPath = UITheme::getCoverThumbPath(book.coverBmpPath, coverHeight); if (!SdMan.exists(coverPath.c_str())) { @@ -81,7 +90,8 @@ void HomeActivity::loadRecentCovers(int coverHeight) { showingLoading = true; popupRect = GUI.drawPopup(renderer, "Loading..."); } - GUI.fillPopupProgress(renderer, popupRect, 10 + progress * (90 / recentBooks.size())); + GUI.fillPopupProgress(renderer, popupRect, 10 + progress * progressStep); + resetWatchdog(); bool success = epub.generateThumbBmp(coverHeight); if (!success) { RECENT_BOOKS.updateBook(book.path, book.title, book.author, ""); @@ -99,7 +109,8 @@ void HomeActivity::loadRecentCovers(int coverHeight) { showingLoading = true; popupRect = GUI.drawPopup(renderer, "Loading..."); } - GUI.fillPopupProgress(renderer, popupRect, 10 + progress * (90 / recentBooks.size())); + GUI.fillPopupProgress(renderer, popupRect, 10 + progress * progressStep); + resetWatchdog(); bool success = xtc.generateThumbBmp(coverHeight); if (!success) { RECENT_BOOKS.updateBook(book.path, book.title, book.author, ""); @@ -251,8 +262,10 @@ void HomeActivity::displayTaskLoop() { while (true) { if (updateRequired) { updateRequired = false; + resetWatchdog(); xSemaphoreTake(renderingMutex, portMAX_DELAY); render(); + resetWatchdog(); xSemaphoreGive(renderingMutex); } vTaskDelay(10 / portTICK_PERIOD_MS); diff --git a/src/activities/home/MyLibraryActivity.cpp b/src/activities/home/MyLibraryActivity.cpp index b2a1f0b..3ca3454 100644 --- a/src/activities/home/MyLibraryActivity.cpp +++ b/src/activities/home/MyLibraryActivity.cpp @@ -2,6 +2,7 @@ #include #include +#include #include @@ -18,6 +19,10 @@ constexpr int SKIP_PAGE_MS = 700; constexpr unsigned long GO_HOME_MS = 1000; //防止误删,把删除改为长按confirm constexpr int COPY_BUF_SIZE = 256; // 256字节缓冲区,适配小运存 + +void resetWatchdog() { + esp_task_wdt_reset(); +} } // namespace //把原来几个函数加上 //删除 @@ -75,6 +80,7 @@ bool copyFile(const char* srcPath, const char* dstPath) { uint8_t buf[COPY_BUF_SIZE]; size_t readBytes = 0; while ((readBytes = srcFile.read(buf, COPY_BUF_SIZE)) > 0) { + resetWatchdog(); dstFile.write(buf, readBytes); } @@ -112,6 +118,7 @@ void searchFilesRecursive(const std::string& currentDir, const std::string& keyw char name[500]; root.rewindDirectory(); for (auto file = root.openNextFile(); file; file = root.openNextFile()) { + resetWatchdog(); file.getName(name, sizeof(name)); if (name[0] == '.' || strcmp(name, "System Volume Information") == 0 || strcmp(name, "fonts") == 0) { file.close(); @@ -252,6 +259,7 @@ void MyLibraryActivity::loadFiles() { char name[500]; for (auto file = root.openNextFile(); file; file = root.openNextFile()) { + resetWatchdog(); file.getName(name, sizeof(name)); if (name[0] == '.' || strcmp(name, "System Volume Information") == 0 || strcmp(name, "fonts") == 0) { file.close(); @@ -498,7 +506,7 @@ void MyLibraryActivity::loop() { const bool downReleased = mappedInput.wasReleased(MappedInputManager::Button::Down); const bool skipPage = mappedInput.getHeldTime() > SKIP_PAGE_MS; - const int pageItems = UITheme::getInstance().getNumberOfItemsPerPage(renderer, true, false, true, false); + const int pageItems = std::max(1, UITheme::getInstance().getNumberOfItemsPerPage(renderer, true, false, true, false)); //把文件打开的逻辑放上面了 //这里去掉了 @@ -530,16 +538,21 @@ if (mappedInput.wasReleased(MappedInputManager::Button::Back)) { const auto& displayList = isSearchMode ? searchResults : files; int listSize = static_cast(displayList.size()); + if (listSize <= 0) { + return; + } if (upReleased) { if (skipPage) { - selectorIndex = std::max(static_cast((selectorIndex / pageItems - 1) * pageItems), 0); + const int currentPage = static_cast(selectorIndex) / pageItems; + selectorIndex = static_cast(std::max((currentPage - 1) * pageItems, 0)); } else { selectorIndex = (selectorIndex + listSize - 1) % listSize; } updateRequired = true; } else if (downReleased) { if (skipPage) { - selectorIndex = std::min(static_cast((selectorIndex / pageItems + 1) * pageItems), listSize - 1); + const int currentPage = static_cast(selectorIndex) / pageItems; + selectorIndex = static_cast(std::min((currentPage + 1) * pageItems, listSize - 1)); } else { selectorIndex = (selectorIndex + 1) % listSize; } @@ -553,8 +566,10 @@ void MyLibraryActivity::displayTaskLoop() { while (true) { if (updateRequired) { updateRequired = false; + resetWatchdog(); xSemaphoreTake(renderingMutex, portMAX_DELAY); render(); + resetWatchdog(); xSemaphoreGive(renderingMutex); } vTaskDelay(10 / portTICK_PERIOD_MS); @@ -644,4 +659,4 @@ size_t MyLibraryActivity::findEntry(const std::string& name) const { for (size_t i = 0; i < files.size(); i++) if (files[i] == name) return i; return 0; -} \ No newline at end of file +} diff --git a/src/activities/home/MyLibraryActivity.h b/src/activities/home/MyLibraryActivity.h index 224f7bb..a27bcb3 100644 --- a/src/activities/home/MyLibraryActivity.h +++ b/src/activities/home/MyLibraryActivity.h @@ -64,7 +64,7 @@ class MyLibraryActivity final : public ActivityWithSubactivity { // CANCEL_SEARCH = 6 // 取消搜索 }; TopOption topSelectorIndex = TopOption::OPEN; - const int topOptionCount = 7; + const int topOptionCount = 5; char SEARCH_KEYWORD[100] = "賽博"; // 搜索关键词(示例:包含“赛博”的文件) diff --git a/src/activities/home/RecentBooksActivity.cpp b/src/activities/home/RecentBooksActivity.cpp index 01f1c45..8e96497 100644 --- a/src/activities/home/RecentBooksActivity.cpp +++ b/src/activities/home/RecentBooksActivity.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -26,6 +27,10 @@ constexpr int TITLE_BOTTOM_PADDING = 4; constexpr int SELECTION_RADIUS = 6; constexpr unsigned long SKIP_PAGE_MS = 700; +void resetWatchdog() { + esp_task_wdt_reset(); +} + int clampPercent(const int percent) { if (percent < 0) { return 0; @@ -101,6 +106,7 @@ void RecentBooksActivity::loadRecentBooks() { recentBooks.reserve(books.size()); for (const auto& book : books) { + resetWatchdog(); // Skip if file no longer exists if (!SdMan.exists(book.path.c_str())) { continue; @@ -220,8 +226,10 @@ void RecentBooksActivity::displayTaskLoop() { while (true) { if (updateRequired) { updateRequired = false; + resetWatchdog(); xSemaphoreTake(renderingMutex, portMAX_DELAY); render(); + resetWatchdog(); xSemaphoreGive(renderingMutex); } vTaskDelay(10 / portTICK_PERIOD_MS); diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index 6466588..021a7df 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include "CrossPointSettings.h" #include "CrossPointState.h" @@ -34,6 +35,10 @@ constexpr uint16_t WALLPAPER_PXC_VERSION = 1; constexpr char WALLPAPER_PXC_PATH[] = "/.crosspoint/wallpaper_bg.pxc"; constexpr uint8_t WALLPAPER_PXC_FIXED_ORIENTATION = CrossPointSettings::ORIENTATION::PORTRAIT; +void resetWatchdog() { + esp_task_wdt_reset(); +} + bool loadWallpaperPxcToFramebuffer(const std::string& pxcPath, GfxRenderer& renderer) { FsFile input; if (!SdMan.openFileForRead("SLP", pxcPath, input)) { @@ -68,6 +73,7 @@ bool loadWallpaperPxcToFramebuffer(const std::string& pxcPath, GfxRenderer& rend size_t totalRead = 0; while (totalRead < payloadSize) { + resetWatchdog(); const size_t toRead = std::min(static_cast(1024), static_cast(payloadSize - totalRead)); const int bytesRead = input.read(frameBuffer + totalRead, toRead); if (bytesRead <= 0) { @@ -105,6 +111,7 @@ bool saveWallpaperPxcFromFramebuffer(const std::string& pxcPath, GfxRenderer& re size_t totalWritten = 0; while (totalWritten < payloadSize) { + resetWatchdog(); const size_t toWrite = std::min(static_cast(1024), static_cast(payloadSize - totalWritten)); const size_t bytesWritten = output.write(frameBuffer + totalWritten, toWrite); if (bytesWritten != toWrite) { @@ -756,9 +763,12 @@ void EpubReaderActivity::displayTaskLoop() { if (updateRequired) { updateRequired = false; // 加锁保证渲染过程独占 + resetWatchdog(); xSemaphoreTake(renderingMutex, portMAX_DELAY); APP_STATE.isRenderComplete = false; // 标记渲染开始 + resetWatchdog(); renderScreen(); // 执行核心渲染逻辑 + resetWatchdog(); APP_STATE.isRenderComplete = true; // 标记渲染完成(包括 saveProgress) APP_STATE.saveToFile(); xSemaphoreGive(renderingMutex); // 释放锁 @@ -818,6 +828,7 @@ void EpubReaderActivity::renderScreen() { if (!section) { const auto filepath = epub->getSpineItem(currentSpineIndex).href; Serial.printf("[%lu] [ERS] Loading file: %s, index: %d\n", millis(), filepath.c_str(), currentSpineIndex); + resetWatchdog(); section = std::unique_ptr
(new Section(epub, currentSpineIndex, renderer)); const uint16_t viewportWidth = renderer.getScreenWidth() - orientedMarginLeft - orientedMarginRight; @@ -830,6 +841,7 @@ void EpubReaderActivity::renderScreen() { SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth, viewportHeight, SETTINGS.hyphenationEnabled,SETTINGS.wordSpacing,SETTINGS.firstlineintented, SETTINGS.embeddedStyle)) { Serial.printf("[%lu] [ERS] Cache not found, building...\n", millis()); + resetWatchdog(); const auto popupFn = [this]() { GUI.drawPopup(renderer, "Indexing..."); }; @@ -843,6 +855,7 @@ void EpubReaderActivity::renderScreen() { } else { Serial.printf("[%lu] [ERS] Cache found, skipping build...\n", millis()); } + resetWatchdog(); if (nextPageNumber == UINT16_MAX) { section->currentPage = section->pageCount - 1; @@ -875,7 +888,8 @@ void EpubReaderActivity::renderScreen() { renderer.clearScreen(); //加背景 if(SETTINGS.ReadingScreenEnabled){ - Serial.printf("[%lu] [ERS] 桌布螢幕開啟,渲染桌布螢幕\n"); + Serial.printf("[%lu] [ERS] 桌布螢幕開啟,渲染桌布螢幕\n", millis()); + resetWatchdog(); renderPngSleepScreen(renderer); } @@ -902,10 +916,13 @@ void EpubReaderActivity::renderScreen() { Serial.printf("[%lu] [ERS] Failed to load page from SD - clearing section cache\n", millis()); section->clearCache(); section.reset(); - return renderScreen(); + updateRequired = true; + return; } const auto start = millis(); + resetWatchdog(); renderContents(std::move(p), orientedMarginTop, orientedMarginRight, orientedMarginBottom, orientedMarginLeft); + resetWatchdog(); Serial.printf("[%lu] [ERS] Rendered page in %dms\n", millis(), millis() - start); } saveProgress(currentSpineIndex, section->currentPage, section->pageCount); @@ -972,9 +989,11 @@ void EpubReaderActivity::renderContents(std::unique_ptr page, const int or // Force full refresh for pages with images when anti-aliasing is on, // as grayscale tones require half refresh to display correctly bool forceFullRefresh = page->hasImages() && SETTINGS.textAntiAliasing; + resetWatchdog(); page->render(renderer, SETTINGS.getReaderFontId(), orientedMarginLeft, orientedMarginTop); applySettingMarginPreviewOverlay(); renderStatusBar(orientedMarginRight, orientedMarginBottom,orientedMarginTop, orientedMarginLeft); + resetWatchdog(); if (forceFullRefresh || pagesUntilFullRefresh <= 1) { renderer.displayBuffer(HalDisplay::HALF_REFRESH); pagesUntilFullRefresh = SETTINGS.getRefreshFrequency(); @@ -991,6 +1010,7 @@ void EpubReaderActivity::renderContents(std::unique_ptr page, const int or if (SETTINGS.textAntiAliasing) { renderer.clearScreen(0x00); renderer.setRenderMode(GfxRenderer::GRAYSCALE_LSB); + resetWatchdog(); page->render(renderer, SETTINGS.getReaderFontId(), orientedMarginLeft, orientedMarginTop); applySettingMarginPreviewOverlay(); renderer.copyGrayscaleLsbBuffers(); @@ -998,11 +1018,13 @@ void EpubReaderActivity::renderContents(std::unique_ptr page, const int or // Render and copy to MSB buffer renderer.clearScreen(0x00); renderer.setRenderMode(GfxRenderer::GRAYSCALE_MSB); + resetWatchdog(); page->render(renderer, SETTINGS.getReaderFontId(), orientedMarginLeft, orientedMarginTop); applySettingMarginPreviewOverlay(); renderer.copyGrayscaleMsbBuffers(); // display grayscale part + resetWatchdog(); renderer.displayGrayBuffer(); renderer.setRenderMode(GfxRenderer::BW); } diff --git a/src/activities/reader/EpubReaderChapterSelectionActivity.cpp b/src/activities/reader/EpubReaderChapterSelectionActivity.cpp index 3f6fd9c..949893c 100644 --- a/src/activities/reader/EpubReaderChapterSelectionActivity.cpp +++ b/src/activities/reader/EpubReaderChapterSelectionActivity.cpp @@ -2,6 +2,8 @@ #include +#include + #include "MappedInputManager.h" #include "components/UITheme.h" #include "fontIds.h" @@ -140,7 +142,9 @@ void EpubReaderChapterSelectionActivity::loop() { } else if (prevReleased) { bool isUpKey = mappedInput.wasReleased(MappedInputManager::Button::Up); if (skipPage || isUpKey) { - selectorIndex = ((selectorIndex / pageItems - 1) * pageItems + totalItems) % totalItems; + const int pageCount = (totalItems + pageItems - 1) / pageItems; + const int currentPage = selectorIndex / pageItems; + selectorIndex = ((currentPage + pageCount - 1) % pageCount) * pageItems; } else { selectorIndex = (selectorIndex + totalItems - 1) % totalItems; } @@ -148,7 +152,9 @@ void EpubReaderChapterSelectionActivity::loop() { } else if (nextReleased) { bool isDownKey = mappedInput.wasReleased(MappedInputManager::Button::Down); if (skipPage || isDownKey) { - selectorIndex = ((selectorIndex / pageItems + 1) * pageItems) % totalItems; + const int pageCount = (totalItems + pageItems - 1) / pageItems; + const int currentPage = selectorIndex / pageItems; + selectorIndex = ((currentPage + 1) % pageCount) * pageItems; } else { selectorIndex = (selectorIndex + 1) % totalItems; } diff --git a/src/main.cpp b/src/main.cpp index c94dfbf..f1c79d6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include @@ -41,6 +42,12 @@ MappedInputManager mappedInputManager(gpio); GfxRenderer renderer(display); Activity* currentActivity; +namespace { +void resetWatchdog() { + esp_task_wdt_reset(); +} +} // namespace + // Fonts EpdFont bookerly14RegularFont(¬osans_18_bold); EpdFont bookerly14BoldFont(¬osans_18_bold); @@ -149,6 +156,7 @@ void verifyPowerButtonDuration() { gpio.update(); // Needed because inputManager.isPressed() may take up to ~500ms to return the correct state while (!gpio.isPressed(HalGPIO::BTN_POWER) && millis() - start < 1000) { + resetWatchdog(); delay(10); // only wait 10ms each iteration to not delay too much in case of short configured duration. gpio.update(); } @@ -156,6 +164,7 @@ void verifyPowerButtonDuration() { t2 = millis(); if (gpio.isPressed(HalGPIO::BTN_POWER)) { do { + resetWatchdog(); delay(10); gpio.update(); } while (gpio.isPressed(HalGPIO::BTN_POWER) && gpio.getHeldTime() < calibratedPressDuration); @@ -174,6 +183,7 @@ void verifyPowerButtonDuration() { void waitForPowerRelease() { gpio.update(); while (gpio.isPressed(HalGPIO::BTN_POWER)) { + resetWatchdog(); delay(50); gpio.update(); } @@ -185,6 +195,7 @@ void enterDeepSleep() { uint32_t waitStart = millis(); const uint32_t MAX_WAIT_TIME = 5000; // 最多等5秒 while (!APP_STATE.isRenderComplete) { + resetWatchdog(); Serial.printf("[%lu] [MAIN] Waiting for main render to complete...\n", millis()); vTaskDelay(100 / portTICK_PERIOD_MS); // 每100ms检查一次 @@ -297,6 +308,7 @@ void setup() { // force serial for debugging Serial.begin(115200); delay(500); + resetWatchdog(); Serial.printf("[%lu] [DBG] setup() start - FIRMWARE DEBUG BUILD 001\n", millis()); Serial.flush(); @@ -310,6 +322,7 @@ void setup() { // Wait up to 3 seconds for Serial to be ready to catch early logs unsigned long start = millis(); while (!Serial && (millis() - start) < 3000) { + resetWatchdog(); delay(10); } } @@ -423,6 +436,7 @@ void setup() { void loop() { + resetWatchdog(); static unsigned long maxLoopDuration = 0; const unsigned long loopStartTime = millis(); static unsigned long lastMemPrint = 0; @@ -484,7 +498,9 @@ void loop() { const unsigned long activityStartTime = millis(); if (currentActivity) { + resetWatchdog(); currentActivity->loop(); + resetWatchdog(); } const unsigned long activityDuration = millis() - activityStartTime; From f3c9ff3ddfb7f4f747fb497d50d139c69450b02a Mon Sep 17 00:00:00 2001 From: SamLaio Date: Sun, 24 May 2026 14:51:26 +0800 Subject: [PATCH 2/2] Fix font subset generation on Windows --- .../source_han_sans_tc_10_regular.h | 2 +- .../source_han_sans_tc_12_regular.h | 2 +- .../source_han_sans_tc_17_regular.h | 2 +- lib/EpdFont/scripts/fontconvert.py | 4 +- lib/Xtc/Xtc.h | 1 + lib/Xtc/Xtc/XtcParser.h | 2 +- scripts/build_ui_fonts.py | 44 ++++++++++++++++--- scripts/generate_ui_font_subset.py | 13 ++++++ src/activities/reader/XtcReaderActivity.h | 2 +- 9 files changed, 61 insertions(+), 11 deletions(-) diff --git a/lib/EpdFont/builtinFonts/source_han_sans_tc_10_regular.h b/lib/EpdFont/builtinFonts/source_han_sans_tc_10_regular.h index 4c72447..00d7a8d 100644 --- a/lib/EpdFont/builtinFonts/source_han_sans_tc_10_regular.h +++ b/lib/EpdFont/builtinFonts/source_han_sans_tc_10_regular.h @@ -3,7 +3,7 @@ * name: source_han_sans_tc_10_regular * size: 10 * mode: 1-bit - * Command used: D:\RURU-ALL\Library\工具\閱星曈刷機\Carousel-繁中版\lib\EpdFont\scripts\fontconvert.py source_han_sans_tc_10_regular 10 D:\RURU-ALL\Library\工具\閱星曈刷機\Carousel-繁中版\lib\EpdFont\builtinFonts\source\SourceHanSansTC\SourceHanSansTC-Regular.otf --charset-file D:\RURU-ALL\Library\工具\閱星曈刷機\Carousel-繁中版\scripts\charsets\ui_charset_merged.txt + * Command used: fontconvert.py source_han_sans_tc_10_regular 10 SourceHanSansTC-Regular.otf --charset-file ui_charset_merged.txt */ #pragma once #include "EpdFontData.h" diff --git a/lib/EpdFont/builtinFonts/source_han_sans_tc_12_regular.h b/lib/EpdFont/builtinFonts/source_han_sans_tc_12_regular.h index 123ec14..58ae41c 100644 --- a/lib/EpdFont/builtinFonts/source_han_sans_tc_12_regular.h +++ b/lib/EpdFont/builtinFonts/source_han_sans_tc_12_regular.h @@ -3,7 +3,7 @@ * name: source_han_sans_tc_12_regular * size: 12 * mode: 1-bit - * Command used: D:\RURU-ALL\Library\工具\閱星曈刷機\Carousel-繁中版\lib\EpdFont\scripts\fontconvert.py source_han_sans_tc_12_regular 12 D:\RURU-ALL\Library\工具\閱星曈刷機\Carousel-繁中版\lib\EpdFont\builtinFonts\source\SourceHanSansTC\SourceHanSansTC-Regular.otf --charset-file D:\RURU-ALL\Library\工具\閱星曈刷機\Carousel-繁中版\scripts\charsets\ui_charset.txt + * Command used: fontconvert.py source_han_sans_tc_12_regular 12 SourceHanSansTC-Regular.otf --charset-file ui_charset.txt */ #pragma once #include "EpdFontData.h" diff --git a/lib/EpdFont/builtinFonts/source_han_sans_tc_17_regular.h b/lib/EpdFont/builtinFonts/source_han_sans_tc_17_regular.h index 7dff9ad..88506ce 100644 --- a/lib/EpdFont/builtinFonts/source_han_sans_tc_17_regular.h +++ b/lib/EpdFont/builtinFonts/source_han_sans_tc_17_regular.h @@ -3,7 +3,7 @@ * name: source_han_sans_tc_17_regular * size: 17 * mode: 1-bit - * Command used: D:\RURU-ALL\Library\工具\閱星曈刷機\Carousel-繁中版\lib\EpdFont\scripts\fontconvert.py source_han_sans_tc_17_regular 17 D:\RURU-ALL\Library\工具\閱星曈刷機\Carousel-繁中版\lib\EpdFont\builtinFonts\source\SourceHanSansTC\SourceHanSansTC-Regular.otf --charset-file D:\RURU-ALL\Library\工具\閱星曈刷機\Carousel-繁中版\scripts\charsets\ui_charset_reader.txt + * Command used: fontconvert.py source_han_sans_tc_17_regular 17 SourceHanSansTC-Regular.otf --charset-file ui_charset_reader.txt */ #pragma once #include "EpdFontData.h" diff --git a/lib/EpdFont/scripts/fontconvert.py b/lib/EpdFont/scripts/fontconvert.py index 809b827..3c5d405 100644 --- a/lib/EpdFont/scripts/fontconvert.py +++ b/lib/EpdFont/scripts/fontconvert.py @@ -7,6 +7,7 @@ import struct import argparse from collections import namedtuple +from pathlib import Path # Originally from https://github.com/vroland/epdiy @@ -437,12 +438,13 @@ def load_glyph(code_point): # ============================================================ # Output C header (original behavior) # ============================================================ + command_used = " ".join(Path(arg).name if Path(arg).is_absolute() else arg for arg in sys.argv) print(f"""/** * generated by fontconvert.py * name: {font_name} * size: {size} * mode: {'2-bit' if is2Bit else '1-bit'} - * Command used: {' '.join(sys.argv)} + * Command used: {command_used} */ #pragma once #include "EpdFontData.h" diff --git a/lib/Xtc/Xtc.h b/lib/Xtc/Xtc.h index a1153d0..c2147a7 100644 --- a/lib/Xtc/Xtc.h +++ b/lib/Xtc/Xtc.h @@ -119,6 +119,7 @@ size_t getmaxchapter(){ if (parser) { return parser->maxChapterCount; } + return 0; }; diff --git a/lib/Xtc/Xtc/XtcParser.h b/lib/Xtc/Xtc/XtcParser.h index 2bf652d..ed2e68d 100644 --- a/lib/Xtc/Xtc/XtcParser.h +++ b/lib/Xtc/Xtc/XtcParser.h @@ -76,8 +76,8 @@ std::string getChapterTitleByIndex(int chapterIndex) { Serial.printf("[%lu] [XTC] 已进入getChapterTitleByIndex,chapterActualCount=%d\n", millis(),chapterActualCount); for(int i = 0; i < 25; i++) { if(ChapterList[i].chapterIndex == chapterIndex) { - return std::string(ChapterList[i].shortTitle); Serial.printf("[%lu] [XTC] getChapterTitleByIndex里第%d章,名字为:%s %u\n", millis(), i, ChapterList[i].shortTitle); + return std::string(ChapterList[i].shortTitle); } } return ""; // 无此章节返回空字符串 diff --git a/scripts/build_ui_fonts.py b/scripts/build_ui_fonts.py index f3dfe9d..11b8366 100644 --- a/scripts/build_ui_fonts.py +++ b/scripts/build_ui_fonts.py @@ -47,6 +47,7 @@ FONT_SOURCE = ROOT / "lib" / "EpdFont" / "builtinFonts" / "source" / "SourceHanSansTC" FONT_OUTPUT_DIR = ROOT / "lib" / "EpdFont" / "builtinFonts" FONTCONVERT = ROOT / "lib" / "EpdFont" / "scripts" / "fontconvert.py" +FONTCONVERT_REQUIREMENTS = ROOT / "lib" / "EpdFont" / "scripts" / "requirements.txt" # UI / 外部文字 / reader 用不同字號,但共用常用字集,避免塞完整閱讀字集 UI_FONT_SIZES = [12] @@ -127,6 +128,28 @@ def merged_charset_hash(*charsets): return h.hexdigest() +def expected_font_outputs(): + """Return the generated font headers required by the firmware build.""" + sizes = UI_FONT_SIZES + EXTERNAL_FONT_SIZES + READER_FONT_SIZES + return [ + FONT_OUTPUT_DIR / f"source_han_sans_tc_{size}_{style}.h" + for size in sizes + for style in UI_FONT_STYLES + ] + + +def fontconvert_install_hint(): + return f"{sys.executable} -m pip install -r {FONTCONVERT_REQUIREMENTS}" + + +def can_run_fontconvert(): + try: + import freetype # noqa: F401 + return True + except ModuleNotFoundError: + return False + + def run_fontconvert(size, style, charset_file, output_path): """跑 fontconvert.py 產 epdfont .h""" font_name = f"source_han_sans_tc_{size}_{style}" @@ -148,7 +171,10 @@ def run_fontconvert(size, style, charset_file, output_path): str(charset_file), ] try: - result = subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8") + env = os.environ.copy() + env["PYTHONIOENCODING"] = "utf-8" + env["PYTHONUTF8"] = "1" + result = subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8", env=env) if result.returncode != 0: print(f" ERROR: fontconvert failed for {font_name}", file=sys.stderr) print(result.stderr, file=sys.stderr) @@ -191,15 +217,23 @@ def main(): HASH_CACHE.parent.mkdir(parents=True, exist_ok=True) if HASH_CACHE.exists(): last_hash = HASH_CACHE.read_text().strip() - expected_sizes = UI_FONT_SIZES + EXTERNAL_FONT_SIZES + READER_FONT_SIZES if last_hash == current_hash and all( - (FONT_OUTPUT_DIR / f"source_han_sans_tc_{size}_{style}.h").exists() - for size in expected_sizes - for style in UI_FONT_STYLES + path.exists() + for path in expected_font_outputs() ): print("[build_ui_fonts] No charset changes since last build, skipping font generation.") return + if not can_run_fontconvert(): + missing_outputs = [path for path in expected_font_outputs() if not path.exists()] + print("[build_ui_fonts] freetype-py is not installed for this Python.", file=sys.stderr) + print(f"[build_ui_fonts] To regenerate fonts, run: {fontconvert_install_hint()}", file=sys.stderr) + if not missing_outputs: + print("[build_ui_fonts] Existing generated font headers found; skipping regeneration.") + return + missing = ", ".join(path.name for path in missing_outputs) + raise SystemExit(f"[build_ui_fonts] Missing generated font headers: {missing}") + # 4. 跑 fontconvert 產生子集字型 print("[build_ui_fonts] Charset changed (or first build), regenerating UI fonts...") success_count = 0 diff --git a/scripts/generate_ui_font_subset.py b/scripts/generate_ui_font_subset.py index 50ff67d..366cf85 100644 --- a/scripts/generate_ui_font_subset.py +++ b/scripts/generate_ui_font_subset.py @@ -3,6 +3,7 @@ from build_ui_fonts import ( FONT_OUTPUT_DIR, + can_run_fontconvert, EXTERNAL_FONT_SIZES, READER_FONT_SIZES, UI_CHARSET, @@ -10,6 +11,8 @@ UI_CHARSET_READER, UI_FONT_SIZES, UI_FONT_STYLES, + expected_font_outputs, + fontconvert_install_hint, run_fontconvert, ) @@ -22,6 +25,16 @@ def main(): if not UI_CHARSET_READER.exists(): raise SystemExit(f"Missing charset file: {UI_CHARSET_READER}") + if not can_run_fontconvert(): + missing_outputs = [path for path in expected_font_outputs() if not path.exists()] + print("[generate_ui_font_subset] freetype-py is not installed for this Python.", flush=True) + print(f"[generate_ui_font_subset] To regenerate fonts, run: {fontconvert_install_hint()}", flush=True) + if not missing_outputs: + print("[generate_ui_font_subset] Existing generated font headers found; skipping regeneration.", flush=True) + return + missing = ", ".join(path.name for path in missing_outputs) + raise SystemExit(f"[generate_ui_font_subset] Missing generated font headers: {missing}") + success_count = 0 for size in UI_FONT_SIZES: for style in UI_FONT_STYLES: diff --git a/src/activities/reader/XtcReaderActivity.h b/src/activities/reader/XtcReaderActivity.h index 8625af2..1f3d972 100644 --- a/src/activities/reader/XtcReaderActivity.h +++ b/src/activities/reader/XtcReaderActivity.h @@ -31,7 +31,7 @@ class XtcReaderActivity final : public ActivityWithSubactivity { static void taskTrampoline(void* param); [[noreturn]] void displayTaskLoop(); void renderScreen(); - bool isRenderUpperHalf; + bool isRenderUpperHalf = false; void renderPage(bool isRenderUpperHalf); void saveProgress() const; void loadProgress();