Files
Crosspoint/src/activities/reader/EpubReaderActivity.h
T
2026-05-19 13:03:22 +02:00

319 lines
13 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#pragma once
#include <Epub.h>
#include <Epub/FootnoteEntry.h>
#include <Epub/Section.h>
#include <atomic>
#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.01.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> epub;
std::unique_ptr<Section> 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<bool> 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<FootnoteEntry> 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> 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> 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);
};