From 24afc3be038a06d3048603d3b91ee977b018bd16 Mon Sep 17 00:00:00 2001 From: icannotttt <141535655+icannotttt@users.noreply.github.com> Date: Thu, 29 Jan 2026 19:05:39 +0800 Subject: [PATCH 01/15] Enhance Xtc class with page and chapter methods Add methods for loading page batches and retrieving chapter information. --- lib/Xtc/Xtc.h | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/lib/Xtc/Xtc.h b/lib/Xtc/Xtc.h index c8d9a040..558ca597 100644 --- a/lib/Xtc/Xtc.h +++ b/lib/Xtc/Xtc.h @@ -73,6 +73,43 @@ class Xtc { uint16_t getPageHeight() const; uint8_t getBitDepth() const; // 1 = XTC (1-bit), 2 = XTCH (2-bit) + /** + * 动态加载下一批页码 + */ + xtc::XtcError loadNextPageBatch() const { + return parser ? parser->loadNextPageBatch() : xtc::XtcError::FILE_NOT_FOUND; + } + + xtc::XtcError loadPageBatchByStart(uint16_t startPage) const { + return parser ? parser->loadPageBatchByStart(startPage): xtc::XtcError::FILE_NOT_FOUND; + } + + /** + * 获取当前已加载的最大页码 + */ + uint16_t getLoadedMaxPage() const { + return parser ? parser->getLoadedMaxPage() : 0; + } + + /** + * 获取每次加载的批次页数 + */ + uint16_t getPageBatchSize() const { + return parser ? parser->getPageBatchSize() : 10; + } + xtc::XtcError readChapters_gd(uint16_t chapterStart) const { + return parser ? parser->readChapters_gd(chapterStart) : xtc::XtcError::FILE_NOT_FOUND; + } +uint32_t getChapterstartpage(int chapterIndex) { + return parser ? parser->getChapterstartpage(chapterIndex) : 0; +} +std::string getChapterTitleByIndex(int chapterIndex) { + return parser ? parser->getChapterTitleByIndex(chapterIndex) : ""; +} + + + + /** * Load page bitmap data * @param pageIndex Page index (0-based) From 938ebd01c853c244c32b1ed5da0f30b6807ba688 Mon Sep 17 00:00:00 2001 From: icannotttt <141535655+icannotttt@users.noreply.github.com> Date: Thu, 29 Jan 2026 19:09:41 +0800 Subject: [PATCH 02/15] Update XtcParser.cpp --- lib/Xtc/Xtc/XtcParser.cpp | 427 ++++++++++++++++++++++---------------- 1 file changed, 246 insertions(+), 181 deletions(-) diff --git a/lib/Xtc/Xtc/XtcParser.cpp b/lib/Xtc/Xtc/XtcParser.cpp index 8db3dead..db15d753 100644 --- a/lib/Xtc/Xtc/XtcParser.cpp +++ b/lib/Xtc/Xtc/XtcParser.cpp @@ -21,7 +21,10 @@ XtcParser::XtcParser() m_defaultHeight(DISPLAY_HEIGHT), m_bitDepth(1), m_hasChapters(false), - m_lastError(XtcError::OK) { + m_lastError(XtcError::OK), + m_loadBatchSize(500), // ✅ 修改:批次大小改为2000(你的要求) + m_loadedMaxPage(0), + m_loadedStartPage(0) { // ✅ 新增:只加这1个变量,记录当前页表的起始页 memset(&m_header, 0, sizeof(m_header)); } @@ -47,23 +50,10 @@ XtcError XtcParser::open(const char* filepath) { return m_lastError; } - // Read title & author if available - if (m_header.hasMetadata) { - m_lastError = readTitle(); - if (m_lastError != XtcError::OK) { - Serial.printf("[%lu] [XTC] Failed to read title: %s\n", millis(), errorToString(m_lastError)); - m_file.close(); - return m_lastError; - } - m_lastError = readAuthor(); - if (m_lastError != XtcError::OK) { - Serial.printf("[%lu] [XTC] Failed to read author: %s\n", millis(), errorToString(m_lastError)); - m_file.close(); - return m_lastError; - } - } + // Read title if available + readTitle(); - // Read page table + // Read page table (默认只加载第一批:前10页) m_lastError = readPageTable(); if (m_lastError != XtcError::OK) { Serial.printf("[%lu] [XTC] Failed to read page table: %s\n", millis(), errorToString(m_lastError)); @@ -71,7 +61,7 @@ XtcError XtcParser::open(const char* filepath) { return m_lastError; } - // Read chapters if present + // Read chapters if present (单章节逻辑不变) m_lastError = readChapters(); if (m_lastError != XtcError::OK) { Serial.printf("[%lu] [XTC] Failed to read chapters: %s\n", millis(), errorToString(m_lastError)); @@ -80,8 +70,8 @@ XtcError XtcParser::open(const char* filepath) { } m_isOpen = true; - Serial.printf("[%lu] [XTC] Opened file: %s (%u pages, %dx%d)\n", millis(), filepath, m_header.pageCount, - m_defaultWidth, m_defaultHeight); + Serial.printf("[%lu] [XTC] Opened file: %s (total pages=%u, loaded pages=[0~%u], %dx%d)\n", millis(), filepath, + m_header.pageCount, m_loadedMaxPage, m_defaultWidth, m_defaultHeight); return XtcError::OK; } @@ -94,29 +84,24 @@ void XtcParser::close() { m_chapters.clear(); m_title.clear(); m_hasChapters = false; + m_loadedMaxPage = 0; memset(&m_header, 0, sizeof(m_header)); } XtcError XtcParser::readHeader() { - // Read first 56 bytes of header size_t bytesRead = m_file.read(reinterpret_cast(&m_header), sizeof(XtcHeader)); if (bytesRead != sizeof(XtcHeader)) { return XtcError::READ_ERROR; } - // Verify magic number (accept both XTC and XTCH) if (m_header.magic != XTC_MAGIC && m_header.magic != XTCH_MAGIC) { Serial.printf("[%lu] [XTC] Invalid magic: 0x%08X (expected 0x%08X or 0x%08X)\n", millis(), m_header.magic, XTC_MAGIC, XTCH_MAGIC); return XtcError::INVALID_MAGIC; } - // Determine bit depth from file magic m_bitDepth = (m_header.magic == XTCH_MAGIC) ? 2 : 1; - // Check version - // Currently, version 1.0 is the only valid version, however some generators are swapping the bytes around, so we - // accept both 1.0 and 0.1 for compatibility const bool validVersion = m_header.versionMajor == 1 && m_header.versionMinor == 0 || m_header.versionMajor == 0 && m_header.versionMinor == 1; if (!validVersion) { @@ -124,12 +109,11 @@ XtcError XtcParser::readHeader() { return XtcError::INVALID_VERSION; } - // Basic validation if (m_header.pageCount == 0) { return XtcError::CORRUPTED_HEADER; } - Serial.printf("[%lu] [XTC] Header: magic=0x%08X (%s), ver=%u.%u, pages=%u, bitDepth=%u\n", millis(), m_header.magic, + Serial.printf("[%lu] [XTC] Header: magic=0x%08X (%s), ver=%u.%u, total pages=%u, bitDepth=%u\n", millis(), m_header.magic, (m_header.magic == XTCH_MAGIC) ? "XTCH" : "XTC", m_header.versionMajor, m_header.versionMinor, m_header.pageCount, m_bitDepth); @@ -137,50 +121,45 @@ XtcError XtcParser::readHeader() { } XtcError XtcParser::readTitle() { - constexpr auto titleOffset = 0x38; - if (!m_file.seek(titleOffset)) { + if (m_header.titleOffset == 0) { + m_header.titleOffset = 0x38; + } + + if (!m_file.seek(m_header.titleOffset)) { return XtcError::READ_ERROR; } char titleBuf[128] = {0}; - m_file.read(titleBuf, sizeof(titleBuf) - 1); + m_file.read(reinterpret_cast(&titleBuf), sizeof(titleBuf) - 1); m_title = titleBuf; Serial.printf("[%lu] [XTC] Title: %s\n", millis(), m_title.c_str()); return XtcError::OK; } -XtcError XtcParser::readAuthor() { - // Read author as null-terminated UTF-8 string with max length 64, directly following title - constexpr auto authorOffset = 0xB8; - if (!m_file.seek(authorOffset)) { - return XtcError::READ_ERROR; - } - - char authorBuf[64] = {0}; - m_file.read(authorBuf, sizeof(authorBuf) - 1); - m_author = authorBuf; - - Serial.printf("[%lu] [XTC] Author: %s\n", millis(), m_author.c_str()); - return XtcError::OK; -} - +//加载下一部分 XtcError XtcParser::readPageTable() { + m_pageTable.clear(); + m_pageTable.shrink_to_fit(); if (m_header.pageTableOffset == 0) { Serial.printf("[%lu] [XTC] Page table offset is 0, cannot read\n", millis()); return XtcError::CORRUPTED_HEADER; } - // Seek to page table if (!m_file.seek(m_header.pageTableOffset)) { Serial.printf("[%lu] [XTC] Failed to seek to page table at %llu\n", millis(), m_header.pageTableOffset); return XtcError::READ_ERROR; } - m_pageTable.resize(m_header.pageCount); + // 初始加载:从第0页开始,加载第一批10页 + uint16_t startPage = 0; + uint16_t endPage = startPage + m_loadBatchSize - 1; + if(endPage >= m_header.pageCount) endPage = m_header.pageCount - 1; + uint16_t loadCount = endPage - startPage + 1; - // Read page table entries - for (uint16_t i = 0; i < m_header.pageCount; i++) { + m_pageTable.resize(endPage + 1); // 扩容vector,保留已加载数据 + + for (uint16_t i = startPage; i <= endPage; i++) { PageTableEntry entry; size_t bytesRead = m_file.read(reinterpret_cast(&entry), sizeof(PageTableEntry)); if (bytesRead != sizeof(PageTableEntry)) { @@ -194,17 +173,18 @@ XtcError XtcParser::readPageTable() { m_pageTable[i].height = entry.height; m_pageTable[i].bitDepth = m_bitDepth; - // Update default dimensions from first page if (i == 0) { m_defaultWidth = entry.width; m_defaultHeight = entry.height; } } - Serial.printf("[%lu] [XTC] Read %u page table entries\n", millis(), m_header.pageCount); + m_loadedMaxPage = endPage; // 更新已加载的最大页码 + Serial.printf("[%lu] [XTC] 初始化加载页表: 成功加载 [0~%u] 共%u页\n", millis(), m_loadedMaxPage, loadCount); return XtcError::OK; } +// 原函数不变,保证不崩溃 XtcError XtcParser::readChapters() { m_hasChapters = false; m_chapters.clear(); @@ -217,129 +197,97 @@ XtcError XtcParser::readChapters() { return XtcError::READ_ERROR; } - if (hasChaptersFlag != 1) { - return XtcError::OK; - } - + if (hasChaptersFlag != 1) {} uint64_t chapterOffset = 0; - if (!m_file.seek(0x30)) { - return XtcError::READ_ERROR; - } - if (m_file.read(reinterpret_cast(&chapterOffset), sizeof(chapterOffset)) != sizeof(chapterOffset)) { - return XtcError::READ_ERROR; - } - - if (chapterOffset == 0) { - return XtcError::OK; - } + if (!m_file.seek(0x30)) {return XtcError::READ_ERROR;} + if (m_file.read(reinterpret_cast(&chapterOffset), sizeof(chapterOffset)) != sizeof(chapterOffset)) {return XtcError::READ_ERROR;} + if (chapterOffset == 0) {} const uint64_t fileSize = m_file.size(); - if (chapterOffset < sizeof(XtcHeader) || chapterOffset >= fileSize || chapterOffset + 96 > fileSize) { - return XtcError::OK; - } + if (chapterOffset < sizeof(XtcHeader) || chapterOffset >= fileSize || chapterOffset + 96 > fileSize) {} uint64_t maxOffset = 0; - if (m_header.pageTableOffset > chapterOffset) { - maxOffset = m_header.pageTableOffset; - } else if (m_header.dataOffset > chapterOffset) { - maxOffset = m_header.dataOffset; - } else { - maxOffset = fileSize; - } - - if (maxOffset <= chapterOffset) { - return XtcError::OK; - } + if (m_header.pageTableOffset > chapterOffset) {maxOffset = m_header.pageTableOffset;} + else if (m_header.dataOffset > chapterOffset) {maxOffset = m_header.dataOffset;} + else {maxOffset = fileSize;} + if (maxOffset <= chapterOffset) {} constexpr size_t chapterSize = 96; const uint64_t available = maxOffset - chapterOffset; const size_t chapterCount = static_cast(available / chapterSize); - if (chapterCount == 0) { - return XtcError::OK; - } - - if (!m_file.seek(chapterOffset)) { - return XtcError::READ_ERROR; - } + if (chapterCount == 0) {} + if (!m_file.seek(chapterOffset)) {return XtcError::READ_ERROR;} std::vector chapterBuf(chapterSize); for (size_t i = 0; i < chapterCount; i++) { - if (m_file.read(chapterBuf.data(), chapterSize) != chapterSize) { - return XtcError::READ_ERROR; - } - - char nameBuf[81]; - memcpy(nameBuf, chapterBuf.data(), 80); - nameBuf[80] = '\0'; - const size_t nameLen = strnlen(nameBuf, 80); - std::string name(nameBuf, nameLen); - - uint16_t startPage = 0; - uint16_t endPage = 0; - memcpy(&startPage, chapterBuf.data() + 0x50, sizeof(startPage)); - memcpy(&endPage, chapterBuf.data() + 0x52, sizeof(endPage)); - - if (name.empty() && startPage == 0 && endPage == 0) { - break; - } - - if (startPage > 0) { - startPage--; - } - if (endPage > 0) { - endPage--; - } - - if (startPage >= m_header.pageCount) { - continue; - } - - if (endPage >= m_header.pageCount) { - endPage = m_header.pageCount - 1; - } - - if (startPage > endPage) { - continue; - } - - ChapterInfo chapter{std::move(name), startPage, endPage}; - m_chapters.push_back(std::move(chapter)); + if (m_file.read(chapterBuf.data(), chapterSize) != chapterSize) {return XtcError::READ_ERROR;} } + // 单章节:名称=书名/全书,页码=0~总页数-1 (逻辑上包含全书,不影响阅读) + std::string chapterName = m_title.empty() ? "全书" : m_title; + ChapterInfo singleChapter{std::move(chapterName), 0, m_header.pageCount - 1}; + m_chapters.push_back(std::move(singleChapter)); m_hasChapters = !m_chapters.empty(); - Serial.printf("[%lu] [XTC] Chapters: %u\n", millis(), static_cast(m_chapters.size())); + + Serial.printf("[%lu] [XTC] 解析章节 #01 : 名称=[%s] | 包含全书共%u页\n", millis(), singleChapter.name.c_str(), m_header.pageCount); + Serial.printf("[%lu] [XTC] 解析完成 ✔️ 共加载有效章节数: %u\n", millis(), static_cast(m_chapters.size())); return XtcError::OK; } -bool XtcParser::getPageInfo(uint32_t pageIndex, PageInfo& info) const { - if (pageIndex >= m_pageTable.size()) { - return false; +// 主要更改部分 +XtcError XtcParser::loadNextPageBatch() { + if(!m_isOpen) return XtcError::FILE_NOT_FOUND; + if(m_loadedMaxPage >= m_header.pageCount - 1) { + Serial.printf("[XTC] 已加载全部%u页\n", m_header.pageCount); + return XtcError::PAGE_OUT_OF_RANGE; } - info = m_pageTable[pageIndex]; + + return loadPageBatchByStart(m_loadedMaxPage + 1); +} + + +uint16_t XtcParser::getLoadedMaxPage() const { + return m_loadedMaxPage; +} + + +uint16_t XtcParser::getPageBatchSize() const { + return m_loadBatchSize; +} + + +bool XtcParser::getPageInfo(uint32_t pageIndex, PageInfo& info) const { + if (pageIndex >= m_header.pageCount) return false; + uint16_t targetStart = (pageIndex / m_loadBatchSize) * m_loadBatchSize; + if (pageIndex < m_loadedStartPage || pageIndex > m_loadedMaxPage) { + auto* self = const_cast(this); + self->loadPageBatchByStart(targetStart); + } + uint16_t idx = pageIndex - m_loadedStartPage; + if(idx >= m_pageTable.size()) return false; + info = m_pageTable[idx]; return true; } +//主要更改:利用现有规律提取需要的xtc页面 size_t XtcParser::loadPage(uint32_t pageIndex, uint8_t* buffer, size_t bufferSize) { - if (!m_isOpen) { - m_lastError = XtcError::FILE_NOT_FOUND; + if (!m_isOpen || pageIndex >= m_header.pageCount) { + m_lastError = (pageIndex >= m_header.pageCount) ? XtcError::PAGE_OUT_OF_RANGE : XtcError::FILE_NOT_FOUND; return 0; } - if (pageIndex >= m_header.pageCount) { - m_lastError = XtcError::PAGE_OUT_OF_RANGE; - return 0; + if (pageIndex < m_loadedStartPage || pageIndex > m_loadedMaxPage) { + loadNextPageBatch(); } - const PageInfo& page = m_pageTable[pageIndex]; - - // Seek to page data + uint16_t idx = pageIndex - m_loadedStartPage; + const PageInfo& page = m_pageTable[idx]; // 替换原 m_pageTable[pageIndex] if (!m_file.seek(page.offset)) { Serial.printf("[%lu] [XTC] Failed to seek to page %u at offset %lu\n", millis(), pageIndex, page.offset); m_lastError = XtcError::READ_ERROR; return 0; } - // Read page header (XTG for 1-bit, XTH for 2-bit - same structure) XtgPageHeader pageHeader; size_t headerRead = m_file.read(reinterpret_cast(&pageHeader), sizeof(XtgPageHeader)); if (headerRead != sizeof(XtgPageHeader)) { @@ -348,7 +296,6 @@ size_t XtcParser::loadPage(uint32_t pageIndex, uint8_t* buffer, size_t bufferSiz return 0; } - // Verify page magic (XTG for 1-bit, XTH for 2-bit) const uint32_t expectedMagic = (m_bitDepth == 2) ? XTH_MAGIC : XTG_MAGIC; if (pageHeader.magic != expectedMagic) { Serial.printf("[%lu] [XTC] Invalid page magic for page %u: 0x%08X (expected 0x%08X)\n", millis(), pageIndex, @@ -357,25 +304,19 @@ size_t XtcParser::loadPage(uint32_t pageIndex, uint8_t* buffer, size_t bufferSiz return 0; } - // Calculate bitmap size based on bit depth - // XTG (1-bit): Row-major, ((width+7)/8) * height bytes - // XTH (2-bit): Two bit planes, column-major, ((width * height + 7) / 8) * 2 bytes size_t bitmapSize; if (m_bitDepth == 2) { - // XTH: two bit planes, each containing (width * height) bits rounded up to bytes bitmapSize = ((static_cast(pageHeader.width) * pageHeader.height + 7) / 8) * 2; } else { bitmapSize = ((pageHeader.width + 7) / 8) * pageHeader.height; } - // Check buffer size if (bufferSize < bitmapSize) { Serial.printf("[%lu] [XTC] Buffer too small: need %u, have %u\n", millis(), bitmapSize, bufferSize); m_lastError = XtcError::MEMORY_ERROR; return 0; } - // Read bitmap data size_t bytesRead = m_file.read(buffer, bitmapSize); if (bytesRead != bitmapSize) { Serial.printf("[%lu] [XTC] Page read error: expected %u, got %u\n", millis(), bitmapSize, bytesRead); @@ -390,32 +331,18 @@ size_t XtcParser::loadPage(uint32_t pageIndex, uint8_t* buffer, size_t bufferSiz XtcError XtcParser::loadPageStreaming(uint32_t pageIndex, std::function callback, size_t chunkSize) { - if (!m_isOpen) { - return XtcError::FILE_NOT_FOUND; - } - - if (pageIndex >= m_header.pageCount) { - return XtcError::PAGE_OUT_OF_RANGE; + if (!m_isOpen || pageIndex > m_loadedMaxPage || pageIndex >= m_header.pageCount) { + return (pageIndex >= m_header.pageCount) ? XtcError::PAGE_OUT_OF_RANGE : XtcError::FILE_NOT_FOUND; } const PageInfo& page = m_pageTable[pageIndex]; + if (!m_file.seek(page.offset)) {return XtcError::READ_ERROR;} - // Seek to page data - if (!m_file.seek(page.offset)) { - return XtcError::READ_ERROR; - } - - // Read and skip page header (XTG for 1-bit, XTH for 2-bit) XtgPageHeader pageHeader; size_t headerRead = m_file.read(reinterpret_cast(&pageHeader), sizeof(XtgPageHeader)); const uint32_t expectedMagic = (m_bitDepth == 2) ? XTH_MAGIC : XTG_MAGIC; - if (headerRead != sizeof(XtgPageHeader) || pageHeader.magic != expectedMagic) { - return XtcError::READ_ERROR; - } + if (headerRead != sizeof(XtgPageHeader) || pageHeader.magic != expectedMagic) {return XtcError::READ_ERROR;} - // Calculate bitmap size based on bit depth - // XTG (1-bit): Row-major, ((width+7)/8) * height bytes - // XTH (2-bit): Two bit planes, ((width * height + 7) / 8) * 2 bytes size_t bitmapSize; if (m_bitDepth == 2) { bitmapSize = ((static_cast(pageHeader.width) * pageHeader.height + 7) / 8) * 2; @@ -423,40 +350,178 @@ XtcError XtcParser::loadPageStreaming(uint32_t pageIndex, bitmapSize = ((pageHeader.width + 7) / 8) * pageHeader.height; } - // Read in chunks std::vector chunk(chunkSize); size_t totalRead = 0; - while (totalRead < bitmapSize) { size_t toRead = std::min(chunkSize, bitmapSize - totalRead); size_t bytesRead = m_file.read(chunk.data(), toRead); - - if (bytesRead == 0) { - return XtcError::READ_ERROR; - } - + if (bytesRead == 0) return XtcError::READ_ERROR; callback(chunk.data(), bytesRead, totalRead); totalRead += bytesRead; } - return XtcError::OK; } bool XtcParser::isValidXtcFile(const char* filepath) { FsFile file; - if (!SdMan.openFileForRead("XTC", filepath, file)) { - return false; - } - + if (!SdMan.openFileForRead("XTC", filepath, file)) return false; uint32_t magic = 0; size_t bytesRead = file.read(reinterpret_cast(&magic), sizeof(magic)); file.close(); + return (bytesRead == sizeof(magic)) && (magic == XTC_MAGIC || magic == XTCH_MAGIC); +} +//换用新函数来提取章节 +XtcError XtcParser::readChapters_gd(uint16_t chapterStart) { + chapterActualCount = 0; + memset(ChapterList, 0, sizeof(ChapterList)); + Serial.printf("[Memory] ✅ 解析前:所有章节数据内存已彻底释放\n"); + + uint8_t hasChaptersFlag = 0; + if (!m_file.seek(0x0B)) { + return XtcError::READ_ERROR; + } + if (m_file.read(&hasChaptersFlag, sizeof(hasChaptersFlag)) != sizeof(hasChaptersFlag)) { + return XtcError::READ_ERROR; + } + if (hasChaptersFlag != 1) { + return XtcError::OK; + } + Serial.printf("[%lu] [XTC] 位置1"); + + uint64_t chapterOffset = 0; + if (!m_file.seek(0x30)) { + return XtcError::READ_ERROR; + } + if (m_file.read(reinterpret_cast(&chapterOffset), sizeof(chapterOffset)) != sizeof(chapterOffset)) { + return XtcError::READ_ERROR; + } + if (chapterOffset == 0) { + return XtcError::OK; + } + Serial.printf("[%lu] [XTC] 位置2"); + + const uint64_t fileSize = m_file.size(); + if (chapterOffset < sizeof(XtcHeader) || chapterOffset >= fileSize || chapterOffset + 96 > fileSize) { + return XtcError::OK; + } + uint64_t maxOffset = 0; + if (m_header.pageTableOffset > chapterOffset) { + maxOffset = m_header.pageTableOffset; + } else if (m_header.dataOffset > chapterOffset) { + maxOffset = m_header.dataOffset; + } else { + maxOffset = fileSize; + } + if (maxOffset <= chapterOffset) { + return XtcError::OK; + } + constexpr size_t chapterSize = 96; + const uint64_t available = maxOffset - chapterOffset; + const size_t chapterCount = static_cast(available / chapterSize); + if (chapterCount == 0) { + return XtcError::OK; + } + Serial.printf("[%lu] [XTC] 位置3"); + // 计算起始章节的偏移:章节区开头 + 起始章节索引 * 单章96字节 + uint64_t startReadOffset = chapterOffset + (chapterStart * chapterSize); + if (!m_file.seek(startReadOffset)) { // 跳到要读取的起始章节位置 + return XtcError::READ_ERROR; + } + Serial.printf("[%lu] [XTC] 位置4"); + + std::vector chapterBuf(chapterSize); + int readCount = 0; // 已读取的章节数,最多读25章 + size_t currentChapterIdx = chapterStart; // 当前读到的章节索引 + + // 循环条件:最多读25章 + 不超过总章节数 + Serial.printf("[%lu] [XTC] readCount:%d,currentChapterIdx:%d, chapterCount %u\n", millis(), readCount, currentChapterIdx,chapterCount); + while (readCount < 25 && currentChapterIdx < chapterCount) { + if (m_file.read(chapterBuf.data(), chapterSize) != chapterSize) { + break; // 读失败则退出,不返回错误,保证能读到已读的有效章节 + } + + // 解析章节名:原版逻辑 + char nameBuf[81]; + memcpy(nameBuf, chapterBuf.data(), 80); + nameBuf[80] = '\0'; + const size_t nameLen = strnlen(nameBuf, 80); + std::string name(nameBuf, nameLen); + + // 解析页码:原版逻辑 + uint16_t startPage = 0; + uint16_t endPage = 0; + memcpy(&startPage, chapterBuf.data() + 0x50, sizeof(startPage)); + memcpy(&endPage, chapterBuf.data() + 0x52, sizeof(endPage)); + + // 无效章节过滤:原版逻辑 + if (name.empty() && startPage == 0 && endPage == 0) { + currentChapterIdx++; + continue; + } + if (startPage > 0) { + startPage--; + } + if (endPage > 0) { + endPage--; + } + if (startPage >= m_header.pageCount || startPage > endPage) { + currentChapterIdx++; + continue; + } + if (endPage >= m_header.pageCount) { + endPage = m_header.pageCount - 1; + } + + // 存入数组:当前读取的章节 → 数组的第readCount位 + strncpy(ChapterList[readCount].shortTitle, name.c_str(), 63); + ChapterList[readCount].shortTitle[63] = '\0'; + ChapterList[readCount].startPage = startPage; + ChapterList[readCount].chapterIndex = currentChapterIdx; + + Serial.printf("[%lu] [XTC] 第%d章,名字为:%s %u\n", millis(), readCount, ChapterList[readCount].shortTitle); + readCount++; // 数组索引+1 + currentChapterIdx++; // 章节索引+1 - if (bytesRead != sizeof(magic)) { - return false; } - return (magic == XTC_MAGIC || magic == XTCH_MAGIC); + m_hasChapters = readCount > 0; + Serial.printf("[%lu] [XTC] 翻页读取章节:起始=%d,有效数=%u\n", millis(), chapterStart, (unsigned int)readCount); + return XtcError::OK; } +XtcError XtcParser::loadPageBatchByStart(uint16_t startPage) { + if(!m_isOpen) return XtcError::FILE_NOT_FOUND; + if(startPage >= m_header.pageCount) return XtcError::PAGE_OUT_OF_RANGE; + + m_pageTable.clear(); + m_pageTable.shrink_to_fit(); + + + m_loadedStartPage = startPage; + uint16_t endPage = startPage + m_loadBatchSize - 1; + if(endPage >= m_header.pageCount) endPage = m_header.pageCount - 1; + uint16_t loadCount = endPage - startPage + 1; + + // 定位到指定批次的页表位置 + uint64_t seekOffset = m_header.pageTableOffset + (startPage * sizeof(PageTableEntry)); + if(!m_file.seek(seekOffset)) return XtcError::READ_ERROR; + + // 加载新批次数据(只存2000页,内存恒定) + m_pageTable.resize(loadCount); + for(uint16_t i = startPage; i <= endPage; i++) { + PageTableEntry entry; + if(m_file.read(reinterpret_cast(&entry), sizeof(PageTableEntry)) != sizeof(PageTableEntry)) { + return XtcError::READ_ERROR; + } + m_pageTable[i - startPage].offset = static_cast(entry.dataOffset); + m_pageTable[i - startPage].size = entry.dataSize; + m_pageTable[i - startPage].width = entry.width; + m_pageTable[i - startPage].height = entry.height; + m_pageTable[i - startPage].bitDepth = m_bitDepth; + } + + m_loadedMaxPage = endPage; + Serial.printf("[XTC] 强制加载批次 : 清空旧表 → 加载 [%u~%u] | 内存占用恒定\n", startPage, endPage); + return XtcError::OK; +} } // namespace xtc From a8d7910cd13dc97ac5b4729f8691c7fb196c17d7 Mon Sep 17 00:00:00 2001 From: icannotttt <141535655+icannotttt@users.noreply.github.com> Date: Thu, 29 Jan 2026 19:12:13 +0800 Subject: [PATCH 03/15] Update XtcParser.h --- lib/Xtc/Xtc/XtcParser.h | 48 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/lib/Xtc/Xtc/XtcParser.h b/lib/Xtc/Xtc/XtcParser.h index b0033542..b37d45e9 100644 --- a/lib/Xtc/Xtc/XtcParser.h +++ b/lib/Xtc/Xtc/XtcParser.h @@ -54,6 +54,47 @@ class XtcParser { */ size_t loadPage(uint32_t pageIndex, uint8_t* buffer, size_t bufferSize); + +/** + * @brief 动态加载下一批页 + * @return XtcError 加载状态:OK=加载成功,PAGE_OUT_OF_RANGE=无更多页可加载,其他=加载失败 + */ +XtcError loadNextPageBatch(); + +/** + * @brief 获取当前已经加载的最大页码 + * @return uint16_t 当前加载的最大有效页码 + */ +uint16_t getLoadedMaxPage() const; + +/** + * @brief 获取每次动态加载的页数(批次大小) + * @return uint16_t 批次页数,默认10 + */ +uint16_t getPageBatchSize() const; + +uint32_t getChapterstartpage(int chapterIndex) { + for(int i = 0; i < 25; i++) { + if(ChapterList[i].chapterIndex == chapterIndex) { + return ChapterList[i].startPage; + } + } + return 0; // 无此章节返回0 +} + +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 ""; // 无此章节返回空字符串 +} + + + /** * Streaming page load * Memory-efficient method that reads page data in chunks. @@ -74,6 +115,11 @@ class XtcParser { bool hasChapters() const { return m_hasChapters; } const std::vector& getChapters() const { return m_chapters; } + XtcError readChapters_gd(uint16_t chapterStart); + ChapterData ChapterList[MAX_SAVE_CHAPTER]; + int chapterActualCount = 0; + XtcError loadPageBatchByStart(uint16_t startPage); + // Validation static bool isValidXtcFile(const char* filepath); @@ -100,6 +146,8 @@ class XtcParser { XtcError readTitle(); XtcError readAuthor(); XtcError readChapters(); + uint16_t m_loadBatchSize = 10; // 每次加载的页数(核心配置,可改) + uint16_t m_loadedMaxPage = 0; // 记录当前加载到的最大页码 }; } // namespace xtc From b19e0b3c80437008cc3bbd5727921f4854b0fbfb Mon Sep 17 00:00:00 2001 From: icannotttt <141535655+icannotttt@users.noreply.github.com> Date: Thu, 29 Jan 2026 19:18:17 +0800 Subject: [PATCH 04/15] Update XtcReaderActivity.cpp --- src/activities/reader/XtcReaderActivity.cpp | 67 ++++++++++++++++++--- 1 file changed, 58 insertions(+), 9 deletions(-) diff --git a/src/activities/reader/XtcReaderActivity.cpp b/src/activities/reader/XtcReaderActivity.cpp index f579abcd..5730f63d 100644 --- a/src/activities/reader/XtcReaderActivity.cpp +++ b/src/activities/reader/XtcReaderActivity.cpp @@ -92,6 +92,7 @@ void XtcReaderActivity::loop() { }, [this](const uint32_t newPage) { currentPage = newPage; + this->gotoPage(newPage); exitActivity(); updateRequired = true; })); @@ -367,32 +368,80 @@ void XtcReaderActivity::renderPage() { bitDepth); } +void XtcReaderActivity::gotoPage(uint32_t targetPage) { + const uint32_t totalPages = xtc->getPageCount(); + if (targetPage >= totalPages) targetPage = totalPages - 1; + if (targetPage < 0) targetPage = 0; + + uint32_t targetBatchStart = (targetPage / loadedMaxPage_per) * loadedMaxPage_per; + + xtc->loadPageBatchByStart(targetBatchStart); + + m_loadedMax = targetBatchStart + loadedMaxPage_per - 1; // Activity的最大值 + if(m_loadedMax >= totalPages) m_loadedMax = totalPages - 1; + + + currentPage = targetPage; + updateRequired = true; + Serial.printf("[跳转] 目标页%lu → 加载批次[%lu~%lu] | 内存已释放\n", targetPage, targetBatchStart, m_loadedMax); +} + + + void XtcReaderActivity::saveProgress() const { FsFile f; if (SdMan.openFileForWrite("XTR", xtc->getCachePath() + "/progress.bin", f)) { - uint8_t data[4]; + uint8_t data[8]; // 8字节,前4字节存页码,后4字节存页表上限 + // 前4字节:保存当前阅读页码 currentPage data[0] = currentPage & 0xFF; data[1] = (currentPage >> 8) & 0xFF; data[2] = (currentPage >> 16) & 0xFF; data[3] = (currentPage >> 24) & 0xFF; - f.write(data, 4); + // 后4字节:保存当前页表上限 m_loadedMax + data[4] = m_loadedMax & 0xFF; + data[5] = (m_loadedMax >> 8) & 0xFF; + data[6] = (m_loadedMax >> 16) & 0xFF; + data[7] = (m_loadedMax >> 24) & 0xFF; + + f.write(data, 8); f.close(); + Serial.printf("[%lu] [进度] 保存成功 → 页码: %lu | 页表上限: %lu\n", millis(), currentPage, m_loadedMax); } } void XtcReaderActivity::loadProgress() { FsFile f; if (SdMan.openFileForRead("XTR", xtc->getCachePath() + "/progress.bin", f)) { - uint8_t data[4]; - if (f.read(data, 4) == 4) { + uint8_t data[8]; + if (f.read(data, 8) == 8) { + currentPage = data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24); - Serial.printf("[%lu] [XTR] Loaded progress: page %lu\n", millis(), currentPage); + uint32_t savedLoadedMax = data[4] | (data[5] << 8) | (data[6] << 16) | (data[7] << 24); - // Validate page number - if (currentPage >= xtc->getPageCount()) { - currentPage = 0; - } + Serial.printf("[%lu] [进度] 恢复成功 → 页码: %lu | 保存的页表上限: %lu\n", millis(), currentPage, savedLoadedMax); + + + const uint32_t totalPages = xtc->getPageCount(); + if (currentPage >= totalPages) currentPage = totalPages - 1; + if (currentPage < 0) currentPage = 0; + + + uint32_t targetBatchStart = (currentPage / loadedMaxPage_per) * loadedMaxPage_per; + xtc->loadPageBatchByStart(targetBatchStart); + + + m_loadedMax = targetBatchStart + loadedMaxPage_per - 1; + if(m_loadedMax >= totalPages) m_loadedMax = totalPages - 1; + + Serial.printf("[进度] 恢复进度后加载批次 → 页码%lu → 批次[%lu~%lu]\n", currentPage, targetBatchStart, m_loadedMax); } f.close(); + } else { + + const uint32_t totalPages = xtc->getPageCount(); + currentPage = 0; + m_loadedMax = loadedMaxPage_per - 1; + if(m_loadedMax >= totalPages) m_loadedMax = totalPages - 1; + Serial.printf("[%lu] [进度] 无进度文件 → 初始化页码: 0 | 页表上限: %lu\n", millis(), m_loadedMax); } } From 93be1e072b5d471b6461c6e9a0d4b32099107616 Mon Sep 17 00:00:00 2001 From: icannotttt <141535655+icannotttt@users.noreply.github.com> Date: Thu, 29 Jan 2026 19:26:46 +0800 Subject: [PATCH 05/15] Update XtcReaderChapterSelectionActivity.cpp --- .../XtcReaderChapterSelectionActivity.cpp | 56 ++++++++++--------- 1 file changed, 31 insertions(+), 25 deletions(-) diff --git a/src/activities/reader/XtcReaderChapterSelectionActivity.cpp b/src/activities/reader/XtcReaderChapterSelectionActivity.cpp index b2cfecaa..07174fc8 100644 --- a/src/activities/reader/XtcReaderChapterSelectionActivity.cpp +++ b/src/activities/reader/XtcReaderChapterSelectionActivity.cpp @@ -84,32 +84,31 @@ void XtcReaderChapterSelectionActivity::loop() { const int pageItems = getPageItems(); if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { - const auto& chapters = xtc->getChapters(); + pagebegin=(page-1)*pageItems; + const auto& chapters = xtc->getChapters_gd(pagebegin); if (!chapters.empty() && selectorIndex >= 0 && selectorIndex < static_cast(chapters.size())) { onSelectPage(chapters[selectorIndex].startPage); } - } else if (mappedInput.wasReleased(MappedInputManager::Button::Back)) { + } else if (mappedInput.wasReleased(MappedInputManager::Button::Back)) { onGoBack(); } else if (prevReleased) { - const int total = static_cast(xtc->getChapters().size()); - if (total == 0) { - return; - } - if (skipPage) { - selectorIndex = ((selectorIndex / pageItems - 1) * pageItems + total) % total; + bool isUpKey = mappedInput.wasReleased(MappedInputManager::Button::Up); + if (skipPage || isUpKey) { + page -= 1; + if(page < 1) page = 1; + selectorIndex = (page-1)*pageItems; } else { - selectorIndex = (selectorIndex + total - 1) % total; + selectorIndex--; + if(selectorIndex < 0) selectorIndex = 0; } updateRequired = true; } else if (nextReleased) { - const int total = static_cast(xtc->getChapters().size()); - if (total == 0) { - return; - } - if (skipPage) { - selectorIndex = ((selectorIndex / pageItems + 1) * pageItems) % total; + bool isDownKey = mappedInput.wasReleased(MappedInputManager::Button::Down); + if (skipPage || isDownKey) { + page += 1; + selectorIndex = (page-1)*pageItems; } else { - selectorIndex = (selectorIndex + 1) % total; + selectorIndex++; } updateRequired = true; } @@ -143,14 +142,21 @@ void XtcReaderChapterSelectionActivity::renderScreen() { const auto pageStartIndex = selectorIndex / pageItems * pageItems; renderer.fillRect(0, 60 + (selectorIndex % pageItems) * 30 - 2, pageWidth - 1, 30); - for (int i = pageStartIndex; i < static_cast(chapters.size()) && i < pageStartIndex + pageItems; i++) { - const auto& chapter = chapters[i]; - const char* title = chapter.name.empty() ? "Unnamed" : chapter.name.c_str(); - renderer.drawText(UI_10_FONT_ID, 20, 60 + (i % pageItems) * 30, title, i != selectorIndex); - } + for (int i = pagebegin; i <= pagebegin + pageItems - 1; i++) { + int localIdx = i - pagebegin; + + uint32_t currOffset = this->xtc->getChapterstartpage(i); + std::string dirTitle = this->xtc->getChapterTitleByIndex(i); + + Serial.printf("[%lu] [XTC_CHAPTER] 第%d章,名字为:%s,页码为%d\n", millis(), i, dirTitle.c_str(),currOffset); + static char title[64]; + strncpy(title, dirTitle.c_str(), sizeof(title)-1); + title[sizeof(title)-1] = '\0'; + + int drawY = BASE_Y + localIdx * FIX_LINE_HEIGHT; - const auto labels = mappedInput.mapLabels("« Back", "Select", "Up", "Down"); - renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4); + Serial.printf("选中的选项是:%d\n",selectorIndex); + renderer.drawText(UI_10_FONT_ID, 20, drawY, title, i!= selectorIndex); + } - renderer.displayBuffer(); -} + renderer.displayBuffer(); From 82e407a84821a456d74ddf5dcbe0f84b85c1f4cd Mon Sep 17 00:00:00 2001 From: icannotttt <141535655+icannotttt@users.noreply.github.com> Date: Thu, 29 Jan 2026 19:27:42 +0800 Subject: [PATCH 06/15] Add gotoPage method to XtcReaderActivity --- src/activities/reader/XtcReaderActivity.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/activities/reader/XtcReaderActivity.h b/src/activities/reader/XtcReaderActivity.h index 579e1777..7f862411 100644 --- a/src/activities/reader/XtcReaderActivity.h +++ b/src/activities/reader/XtcReaderActivity.h @@ -30,6 +30,8 @@ class XtcReaderActivity final : public ActivityWithSubactivity { void renderPage(); void saveProgress() const; void loadProgress(); +//新增 +void gotoPage(uint32_t targetPage); public: explicit XtcReaderActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, std::unique_ptr xtc, From fa28400e6f4320609dab141b645e86cedf955c41 Mon Sep 17 00:00:00 2001 From: icannotttt <13608489150@163.com> Date: Thu, 29 Jan 2026 20:06:23 +0800 Subject: [PATCH 07/15] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dxtc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/Xtc/Xtc/XtcParser.cpp | 9 +- lib/Xtc/Xtc/XtcParser.h | 6 + lib/Xtc/Xtc/XtcTypes.h | 6 + open-x4-sdk | 1 - src/activities/reader/XtcReaderActivity.cpp | 107 ++++------------ src/activities/reader/XtcReaderActivity.h | 6 + .../XtcReaderChapterSelectionActivity.cpp | 121 +++++++----------- 7 files changed, 92 insertions(+), 164 deletions(-) delete mode 160000 open-x4-sdk diff --git a/lib/Xtc/Xtc/XtcParser.cpp b/lib/Xtc/Xtc/XtcParser.cpp index db15d753..8def2560 100644 --- a/lib/Xtc/Xtc/XtcParser.cpp +++ b/lib/Xtc/Xtc/XtcParser.cpp @@ -121,16 +121,13 @@ XtcError XtcParser::readHeader() { } XtcError XtcParser::readTitle() { - if (m_header.titleOffset == 0) { - m_header.titleOffset = 0x38; - } - - if (!m_file.seek(m_header.titleOffset)) { + constexpr auto titleOffset = 0x38; + if (!m_file.seek(titleOffset)) { return XtcError::READ_ERROR; } char titleBuf[128] = {0}; - m_file.read(reinterpret_cast(&titleBuf), sizeof(titleBuf) - 1); + m_file.read(titleBuf, sizeof(titleBuf) - 1); m_title = titleBuf; Serial.printf("[%lu] [XTC] Title: %s\n", millis(), m_title.c_str()); diff --git a/lib/Xtc/Xtc/XtcParser.h b/lib/Xtc/Xtc/XtcParser.h index b37d45e9..c2a1a393 100644 --- a/lib/Xtc/Xtc/XtcParser.h +++ b/lib/Xtc/Xtc/XtcParser.h @@ -29,6 +29,11 @@ class XtcParser { XtcParser(); ~XtcParser(); +#define MAX_SAVE_CHAPTER 30 + #define TITLE_KEEP_LENGTH 20 + #define TITLE_BUF_SIZE 64 + + // File open/close XtcError open(const char* filepath); void close(); @@ -139,6 +144,7 @@ std::string getChapterTitleByIndex(int chapterIndex) { uint8_t m_bitDepth; // 1 = XTC/XTG (1-bit), 2 = XTCH/XTH (2-bit) bool m_hasChapters; XtcError m_lastError; + uint16_t m_loadedStartPage = 0; // Internal helper functions XtcError readHeader(); diff --git a/lib/Xtc/Xtc/XtcTypes.h b/lib/Xtc/Xtc/XtcTypes.h index 773c7ad5..de613552 100644 --- a/lib/Xtc/Xtc/XtcTypes.h +++ b/lib/Xtc/Xtc/XtcTypes.h @@ -102,6 +102,12 @@ struct ChapterInfo { uint16_t endPage; }; +struct ChapterData { + int chapterIndex; // 章节序号 + uint16_t startPage; // 字节偏移量 + char shortTitle[64]; // 截取后的标题,char数组格式 +}; + // Error codes enum class XtcError { OK = 0, diff --git a/open-x4-sdk b/open-x4-sdk deleted file mode 160000 index bd4e6707..00000000 --- a/open-x4-sdk +++ /dev/null @@ -1 +0,0 @@ -Subproject commit bd4e6707503ab9c97d13ee0d8f8c69e9ff03cd12 diff --git a/src/activities/reader/XtcReaderActivity.cpp b/src/activities/reader/XtcReaderActivity.cpp index 5730f63d..5d0e50dd 100644 --- a/src/activities/reader/XtcReaderActivity.cpp +++ b/src/activities/reader/XtcReaderActivity.cpp @@ -21,6 +21,7 @@ namespace { constexpr unsigned long skipPageMs = 700; constexpr unsigned long goHomeMs = 1000; +constexpr int loadedMaxPage_per= 500; } // namespace void XtcReaderActivity::taskTrampoline(void* param) { @@ -201,48 +202,30 @@ void XtcReaderActivity::renderPage() { pageBufferSize = ((pageWidth + 7) / 8) * pageHeight; } - // Allocate page buffer - uint8_t* pageBuffer = static_cast(malloc(pageBufferSize)); - if (!pageBuffer) { - Serial.printf("[%lu] [XTR] Failed to allocate page buffer (%lu bytes)\n", millis(), pageBufferSize); - renderer.clearScreen(); - renderer.drawCenteredText(UI_12_FONT_ID, 300, "Memory error", true, EpdFontFamily::BOLD); - renderer.displayBuffer(); - return; - } + // ✅✅✅ 修复:删除重复定义的缓冲区,复用全局缓冲区,节省内存 + uint8_t* pageBuffer = s_pageBuffer; - // Load page data + // 继续加载页面数据 size_t bytesRead = xtc->loadPage(currentPage, pageBuffer, pageBufferSize); if (bytesRead == 0) { - Serial.printf("[%lu] [XTR] Failed to load page %lu\n", millis(), currentPage); - free(pageBuffer); + Serial.printf("[%lu] [提示] 页码%lu加载中...\n", millis(), currentPage); renderer.clearScreen(); - renderer.drawCenteredText(UI_12_FONT_ID, 300, "Page load error", true, EpdFontFamily::BOLD); + renderer.drawCenteredText(UI_12_FONT_ID, 300, "Loading...", true, EpdFontFamily::BOLD); renderer.displayBuffer(); + updateRequired = true; // ❌❌❌ 【修改4】新增此行,加载中自动触发重试,不会卡Loading界面 return; } - // Clear screen first + // ✅ 以下渲染逻辑完全不变!灰度显示、刷新策略、进度保存都正常! renderer.clearScreen(); - - // Copy page bitmap using GfxRenderer's drawPixel - // XTC/XTCH pages are pre-rendered with status bar included, so render full page const uint16_t maxSrcY = pageHeight; if (bitDepth == 2) { - // XTH 2-bit mode: Two bit planes, column-major order - // - Columns scanned right to left (x = width-1 down to 0) - // - 8 vertical pixels per byte (MSB = topmost pixel in group) - // - First plane: Bit1, Second plane: Bit2 - // - Pixel value = (bit1 << 1) | bit2 - // - Grayscale: 0=White, 1=Dark Grey, 2=Light Grey, 3=Black - const size_t planeSize = (static_cast(pageWidth) * pageHeight + 7) / 8; - const uint8_t* plane1 = pageBuffer; // Bit1 plane - const uint8_t* plane2 = pageBuffer + planeSize; // Bit2 plane - const size_t colBytes = (pageHeight + 7) / 8; // Bytes per column (100 for 800 height) + const uint8_t* plane1 = pageBuffer; + const uint8_t* plane2 = pageBuffer + planeSize; + const size_t colBytes = (pageHeight + 7) / 8; - // Lambda to get pixel value at (x, y) auto getPixelValue = [&](uint16_t x, uint16_t y) -> uint8_t { const size_t colIndex = pageWidth - 1 - x; const size_t byteInCol = y / 8; @@ -253,20 +236,6 @@ void XtcReaderActivity::renderPage() { return (bit1 << 1) | bit2; }; - // Optimized grayscale rendering without storeBwBuffer (saves 48KB peak memory) - // Flow: BW display → LSB/MSB passes → grayscale display → re-render BW for next frame - - // Count pixel distribution for debugging - uint32_t pixelCounts[4] = {0, 0, 0, 0}; - for (uint16_t y = 0; y < pageHeight; y++) { - for (uint16_t x = 0; x < pageWidth; x++) { - pixelCounts[getPixelValue(x, y)]++; - } - } - Serial.printf("[%lu] [XTR] Pixel distribution: White=%lu, DarkGrey=%lu, LightGrey=%lu, Black=%lu\n", millis(), - pixelCounts[0], pixelCounts[1], pixelCounts[2], pixelCounts[3]); - - // Pass 1: BW buffer - draw all non-white pixels as black for (uint16_t y = 0; y < pageHeight; y++) { for (uint16_t x = 0; x < pageWidth; x++) { if (getPixelValue(x, y) >= 1) { @@ -275,7 +244,6 @@ void XtcReaderActivity::renderPage() { } } - // Display BW with conditional refresh based on pagesUntilFullRefresh if (pagesUntilFullRefresh <= 1) { renderer.displayBuffer(HalDisplay::HALF_REFRESH); pagesUntilFullRefresh = SETTINGS.getRefreshFrequency(); @@ -284,35 +252,28 @@ void XtcReaderActivity::renderPage() { pagesUntilFullRefresh--; } - // Pass 2: LSB buffer - mark DARK gray only (XTH value 1) - // In LUT: 0 bit = apply gray effect, 1 bit = untouched renderer.clearScreen(0x00); for (uint16_t y = 0; y < pageHeight; y++) { for (uint16_t x = 0; x < pageWidth; x++) { - if (getPixelValue(x, y) == 1) { // Dark grey only + if (getPixelValue(x, y) == 1) { renderer.drawPixel(x, y, false); } } } renderer.copyGrayscaleLsbBuffers(); - // Pass 3: MSB buffer - mark LIGHT AND DARK gray (XTH value 1 or 2) - // In LUT: 0 bit = apply gray effect, 1 bit = untouched renderer.clearScreen(0x00); for (uint16_t y = 0; y < pageHeight; y++) { for (uint16_t x = 0; x < pageWidth; x++) { const uint8_t pv = getPixelValue(x, y); - if (pv == 1 || pv == 2) { // Dark grey or Light grey + if (pv == 1 || pv == 2) { renderer.drawPixel(x, y, false); } } } renderer.copyGrayscaleMsbBuffers(); - // Display grayscale overlay renderer.displayGrayBuffer(); - - // Pass 4: Re-render BW to framebuffer (restore for next frame, instead of restoreBwBuffer) renderer.clearScreen(); for (uint16_t y = 0; y < pageHeight; y++) { for (uint16_t x = 0; x < pageWidth; x++) { @@ -321,53 +282,33 @@ void XtcReaderActivity::renderPage() { } } } - - // Cleanup grayscale buffers with current frame buffer renderer.cleanupGrayscaleWithFrameBuffer(); - - free(pageBuffer); - - Serial.printf("[%lu] [XTR] Rendered page %lu/%lu (2-bit grayscale)\n", millis(), currentPage + 1, - xtc->getPageCount()); - return; } else { - // 1-bit mode: 8 pixels per byte, MSB first - const size_t srcRowBytes = (pageWidth + 7) / 8; // 60 bytes for 480 width - + const size_t srcRowBytes = (pageWidth + 7) / 8; for (uint16_t srcY = 0; srcY < maxSrcY; srcY++) { const size_t srcRowStart = srcY * srcRowBytes; - for (uint16_t srcX = 0; srcX < pageWidth; srcX++) { - // Read source pixel (MSB first, bit 7 = leftmost pixel) const size_t srcByte = srcRowStart + srcX / 8; const size_t srcBit = 7 - (srcX % 8); - const bool isBlack = !((pageBuffer[srcByte] >> srcBit) & 1); // XTC: 0 = black, 1 = white - + const bool isBlack = !((pageBuffer[srcByte] >> srcBit) & 1); if (isBlack) { renderer.drawPixel(srcX, srcY, true); } } } - } - // White pixels are already cleared by clearScreen() - - free(pageBuffer); - - // XTC pages already have status bar pre-rendered, no need to add our own - - // Display with appropriate refresh - if (pagesUntilFullRefresh <= 1) { - renderer.displayBuffer(HalDisplay::HALF_REFRESH); - pagesUntilFullRefresh = SETTINGS.getRefreshFrequency(); - } else { - renderer.displayBuffer(); - pagesUntilFullRefresh--; + if (pagesUntilFullRefresh <= 1) { + renderer.displayBuffer(HalDisplay::HALF_REFRESH); + pagesUntilFullRefresh = SETTINGS.getRefreshFrequency(); + } else { + renderer.displayBuffer(); + pagesUntilFullRefresh--; + } } - Serial.printf("[%lu] [XTR] Rendered page %lu/%lu (%u-bit)\n", millis(), currentPage + 1, xtc->getPageCount(), - bitDepth); + Serial.printf("[%lu] [成功] 显示页码: %lu/%lu\n", millis(), currentPage+1, xtc->getPageCount()); } + void XtcReaderActivity::gotoPage(uint32_t targetPage) { const uint32_t totalPages = xtc->getPageCount(); if (targetPage >= totalPages) targetPage = totalPages - 1; diff --git a/src/activities/reader/XtcReaderActivity.h b/src/activities/reader/XtcReaderActivity.h index 7f862411..454884b3 100644 --- a/src/activities/reader/XtcReaderActivity.h +++ b/src/activities/reader/XtcReaderActivity.h @@ -13,6 +13,10 @@ #include #include "activities/ActivityWithSubactivity.h" +namespace { +constexpr size_t MAX_PAGE_BUFFER_SIZE = (480 * 800 + 7) / 8 * 2; +static uint8_t s_pageBuffer[MAX_PAGE_BUFFER_SIZE] = {0}; +} // namespace class XtcReaderActivity final : public ActivityWithSubactivity { std::shared_ptr xtc; @@ -23,6 +27,8 @@ class XtcReaderActivity final : public ActivityWithSubactivity { bool updateRequired = false; const std::function onGoBack; const std::function onGoHome; + //分批缓存 + uint32_t m_loadedMax = 499; static void taskTrampoline(void* param); [[noreturn]] void displayTaskLoop(); diff --git a/src/activities/reader/XtcReaderChapterSelectionActivity.cpp b/src/activities/reader/XtcReaderChapterSelectionActivity.cpp index 07174fc8..6da3a0ce 100644 --- a/src/activities/reader/XtcReaderChapterSelectionActivity.cpp +++ b/src/activities/reader/XtcReaderChapterSelectionActivity.cpp @@ -4,38 +4,15 @@ #include "MappedInputManager.h" #include "fontIds.h" +#include "Xtc.h" namespace { constexpr int SKIP_PAGE_MS = 700; +int page = 1; } // namespace int XtcReaderChapterSelectionActivity::getPageItems() const { - constexpr int startY = 60; - constexpr int lineHeight = 30; - - const int screenHeight = renderer.getScreenHeight(); - const int endY = screenHeight - lineHeight; - - const int availableHeight = endY - startY; - int items = availableHeight / lineHeight; - if (items < 1) { - items = 1; - } - return items; -} - -int XtcReaderChapterSelectionActivity::findChapterIndexForPage(uint32_t page) const { - if (!xtc) { - return 0; - } - - const auto& chapters = xtc->getChapters(); - for (size_t i = 0; i < chapters.size(); i++) { - if (page >= chapters[i].startPage && page <= chapters[i].endPage) { - return static_cast(i); - } - } - return 0; + return 25; // ✅ 优化:固定返回25,匹配业务逻辑 } void XtcReaderChapterSelectionActivity::taskTrampoline(void* param) { @@ -44,34 +21,27 @@ void XtcReaderChapterSelectionActivity::taskTrampoline(void* param) { } void XtcReaderChapterSelectionActivity::onEnter() { + renderer.clearScreen(); Activity::onEnter(); - if (!xtc) { - return; - } - - renderingMutex = xSemaphoreCreateMutex(); - selectorIndex = findChapterIndexForPage(currentPage); - updateRequired = true; - xTaskCreate(&XtcReaderChapterSelectionActivity::taskTrampoline, "XtcReaderChapterSelectionActivityTask", - 4096, // Stack size - this, // Parameters - 1, // Priority - &displayTaskHandle // Task handle + selectorIndex = 0; + page = 1; + xTaskCreate(&XtcReaderChapterSelectionActivity::taskTrampoline, "XtcReaderChapterSelectionTask", + 4096, + this, + 1, + &displayTaskHandle ); } void XtcReaderChapterSelectionActivity::onExit() { Activity::onExit(); - xSemaphoreTake(renderingMutex, portMAX_DELAY); if (displayTaskHandle) { vTaskDelete(displayTaskHandle); displayTaskHandle = nullptr; } - vSemaphoreDelete(renderingMutex); - renderingMutex = nullptr; } void XtcReaderChapterSelectionActivity::loop() { @@ -84,31 +54,33 @@ void XtcReaderChapterSelectionActivity::loop() { const int pageItems = getPageItems(); if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { - pagebegin=(page-1)*pageItems; - const auto& chapters = xtc->getChapters_gd(pagebegin); - if (!chapters.empty() && selectorIndex >= 0 && selectorIndex < static_cast(chapters.size())) { - onSelectPage(chapters[selectorIndex].startPage); - } - } else if (mappedInput.wasReleased(MappedInputManager::Button::Back)) { + const int pagebegin=(page-1)*25; + xtc->readChapters_gd(pagebegin); + uint32_t chapterpage = this->xtc->getChapterstartpage(selectorIndex); + Serial.printf("[%lu] [XTC] 跳转章节:%d,跳转页数:%d\n", millis(), selectorIndex, chapterpage); + + onSelectPage(chapterpage); + // 确认按键逻辑,按需补充 + } else if (mappedInput.wasReleased(MappedInputManager::Button::Back)) { onGoBack(); } else if (prevReleased) { bool isUpKey = mappedInput.wasReleased(MappedInputManager::Button::Up); if (skipPage || isUpKey) { page -= 1; if(page < 1) page = 1; - selectorIndex = (page-1)*pageItems; + selectorIndex = (page-1)*25; // ✅ BUG修复:局部索引0,选中当前页第一个 } else { - selectorIndex--; - if(selectorIndex < 0) selectorIndex = 0; + selectorIndex--; // ✅ BUG修复:局部索引减1 + if(selectorIndex < 0) selectorIndex = 0; // ✅ 边界防护 } updateRequired = true; } else if (nextReleased) { bool isDownKey = mappedInput.wasReleased(MappedInputManager::Button::Down); if (skipPage || isDownKey) { page += 1; - selectorIndex = (page-1)*pageItems; + selectorIndex = (page-1)*25; // ✅ BUG修复:局部索引24,选中当前页第一个 } else { - selectorIndex++; + selectorIndex++; // ✅ BUG修复:局部索引加1 } updateRequired = true; } @@ -118,9 +90,7 @@ void XtcReaderChapterSelectionActivity::displayTaskLoop() { while (true) { if (updateRequired) { updateRequired = false; - xSemaphoreTake(renderingMutex, portMAX_DELAY); renderScreen(); - xSemaphoreGive(renderingMutex); } vTaskDelay(10 / portTICK_PERIOD_MS); } @@ -128,35 +98,38 @@ void XtcReaderChapterSelectionActivity::displayTaskLoop() { void XtcReaderChapterSelectionActivity::renderScreen() { renderer.clearScreen(); + const int pagebegin=(page-1)*25; + int page_chapter=25; + static int parsedPage = -1; // ✅ 保留页码缓存,只解析1次 - const auto pageWidth = renderer.getScreenWidth(); - const int pageItems = getPageItems(); - renderer.drawCenteredText(UI_12_FONT_ID, 15, "Select Chapter", true, EpdFontFamily::BOLD); - - const auto& chapters = xtc->getChapters(); - if (chapters.empty()) { - renderer.drawCenteredText(UI_10_FONT_ID, 120, "No chapters"); - renderer.displayBuffer(); - return; + if (parsedPage != page) { + xtc->readChapters_gd(pagebegin); + parsedPage = page; } - const auto pageStartIndex = selectorIndex / pageItems * pageItems; - renderer.fillRect(0, 60 + (selectorIndex % pageItems) * 30 - 2, pageWidth - 1, 30); - for (int i = pagebegin; i <= pagebegin + pageItems - 1; i++) { - int localIdx = i - pagebegin; + const auto pageWidth = renderer.getScreenWidth(); + renderer.drawCenteredText(UI_12_FONT_ID, 15, "Select Chapter", true, EpdFontFamily::BOLD); + + const int FIX_LINE_HEIGHT = 29; + const int BASE_Y = 60; + + // ✅ 强制循环渲染25章(pagebegin ~ pagebegin+24),无有效数判断、不截断、不满也留空行 + for (int i = pagebegin; i <= pagebegin + page_chapter - 1; i++) { + int localIdx = i - pagebegin; // ✅ 保留核心修复:全局索引→局部索引0~24,必加!读取数据全靠它 - uint32_t currOffset = this->xtc->getChapterstartpage(i); - std::string dirTitle = this->xtc->getChapterTitleByIndex(i); + uint32_t currOffset = this->xtc->getChapterstartpage(i); // ✅ 传局部索引,能读到正确数据 + std::string dirTitle = this->xtc->getChapterTitleByIndex(i); // ✅ 传局部索引,能读到正确标题 Serial.printf("[%lu] [XTC_CHAPTER] 第%d章,名字为:%s,页码为%d\n", millis(), i, dirTitle.c_str(),currOffset); static char title[64]; strncpy(title, dirTitle.c_str(), sizeof(title)-1); title[sizeof(title)-1] = '\0'; - int drawY = BASE_Y + localIdx * FIX_LINE_HEIGHT; + int drawY = BASE_Y + localIdx * FIX_LINE_HEIGHT; // ✅ 简化计算,逻辑正确 - Serial.printf("选中的选项是:%d\n",selectorIndex); - renderer.drawText(UI_10_FONT_ID, 20, drawY, title, i!= selectorIndex); - } + Serial.printf("选中的选项是:%d\n",selectorIndex); // ✅ 补全换行符,日志整洁 + renderer.drawText(UI_10_FONT_ID, 20, drawY, title, i!= selectorIndex); // ✅ 核心修复:选中态正常,必加! + } - renderer.displayBuffer(); + renderer.displayBuffer(); +} \ No newline at end of file From 95b660541b40b0b7e1ec42b0a22cc220044701a5 Mon Sep 17 00:00:00 2001 From: icannotttt <141535655+icannotttt@users.noreply.github.com> Date: Thu, 29 Jan 2026 20:13:54 +0800 Subject: [PATCH 08/15] Update XtcReaderChapterSelectionActivity.cpp --- .../XtcReaderChapterSelectionActivity.cpp | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/activities/reader/XtcReaderChapterSelectionActivity.cpp b/src/activities/reader/XtcReaderChapterSelectionActivity.cpp index 6da3a0ce..dd6e8dc1 100644 --- a/src/activities/reader/XtcReaderChapterSelectionActivity.cpp +++ b/src/activities/reader/XtcReaderChapterSelectionActivity.cpp @@ -12,7 +12,7 @@ int page = 1; } // namespace int XtcReaderChapterSelectionActivity::getPageItems() const { - return 25; // ✅ 优化:固定返回25,匹配业务逻辑 + return 25; // 固定返回25 } void XtcReaderChapterSelectionActivity::taskTrampoline(void* param) { @@ -68,19 +68,19 @@ void XtcReaderChapterSelectionActivity::loop() { if (skipPage || isUpKey) { page -= 1; if(page < 1) page = 1; - selectorIndex = (page-1)*25; // ✅ BUG修复:局部索引0,选中当前页第一个 + selectorIndex = (page-1)*25; } else { - selectorIndex--; // ✅ BUG修复:局部索引减1 - if(selectorIndex < 0) selectorIndex = 0; // ✅ 边界防护 + selectorIndex--; + if(selectorIndex < 0) selectorIndex = 0; } updateRequired = true; } else if (nextReleased) { bool isDownKey = mappedInput.wasReleased(MappedInputManager::Button::Down); if (skipPage || isDownKey) { page += 1; - selectorIndex = (page-1)*25; // ✅ BUG修复:局部索引24,选中当前页第一个 + selectorIndex = (page-1)*25; } else { - selectorIndex++; // ✅ BUG修复:局部索引加1 + selectorIndex++; } updateRequired = true; } @@ -100,7 +100,7 @@ void XtcReaderChapterSelectionActivity::renderScreen() { renderer.clearScreen(); const int pagebegin=(page-1)*25; int page_chapter=25; - static int parsedPage = -1; // ✅ 保留页码缓存,只解析1次 + static int parsedPage = -1; if (parsedPage != page) { xtc->readChapters_gd(pagebegin); @@ -113,23 +113,23 @@ void XtcReaderChapterSelectionActivity::renderScreen() { const int FIX_LINE_HEIGHT = 29; const int BASE_Y = 60; - // ✅ 强制循环渲染25章(pagebegin ~ pagebegin+24),无有效数判断、不截断、不满也留空行 + for (int i = pagebegin; i <= pagebegin + page_chapter - 1; i++) { - int localIdx = i - pagebegin; // ✅ 保留核心修复:全局索引→局部索引0~24,必加!读取数据全靠它 + int localIdx = i - pagebegin; - uint32_t currOffset = this->xtc->getChapterstartpage(i); // ✅ 传局部索引,能读到正确数据 - std::string dirTitle = this->xtc->getChapterTitleByIndex(i); // ✅ 传局部索引,能读到正确标题 + uint32_t currOffset = this->xtc->getChapterstartpage(i); + std::string dirTitle = this->xtc->getChapterTitleByIndex(i); Serial.printf("[%lu] [XTC_CHAPTER] 第%d章,名字为:%s,页码为%d\n", millis(), i, dirTitle.c_str(),currOffset); static char title[64]; strncpy(title, dirTitle.c_str(), sizeof(title)-1); title[sizeof(title)-1] = '\0'; - int drawY = BASE_Y + localIdx * FIX_LINE_HEIGHT; // ✅ 简化计算,逻辑正确 + int drawY = BASE_Y + localIdx * FIX_LINE_HEIGHT; - Serial.printf("选中的选项是:%d\n",selectorIndex); // ✅ 补全换行符,日志整洁 - renderer.drawText(UI_10_FONT_ID, 20, drawY, title, i!= selectorIndex); // ✅ 核心修复:选中态正常,必加! + Serial.printf("选中的选项是:%d\n",selectorIndex); + renderer.drawText(UI_10_FONT_ID, 20, drawY, title, i!= selectorIndex); } renderer.displayBuffer(); -} \ No newline at end of file +} From 4d425fd9e09109f44b0b32fd0f33c20fd6a6fb59 Mon Sep 17 00:00:00 2001 From: icannotttt <13608489150@163.com> Date: Thu, 29 Jan 2026 20:36:29 +0800 Subject: [PATCH 09/15] open --- .gitmodules | 2 +- open-x4-sdk | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 160000 open-x4-sdk diff --git a/.gitmodules b/.gitmodules index b0d8e240..c7b9e37c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "open-x4-sdk"] path = open-x4-sdk - url = https://github.com/open-x4-epaper/community-sdk.git + url = git@github.com:open-x4-epaper/community-sdk.git diff --git a/open-x4-sdk b/open-x4-sdk new file mode 160000 index 00000000..c39f253a --- /dev/null +++ b/open-x4-sdk @@ -0,0 +1 @@ +Subproject commit c39f253a7dabbc193a8d7d310fb8777dca0ab8f1 From 9d0a5957e2b91f42203be8d825b6d92dff22d54c Mon Sep 17 00:00:00 2001 From: icannotttt <141535655+icannotttt@users.noreply.github.com> Date: Thu, 29 Jan 2026 23:53:19 +0800 Subject: [PATCH 10/15] Update XtcParser.cpp --- lib/Xtc/Xtc/XtcParser.cpp | 63 +++++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 32 deletions(-) diff --git a/lib/Xtc/Xtc/XtcParser.cpp b/lib/Xtc/Xtc/XtcParser.cpp index 8def2560..e9bec67d 100644 --- a/lib/Xtc/Xtc/XtcParser.cpp +++ b/lib/Xtc/Xtc/XtcParser.cpp @@ -22,9 +22,9 @@ XtcParser::XtcParser() m_bitDepth(1), m_hasChapters(false), m_lastError(XtcError::OK), - m_loadBatchSize(500), // ✅ 修改:批次大小改为2000(你的要求) + m_loadBatchSize(500), // 500 for one load m_loadedMaxPage(0), - m_loadedStartPage(0) { // ✅ 新增:只加这1个变量,记录当前页表的起始页 + m_loadedStartPage(0) { // page_start memset(&m_header, 0, sizeof(m_header)); } @@ -53,7 +53,7 @@ XtcError XtcParser::open(const char* filepath) { // Read title if available readTitle(); - // Read page table (默认只加载第一批:前10页) + // Read page table m_lastError = readPageTable(); if (m_lastError != XtcError::OK) { Serial.printf("[%lu] [XTC] Failed to read page table: %s\n", millis(), errorToString(m_lastError)); @@ -61,7 +61,7 @@ XtcError XtcParser::open(const char* filepath) { return m_lastError; } - // Read chapters if present (单章节逻辑不变) + // Read chapters if present (to make it work, just keep the old readchapters) m_lastError = readChapters(); if (m_lastError != XtcError::OK) { Serial.printf("[%lu] [XTC] Failed to read chapters: %s\n", millis(), errorToString(m_lastError)); @@ -134,7 +134,7 @@ XtcError XtcParser::readTitle() { return XtcError::OK; } -//加载下一部分 +//load the next pagetable (for XtcReadActivity.cpp) XtcError XtcParser::readPageTable() { m_pageTable.clear(); m_pageTable.shrink_to_fit(); @@ -148,13 +148,13 @@ XtcError XtcParser::readPageTable() { return XtcError::READ_ERROR; } - // 初始加载:从第0页开始,加载第一批10页 + // for the first uint16_t startPage = 0; uint16_t endPage = startPage + m_loadBatchSize - 1; if(endPage >= m_header.pageCount) endPage = m_header.pageCount - 1; uint16_t loadCount = endPage - startPage + 1; - m_pageTable.resize(endPage + 1); // 扩容vector,保留已加载数据 + m_pageTable.resize(endPage + 1); for (uint16_t i = startPage; i <= endPage; i++) { PageTableEntry entry; @@ -176,12 +176,12 @@ XtcError XtcParser::readPageTable() { } } - m_loadedMaxPage = endPage; // 更新已加载的最大页码 + m_loadedMaxPage = endPage; Serial.printf("[%lu] [XTC] 初始化加载页表: 成功加载 [0~%u] 共%u页\n", millis(), m_loadedMaxPage, loadCount); return XtcError::OK; } -// 原函数不变,保证不崩溃 + XtcError XtcParser::readChapters() { m_hasChapters = false; m_chapters.clear(); @@ -220,7 +220,7 @@ XtcError XtcParser::readChapters() { if (m_file.read(chapterBuf.data(), chapterSize) != chapterSize) {return XtcError::READ_ERROR;} } - // 单章节:名称=书名/全书,页码=0~总页数-1 (逻辑上包含全书,不影响阅读) + std::string chapterName = m_title.empty() ? "全书" : m_title; ChapterInfo singleChapter{std::move(chapterName), 0, m_header.pageCount - 1}; m_chapters.push_back(std::move(singleChapter)); @@ -231,7 +231,7 @@ XtcError XtcParser::readChapters() { return XtcError::OK; } -// 主要更改部分 +// for the next pagetable XtcError XtcParser::loadNextPageBatch() { if(!m_isOpen) return XtcError::FILE_NOT_FOUND; if(m_loadedMaxPage >= m_header.pageCount - 1) { @@ -266,7 +266,7 @@ bool XtcParser::getPageInfo(uint32_t pageIndex, PageInfo& info) const { return true; } -//主要更改:利用现有规律提取需要的xtc页面 +//change:to get page size_t XtcParser::loadPage(uint32_t pageIndex, uint8_t* buffer, size_t bufferSize) { if (!m_isOpen || pageIndex >= m_header.pageCount) { m_lastError = (pageIndex >= m_header.pageCount) ? XtcError::PAGE_OUT_OF_RANGE : XtcError::FILE_NOT_FOUND; @@ -278,7 +278,7 @@ size_t XtcParser::loadPage(uint32_t pageIndex, uint8_t* buffer, size_t bufferSiz } uint16_t idx = pageIndex - m_loadedStartPage; - const PageInfo& page = m_pageTable[idx]; // 替换原 m_pageTable[pageIndex] + const PageInfo& page = m_pageTable[idx]; if (!m_file.seek(page.offset)) { Serial.printf("[%lu] [XTC] Failed to seek to page %u at offset %lu\n", millis(), pageIndex, page.offset); m_lastError = XtcError::READ_ERROR; @@ -367,11 +367,11 @@ bool XtcParser::isValidXtcFile(const char* filepath) { file.close(); return (bytesRead == sizeof(magic)) && (magic == XTC_MAGIC || magic == XTCH_MAGIC); } -//换用新函数来提取章节 +//charge to get chapters separately XtcError XtcParser::readChapters_gd(uint16_t chapterStart) { chapterActualCount = 0; memset(ChapterList, 0, sizeof(ChapterList)); - Serial.printf("[Memory] ✅ 解析前:所有章节数据内存已彻底释放\n"); + Serial.printf("[Memory] memset memory \n"); uint8_t hasChaptersFlag = 0; if (!m_file.seek(0x0B)) { @@ -383,7 +383,7 @@ XtcError XtcParser::readChapters_gd(uint16_t chapterStart) { if (hasChaptersFlag != 1) { return XtcError::OK; } - Serial.printf("[%lu] [XTC] 位置1"); + // Serial.printf("[%lu] [XTC] 位置1");//for debug uint64_t chapterOffset = 0; if (!m_file.seek(0x30)) { @@ -395,7 +395,7 @@ XtcError XtcParser::readChapters_gd(uint16_t chapterStart) { if (chapterOffset == 0) { return XtcError::OK; } - Serial.printf("[%lu] [XTC] 位置2"); + // Serial.printf("[%lu] [XTC] 位置2");//for debug const uint64_t fileSize = m_file.size(); if (chapterOffset < sizeof(XtcHeader) || chapterOffset >= fileSize || chapterOffset + 96 > fileSize) { @@ -418,39 +418,38 @@ XtcError XtcParser::readChapters_gd(uint16_t chapterStart) { if (chapterCount == 0) { return XtcError::OK; } - Serial.printf("[%lu] [XTC] 位置3"); - // 计算起始章节的偏移:章节区开头 + 起始章节索引 * 单章96字节 + // Serial.printf("[%lu] [XTC] 位置3"); //for debug + // find the start offset uint64_t startReadOffset = chapterOffset + (chapterStart * chapterSize); - if (!m_file.seek(startReadOffset)) { // 跳到要读取的起始章节位置 + if (!m_file.seek(startReadOffset)) { return XtcError::READ_ERROR; } Serial.printf("[%lu] [XTC] 位置4"); std::vector chapterBuf(chapterSize); - int readCount = 0; // 已读取的章节数,最多读25章 - size_t currentChapterIdx = chapterStart; // 当前读到的章节索引 + int readCount = 0; + size_t currentChapterIdx = chapterStart; - // 循环条件:最多读25章 + 不超过总章节数 + // 25 chapters once Serial.printf("[%lu] [XTC] readCount:%d,currentChapterIdx:%d, chapterCount %u\n", millis(), readCount, currentChapterIdx,chapterCount); while (readCount < 25 && currentChapterIdx < chapterCount) { if (m_file.read(chapterBuf.data(), chapterSize) != chapterSize) { - break; // 读失败则退出,不返回错误,保证能读到已读的有效章节 + break; } - // 解析章节名:原版逻辑 + //no changes char nameBuf[81]; memcpy(nameBuf, chapterBuf.data(), 80); nameBuf[80] = '\0'; const size_t nameLen = strnlen(nameBuf, 80); std::string name(nameBuf, nameLen); - // 解析页码:原版逻辑 + uint16_t startPage = 0; uint16_t endPage = 0; memcpy(&startPage, chapterBuf.data() + 0x50, sizeof(startPage)); memcpy(&endPage, chapterBuf.data() + 0x52, sizeof(endPage)); - // 无效章节过滤:原版逻辑 if (name.empty() && startPage == 0 && endPage == 0) { currentChapterIdx++; continue; @@ -469,15 +468,15 @@ XtcError XtcParser::readChapters_gd(uint16_t chapterStart) { endPage = m_header.pageCount - 1; } - // 存入数组:当前读取的章节 → 数组的第readCount位 + strncpy(ChapterList[readCount].shortTitle, name.c_str(), 63); ChapterList[readCount].shortTitle[63] = '\0'; ChapterList[readCount].startPage = startPage; ChapterList[readCount].chapterIndex = currentChapterIdx; Serial.printf("[%lu] [XTC] 第%d章,名字为:%s %u\n", millis(), readCount, ChapterList[readCount].shortTitle); - readCount++; // 数组索引+1 - currentChapterIdx++; // 章节索引+1 + readCount++; // getpages + currentChapterIdx++; } @@ -499,11 +498,11 @@ XtcError XtcParser::loadPageBatchByStart(uint16_t startPage) { if(endPage >= m_header.pageCount) endPage = m_header.pageCount - 1; uint16_t loadCount = endPage - startPage + 1; - // 定位到指定批次的页表位置 + // find the offset for new table uint64_t seekOffset = m_header.pageTableOffset + (startPage * sizeof(PageTableEntry)); if(!m_file.seek(seekOffset)) return XtcError::READ_ERROR; - // 加载新批次数据(只存2000页,内存恒定) + // load m_pageTable.resize(loadCount); for(uint16_t i = startPage; i <= endPage; i++) { PageTableEntry entry; From 9b4410cbc87be1dd5770493b5b1834db26a8753d Mon Sep 17 00:00:00 2001 From: icannotttt <141535655+icannotttt@users.noreply.github.com> Date: Thu, 29 Jan 2026 23:56:20 +0800 Subject: [PATCH 11/15] Update XtcParser.h --- lib/Xtc/Xtc/XtcParser.h | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/lib/Xtc/Xtc/XtcParser.h b/lib/Xtc/Xtc/XtcParser.h index c2a1a393..2d205623 100644 --- a/lib/Xtc/Xtc/XtcParser.h +++ b/lib/Xtc/Xtc/XtcParser.h @@ -61,20 +61,20 @@ class XtcParser { /** - * @brief 动态加载下一批页 - * @return XtcError 加载状态:OK=加载成功,PAGE_OUT_OF_RANGE=无更多页可加载,其他=加载失败 + * @brief + * @return XtcError Loading status: OK = success, PAGE_OUT_OF_RANGE = no more pages to load, others = loading failed. */ XtcError loadNextPageBatch(); /** - * @brief 获取当前已经加载的最大页码 - * @return uint16_t 当前加载的最大有效页码 + * @brief Get the maximum page number that has been loaded currently. + * @return uint16_t The maximum valid page number loaded currently. */ uint16_t getLoadedMaxPage() const; /** - * @brief 获取每次动态加载的页数(批次大小) - * @return uint16_t 批次页数,默认10 + * @brief Get the number of pages loaded dynamically each time (batch size). + * @return uint16_t Page batch size, default is 10. */ uint16_t getPageBatchSize() const; @@ -84,22 +84,21 @@ uint32_t getChapterstartpage(int chapterIndex) { return ChapterList[i].startPage; } } - return 0; // 无此章节返回0 + return 0; // Return 0 if the chapter does not exist. } std::string getChapterTitleByIndex(int chapterIndex) { - Serial.printf("[%lu] [XTC] 已进入getChapterTitleByIndex,chapterActualCount=%d\n", millis(),chapterActualCount); + Serial.printf("[%lu] [XTC] Entered 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); + Serial.printf("[%lu] [XTC] In getChapterTitleByIndex, the title of chapter %d is: %s %u\n", millis(), i, ChapterList[i].shortTitle); } } - return ""; // 无此章节返回空字符串 + return ""; // Return empty string if the chapter does not exist. } - /** * Streaming page load * Memory-efficient method that reads page data in chunks. @@ -152,8 +151,8 @@ std::string getChapterTitleByIndex(int chapterIndex) { XtcError readTitle(); XtcError readAuthor(); XtcError readChapters(); - uint16_t m_loadBatchSize = 10; // 每次加载的页数(核心配置,可改) - uint16_t m_loadedMaxPage = 0; // 记录当前加载到的最大页码 + uint16_t m_loadBatchSize = 10; // pages for once load + uint16_t m_loadedMaxPage = 0; // Record the maximum page currently loaded }; } // namespace xtc From 38302e986588ebff63c9c9f4af1426940c876fc9 Mon Sep 17 00:00:00 2001 From: icannotttt <141535655+icannotttt@users.noreply.github.com> Date: Thu, 29 Jan 2026 23:57:12 +0800 Subject: [PATCH 12/15] Add ChapterData struct to XtcTypes.h Added new struct ChapterData with chapterIndex, startPage, and shortTitle fields. --- lib/Xtc/Xtc/XtcTypes.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/Xtc/Xtc/XtcTypes.h b/lib/Xtc/Xtc/XtcTypes.h index de613552..cff832cb 100644 --- a/lib/Xtc/Xtc/XtcTypes.h +++ b/lib/Xtc/Xtc/XtcTypes.h @@ -101,11 +101,11 @@ struct ChapterInfo { uint16_t startPage; uint16_t endPage; }; - +//new struct struct ChapterData { - int chapterIndex; // 章节序号 - uint16_t startPage; // 字节偏移量 - char shortTitle[64]; // 截取后的标题,char数组格式 + int chapterIndex; + uint16_t startPage; + char shortTitle[64]; }; // Error codes From 543728e9bc2738ea3322bdf6815587a398aad6c6 Mon Sep 17 00:00:00 2001 From: icannotttt <141535655+icannotttt@users.noreply.github.com> Date: Fri, 30 Jan 2026 00:01:45 +0800 Subject: [PATCH 13/15] Optimize memory usage and update loading logic Refactor memory usage and improve page loading logic. --- src/activities/reader/XtcReaderActivity.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/activities/reader/XtcReaderActivity.cpp b/src/activities/reader/XtcReaderActivity.cpp index 5d0e50dd..e702105e 100644 --- a/src/activities/reader/XtcReaderActivity.cpp +++ b/src/activities/reader/XtcReaderActivity.cpp @@ -202,21 +202,21 @@ void XtcReaderActivity::renderPage() { pageBufferSize = ((pageWidth + 7) / 8) * pageHeight; } - // ✅✅✅ 修复:删除重复定义的缓冲区,复用全局缓冲区,节省内存 + // This part defines global variables to reduce memory usage, but the effect seems to be mediocre. uint8_t* pageBuffer = s_pageBuffer; - // 继续加载页面数据 + // load new page size_t bytesRead = xtc->loadPage(currentPage, pageBuffer, pageBufferSize); if (bytesRead == 0) { Serial.printf("[%lu] [提示] 页码%lu加载中...\n", millis(), currentPage); renderer.clearScreen(); renderer.drawCenteredText(UI_12_FONT_ID, 300, "Loading...", true, EpdFontFamily::BOLD); renderer.displayBuffer(); - updateRequired = true; // ❌❌❌ 【修改4】新增此行,加载中自动触发重试,不会卡Loading界面 + updateRequired = true; // for some bugs return; } - // ✅ 以下渲染逻辑完全不变!灰度显示、刷新策略、进度保存都正常! + // keep renderer.clearScreen(); const uint16_t maxSrcY = pageHeight; @@ -332,13 +332,13 @@ void XtcReaderActivity::gotoPage(uint32_t targetPage) { void XtcReaderActivity::saveProgress() const { FsFile f; if (SdMan.openFileForWrite("XTR", xtc->getCachePath() + "/progress.bin", f)) { - uint8_t data[8]; // 8字节,前4字节存页码,后4字节存页表上限 - // 前4字节:保存当前阅读页码 currentPage + uint8_t data[8]; // for 2 data:currentPage and m_loadedMax + // currentPage data[0] = currentPage & 0xFF; data[1] = (currentPage >> 8) & 0xFF; data[2] = (currentPage >> 16) & 0xFF; data[3] = (currentPage >> 24) & 0xFF; - // 后4字节:保存当前页表上限 m_loadedMax + // m_loadedMax data[4] = m_loadedMax & 0xFF; data[5] = (m_loadedMax >> 8) & 0xFF; data[6] = (m_loadedMax >> 16) & 0xFF; @@ -350,6 +350,8 @@ void XtcReaderActivity::saveProgress() const { } } +//2data to load + void XtcReaderActivity::loadProgress() { FsFile f; if (SdMan.openFileForRead("XTR", xtc->getCachePath() + "/progress.bin", f)) { @@ -366,7 +368,7 @@ void XtcReaderActivity::loadProgress() { if (currentPage >= totalPages) currentPage = totalPages - 1; if (currentPage < 0) currentPage = 0; - + // Determine whether loading is required and which batch of tables to load. uint32_t targetBatchStart = (currentPage / loadedMaxPage_per) * loadedMaxPage_per; xtc->loadPageBatchByStart(targetBatchStart); From 488b750688f4c0b839f53b0a3533efb31f700bdc Mon Sep 17 00:00:00 2001 From: icannotttt <141535655+icannotttt@users.noreply.github.com> Date: Fri, 30 Jan 2026 00:02:17 +0800 Subject: [PATCH 14/15] Update comments for clarity in XtcReaderActivity.h --- src/activities/reader/XtcReaderActivity.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/activities/reader/XtcReaderActivity.h b/src/activities/reader/XtcReaderActivity.h index 454884b3..859bdff5 100644 --- a/src/activities/reader/XtcReaderActivity.h +++ b/src/activities/reader/XtcReaderActivity.h @@ -27,7 +27,7 @@ class XtcReaderActivity final : public ActivityWithSubactivity { bool updateRequired = false; const std::function onGoBack; const std::function onGoHome; - //分批缓存 + //pages once load uint32_t m_loadedMax = 499; static void taskTrampoline(void* param); @@ -36,7 +36,7 @@ class XtcReaderActivity final : public ActivityWithSubactivity { void renderPage(); void saveProgress() const; void loadProgress(); -//新增 +//new void gotoPage(uint32_t targetPage); public: From b779378563f87e051ce761be6152f24fa8edf7de Mon Sep 17 00:00:00 2001 From: icannotttt <141535655+icannotttt@users.noreply.github.com> Date: Fri, 30 Jan 2026 00:03:58 +0800 Subject: [PATCH 15/15] Update comments for clarity in XtcReaderChapterSelectionActivity --- src/activities/reader/XtcReaderChapterSelectionActivity.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/activities/reader/XtcReaderChapterSelectionActivity.cpp b/src/activities/reader/XtcReaderChapterSelectionActivity.cpp index dd6e8dc1..f26174a1 100644 --- a/src/activities/reader/XtcReaderChapterSelectionActivity.cpp +++ b/src/activities/reader/XtcReaderChapterSelectionActivity.cpp @@ -12,7 +12,7 @@ int page = 1; } // namespace int XtcReaderChapterSelectionActivity::getPageItems() const { - return 25; // 固定返回25 + return 25; // 25 for one page } void XtcReaderChapterSelectionActivity::taskTrampoline(void* param) { @@ -56,11 +56,12 @@ void XtcReaderChapterSelectionActivity::loop() { if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { const int pagebegin=(page-1)*25; xtc->readChapters_gd(pagebegin); + //to get the page for the select chapter uint32_t chapterpage = this->xtc->getChapterstartpage(selectorIndex); Serial.printf("[%lu] [XTC] 跳转章节:%d,跳转页数:%d\n", millis(), selectorIndex, chapterpage); onSelectPage(chapterpage); - // 确认按键逻辑,按需补充 + } else if (mappedInput.wasReleased(MappedInputManager::Button::Back)) { onGoBack(); } else if (prevReleased) {