mirror of
https://github.com/daveallie/crosspoint-reader.git
synced 2026-02-04 14:47:37 +03:00
Compare commits
4 Commits
f41e7496ad
...
8e4ce5225c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8e4ce5225c | ||
|
|
11a059f39a | ||
|
|
d3848779f9 | ||
|
|
abe3e6c6db |
@ -106,6 +106,18 @@ std::vector<size_t> ParsedText::computeLineBreaks(const int pageWidth, const int
|
||||
ans[i] = j; // j is the index of the last word in this optimal line
|
||||
}
|
||||
}
|
||||
|
||||
// Handle oversized word: if no valid configuration found, force single-word line
|
||||
// This prevents cascade failure where one oversized word breaks all preceding words
|
||||
if (dp[i] == MAX_COST) {
|
||||
ans[i] = i; // Just this word on its own line
|
||||
// Inherit cost from next word to allow subsequent words to find valid configurations
|
||||
if (i + 1 < static_cast<int>(totalWordCount)) {
|
||||
dp[i] = dp[i + 1];
|
||||
} else {
|
||||
dp[i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stores the index of the word that starts the next line (last_word_index + 1)
|
||||
|
||||
@ -115,24 +115,39 @@ bool Section::clearCache() const {
|
||||
|
||||
bool Section::persistPageDataToSD(const int fontId, const float lineCompression, const int marginTop,
|
||||
const int marginRight, const int marginBottom, const int marginLeft,
|
||||
const bool extraParagraphSpacing, const std::function<void(int)>& progressFn) {
|
||||
const bool extraParagraphSpacing, const std::function<void()>& progressSetupFn,
|
||||
const std::function<void(int)>& progressFn) {
|
||||
constexpr size_t MIN_SIZE_FOR_PROGRESS = 50 * 1024; // 50KB
|
||||
const auto localPath = epub->getSpineItem(spineIndex).href;
|
||||
const auto tmpHtmlPath = epub->getCachePath() + "/.tmp_" + std::to_string(spineIndex) + ".html";
|
||||
|
||||
// Retry logic for SD card timing issues
|
||||
bool success = false;
|
||||
size_t fileSize = 0;
|
||||
for (int attempt = 0; attempt < 3 && !success; attempt++) {
|
||||
if (attempt > 0) {
|
||||
Serial.printf("[%lu] [SCT] Retrying stream (attempt %d)...\n", millis(), attempt + 1);
|
||||
delay(50); // Brief delay before retry
|
||||
}
|
||||
|
||||
// Remove any incomplete file from previous attempt before retrying
|
||||
if (SD.exists(tmpHtmlPath.c_str())) {
|
||||
SD.remove(tmpHtmlPath.c_str());
|
||||
}
|
||||
|
||||
File tmpHtml;
|
||||
if (!FsHelpers::openFileForWrite("SCT", tmpHtmlPath, tmpHtml)) {
|
||||
continue;
|
||||
}
|
||||
success = epub->readItemContentsToStream(localPath, tmpHtml, 1024);
|
||||
fileSize = tmpHtml.size();
|
||||
tmpHtml.close();
|
||||
|
||||
// If streaming failed, remove the incomplete file immediately
|
||||
if (!success && SD.exists(tmpHtmlPath.c_str())) {
|
||||
SD.remove(tmpHtmlPath.c_str());
|
||||
Serial.printf("[%lu] [SCT] Removed incomplete temp file after failed attempt\n", millis());
|
||||
}
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
@ -140,7 +155,12 @@ bool Section::persistPageDataToSD(const int fontId, const float lineCompression,
|
||||
return false;
|
||||
}
|
||||
|
||||
Serial.printf("[%lu] [SCT] Streamed temp HTML to %s\n", millis(), tmpHtmlPath.c_str());
|
||||
Serial.printf("[%lu] [SCT] Streamed temp HTML to %s (%d bytes)\n", millis(), tmpHtmlPath.c_str(), fileSize);
|
||||
|
||||
// Only show progress bar for larger chapters where rendering overhead is worth it
|
||||
if (progressSetupFn && fileSize >= MIN_SIZE_FOR_PROGRESS) {
|
||||
progressSetupFn();
|
||||
}
|
||||
|
||||
ChapterHtmlSlimParser visitor(
|
||||
tmpHtmlPath, renderer, fontId, lineCompression, marginTop, marginRight, marginBottom, marginLeft,
|
||||
|
||||
@ -33,6 +33,7 @@ class Section {
|
||||
bool clearCache() const;
|
||||
bool persistPageDataToSD(int fontId, float lineCompression, int marginTop, int marginRight, int marginBottom,
|
||||
int marginLeft, bool extraParagraphSpacing,
|
||||
const std::function<void()>& progressSetupFn = nullptr,
|
||||
const std::function<void(int)>& progressFn = nullptr);
|
||||
std::unique_ptr<Page> loadPageFromSD() const;
|
||||
};
|
||||
|
||||
@ -4,11 +4,18 @@
|
||||
#include <Serialization.h>
|
||||
|
||||
void TextBlock::render(const GfxRenderer& renderer, const int fontId, const int x, const int y) const {
|
||||
// Validate iterator bounds before rendering
|
||||
if (words.size() != wordXpos.size() || words.size() != wordStyles.size()) {
|
||||
Serial.printf("[%lu] [TXB] Render skipped: size mismatch (words=%u, xpos=%u, styles=%u)\n", millis(),
|
||||
(uint32_t)words.size(), (uint32_t)wordXpos.size(), (uint32_t)wordStyles.size());
|
||||
return;
|
||||
}
|
||||
|
||||
auto wordIt = words.begin();
|
||||
auto wordStylesIt = wordStyles.begin();
|
||||
auto wordXposIt = wordXpos.begin();
|
||||
|
||||
for (int i = 0; i < words.size(); i++) {
|
||||
for (size_t i = 0; i < words.size(); i++) {
|
||||
renderer.drawText(fontId, *wordXposIt + x, y, wordIt->c_str(), true, *wordStylesIt);
|
||||
|
||||
std::advance(wordIt, 1);
|
||||
@ -46,6 +53,13 @@ std::unique_ptr<TextBlock> TextBlock::deserialize(File& file) {
|
||||
|
||||
// words
|
||||
serialization::readPod(file, wc);
|
||||
|
||||
// Sanity check: prevent allocation of unreasonably large lists (max 10000 words per block)
|
||||
if (wc > 10000) {
|
||||
Serial.printf("[%lu] [TXB] Deserialization failed: word count %u exceeds maximum\n", millis(), wc);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
words.resize(wc);
|
||||
for (auto& w : words) serialization::readString(file, w);
|
||||
|
||||
@ -59,6 +73,13 @@ std::unique_ptr<TextBlock> TextBlock::deserialize(File& file) {
|
||||
wordStyles.resize(sc);
|
||||
for (auto& s : wordStyles) serialization::readPod(file, s);
|
||||
|
||||
// Validate data consistency: all three lists must have the same size
|
||||
if (wc != xc || wc != sc) {
|
||||
Serial.printf("[%lu] [TXB] Deserialization failed: size mismatch (words=%u, xpos=%u, styles=%u)\n", millis(), wc,
|
||||
xc, sc);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// style
|
||||
serialization::readPod(file, style);
|
||||
|
||||
|
||||
@ -224,26 +224,37 @@ void EpubReaderActivity::renderScreen() {
|
||||
constexpr int barHeight = 10;
|
||||
constexpr int boxMargin = 20;
|
||||
const int textWidth = renderer.getTextWidth(READER_FONT_ID, "Indexing...");
|
||||
const int boxWidth = (barWidth > textWidth ? barWidth : textWidth) + boxMargin * 2;
|
||||
const int boxHeight = renderer.getLineHeight(READER_FONT_ID) + barHeight + boxMargin * 3;
|
||||
const int boxX = (GfxRenderer::getScreenWidth() - boxWidth) / 2;
|
||||
const int boxWidthWithBar = (barWidth > textWidth ? barWidth : textWidth) + boxMargin * 2;
|
||||
const int boxWidthNoBar = textWidth + boxMargin * 2;
|
||||
const int boxHeightWithBar = renderer.getLineHeight(READER_FONT_ID) + barHeight + boxMargin * 3;
|
||||
const int boxHeightNoBar = renderer.getLineHeight(READER_FONT_ID) + boxMargin * 2;
|
||||
const int boxXWithBar = (GfxRenderer::getScreenWidth() - boxWidthWithBar) / 2;
|
||||
const int boxXNoBar = (GfxRenderer::getScreenWidth() - boxWidthNoBar) / 2;
|
||||
constexpr int boxY = 50;
|
||||
const int barX = boxX + (boxWidth - barWidth) / 2;
|
||||
const int barX = boxXWithBar + (boxWidthWithBar - barWidth) / 2;
|
||||
const int barY = boxY + renderer.getLineHeight(READER_FONT_ID) + boxMargin * 2;
|
||||
|
||||
// Draw initial indexing box with 0% progress
|
||||
// Always show "Indexing..." text first
|
||||
{
|
||||
renderer.fillRect(boxX, boxY, boxWidth, boxHeight, false);
|
||||
renderer.drawText(READER_FONT_ID, boxX + boxMargin, boxY + boxMargin, "Indexing...");
|
||||
renderer.drawRect(boxX + 5, boxY + 5, boxWidth - 10, boxHeight - 10);
|
||||
// Draw empty progress bar outline
|
||||
renderer.drawRect(barX, barY, barWidth, barHeight);
|
||||
renderer.fillRect(boxXNoBar, boxY, boxWidthNoBar, boxHeightNoBar, false);
|
||||
renderer.drawText(READER_FONT_ID, boxXNoBar + boxMargin, boxY + boxMargin, "Indexing...");
|
||||
renderer.drawRect(boxXNoBar + 5, boxY + 5, boxWidthNoBar - 10, boxHeightNoBar - 10);
|
||||
renderer.displayBuffer();
|
||||
pagesUntilFullRefresh = 0;
|
||||
}
|
||||
|
||||
section->setupCacheDir();
|
||||
|
||||
// Setup callback - only called for chapters >= 50KB, redraws with progress bar
|
||||
auto progressSetup = [this, boxXWithBar, boxWidthWithBar, boxHeightWithBar, boxMargin, barX, barY, barWidth,
|
||||
barHeight]() {
|
||||
renderer.fillRect(boxXWithBar, boxY, boxWidthWithBar, boxHeightWithBar, false);
|
||||
renderer.drawText(READER_FONT_ID, boxXWithBar + boxMargin, boxY + boxMargin, "Indexing...");
|
||||
renderer.drawRect(boxXWithBar + 5, boxY + 5, boxWidthWithBar - 10, boxHeightWithBar - 10);
|
||||
renderer.drawRect(barX, barY, barWidth, barHeight);
|
||||
renderer.displayBuffer();
|
||||
};
|
||||
|
||||
// Progress callback to update progress bar
|
||||
auto progressCallback = [this, barX, barY, barWidth, barHeight](int progress) {
|
||||
const int fillWidth = (barWidth - 2) * progress / 100;
|
||||
@ -252,7 +263,7 @@ void EpubReaderActivity::renderScreen() {
|
||||
};
|
||||
|
||||
if (!section->persistPageDataToSD(READER_FONT_ID, lineCompression, marginTop, marginRight, marginBottom,
|
||||
marginLeft, SETTINGS.extraParagraphSpacing, progressCallback)) {
|
||||
marginLeft, SETTINGS.extraParagraphSpacing, progressSetup, progressCallback)) {
|
||||
Serial.printf("[%lu] [ERS] Failed to persist page data to SD\n", millis());
|
||||
section.reset();
|
||||
return;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user