#pragma once #include #include #include #include "CrossPointSettings.h" #include "ReaderUtils.h" #include "activities/Activity.h" struct MdHeading { size_t offset = 0; int level = 1; std::string title; int pageIndex = -1; }; class MdReaderActivity final : public Activity { std::unique_ptr txt; int currentPage = 0; int totalPages = 1; int pagesUntilFullRefresh = 0; // A single rendered line on screen (after word-wrapping) struct RenderedLine { std::vector spans; int indent = 0; // left indent in pixels bool isHR = false; // draw as horizontal rule bool isCodeBlock = false; }; // Streaming reader state std::vector pageOffsets; std::vector pageCodeBlockState; // 1 if page starts inside a code block std::vector currentPageLines; std::vector pageBuffer; std::vector headings; int currentHeadingIndex = -1; int linesPerPage = 0; int viewportWidth = 0; bool initialized = false; // Cached settings for cache validation int cachedFontId = 0; uint8_t cachedScreenMargin = 0; uint8_t cachedParagraphAlignment = CrossPointSettings::LEFT_ALIGN; int cachedOrientedMarginTop = 0; int cachedOrientedMarginRight = 0; int cachedOrientedMarginBottom = 0; int cachedOrientedMarginLeft = 0; // Indent constants (in pixels) static constexpr int LIST_INDENT = 20; static constexpr int BLOCKQUOTE_INDENT = 16; static constexpr int CODE_INDENT = 8; void renderPage(); void renderStatusBar() const; void initializeReader(); bool loadPageAtOffset(size_t offset, bool startInCodeBlock, std::vector& outLines, size_t& nextOffset, bool& endInCodeBlock); void buildPageIndex(); bool loadPageIndexCache(); void savePageIndexCache() const; void saveProgress() const; void loadProgress(); void scanHeadings(); void assignHeadingPageNumbers(); int getHeadingIndexForOffset(size_t offset) const; void jumpToHeading(bool next); ReaderUtils::InputDrainGuard inputDrainGuard; // Word-wrap a parsed markdown line into one or more RenderedLines. // Returns true if all content was emitted, false if truncated by maxLines. bool wordWrapParsedLine(const MdParser::ParsedLine& parsed, int indent, std::vector& outLines, int maxLines, bool isCodeBlock = false); // Measure total pixel width of a span list int measureSpans(const std::vector& spans) const; public: explicit MdReaderActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, std::unique_ptr txt) : Activity("MdReader", renderer, mappedInput), txt(std::move(txt)) {} void onEnter() override; void onExit() override; void loop() override; void render(RenderLock&&) override; bool isReaderActivity() const override { return true; } void onButtonAction(CrossPointSettings::BUTTON_ACTION action) override; };