#pragma once #include #include #include #include #include "BookmarkStore.h" #include "EpubReaderMenuActivity.h" #include "ReaderUtils.h" #include "activities/Activity.h" class EpubReaderActivity final : public Activity { // Reader can launch sync in several UX modes: // - COMPARE: legacy chooser (apply/upload) for power users. // - PULL_REMOTE / PUSH_LOCAL: direct one-step actions from menu entries. // - AUTO_PUSH: silent push on reader exit; skips when remote is already ahead. // Keeping this split in the caller avoids branching on menu semantics deep // inside generic reader state handling. enum class SyncLaunchMode { COMPARE, PULL_REMOTE, PUSH_LOCAL, AUTO_PUSH, }; // Encodes pending navigation intent — where to land once the target section is loaded. // Replaces the scattered nextPageNumber / pendingTocIndex / pendingAnchor / // cachedSpineIndex / cachedChapterTotalPageCount / pendingPercent* / pendingParagraph* fields. struct NavigationTarget { enum class Kind : uint8_t { Page, // go to page n (0-based) LastPage, // go to last page of section (was UINT16_MAX sentinel) Anchor, // href fragment (e.g. "note1") TocIndex, // TOC entry index Percent, // normalised 0.0–1.0 within spine Paragraph, // KOReader paragraph LUT index ListItem, // KOReader li-anchored LUT index }; Kind kind = Kind::Page; union { int page; // Kind::Page int tocIndex; // Kind::TocIndex float spineProgress; // Kind::Percent uint16_t lutIndex; // Kind::Paragraph / Kind::ListItem }; std::string anchorStr; // Kind::Anchor; empty for all others // Cross-font rescaling: page count of this spine at save time. // Non-zero only for Kind::Page when loaded from progress.bin or written during reflow. int cachedPageCount = 0; int cachedSpineIdx = 0; NavigationTarget() : kind(Kind::Page), page(0) {} static NavigationTarget makePage(int n) { NavigationTarget t; t.kind = Kind::Page; t.page = n; return t; } static NavigationTarget makeLastPage() { NavigationTarget t; t.kind = Kind::LastPage; t.page = 0; return t; } static NavigationTarget makeAnchor(std::string a) { NavigationTarget t; t.kind = Kind::Anchor; t.page = 0; t.anchorStr = std::move(a); return t; } static NavigationTarget makeTocIndex(int idx) { NavigationTarget t; t.kind = Kind::TocIndex; t.tocIndex = idx; return t; } static NavigationTarget makePercent(float sp) { NavigationTarget t; t.kind = Kind::Percent; t.spineProgress = sp; return t; } static NavigationTarget makeParagraph(uint16_t i) { NavigationTarget t; t.kind = Kind::Paragraph; t.lutIndex = i; return t; } static NavigationTarget makeListItem(uint16_t i) { NavigationTarget t; t.kind = Kind::ListItem; t.lutIndex = i; return t; } // Resolves the target into section.currentPage. Must be called on the render task // after the section has been loaded (pageCount is known). void resolveInto(Section& section, int spineIndex) const; }; std::shared_ptr epub; std::unique_ptr
section = nullptr; int currentSpineIndex = 0; NavigationTarget navTarget; int pagesUntilFullRefresh = 0; unsigned long lastPageTurnTime = 0UL; unsigned long pageTurnDuration = 0UL; bool pendingHalfRefreshAfterImagePage = false; // Temporary AA suspension when BW snapshot allocation fails under memory pressure. // Automatically lifted once heap recovers above hysteresis thresholds. bool antiAliasingSuspendedLowMemory = false; bool showTruncatedSectionHintThisRender = false; uint8_t truncatedSectionHintRendersRemaining = 0; int lastWarnedTruncatedSpineIndex = -1; struct RenderPhaseStats { unsigned long prewarmMs = 0UL; unsigned long bwRenderMs = 0UL; unsigned long displayMs = 0UL; unsigned long bwStoreMs = 0UL; unsigned long grayLsbMs = 0UL; unsigned long grayMsbMs = 0UL; unsigned long grayDisplayMs = 0UL; unsigned long bwRestoreMs = 0UL; unsigned long totalMs = 0UL; }; struct LastRenderStats { bool valid = false; bool cacheRebuilt = false; bool usedGrayscale = false; bool hadImages = false; bool imagePageWithAA = false; bool forcedHalfRefresh = false; uint8_t orientation = 0; uint8_t imageRendering = 0; bool embeddedStyle = false; bool textAntiAliasing = false; int effectiveFontId = 0; int spineIndex = 0; int pageIndex = 0; int pageCount = 0; int footnoteCount = 0; int marginTop = 0; int marginRight = 0; int marginBottom = 0; int marginLeft = 0; uint16_t viewportWidth = 0; uint16_t viewportHeight = 0; unsigned long sectionLoadMs = 0UL; unsigned long pageLoadMs = 0UL; unsigned long requestRenderMs = 0UL; RenderPhaseStats phases; uint32_t freeHeapBefore = 0; uint32_t largestFreeBlockBefore = 0; uint32_t freeHeapAfter = 0; uint32_t largestFreeBlockAfter = 0; uint32_t fontCacheHits = 0; uint32_t fontCacheMisses = 0; uint32_t fontDecompressMs = 0; uint16_t fontUniqueGroups = 0; uint32_t fontPageBufferBytes = 0; uint32_t fontPageGlyphsBytes = 0; uint32_t fontPeakTempBytes = 0; uint32_t fontGetBitmapTimeUs = 0; uint32_t fontGetBitmapCalls = 0; }; struct BenchmarkAggregate { int renderCount = 0; int imagePageCount = 0; int cacheRebuildCount = 0; int maxFootnotes = 0; unsigned long totalRequestRenderMs = 0UL; unsigned long minRequestRenderMs = 0UL; unsigned long maxRequestRenderMs = 0UL; unsigned long totalRenderMs = 0UL; unsigned long minRenderMs = 0UL; unsigned long maxRenderMs = 0UL; unsigned long totalSectionLoadMs = 0UL; unsigned long totalPageLoadMs = 0UL; RenderPhaseStats totalPhases; uint32_t totalFontCacheHits = 0; uint32_t totalFontCacheMisses = 0; uint32_t totalFontDecompressMs = 0; uint32_t totalFontGetBitmapTimeUs = 0; uint32_t totalFontGetBitmapCalls = 0; uint32_t minFreeHeapAfter = 0; uint32_t maxFreeHeapAfter = 0; }; LastRenderStats lastRenderStats; // Pre-rendered next page: frame buffer holds page content (no status bar) ready to display. // Set by the pre-render pass in render(); consumed and cleared by the fast path in pageTurn(). // Invalidated (ready=false) on any navigation that is not a simple forward page turn. struct PreRenderedPage { bool ready = false; int spineIndex = -1; int pageIndex = -1; }; PreRenderedPage preRenderedPage; // Set by render() after a normal page render to request a pre-render of the next page. bool pendingPreRender = false; // Set by pageTurn() fast path to tell render() the frame buffer already holds the next page // content and only the status bar + display flush are needed. bool usePreRenderedBuffer = false; // Progress save is posted by render() and consumed by loop() to keep SD I/O off the render task. // render() writes spineIndex/page/pageCount then sets pending with release semantics so loop() // sees a coherent snapshot when it observes pending==true via acquire. struct PendingProgressSave { std::atomic pending{false}; int spineIndex = 0; int page = 0; int pageCount = 0; }; PendingProgressSave pendingProgressSave; bool pendingScreenshot = false; bool skipNextButtonCheck = false; // Skip button processing for one frame after subactivity exit bool finishedBookActivityStarted_ = false; ReaderUtils::InputDrainGuard inputDrainGuard; bool automaticPageTurnActive = false; // Pages turned in the current reader session. Used to gate auto-push-on-close: a brief // inspection of a book should not trigger a network round-trip. Reset on every reader // entry; not persisted, since "session" means the lifetime of this activity instance. int sessionPagesAdvanced = 0; // -1 means use global SETTINGS value. int8_t bookEmbeddedStyleOverride = -1; int8_t bookImageRenderingOverride = -1; int8_t bookFontFamilyOverride = -1; std::string bookSdFontFamilyOverride; int8_t bookFontSizeOverride = -1; bool bookBionicReadingOverride = false; int8_t bookParagraphAlignmentOverride = -1; // Bookmarks (starred pages) BookmarkStore bookmarkStore; // Footnote support std::vector currentPageFootnotes; struct SavedPosition { int spineIndex; int pageNumber; }; static constexpr int MAX_FOOTNOTE_DEPTH = 3; SavedPosition savedPositions[MAX_FOOTNOTE_DEPTH] = {}; int footnoteDepth = 0; void renderContents(std::unique_ptr page, int orientedMarginTop, int orientedMarginRight, int orientedMarginBottom, int orientedMarginLeft); // Renders page content into the frame buffer (prewarm + BW pass) without drawing the status bar // or flushing to the display. Used by the pre-render pass so the status bar can be superimposed // at display time with live values (clock, battery). void renderPageContentOnly(const Page& page, int orientedMarginTop, int orientedMarginRight, int orientedMarginBottom, int orientedMarginLeft); // Draws the status bar over the current frame buffer and flushes to the display. // Handles the refresh cycle and grayscale AA pass. page must be the same page // that was last rendered into the buffer (needed for image AA re-render). void displayPreRenderedPage(const Page& page, int orientedMarginTop, int orientedMarginRight, int orientedMarginBottom, int orientedMarginLeft); void renderStatusBar() const; void silentIndexNextChapterIfNeeded(uint16_t viewportWidth, uint16_t viewportHeight); void saveProgress(int spineIndex, int currentPage, int pageCount); // Jump to a percentage of the book (0-100), mapping it to spine and page. void jumpToPercent(int percent); void onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction action); void launchKOReaderSync(SyncLaunchMode mode = SyncLaunchMode::COMPARE); // Reader-close auto-push gate. Returns true if AUTO_PUSH was launched (the caller // must not perform its own exit — the sync activity will route to home on completion). // Returns false when any of the gates fails (setting off, no credentials, < 3 pages), // letting the caller take its normal exit path. bool tryAutoPushOnClose(); // Consume a persisted standalone KOReader sync session for this EPUB. Remote // apply writes the mapped reopen position into progress.bin before the normal // reader startup path reads it. Upload-complete leaves the existing local // progress.bin untouched and simply clears the pending session marker. void applyPendingSyncSession(); // Consume a persisted bookmark-jump request (from GlobalBookmarksActivity) for // this book. Rewrites progress.bin to the bookmarked position before the normal // reader startup path reads it. void applyPendingBookmarkJump(); void applyOrientation(uint8_t orientation); void applyTextDarkness(uint8_t textDarkness); void toggleAutoPageTurn(uint8_t selectedPageTurnOption); void stopAutomaticPageTurn(); void applyBookReaderOverrides(int8_t embeddedStyleOverride, int8_t imageRenderingOverride, int8_t fontFamilyOverride, const std::string& sdFontFamilyOverride, int8_t fontSizeOverride, bool bionicReadingOverride, int8_t paragraphAlignmentOverride); void openReaderMenu(); bool getEffectiveEmbeddedStyle() const; uint8_t getEffectiveImageRendering() const; uint8_t getEffectiveParagraphAlignment() const; int getEffectiveReaderFontId() const; float getEffectiveReaderLineCompression() const; bool stepPageState(bool isForwardTurn); void pageTurn(bool isForwardTurn); void runRenderBenchmark(); std::string buildRenderBenchmarkReport(const LastRenderStats& startSnapshot, const BenchmarkAggregate& aggregate, int forwardTurns, unsigned long forwardMs, int backwardTurns, unsigned long backwardMs) const; // Footnote navigation void navigateToHref(const std::string& href, bool savePosition = false); void restoreSavedPosition(); public: explicit EpubReaderActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, std::unique_ptr epub) : Activity("EpubReader", renderer, mappedInput), epub(std::move(epub)) {} void onEnter() override; void onExit() override; void loop() override; void render(RenderLock&& lock) override; bool isReaderActivity() const override { return true; } void onButtonAction(CrossPointSettings::BUTTON_ACTION action) override; // Renders the last saved page to the frame buffer without flushing to display. // Used by SleepActivity to prepare the background for the overlay sleep mode. // Returns false if the page cannot be loaded (missing cache / file error). static bool drawCurrentPageToBuffer(const std::string& filePath, GfxRenderer& renderer); };