A fork of https://github.com/crosspoint-reader/crosspoint-reader
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Move to smart pointers and split out ParsedText class (#6)

* Move to smart pointers and split out ParsedText class

* Cleanup ParsedText

* Fix clearCache functions and clear section cache if page load fails

* Bump Page and Section file versions

* Combine removeDir implementations in Epub

* Adjust screen margins

authored by

Dave Allie and committed by
GitHub
69f35799 09f68a3d

+430 -322
+2
.clangd
··· 1 + CompileFlags: 2 + Add: [-std=c++2a]
+16 -1
lib/Epub/Epub.cpp
··· 6 6 7 7 #include <map> 8 8 9 + #include "Epub/FsHelpers.h" 10 + 9 11 bool Epub::findContentOpfFile(const ZipFile& zip, std::string& contentOpfFile) { 10 12 // open up the meta data to find where the content.opf file lives 11 13 size_t s; ··· 249 251 return true; 250 252 } 251 253 252 - void Epub::clearCache() const { SD.rmdir(cachePath.c_str()); } 254 + bool Epub::clearCache() const { 255 + if (!SD.exists(cachePath.c_str())) { 256 + Serial.printf("[%lu] [EPB] Cache does not exist, no action needed\n", millis()); 257 + return true; 258 + } 259 + 260 + if (!FsHelpers::removeDir(cachePath.c_str())) { 261 + Serial.printf("[%lu] [EPB] Failed to clear cache\n", millis()); 262 + return false; 263 + } 264 + 265 + Serial.printf("[%lu] [EPB] Cache cleared successfully\n", millis()); 266 + return true; 267 + } 253 268 254 269 void Epub::setupCacheDir() const { 255 270 if (SD.exists(cachePath.c_str())) {
+1 -1
lib/Epub/Epub.h
··· 50 50 ~Epub() = default; 51 51 std::string& getBasePath() { return contentBasePath; } 52 52 bool load(); 53 - void clearCache() const; 53 + bool clearCache() const; 54 54 void setupCacheDir() const; 55 55 const std::string& getCachePath() const; 56 56 const std::string& getPath() const;
+41 -41
lib/Epub/Epub/EpubHtmlParserSlim.cpp
··· 38 38 } 39 39 40 40 // start a new text block if needed 41 - void EpubHtmlParserSlim::startNewTextBlock(const BLOCK_STYLE style) { 41 + void EpubHtmlParserSlim::startNewTextBlock(const TextBlock::BLOCK_STYLE style) { 42 42 if (currentTextBlock) { 43 43 // already have a text block running and it is empty - just reuse it 44 44 if (currentTextBlock->isEmpty()) { ··· 46 46 return; 47 47 } 48 48 49 - currentTextBlock->finish(); 50 49 makePages(); 51 - delete currentTextBlock; 52 50 } 53 - currentTextBlock = new TextBlock(style); 51 + currentTextBlock.reset(new ParsedText(style)); 54 52 } 55 53 56 54 void XMLCALL EpubHtmlParserSlim::startElement(void* userData, const XML_Char* name, const XML_Char** atts) { ··· 94 92 } 95 93 96 94 if (matches(name, HEADER_TAGS, NUM_HEADER_TAGS)) { 97 - self->startNewTextBlock(CENTER_ALIGN); 95 + self->startNewTextBlock(TextBlock::CENTER_ALIGN); 98 96 self->boldUntilDepth = min(self->boldUntilDepth, self->depth); 99 97 } else if (matches(name, BLOCK_TAGS, NUM_BLOCK_TAGS)) { 100 98 if (strcmp(name, "br") == 0) { 101 99 self->startNewTextBlock(self->currentTextBlock->getStyle()); 102 100 } else { 103 - self->startNewTextBlock(JUSTIFIED); 101 + self->startNewTextBlock(TextBlock::JUSTIFIED); 104 102 } 105 103 } else if (matches(name, BOLD_TAGS, NUM_BOLD_TAGS)) { 106 104 self->boldUntilDepth = min(self->boldUntilDepth, self->depth); ··· 119 117 return; 120 118 } 121 119 120 + EpdFontStyle fontStyle = REGULAR; 121 + if (self->boldUntilDepth < self->depth && self->italicUntilDepth < self->depth) { 122 + fontStyle = BOLD_ITALIC; 123 + } else if (self->boldUntilDepth < self->depth) { 124 + fontStyle = BOLD; 125 + } else if (self->italicUntilDepth < self->depth) { 126 + fontStyle = ITALIC; 127 + } 128 + 122 129 for (int i = 0; i < len; i++) { 123 130 if (isWhitespace(s[i])) { 124 131 // Currently looking at whitespace, if there's anything in the partWordBuffer, flush it 125 132 if (self->partWordBufferIndex > 0) { 126 133 self->partWordBuffer[self->partWordBufferIndex] = '\0'; 127 - self->currentTextBlock->addWord(replaceHtmlEntities(self->partWordBuffer), self->boldUntilDepth < self->depth, 128 - self->italicUntilDepth < self->depth); 134 + self->currentTextBlock->addWord(std::move(replaceHtmlEntities(self->partWordBuffer)), fontStyle); 129 135 self->partWordBufferIndex = 0; 130 136 } 131 137 // Skip the whitespace char ··· 135 141 // If we're about to run out of space, then cut the word off and start a new one 136 142 if (self->partWordBufferIndex >= MAX_WORD_SIZE) { 137 143 self->partWordBuffer[self->partWordBufferIndex] = '\0'; 138 - self->currentTextBlock->addWord(replaceHtmlEntities(self->partWordBuffer), self->boldUntilDepth < self->depth, 139 - self->italicUntilDepth < self->depth); 144 + self->currentTextBlock->addWord(std::move(replaceHtmlEntities(self->partWordBuffer)), fontStyle); 140 145 self->partWordBufferIndex = 0; 141 146 } 142 147 ··· 158 163 matches(name, BOLD_TAGS, NUM_BOLD_TAGS) || matches(name, ITALIC_TAGS, NUM_ITALIC_TAGS) || self->depth == 1; 159 164 160 165 if (shouldBreakText) { 166 + EpdFontStyle fontStyle = REGULAR; 167 + if (self->boldUntilDepth < self->depth && self->italicUntilDepth < self->depth) { 168 + fontStyle = BOLD_ITALIC; 169 + } else if (self->boldUntilDepth < self->depth) { 170 + fontStyle = BOLD; 171 + } else if (self->italicUntilDepth < self->depth) { 172 + fontStyle = ITALIC; 173 + } 174 + 161 175 self->partWordBuffer[self->partWordBufferIndex] = '\0'; 162 - self->currentTextBlock->addWord(replaceHtmlEntities(self->partWordBuffer), self->boldUntilDepth < self->depth, 163 - self->italicUntilDepth < self->depth); 176 + self->currentTextBlock->addWord(std::move(replaceHtmlEntities(self->partWordBuffer)), fontStyle); 164 177 self->partWordBufferIndex = 0; 165 178 } 166 179 } ··· 184 197 } 185 198 186 199 bool EpubHtmlParserSlim::parseAndBuildPages() { 187 - startNewTextBlock(JUSTIFIED); 200 + startNewTextBlock(TextBlock::JUSTIFIED); 188 201 189 202 const XML_Parser parser = XML_ParserCreate(nullptr); 190 203 int done; ··· 240 253 // Process last page if there is still text 241 254 if (currentTextBlock) { 242 255 makePages(); 243 - completePageFn(currentPage); 244 - currentPage = nullptr; 245 - delete currentTextBlock; 246 - currentTextBlock = nullptr; 256 + completePageFn(std::move(currentPage)); 257 + currentPage.reset(); 258 + currentTextBlock.reset(); 247 259 } 248 260 249 261 return true; ··· 256 268 } 257 269 258 270 if (!currentPage) { 259 - currentPage = new Page(); 271 + currentPage.reset(new Page()); 260 272 currentPageNextY = marginTop; 261 273 } 262 274 ··· 266 278 // Long running task, make sure to let other things happen 267 279 vTaskDelay(1); 268 280 269 - if (currentTextBlock->getType() == TEXT_BLOCK) { 270 - const auto lines = currentTextBlock->splitIntoLines(renderer, fontId, marginLeft + marginRight); 281 + const auto lines = currentTextBlock->layoutAndExtractLines(renderer, fontId, marginLeft + marginRight); 271 282 272 - for (const auto line : lines) { 273 - if (currentPageNextY + lineHeight > pageHeight) { 274 - completePageFn(currentPage); 275 - currentPage = new Page(); 276 - currentPageNextY = marginTop; 277 - } 283 + for (auto&& line : lines) { 284 + if (currentPageNextY + lineHeight > pageHeight) { 285 + completePageFn(std::move(currentPage)); 286 + currentPage.reset(new Page()); 287 + currentPageNextY = marginTop; 288 + } 278 289 279 - currentPage->elements.push_back(new PageLine(line, marginLeft, currentPageNextY)); 280 - currentPageNextY += lineHeight; 281 - } 282 - // add some extra line between blocks 283 - currentPageNextY += lineHeight / 2; 290 + currentPage->elements.push_back(std::make_shared<PageLine>(line, marginLeft, currentPageNextY)); 291 + currentPageNextY += lineHeight; 284 292 } 285 - // TODO: Image block support 286 - // if (block->getType() == BlockType::IMAGE_BLOCK) { 287 - // ImageBlock *imageBlock = (ImageBlock *)block; 288 - // if (y + imageBlock->height > page_height) { 289 - // pages.push_back(new Page()); 290 - // y = 0; 291 - // } 292 - // pages.back()->elements.push_back(new PageImage(imageBlock, y)); 293 - // y += imageBlock->height; 294 - // } 293 + // add some extra line between blocks 294 + currentPageNextY += lineHeight / 2; 295 295 }
+7 -5
lib/Epub/Epub/EpubHtmlParserSlim.h
··· 4 4 5 5 #include <climits> 6 6 #include <functional> 7 + #include <memory> 7 8 9 + #include "ParsedText.h" 8 10 #include "blocks/TextBlock.h" 9 11 10 12 class Page; ··· 15 17 class EpubHtmlParserSlim { 16 18 const char* filepath; 17 19 GfxRenderer& renderer; 18 - std::function<void(Page*)> completePageFn; 20 + std::function<void(std::unique_ptr<Page>)> completePageFn; 19 21 int depth = 0; 20 22 int skipUntilDepth = INT_MAX; 21 23 int boldUntilDepth = INT_MAX; ··· 24 26 // leave one char at end for null pointer 25 27 char partWordBuffer[MAX_WORD_SIZE + 1] = {}; 26 28 int partWordBufferIndex = 0; 27 - TextBlock* currentTextBlock = nullptr; 28 - Page* currentPage = nullptr; 29 + std::unique_ptr<ParsedText> currentTextBlock = nullptr; 30 + std::unique_ptr<Page> currentPage = nullptr; 29 31 int currentPageNextY = 0; 30 32 int fontId; 31 33 float lineCompression; ··· 34 36 int marginBottom; 35 37 int marginLeft; 36 38 37 - void startNewTextBlock(BLOCK_STYLE style); 39 + void startNewTextBlock(TextBlock::BLOCK_STYLE style); 38 40 void makePages(); 39 41 // XML callbacks 40 42 static void XMLCALL startElement(void* userData, const XML_Char* name, const XML_Char** atts); ··· 45 47 explicit EpubHtmlParserSlim(const char* filepath, GfxRenderer& renderer, const int fontId, 46 48 const float lineCompression, const int marginTop, const int marginRight, 47 49 const int marginBottom, const int marginLeft, 48 - const std::function<void(Page*)>& completePageFn) 50 + const std::function<void(std::unique_ptr<Page>)>& completePageFn) 49 51 : filepath(filepath), 50 52 renderer(renderer), 51 53 fontId(fontId),
+36
lib/Epub/Epub/FsHelpers.cpp
··· 1 + #include "FsHelpers.h" 2 + 3 + #include <SD.h> 4 + 5 + bool FsHelpers::removeDir(const char* path) { 6 + // 1. Open the directory 7 + File dir = SD.open(path); 8 + if (!dir) { 9 + return false; 10 + } 11 + if (!dir.isDirectory()) { 12 + return false; 13 + } 14 + 15 + File file = dir.openNextFile(); 16 + while (file) { 17 + String filePath = path; 18 + if (!filePath.endsWith("/")) { 19 + filePath += "/"; 20 + } 21 + filePath += file.name(); 22 + 23 + if (file.isDirectory()) { 24 + if (!removeDir(filePath.c_str())) { 25 + return false; 26 + } 27 + } else { 28 + if (!SD.remove(filePath.c_str())) { 29 + return false; 30 + } 31 + } 32 + file = dir.openNextFile(); 33 + } 34 + 35 + return SD.rmdir(path); 36 + }
+6
lib/Epub/Epub/FsHelpers.h
··· 1 + #pragma once 2 + 3 + class FsHelpers { 4 + public: 5 + static bool removeDir(const char* path); 6 + };
+12 -11
lib/Epub/Epub/Page.cpp
··· 3 3 #include <HardwareSerial.h> 4 4 #include <Serialization.h> 5 5 6 - constexpr uint8_t PAGE_FILE_VERSION = 1; 6 + constexpr uint8_t PAGE_FILE_VERSION = 2; 7 7 8 8 void PageLine::render(GfxRenderer& renderer, const int fontId) { block->render(renderer, fontId, xPos, yPos); } 9 9 ··· 15 15 block->serialize(os); 16 16 } 17 17 18 - PageLine* PageLine::deserialize(std::istream& is) { 18 + std::unique_ptr<PageLine> PageLine::deserialize(std::istream& is) { 19 19 int32_t xPos; 20 20 int32_t yPos; 21 21 serialization::readPod(is, xPos); 22 22 serialization::readPod(is, yPos); 23 23 24 - const auto tb = TextBlock::deserialize(is); 25 - return new PageLine(tb, xPos, yPos); 24 + auto tb = TextBlock::deserialize(is); 25 + return std::unique_ptr<PageLine>(new PageLine(std::move(tb), xPos, yPos)); 26 26 } 27 27 28 28 void Page::render(GfxRenderer& renderer, const int fontId) const { ··· 37 37 const uint32_t count = elements.size(); 38 38 serialization::writePod(os, count); 39 39 40 - for (auto* el : elements) { 40 + for (const auto& el : elements) { 41 41 // Only PageLine exists currently 42 42 serialization::writePod(os, static_cast<uint8_t>(TAG_PageLine)); 43 - static_cast<PageLine*>(el)->serialize(os); 43 + el->serialize(os); 44 44 } 45 45 } 46 46 47 - Page* Page::deserialize(std::istream& is) { 47 + std::unique_ptr<Page> Page::deserialize(std::istream& is) { 48 48 uint8_t version; 49 49 serialization::readPod(is, version); 50 50 if (version != PAGE_FILE_VERSION) { ··· 52 52 return nullptr; 53 53 } 54 54 55 - auto* page = new Page(); 55 + auto page = std::unique_ptr<Page>(new Page()); 56 56 57 57 uint32_t count; 58 58 serialization::readPod(is, count); ··· 62 62 serialization::readPod(is, tag); 63 63 64 64 if (tag == TAG_PageLine) { 65 - auto* pl = PageLine::deserialize(is); 66 - page->elements.push_back(pl); 65 + auto pl = PageLine::deserialize(is); 66 + page->elements.push_back(std::move(pl)); 67 67 } else { 68 - throw std::runtime_error("Unknown PageElement tag"); 68 + Serial.printf("[%lu] [PGE] Deserialization failed: Unknown tag %u\n", millis(), tag); 69 + return nullptr; 69 70 } 70 71 } 71 72
+9 -12
lib/Epub/Epub/Page.h
··· 1 1 #pragma once 2 + #include <utility> 3 + #include <vector> 4 + 2 5 #include "blocks/TextBlock.h" 3 6 4 7 enum PageElementTag : uint8_t { ··· 18 21 19 22 // a line from a block element 20 23 class PageLine final : public PageElement { 21 - const TextBlock* block; 24 + std::shared_ptr<TextBlock> block; 22 25 23 26 public: 24 - PageLine(const TextBlock* block, const int xPos, const int yPos) : PageElement(xPos, yPos), block(block) {} 25 - ~PageLine() override { delete block; } 27 + PageLine(std::shared_ptr<TextBlock> block, const int xPos, const int yPos) 28 + : PageElement(xPos, yPos), block(std::move(block)) {} 26 29 void render(GfxRenderer& renderer, int fontId) override; 27 30 void serialize(std::ostream& os) override; 28 - static PageLine* deserialize(std::istream& is); 31 + static std::unique_ptr<PageLine> deserialize(std::istream& is); 29 32 }; 30 33 31 34 class Page { 32 35 public: 33 - ~Page() { 34 - for (const auto element : elements) { 35 - delete element; 36 - } 37 - } 38 - 39 36 // the list of block index and line numbers on this page 40 - std::vector<PageElement*> elements; 37 + std::vector<std::shared_ptr<PageElement>> elements; 41 38 void render(GfxRenderer& renderer, int fontId) const; 42 39 void serialize(std::ostream& os) const; 43 - static Page* deserialize(std::istream& is); 40 + static std::unique_ptr<Page> deserialize(std::istream& is); 44 41 };
+167
lib/Epub/Epub/ParsedText.cpp
··· 1 + #include "ParsedText.h" 2 + 3 + #include <GfxRenderer.h> 4 + 5 + #include <algorithm> 6 + #include <cmath> 7 + #include <limits> 8 + #include <vector> 9 + 10 + constexpr int MAX_COST = std::numeric_limits<int>::max(); 11 + 12 + void ParsedText::addWord(std::string word, const EpdFontStyle fontStyle) { 13 + if (word.empty()) return; 14 + 15 + words.push_back(std::move(word)); 16 + wordStyles.push_back(fontStyle); 17 + } 18 + 19 + // Consumes data to minimize memory usage 20 + std::list<std::shared_ptr<TextBlock>> ParsedText::layoutAndExtractLines(const GfxRenderer& renderer, const int fontId, 21 + const int horizontalMargin) { 22 + if (words.empty()) { 23 + return {}; 24 + } 25 + 26 + const size_t totalWordCount = words.size(); 27 + const int pageWidth = renderer.getScreenWidth() - horizontalMargin; 28 + const int spaceWidth = renderer.getSpaceWidth(fontId); 29 + 30 + std::vector<uint16_t> wordWidths; 31 + wordWidths.reserve(totalWordCount); 32 + 33 + auto wordsIt = words.begin(); 34 + auto wordStylesIt = wordStyles.begin(); 35 + 36 + while (wordsIt != words.end()) { 37 + wordWidths.push_back(renderer.getTextWidth(fontId, wordsIt->c_str(), *wordStylesIt)); 38 + 39 + std::advance(wordsIt, 1); 40 + std::advance(wordStylesIt, 1); 41 + } 42 + 43 + // DP table to store the minimum badness (cost) of lines starting at index i 44 + std::vector<int> dp(totalWordCount); 45 + // 'ans[i]' stores the index 'j' of the *last word* in the optimal line starting at 'i' 46 + std::vector<size_t> ans(totalWordCount); 47 + 48 + // Base Case 49 + dp[totalWordCount - 1] = 0; 50 + ans[totalWordCount - 1] = totalWordCount - 1; 51 + 52 + for (int i = totalWordCount - 2; i >= 0; --i) { 53 + int currlen = -spaceWidth; 54 + dp[i] = MAX_COST; 55 + 56 + for (size_t j = i; j < totalWordCount; ++j) { 57 + // Current line length: previous width + space + current word width 58 + currlen += wordWidths[j] + spaceWidth; 59 + 60 + if (currlen > pageWidth) { 61 + break; 62 + } 63 + 64 + int cost; 65 + if (j == totalWordCount - 1) { 66 + cost = 0; // Last line 67 + } else { 68 + const int remainingSpace = pageWidth - currlen; 69 + // Use long long for the square to prevent overflow 70 + const long long cost_ll = static_cast<long long>(remainingSpace) * remainingSpace + dp[j + 1]; 71 + 72 + if (cost_ll > MAX_COST) { 73 + cost = MAX_COST; 74 + } else { 75 + cost = static_cast<int>(cost_ll); 76 + } 77 + } 78 + 79 + if (cost < dp[i]) { 80 + dp[i] = cost; 81 + ans[i] = j; // j is the index of the last word in this optimal line 82 + } 83 + } 84 + } 85 + 86 + // Stores the index of the word that starts the next line (last_word_index + 1) 87 + std::vector<size_t> lineBreakIndices; 88 + size_t currentWordIndex = 0; 89 + constexpr size_t MAX_LINES = 1000; 90 + 91 + while (currentWordIndex < totalWordCount) { 92 + if (lineBreakIndices.size() >= MAX_LINES) { 93 + break; 94 + } 95 + 96 + size_t nextBreakIndex = ans[currentWordIndex] + 1; 97 + lineBreakIndices.push_back(nextBreakIndex); 98 + 99 + currentWordIndex = nextBreakIndex; 100 + } 101 + 102 + std::list<std::shared_ptr<TextBlock>> lines; 103 + 104 + // Initialize iterators for consumption 105 + auto wordStartIt = words.begin(); 106 + auto wordStyleStartIt = wordStyles.begin(); 107 + size_t wordWidthIndex = 0; 108 + 109 + size_t lastBreakAt = 0; 110 + for (const size_t lineBreak : lineBreakIndices) { 111 + const size_t lineWordCount = lineBreak - lastBreakAt; 112 + 113 + // Calculate end iterators for the range to splice 114 + auto wordEndIt = wordStartIt; 115 + auto wordStyleEndIt = wordStyleStartIt; 116 + std::advance(wordEndIt, lineWordCount); 117 + std::advance(wordStyleEndIt, lineWordCount); 118 + 119 + // Calculate total word width for this line 120 + int lineWordWidthSum = 0; 121 + for (size_t i = 0; i < lineWordCount; ++i) { 122 + lineWordWidthSum += wordWidths[wordWidthIndex + i]; 123 + } 124 + 125 + // Calculate spacing 126 + const int spareSpace = pageWidth - lineWordWidthSum; 127 + int spacing = spaceWidth; 128 + const bool isLastLine = lineBreak == totalWordCount; 129 + 130 + if (style == TextBlock::JUSTIFIED && !isLastLine && lineWordCount >= 2) { 131 + spacing = spareSpace / (lineWordCount - 1); 132 + } 133 + 134 + // Calculate initial x position 135 + uint16_t xpos = 0; 136 + if (style == TextBlock::RIGHT_ALIGN) { 137 + xpos = spareSpace - (lineWordCount - 1) * spaceWidth; 138 + } else if (style == TextBlock::CENTER_ALIGN) { 139 + xpos = (spareSpace - (lineWordCount - 1) * spaceWidth) / 2; 140 + } 141 + 142 + // Pre-calculate X positions for words 143 + std::list<uint16_t> lineXPos; 144 + for (size_t i = 0; i < lineWordCount; ++i) { 145 + const uint16_t currentWordWidth = wordWidths[wordWidthIndex + i]; 146 + lineXPos.push_back(xpos); 147 + xpos += currentWordWidth + spacing; 148 + } 149 + 150 + // *** CRITICAL STEP: CONSUME DATA USING SPLICE *** 151 + std::list<std::string> lineWords; 152 + lineWords.splice(lineWords.begin(), words, wordStartIt, wordEndIt); 153 + std::list<EpdFontStyle> lineWordStyles; 154 + lineWordStyles.splice(lineWordStyles.begin(), wordStyles, wordStyleStartIt, wordStyleEndIt); 155 + 156 + lines.push_back( 157 + std::make_shared<TextBlock>(std::move(lineWords), std::move(lineXPos), std::move(lineWordStyles), style)); 158 + 159 + // Update pointers/indices for the next line 160 + wordStartIt = wordEndIt; 161 + wordStyleStartIt = wordStyleEndIt; 162 + wordWidthIndex += lineWordCount; 163 + lastBreakAt = lineBreak; 164 + } 165 + 166 + return lines; 167 + }
+29
lib/Epub/Epub/ParsedText.h
··· 1 + #pragma once 2 + 3 + #include <EpdFontFamily.h> 4 + 5 + #include <cstdint> 6 + #include <list> 7 + #include <memory> 8 + #include <string> 9 + 10 + #include "blocks/TextBlock.h" 11 + 12 + class GfxRenderer; 13 + 14 + class ParsedText { 15 + std::list<std::string> words; 16 + std::list<EpdFontStyle> wordStyles; 17 + TextBlock::BLOCK_STYLE style; 18 + 19 + public: 20 + explicit ParsedText(const TextBlock::BLOCK_STYLE style) : style(style) {} 21 + ~ParsedText() = default; 22 + 23 + void addWord(std::string word, EpdFontStyle fontStyle); 24 + void setStyle(const TextBlock::BLOCK_STYLE style) { this->style = style; } 25 + TextBlock::BLOCK_STYLE getStyle() const { return style; } 26 + bool isEmpty() const { return words.empty(); } 27 + std::list<std::shared_ptr<TextBlock>> layoutAndExtractLines(const GfxRenderer& renderer, int fontId, 28 + int horizontalMargin); 29 + };
+27 -13
lib/Epub/Epub/Section.cpp
··· 1 1 #include "Section.h" 2 2 3 - #include <GfxRenderer.h> 4 3 #include <SD.h> 4 + #include <Serialization.h> 5 5 6 6 #include <fstream> 7 7 8 8 #include "EpubHtmlParserSlim.h" 9 + #include "FsHelpers.h" 9 10 #include "Page.h" 10 - #include "Serialization.h" 11 11 12 - constexpr uint8_t SECTION_FILE_VERSION = 3; 12 + constexpr uint8_t SECTION_FILE_VERSION = 4; 13 13 14 - void Section::onPageComplete(const Page* page) { 14 + void Section::onPageComplete(std::unique_ptr<Page> page) { 15 15 const auto filePath = cachePath + "/page_" + std::to_string(pageCount) + ".bin"; 16 16 17 17 std::ofstream outputFile("/sd" + filePath); ··· 21 21 Serial.printf("[%lu] [SCT] Page %d processed\n", millis(), pageCount); 22 22 23 23 pageCount++; 24 - delete page; 25 24 } 26 25 27 26 void Section::writeCacheMetadata(const int fontId, const float lineCompression, const int marginTop, ··· 57 56 serialization::readPod(inputFile, version); 58 57 if (version != SECTION_FILE_VERSION) { 59 58 inputFile.close(); 60 - clearCache(); 61 59 Serial.printf("[%lu] [SCT] Deserialization failed: Unknown version %u\n", millis(), version); 60 + clearCache(); 62 61 return false; 63 62 } 64 63 ··· 74 73 if (fontId != fileFontId || lineCompression != fileLineCompression || marginTop != fileMarginTop || 75 74 marginRight != fileMarginRight || marginBottom != fileMarginBottom || marginLeft != fileMarginLeft) { 76 75 inputFile.close(); 77 - clearCache(); 78 76 Serial.printf("[%lu] [SCT] Deserialization failed: Parameters do not match\n", millis()); 77 + clearCache(); 79 78 return false; 80 79 } 81 80 } ··· 91 90 SD.mkdir(cachePath.c_str()); 92 91 } 93 92 94 - void Section::clearCache() const { SD.rmdir(cachePath.c_str()); } 93 + // Your updated class method (assuming you are using the 'SD' object, which is a wrapper for a specific filesystem) 94 + bool Section::clearCache() const { 95 + if (!SD.exists(cachePath.c_str())) { 96 + Serial.printf("[%lu] [SCT] Cache does not exist, no action needed\n", millis()); 97 + return true; 98 + } 99 + 100 + if (!FsHelpers::removeDir(cachePath.c_str())) { 101 + Serial.printf("[%lu] [SCT] Failed to clear cache\n", millis()); 102 + return false; 103 + } 104 + 105 + Serial.printf("[%lu] [SCT] Cache cleared successfully\n", millis()); 106 + return true; 107 + } 95 108 96 109 bool Section::persistPageDataToSD(const int fontId, const float lineCompression, const int marginTop, 97 110 const int marginRight, const int marginBottom, const int marginLeft) { ··· 114 127 115 128 const auto sdTmpHtmlPath = "/sd" + tmpHtmlPath; 116 129 117 - auto visitor = EpubHtmlParserSlim(sdTmpHtmlPath.c_str(), renderer, fontId, lineCompression, marginTop, marginRight, 118 - marginBottom, marginLeft, [this](const Page* page) { this->onPageComplete(page); }); 130 + EpubHtmlParserSlim visitor(sdTmpHtmlPath.c_str(), renderer, fontId, lineCompression, marginTop, marginRight, 131 + marginBottom, marginLeft, 132 + [this](std::unique_ptr<Page> page) { this->onPageComplete(std::move(page)); }); 119 133 success = visitor.parseAndBuildPages(); 120 134 121 135 SD.remove(tmpHtmlPath.c_str()); ··· 129 143 return true; 130 144 } 131 145 132 - Page* Section::loadPageFromSD() const { 146 + std::unique_ptr<Page> Section::loadPageFromSD() const { 133 147 const auto filePath = "/sd" + cachePath + "/page_" + std::to_string(currentPage) + ".bin"; 134 148 if (!SD.exists(filePath.c_str() + 3)) { 135 149 Serial.printf("[%lu] [SCT] Page file does not exist: %s\n", millis(), filePath.c_str()); ··· 137 151 } 138 152 139 153 std::ifstream inputFile(filePath); 140 - Page* p = Page::deserialize(inputFile); 154 + auto page = Page::deserialize(inputFile); 141 155 inputFile.close(); 142 - return p; 156 + return page; 143 157 }
+7 -5
lib/Epub/Epub/Section.h
··· 1 1 #pragma once 2 + #include <memory> 3 + 2 4 #include "Epub.h" 3 5 4 6 class Page; 5 7 class GfxRenderer; 6 8 7 9 class Section { 8 - Epub* epub; 10 + std::shared_ptr<Epub> epub; 9 11 const int spineIndex; 10 12 GfxRenderer& renderer; 11 13 std::string cachePath; 12 14 13 15 void writeCacheMetadata(int fontId, float lineCompression, int marginTop, int marginRight, int marginBottom, 14 16 int marginLeft) const; 15 - void onPageComplete(const Page* page); 17 + void onPageComplete(std::unique_ptr<Page> page); 16 18 17 19 public: 18 20 int pageCount = 0; 19 21 int currentPage = 0; 20 22 21 - explicit Section(Epub* epub, const int spineIndex, GfxRenderer& renderer) 23 + explicit Section(const std::shared_ptr<Epub>& epub, const int spineIndex, GfxRenderer& renderer) 22 24 : epub(epub), spineIndex(spineIndex), renderer(renderer) { 23 25 cachePath = epub->getCachePath() + "/" + std::to_string(spineIndex); 24 26 } ··· 26 28 bool loadCacheMetadata(int fontId, float lineCompression, int marginTop, int marginRight, int marginBottom, 27 29 int marginLeft); 28 30 void setupCacheDir() const; 29 - void clearCache() const; 31 + bool clearCache() const; 30 32 bool persistPageDataToSD(int fontId, float lineCompression, int marginTop, int marginRight, int marginBottom, 31 33 int marginLeft); 32 - Page* loadPageFromSD() const; 34 + std::unique_ptr<Page> loadPageFromSD() const; 33 35 };
+14 -167
lib/Epub/Epub/blocks/TextBlock.cpp
··· 3 3 #include <GfxRenderer.h> 4 4 #include <Serialization.h> 5 5 6 - void TextBlock::addWord(const std::string& word, const bool is_bold, const bool is_italic) { 7 - if (word.length() == 0) return; 6 + void TextBlock::render(const GfxRenderer& renderer, const int fontId, const int x, const int y) const { 7 + auto wordIt = words.begin(); 8 + auto wordStylesIt = wordStyles.begin(); 9 + auto wordXposIt = wordXpos.begin(); 8 10 9 - words.push_back(word); 10 - wordStyles.push_back((is_bold ? BOLD_SPAN : 0) | (is_italic ? ITALIC_SPAN : 0)); 11 - } 11 + for (int i = 0; i < words.size(); i++) { 12 + renderer.drawText(fontId, *wordXposIt + x, y, wordIt->c_str(), true, *wordStylesIt); 12 13 13 - std::list<TextBlock*> TextBlock::splitIntoLines(const GfxRenderer& renderer, const int fontId, 14 - const int horizontalMargin) { 15 - const int totalWordCount = words.size(); 16 - const int pageWidth = GfxRenderer::getScreenWidth() - horizontalMargin; 17 - const int spaceWidth = renderer.getSpaceWidth(fontId); 18 - 19 - words.shrink_to_fit(); 20 - wordStyles.shrink_to_fit(); 21 - wordXpos.reserve(totalWordCount); 22 - 23 - // measure each word 24 - uint16_t wordWidths[totalWordCount]; 25 - for (int i = 0; i < totalWordCount; i++) { 26 - // measure the word 27 - EpdFontStyle fontStyle = REGULAR; 28 - if (wordStyles[i] & BOLD_SPAN) { 29 - if (wordStyles[i] & ITALIC_SPAN) { 30 - fontStyle = BOLD_ITALIC; 31 - } else { 32 - fontStyle = BOLD; 33 - } 34 - } else if (wordStyles[i] & ITALIC_SPAN) { 35 - fontStyle = ITALIC; 36 - } 37 - const int width = renderer.getTextWidth(fontId, words[i].c_str(), fontStyle); 38 - wordWidths[i] = width; 39 - } 40 - 41 - // now apply the dynamic programming algorithm to find the best line breaks 42 - // DP table in which dp[i] represents cost of line starting with word words[i] 43 - int dp[totalWordCount]; 44 - 45 - // Array in which ans[i] store index of last word in line starting with word 46 - // word[i] 47 - size_t ans[totalWordCount]; 48 - 49 - // If only one word is present then only one line is required. Cost of last 50 - // line is zero. Hence cost of this line is zero. Ending point is also n-1 as 51 - // single word is present 52 - dp[totalWordCount - 1] = 0; 53 - ans[totalWordCount - 1] = totalWordCount - 1; 54 - 55 - // Make each word first word of line by iterating over each index in arr. 56 - for (int i = totalWordCount - 2; i >= 0; i--) { 57 - int currlen = -1; 58 - dp[i] = INT_MAX; 59 - 60 - // Variable to store possible minimum cost of line. 61 - int cost; 62 - 63 - // Keep on adding words in current line by iterating from starting word upto 64 - // last word in arr. 65 - for (int j = i; j < totalWordCount; j++) { 66 - // Update the width of the words in current line + the space between two 67 - // words. 68 - currlen += wordWidths[j] + spaceWidth; 69 - 70 - // If we're bigger than the current pagewidth then we can't add more words 71 - if (currlen > pageWidth) break; 72 - 73 - // if we've run out of words then this is last line and the cost should be 74 - // 0 Otherwise the cost is the sqaure of the left over space + the costs 75 - // of all the previous lines 76 - if (j == totalWordCount - 1) 77 - cost = 0; 78 - else 79 - cost = (pageWidth - currlen) * (pageWidth - currlen) + dp[j + 1]; 80 - 81 - // Check if this arrangement gives minimum cost for line starting with 82 - // word words[i]. 83 - if (cost < dp[i]) { 84 - dp[i] = cost; 85 - ans[i] = j; 86 - } 87 - } 88 - } 89 - 90 - // We can now iterate through the answer to find the line break positions 91 - std::list<uint16_t> lineBreaks; 92 - for (size_t i = 0; i < totalWordCount;) { 93 - i = ans[i] + 1; 94 - if (i > totalWordCount) { 95 - break; 96 - } 97 - lineBreaks.push_back(i); 98 - // Text too big, just exit 99 - if (lineBreaks.size() > 1000) { 100 - break; 101 - } 102 - } 103 - 104 - std::list<TextBlock*> lines; 105 - 106 - // With the line breaks calculated we can now position the words along the 107 - // line 108 - int startWord = 0; 109 - for (const auto lineBreak : lineBreaks) { 110 - const int lineWordCount = lineBreak - startWord; 111 - 112 - int lineWordWidthSum = 0; 113 - for (int i = startWord; i < lineBreak; i++) { 114 - lineWordWidthSum += wordWidths[i]; 115 - } 116 - 117 - // Calculate spacing between words 118 - const uint16_t spareSpace = pageWidth - lineWordWidthSum; 119 - uint16_t spacing = spaceWidth; 120 - // evenly space words if using justified style, not the last line, and at 121 - // least 2 words 122 - if (style == JUSTIFIED && lineBreak != lineBreaks.back() && lineWordCount >= 2) { 123 - spacing = spareSpace / (lineWordCount - 1); 124 - } 125 - 126 - uint16_t xpos = 0; 127 - if (style == RIGHT_ALIGN) { 128 - xpos = spareSpace - (lineWordCount - 1) * spaceWidth; 129 - } else if (style == CENTER_ALIGN) { 130 - xpos = (spareSpace - (lineWordCount - 1) * spaceWidth) / 2; 131 - } 132 - 133 - for (int i = startWord; i < lineBreak; i++) { 134 - wordXpos[i] = xpos; 135 - xpos += wordWidths[i] + spacing; 136 - } 137 - 138 - std::vector<std::string> lineWords; 139 - std::vector<uint16_t> lineXPos; 140 - std::vector<uint8_t> lineWordStyles; 141 - lineWords.reserve(lineWordCount); 142 - lineXPos.reserve(lineWordCount); 143 - lineWordStyles.reserve(lineWordCount); 144 - 145 - for (int i = startWord; i < lineBreak; i++) { 146 - lineWords.push_back(words[i]); 147 - lineXPos.push_back(wordXpos[i]); 148 - lineWordStyles.push_back(wordStyles[i]); 149 - } 150 - const auto textLine = new TextBlock(lineWords, lineXPos, lineWordStyles, style); 151 - lines.push_back(textLine); 152 - startWord = lineBreak; 153 - } 154 - 155 - return lines; 156 - } 157 - 158 - void TextBlock::render(const GfxRenderer& renderer, const int fontId, const int x, const int y) const { 159 - for (int i = 0; i < words.size(); i++) { 160 - // render the word 161 - EpdFontStyle fontStyle = REGULAR; 162 - if (wordStyles[i] & BOLD_SPAN && wordStyles[i] & ITALIC_SPAN) { 163 - fontStyle = BOLD_ITALIC; 164 - } else if (wordStyles[i] & BOLD_SPAN) { 165 - fontStyle = BOLD; 166 - } else if (wordStyles[i] & ITALIC_SPAN) { 167 - fontStyle = ITALIC; 168 - } 169 - renderer.drawText(fontId, x + wordXpos[i], y, words[i].c_str(), true, fontStyle); 14 + std::advance(wordIt, 1); 15 + std::advance(wordStylesIt, 1); 16 + std::advance(wordXposIt, 1); 170 17 } 171 18 } 172 19 ··· 190 37 serialization::writePod(os, style); 191 38 } 192 39 193 - TextBlock* TextBlock::deserialize(std::istream& is) { 40 + std::unique_ptr<TextBlock> TextBlock::deserialize(std::istream& is) { 194 41 uint32_t wc, xc, sc; 195 - std::vector<std::string> words; 196 - std::vector<uint16_t> wordXpos; 197 - std::vector<uint8_t> wordStyles; 42 + std::list<std::string> words; 43 + std::list<uint16_t> wordXpos; 44 + std::list<EpdFontStyle> wordStyles; 198 45 BLOCK_STYLE style; 199 46 200 47 // words ··· 215 62 // style 216 63 serialization::readPod(is, style); 217 64 218 - return new TextBlock(words, wordXpos, wordStyles, style); 65 + return std::unique_ptr<TextBlock>(new TextBlock(std::move(words), std::move(wordXpos), std::move(wordStyles), style)); 219 66 }
+18 -28
lib/Epub/Epub/blocks/TextBlock.h
··· 1 1 #pragma once 2 + #include <EpdFontFamily.h> 3 + 2 4 #include <list> 5 + #include <memory> 3 6 #include <string> 4 - #include <vector> 5 7 6 8 #include "Block.h" 7 9 8 - enum SPAN_STYLE : uint8_t { 9 - BOLD_SPAN = 1, 10 - ITALIC_SPAN = 2, 11 - }; 12 - 13 - enum BLOCK_STYLE : uint8_t { 14 - JUSTIFIED = 0, 15 - LEFT_ALIGN = 1, 16 - CENTER_ALIGN = 2, 17 - RIGHT_ALIGN = 3, 18 - }; 19 - 20 10 // represents a block of words in the html document 21 11 class TextBlock final : public Block { 22 - // pointer to each word 23 - std::vector<std::string> words; 24 - // x position of each word 25 - std::vector<uint16_t> wordXpos; 26 - // the styles of each word 27 - std::vector<uint8_t> wordStyles; 12 + public: 13 + enum BLOCK_STYLE : uint8_t { 14 + JUSTIFIED = 0, 15 + LEFT_ALIGN = 1, 16 + CENTER_ALIGN = 2, 17 + RIGHT_ALIGN = 3, 18 + }; 28 19 29 - // the style of the block - left, center, right aligned 20 + private: 21 + std::list<std::string> words; 22 + std::list<uint16_t> wordXpos; 23 + std::list<EpdFontStyle> wordStyles; 30 24 BLOCK_STYLE style; 31 25 32 26 public: 33 - explicit TextBlock(const BLOCK_STYLE style) : style(style) {} 34 - explicit TextBlock(const std::vector<std::string>& words, const std::vector<uint16_t>& word_xpos, 35 - // the styles of each word 36 - const std::vector<uint8_t>& word_styles, const BLOCK_STYLE style) 37 - : words(words), wordXpos(word_xpos), wordStyles(word_styles), style(style) {} 27 + explicit TextBlock(std::list<std::string> words, std::list<uint16_t> word_xpos, std::list<EpdFontStyle> word_styles, 28 + const BLOCK_STYLE style) 29 + : words(std::move(words)), wordXpos(std::move(word_xpos)), wordStyles(std::move(word_styles)), style(style) {} 38 30 ~TextBlock() override = default; 39 - void addWord(const std::string& word, bool is_bold, bool is_italic); 40 31 void setStyle(const BLOCK_STYLE style) { this->style = style; } 41 32 BLOCK_STYLE getStyle() const { return style; } 42 33 bool isEmpty() override { return words.empty(); } 43 34 void layout(GfxRenderer& renderer) override {}; 44 35 // given a renderer works out where to break the words into lines 45 - std::list<TextBlock*> splitIntoLines(const GfxRenderer& renderer, int fontId, int horizontalMargin); 46 36 void render(const GfxRenderer& renderer, int fontId, int x, int y) const; 47 37 BlockType getType() override { return TEXT_BLOCK; } 48 38 void serialize(std::ostream& os) const; 49 - static TextBlock* deserialize(std::istream& is); 39 + static std::unique_ptr<TextBlock> deserialize(std::istream& is); 50 40 };
+1 -2
lib/GfxRenderer/GfxRenderer.h
··· 1 1 #pragma once 2 2 3 3 #include <EInkDisplay.h> 4 + #include <EpdFontFamily.h> 4 5 5 6 #include <map> 6 - 7 - #include "EpdFontFamily.h" 8 7 9 8 class GfxRenderer { 10 9 public:
+1
platformio.ini
··· 20 20 # https://libexpat.github.io/doc/api/latest/#XML_GE 21 21 -DXML_GE=0 22 22 -DXML_CONTEXT_BYTES=1024 23 + -std=c++2a 23 24 24 25 ; Board configuration 25 26 board_build.flash_mode = dio
+6 -7
src/main.cpp
··· 62 62 // Time required to enter sleep mode 63 63 constexpr unsigned long POWER_BUTTON_SLEEP_MS = 1000; 64 64 65 - Epub* loadEpub(const std::string& path) { 65 + std::unique_ptr<Epub> loadEpub(const std::string& path) { 66 66 if (!SD.exists(path.c_str())) { 67 67 Serial.printf("[%lu] [ ] File does not exist: %s\n", millis(), path.c_str()); 68 68 return nullptr; 69 69 } 70 70 71 - const auto epub = new Epub(path, "/.crosspoint"); 71 + auto epub = std::unique_ptr<Epub>(new Epub(path, "/.crosspoint")); 72 72 if (epub->load()) { 73 73 return epub; 74 74 } 75 75 76 76 Serial.printf("[%lu] [ ] Failed to load epub\n", millis()); 77 - delete epub; 78 77 return nullptr; 79 78 } 80 79 ··· 151 150 exitScreen(); 152 151 enterNewScreen(new FullScreenMessageScreen(renderer, inputManager, "Loading...")); 153 152 154 - Epub* epub = loadEpub(path); 153 + auto epub = loadEpub(path); 155 154 if (epub) { 156 155 appState.openEpubPath = path; 157 156 appState.saveToFile(); 158 157 exitScreen(); 159 - enterNewScreen(new EpubReaderScreen(renderer, inputManager, epub, onGoHome)); 158 + enterNewScreen(new EpubReaderScreen(renderer, inputManager, std::move(epub), onGoHome)); 160 159 } else { 161 160 exitScreen(); 162 161 enterNewScreen( ··· 206 205 207 206 appState.loadFromFile(); 208 207 if (!appState.openEpubPath.empty()) { 209 - Epub* epub = loadEpub(appState.openEpubPath); 208 + auto epub = loadEpub(appState.openEpubPath); 210 209 if (epub) { 211 210 exitScreen(); 212 - enterNewScreen(new EpubReaderScreen(renderer, inputManager, epub, onGoHome)); 211 + enterNewScreen(new EpubReaderScreen(renderer, inputManager, std::move(epub), onGoHome)); 213 212 // Ensure we're not still holding the power button before leaving setup 214 213 waitForPowerRelease(); 215 214 return;
+25 -24
src/screens/EpubReaderScreen.cpp
··· 10 10 constexpr int PAGES_PER_REFRESH = 15; 11 11 constexpr unsigned long SKIP_CHAPTER_MS = 700; 12 12 constexpr float lineCompression = 0.95f; 13 - constexpr int marginTop = 11; 13 + constexpr int marginTop = 10; 14 14 constexpr int marginRight = 10; 15 - constexpr int marginBottom = 30; 15 + constexpr int marginBottom = 20; 16 16 constexpr int marginLeft = 10; 17 17 18 18 void EpubReaderScreen::taskTrampoline(void* param) { ··· 60 60 } 61 61 vSemaphoreDelete(renderingMutex); 62 62 renderingMutex = nullptr; 63 - delete section; 64 - section = nullptr; 65 - delete epub; 66 - epub = nullptr; 63 + section.reset(); 64 + epub.reset(); 67 65 } 68 66 69 67 void EpubReaderScreen::handleInput() { ··· 88 86 xSemaphoreTake(renderingMutex, portMAX_DELAY); 89 87 nextPageNumber = 0; 90 88 currentSpineIndex = nextReleased ? currentSpineIndex + 1 : currentSpineIndex - 1; 91 - delete section; 92 - section = nullptr; 89 + section.reset(); 93 90 xSemaphoreGive(renderingMutex); 94 91 updateRequired = true; 95 92 return; ··· 109 106 xSemaphoreTake(renderingMutex, portMAX_DELAY); 110 107 nextPageNumber = UINT16_MAX; 111 108 currentSpineIndex--; 112 - delete section; 113 - section = nullptr; 109 + section.reset(); 114 110 xSemaphoreGive(renderingMutex); 115 111 } 116 112 updateRequired = true; ··· 122 118 xSemaphoreTake(renderingMutex, portMAX_DELAY); 123 119 nextPageNumber = 0; 124 120 currentSpineIndex++; 125 - delete section; 126 - section = nullptr; 121 + section.reset(); 127 122 xSemaphoreGive(renderingMutex); 128 123 } 129 124 updateRequired = true; ··· 155 150 if (!section) { 156 151 const auto filepath = epub->getSpineItem(currentSpineIndex); 157 152 Serial.printf("[%lu] [ERS] Loading file: %s, index: %d\n", millis(), filepath.c_str(), currentSpineIndex); 158 - section = new Section(epub, currentSpineIndex, renderer); 153 + section = std::unique_ptr<Section>(new Section(epub, currentSpineIndex, renderer)); 159 154 if (!section->loadCacheMetadata(READER_FONT_ID, lineCompression, marginTop, marginRight, marginBottom, 160 155 marginLeft)) { 161 156 Serial.printf("[%lu] [ERS] Cache not found, building...\n", millis()); ··· 179 174 if (!section->persistPageDataToSD(READER_FONT_ID, lineCompression, marginTop, marginRight, marginBottom, 180 175 marginLeft)) { 181 176 Serial.printf("[%lu] [ERS] Failed to persist page data to SD\n", millis()); 182 - delete section; 183 - section = nullptr; 177 + section.reset(); 184 178 return; 185 179 } 186 180 } else { ··· 212 206 return; 213 207 } 214 208 215 - const Page* p = section->loadPageFromSD(); 216 - const auto start = millis(); 217 - renderContents(p); 218 - Serial.printf("[%lu] [ERS] Rendered page in %dms\n", millis(), millis() - start); 219 - delete p; 209 + { 210 + auto p = section->loadPageFromSD(); 211 + if (!p) { 212 + Serial.printf("[%lu] [ERS] Failed to load page from SD - clearing section cache\n", millis()); 213 + section->clearCache(); 214 + section.reset(); 215 + return renderScreen(); 216 + } 217 + const auto start = millis(); 218 + renderContents(std::move(p)); 219 + Serial.printf("[%lu] [ERS] Rendered page in %dms\n", millis(), millis() - start); 220 + } 220 221 221 222 File f = SD.open((epub->getCachePath() + "/progress.bin").c_str(), FILE_WRITE); 222 223 uint8_t data[4]; ··· 228 229 f.close(); 229 230 } 230 231 231 - void EpubReaderScreen::renderContents(const Page* p) { 232 - p->render(renderer, READER_FONT_ID); 232 + void EpubReaderScreen::renderContents(std::unique_ptr<Page> page) { 233 + page->render(renderer, READER_FONT_ID); 233 234 renderStatusBar(); 234 235 if (pagesUntilFullRefresh <= 1) { 235 236 renderer.displayBuffer(EInkDisplay::HALF_REFRESH); ··· 244 245 { 245 246 renderer.clearScreen(0x00); 246 247 renderer.setFontRenderMode(GfxRenderer::GRAYSCALE_LSB); 247 - p->render(renderer, READER_FONT_ID); 248 + page->render(renderer, READER_FONT_ID); 248 249 renderer.copyGrayscaleLsbBuffers(); 249 250 250 251 // Render and copy to MSB buffer 251 252 renderer.clearScreen(0x00); 252 253 renderer.setFontRenderMode(GfxRenderer::GRAYSCALE_MSB); 253 - p->render(renderer, READER_FONT_ID); 254 + page->render(renderer, READER_FONT_ID); 254 255 renderer.copyGrayscaleMsbBuffers(); 255 256 256 257 // display grayscale part
+5 -5
src/screens/EpubReaderScreen.h
··· 8 8 #include "Screen.h" 9 9 10 10 class EpubReaderScreen final : public Screen { 11 - Epub* epub; 12 - Section* section = nullptr; 11 + std::shared_ptr<Epub> epub; 12 + std::unique_ptr<Section> section = nullptr; 13 13 TaskHandle_t displayTaskHandle = nullptr; 14 14 SemaphoreHandle_t renderingMutex = nullptr; 15 15 int currentSpineIndex = 0; ··· 21 21 static void taskTrampoline(void* param); 22 22 [[noreturn]] void displayTaskLoop(); 23 23 void renderScreen(); 24 - void renderContents(const Page* p); 24 + void renderContents(std::unique_ptr<Page> p); 25 25 void renderStatusBar() const; 26 26 27 27 public: 28 - explicit EpubReaderScreen(GfxRenderer& renderer, InputManager& inputManager, Epub* epub, 28 + explicit EpubReaderScreen(GfxRenderer& renderer, InputManager& inputManager, std::unique_ptr<Epub> epub, 29 29 const std::function<void()>& onGoHome) 30 - : Screen(renderer, inputManager), epub(epub), onGoHome(onGoHome) {} 30 + : Screen(renderer, inputManager), epub(std::move(epub)), onGoHome(onGoHome) {} 31 31 void onEnter() override; 32 32 void onExit() override; 33 33 void handleInput() override;