Something went wrong. Try again.
A fork of https://github.com/crosspoint-reader/crosspoint-reader
Something went wrong. Try again.
13 kB · 372 lines
C++
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373#include "XtcReaderActivity.h"
#include <FsHelpers.h>#include <GfxRenderer.h>#include <HalStorage.h>#include <I18n.h>#include <Memory.h>
#include <algorithm>
#include "CrossPointSettings.h"#include "ProgressFile.h"#include "ReaderActivity.h"#include "ReaderUtils.h"#include "XtcReaderChapterSelectionActivity.h"#include "components/UITheme.h"#include "fontIds.h"
bool XtcReaderActivity::loadBook() { auto loadedXtc = makeUniqueNoThrow<Xtc>(bookPath, "/.crosspoint"); if (!loadedXtc) { LOG_ERR("XTR", "Failed to allocate XTC object"); return false; } if (!loadedXtc->load()) { LOG_ERR("XTR", "Failed to load XTC"); return false; } xtc = std::move(loadedXtc); xtc->setupCacheDir(); loadProgress(); return true;}
void XtcReaderActivity::openChapterSelection() { if (xtc && xtc->hasChapters() && !xtc->getChapters().empty()) { startActivityForResult(std::make_unique<XtcReaderChapterSelectionActivity>(renderer, mappedInput, xtc, currentPage), [this](const ActivityResult& result) { if (!result.isCancelled) { currentPage = std::get<PageResult>(result.data).page; requestUpdate(); } }); }}
bool XtcReaderActivity::handleFormatInput() { if (!xtc) { return false; }
// Enter chapter selection activity on Confirm release or touch menu gesture if (mappedInput.wasReleased(MappedInputManager::Button::Confirm) || ReaderUtils::isTouchMenuGesture(renderer, mappedInput)) { openChapterSelection(); return true; } return false;}
void XtcReaderActivity::applyInitialOrientation() { renderer.setOrientation(GfxRenderer::Orientation::Portrait); }
void XtcReaderActivity::renderBook() { if (!xtc) { return; }
renderPage(); saveProgress();}
XtcReaderActivity::StatusBarInfo XtcReaderActivity::getStatusBarInfo() const { const auto sb = SETTINGS.statusBarSpec(); const int bookPageCount = static_cast<int>(xtc->getPageCount()); const int bookPage = static_cast<int>(currentPage) + 1; std::string title = sb.titleMode == CrossPointSettings::STATUS_BAR_TITLE::BOOK_TITLE ? xtc->getTitle() : "";
if (!xtc->hasChapters()) { return StatusBarInfo{bookPage, bookPageCount, std::move(title)}; }
const auto& chapters = xtc->getChapters(); const auto chapterIt = std::find_if(chapters.begin(), chapters.end(), [this](const xtc::ChapterInfo& chapter) { return currentPage >= chapter.startPage && currentPage <= chapter.endPage; });
if (chapterIt == chapters.end() || chapterIt->endPage < chapterIt->startPage) { return StatusBarInfo{bookPage, bookPageCount, std::move(title)}; }
if (sb.titleMode == CrossPointSettings::STATUS_BAR_TITLE::CHAPTER_TITLE) { title = chapterIt->name.empty() ? tr(STR_UNNAMED) : chapterIt->name; }
return StatusBarInfo{static_cast<int>(currentPage - chapterIt->startPage) + 1, static_cast<int>(chapterIt->endPage - chapterIt->startPage) + 1, std::move(title)};}
void XtcReaderActivity::renderStatusBarOverlay(GfxRenderer& renderer, const StatusBarOverlayPosition position) const { const auto sb = SETTINGS.statusBarSpec(); const bool drawBottom = sb.xtcMode == CrossPointSettings::XTC_STATUS_BAR_MODE::XTC_STATUS_BAR_BOTTOM && position == StatusBarOverlayPosition::Bottom; const bool drawTop = sb.xtcMode == CrossPointSettings::XTC_STATUS_BAR_MODE::XTC_STATUS_BAR_TOP && position == StatusBarOverlayPosition::Top; if (!drawBottom && !drawTop) { return; }
const int statusBarHeight = UITheme::getInstance().getStatusBarHeight(); if (statusBarHeight <= 0) { return; }
int orientedMarginTop, orientedMarginRight, orientedMarginBottom, orientedMarginLeft; renderer.getOrientedViewableTRBL(&orientedMarginTop, &orientedMarginRight, &orientedMarginBottom, &orientedMarginLeft);
int clearY; int paddingBottom = 0; if (position == StatusBarOverlayPosition::Bottom) { clearY = renderer.getScreenHeight() - orientedMarginBottom - statusBarHeight - 4; if (clearY < 0) { clearY = 0; } } else { clearY = orientedMarginTop; paddingBottom = renderer.getScreenHeight() - statusBarHeight - orientedMarginBottom - orientedMarginTop - 4; } const int clearHeight = position == StatusBarOverlayPosition::Bottom ? renderer.getScreenHeight() - orientedMarginBottom - clearY : statusBarHeight + 4; if (clearHeight > 0) { renderer.fillRect(0, clearY, renderer.getScreenWidth(), clearHeight, false); }
const int pageCount = static_cast<int>(xtc->getPageCount()); const int displayPage = static_cast<int>(currentPage) + 1; const float progress = pageCount > 0 ? (static_cast<float>(displayPage) * 100.0f) / pageCount : 0.0f; const auto pageInfo = getStatusBarInfo(); GUI.drawStatusBar(renderer, progress, pageInfo.currentPage, pageInfo.pageCount, pageInfo.title, paddingBottom);}
void XtcReaderActivity::renderPage() { const uint16_t pageWidth = xtc->getPageWidth(); const uint16_t pageHeight = xtc->getPageHeight(); const uint8_t bitDepth = xtc->getBitDepth();
size_t pageBufferSize; if (bitDepth == 2) { pageBufferSize = static_cast<size_t>(pageWidth) * ((static_cast<size_t>(pageHeight) + 7) / 8) * 2; } else { pageBufferSize = ((pageWidth + 7) / 8) * pageHeight; }
uint8_t* pageBuffer = static_cast<uint8_t*>(malloc(pageBufferSize)); if (!pageBuffer) { LOG_ERR("XTR", "Failed to allocate page buffer (%lu bytes)", pageBufferSize); renderer.clearScreen(); renderer.drawCenteredText(UI_12_FONT_ID, 300, tr(STR_MEMORY_ERROR), true, EpdFontFamily::BOLD); renderer.displayBuffer(); return; }
size_t bytesRead = xtc->loadPage(currentPage, pageBuffer, pageBufferSize); if (bytesRead == 0) { LOG_ERR("XTR", "Failed to load page %lu: bufferSize=%lu bitDepth=%u error=%s", currentPage, pageBufferSize, bitDepth, xtc::errorToString(xtc->getLastError())); free(pageBuffer); renderer.clearScreen(); renderer.drawCenteredText(UI_12_FONT_ID, 300, tr(STR_PAGE_LOAD_ERROR), true, EpdFontFamily::BOLD); renderer.displayBuffer(); return; }
renderer.clearScreen();
const uint16_t maxSrcY = pageHeight;
if (bitDepth == 2) { const size_t planeSize = static_cast<size_t>(pageWidth) * ((static_cast<size_t>(pageHeight) + 7) / 8); const uint8_t* plane1 = pageBuffer; const uint8_t* plane2 = pageBuffer + planeSize; const size_t colBytes = (pageHeight + 7) / 8;
auto getPixelValue = [&](uint16_t x, uint16_t y) -> uint8_t { const size_t colIndex = pageWidth - 1 - x; const size_t byteInCol = y / 8; const size_t bitInByte = 7 - (y % 8); const size_t byteOffset = colIndex * colBytes + byteInCol; const uint8_t bit1 = (plane1[byteOffset] >> bitInByte) & 1; const uint8_t bit2 = (plane2[byteOffset] >> bitInByte) & 1; return (bit1 << 1) | bit2; };
for (uint16_t y = 0; y < pageHeight; y++) { for (uint16_t x = 0; x < pageWidth; x++) { if (getPixelValue(x, y) >= 1) { renderer.drawPixel(x, y, true); } } }
if (pagesUntilFullRefresh <= 1) { // Periodic ghost cleanup: scrub via the normal path, then run the // settle flavor of the grayscale base pass (DTM planes are equal after // the display sync, so only the gentle reinforcement cells fire). // Combined-base panels (Paper Mono) instead defer the base so the gray // planes below join it in one waveform. if (renderer.grayscaleCapabilities().base == HalDisplay::GrayscaleBase::Combined) { renderer.displayGrayscaleBase(HalDisplay::HALF_REFRESH); } else { renderer.displayBuffer(HalDisplay::HALF_REFRESH); renderer.preconditionGrayscale(); } pagesUntilFullRefresh = SETTINGS.getRefreshFrequency(); } else { renderer.displayGrayscaleBase(HalDisplay::FAST_REFRESH); pagesUntilFullRefresh--; }
renderer.clearScreen(0x00); for (uint16_t y = 0; y < pageHeight; y++) { for (uint16_t x = 0; x < pageWidth; x++) { if (getPixelValue(x, y) == 1) { renderer.drawPixel(x, y, false); } } } renderer.copyGrayscaleLsbBuffers();
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) { renderer.drawPixel(x, y, false); } } } renderer.copyGrayscaleMsbBuffers();
renderer.displayGrayBuffer();
renderer.clearScreen(); for (uint16_t y = 0; y < pageHeight; y++) { for (uint16_t x = 0; x < pageWidth; x++) { if (getPixelValue(x, y) >= 1) { renderer.drawPixel(x, y, true); } } }
renderer.cleanupGrayscaleWithFrameBuffer();
free(pageBuffer);
LOG_DBG("XTR", "Rendered page %lu/%lu (2-bit grayscale)", currentPage + 1, xtc->getPageCount()); markPageRendered(); return; } else { 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++) { const size_t srcByte = srcRowStart + srcX / 8; const size_t srcBit = 7 - (srcX % 8); const bool isBlack = !((pageBuffer[srcByte] >> srcBit) & 1);
if (isBlack) { renderer.drawPixel(srcX, srcY, true); } } } }
free(pageBuffer);
if (SETTINGS.statusBarSpec().xtcMode == CrossPointSettings::XTC_STATUS_BAR_MODE::XTC_STATUS_BAR_TOP) { renderStatusBarOverlay(renderer, StatusBarOverlayPosition::Top); } else { renderStatusBarOverlay(renderer, StatusBarOverlayPosition::Bottom); }
ReaderUtils::displayWithRefreshCycle(renderer, pagesUntilFullRefresh);
LOG_DBG("XTR", "Rendered page %lu/%lu (%u-bit)", currentPage + 1, xtc->getPageCount(), bitDepth); markPageRendered();}
bool XtcReaderActivity::pageTurn(bool isForward) { if (!xtc) return false; if (isForward) { if (currentPage < xtc->getPageCount()) { currentPage++; return true; } } else { if (currentPage > 0) { currentPage--; return true; } } return false;}
bool XtcReaderActivity::skipPages(int amount) { if (!xtc) return false; int newPage = static_cast<int>(currentPage) + amount; if (newPage < 0) newPage = 0; if (newPage > static_cast<int>(xtc->getPageCount())) newPage = static_cast<int>(xtc->getPageCount()); if (newPage != static_cast<int>(currentPage)) { currentPage = static_cast<uint32_t>(newPage); return true; } return false;}
bool XtcReaderActivity::isAtEndOfBook() const { return xtc && currentPage >= xtc->getPageCount(); }
void XtcReaderActivity::onReturnFromEndOfBook() { if (xtc && xtc->getPageCount() > 0) { currentPage = xtc->getPageCount() - 1; } else { currentPage = 0; }}
void XtcReaderActivity::saveProgress() const { if (!xtc) return; uint8_t data[4]; data[0] = currentPage & 0xFF; data[1] = (currentPage >> 8) & 0xFF; data[2] = (currentPage >> 16) & 0xFF; data[3] = (currentPage >> 24) & 0xFF; if (!ProgressFile::writeAtomic(xtc->getCachePath(), data, sizeof(data))) { LOG_ERR("XTC", "Failed to save progress: page %lu", currentPage); }}
void XtcReaderActivity::loadProgress() { if (!xtc) return; HalFile f; if (Storage.openFileForRead("XTC", xtc->getCachePath() + "/progress.bin", f)) { uint8_t data[4]; if (f.read(data, 4) == 4) { currentPage = data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24); if (currentPage >= xtc->getPageCount() && xtc->getPageCount() > 0) { currentPage = xtc->getPageCount() - 1; } LOG_DBG("XTC", "Loaded progress: page %lu/%lu", currentPage + 1, xtc->getPageCount()); } }}
ScreenshotInfo XtcReaderActivity::getScreenshotInfo() const { ScreenshotInfo info; info.readerType = ScreenshotInfo::ReaderType::Xtc; if (xtc) { const std::string t = xtc->getTitle(); snprintf(info.title, sizeof(info.title), "%s", t.c_str()); const uint32_t pageCount = xtc->getPageCount(); info.totalPages = pageCount; uint32_t clampedPage = (pageCount > 0 && currentPage >= pageCount) ? pageCount - 1 : currentPage; info.progressPercent = pageCount > 0 ? xtc->calculateProgress(clampedPage) : 0; info.currentPage = static_cast<int>(clampedPage) + 1; } else { info.currentPage = currentPage + 1; } return info;}