mirror of
https://github.com/daveallie/crosspoint-reader.git
synced 2026-02-08 08:37:38 +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
|
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)
|
// 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,
|
bool Section::persistPageDataToSD(const int fontId, const float lineCompression, const int marginTop,
|
||||||
const int marginRight, const int marginBottom, const int marginLeft,
|
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 localPath = epub->getSpineItem(spineIndex).href;
|
||||||
const auto tmpHtmlPath = epub->getCachePath() + "/.tmp_" + std::to_string(spineIndex) + ".html";
|
const auto tmpHtmlPath = epub->getCachePath() + "/.tmp_" + std::to_string(spineIndex) + ".html";
|
||||||
|
|
||||||
// Retry logic for SD card timing issues
|
// Retry logic for SD card timing issues
|
||||||
bool success = false;
|
bool success = false;
|
||||||
|
size_t fileSize = 0;
|
||||||
for (int attempt = 0; attempt < 3 && !success; attempt++) {
|
for (int attempt = 0; attempt < 3 && !success; attempt++) {
|
||||||
if (attempt > 0) {
|
if (attempt > 0) {
|
||||||
Serial.printf("[%lu] [SCT] Retrying stream (attempt %d)...\n", millis(), attempt + 1);
|
Serial.printf("[%lu] [SCT] Retrying stream (attempt %d)...\n", millis(), attempt + 1);
|
||||||
delay(50); // Brief delay before retry
|
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;
|
File tmpHtml;
|
||||||
if (!FsHelpers::openFileForWrite("SCT", tmpHtmlPath, tmpHtml)) {
|
if (!FsHelpers::openFileForWrite("SCT", tmpHtmlPath, tmpHtml)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
success = epub->readItemContentsToStream(localPath, tmpHtml, 1024);
|
success = epub->readItemContentsToStream(localPath, tmpHtml, 1024);
|
||||||
|
fileSize = tmpHtml.size();
|
||||||
tmpHtml.close();
|
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) {
|
if (!success) {
|
||||||
@ -140,7 +155,12 @@ bool Section::persistPageDataToSD(const int fontId, const float lineCompression,
|
|||||||
return false;
|
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(
|
ChapterHtmlSlimParser visitor(
|
||||||
tmpHtmlPath, renderer, fontId, lineCompression, marginTop, marginRight, marginBottom, marginLeft,
|
tmpHtmlPath, renderer, fontId, lineCompression, marginTop, marginRight, marginBottom, marginLeft,
|
||||||
|
|||||||
@ -33,6 +33,7 @@ class Section {
|
|||||||
bool clearCache() const;
|
bool clearCache() const;
|
||||||
bool persistPageDataToSD(int fontId, float lineCompression, int marginTop, int marginRight, int marginBottom,
|
bool persistPageDataToSD(int fontId, float lineCompression, int marginTop, int marginRight, int marginBottom,
|
||||||
int marginLeft, bool extraParagraphSpacing,
|
int marginLeft, bool extraParagraphSpacing,
|
||||||
|
const std::function<void()>& progressSetupFn = nullptr,
|
||||||
const std::function<void(int)>& progressFn = nullptr);
|
const std::function<void(int)>& progressFn = nullptr);
|
||||||
std::unique_ptr<Page> loadPageFromSD() const;
|
std::unique_ptr<Page> loadPageFromSD() const;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -4,11 +4,18 @@
|
|||||||
#include <Serialization.h>
|
#include <Serialization.h>
|
||||||
|
|
||||||
void TextBlock::render(const GfxRenderer& renderer, const int fontId, const int x, const int y) const {
|
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 wordIt = words.begin();
|
||||||
auto wordStylesIt = wordStyles.begin();
|
auto wordStylesIt = wordStyles.begin();
|
||||||
auto wordXposIt = wordXpos.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);
|
renderer.drawText(fontId, *wordXposIt + x, y, wordIt->c_str(), true, *wordStylesIt);
|
||||||
|
|
||||||
std::advance(wordIt, 1);
|
std::advance(wordIt, 1);
|
||||||
@ -46,6 +53,13 @@ std::unique_ptr<TextBlock> TextBlock::deserialize(File& file) {
|
|||||||
|
|
||||||
// words
|
// words
|
||||||
serialization::readPod(file, wc);
|
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);
|
words.resize(wc);
|
||||||
for (auto& w : words) serialization::readString(file, w);
|
for (auto& w : words) serialization::readString(file, w);
|
||||||
|
|
||||||
@ -59,6 +73,13 @@ std::unique_ptr<TextBlock> TextBlock::deserialize(File& file) {
|
|||||||
wordStyles.resize(sc);
|
wordStyles.resize(sc);
|
||||||
for (auto& s : wordStyles) serialization::readPod(file, s);
|
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
|
// style
|
||||||
serialization::readPod(file, style);
|
serialization::readPod(file, style);
|
||||||
|
|
||||||
|
|||||||
@ -224,26 +224,37 @@ void EpubReaderActivity::renderScreen() {
|
|||||||
constexpr int barHeight = 10;
|
constexpr int barHeight = 10;
|
||||||
constexpr int boxMargin = 20;
|
constexpr int boxMargin = 20;
|
||||||
const int textWidth = renderer.getTextWidth(READER_FONT_ID, "Indexing...");
|
const int textWidth = renderer.getTextWidth(READER_FONT_ID, "Indexing...");
|
||||||
const int boxWidth = (barWidth > textWidth ? barWidth : textWidth) + boxMargin * 2;
|
const int boxWidthWithBar = (barWidth > textWidth ? barWidth : textWidth) + boxMargin * 2;
|
||||||
const int boxHeight = renderer.getLineHeight(READER_FONT_ID) + barHeight + boxMargin * 3;
|
const int boxWidthNoBar = textWidth + boxMargin * 2;
|
||||||
const int boxX = (GfxRenderer::getScreenWidth() - boxWidth) / 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;
|
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;
|
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.fillRect(boxXNoBar, boxY, boxWidthNoBar, boxHeightNoBar, false);
|
||||||
renderer.drawText(READER_FONT_ID, boxX + boxMargin, boxY + boxMargin, "Indexing...");
|
renderer.drawText(READER_FONT_ID, boxXNoBar + boxMargin, boxY + boxMargin, "Indexing...");
|
||||||
renderer.drawRect(boxX + 5, boxY + 5, boxWidth - 10, boxHeight - 10);
|
renderer.drawRect(boxXNoBar + 5, boxY + 5, boxWidthNoBar - 10, boxHeightNoBar - 10);
|
||||||
// Draw empty progress bar outline
|
|
||||||
renderer.drawRect(barX, barY, barWidth, barHeight);
|
|
||||||
renderer.displayBuffer();
|
renderer.displayBuffer();
|
||||||
pagesUntilFullRefresh = 0;
|
pagesUntilFullRefresh = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
section->setupCacheDir();
|
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
|
// Progress callback to update progress bar
|
||||||
auto progressCallback = [this, barX, barY, barWidth, barHeight](int progress) {
|
auto progressCallback = [this, barX, barY, barWidth, barHeight](int progress) {
|
||||||
const int fillWidth = (barWidth - 2) * progress / 100;
|
const int fillWidth = (barWidth - 2) * progress / 100;
|
||||||
@ -252,7 +263,7 @@ void EpubReaderActivity::renderScreen() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (!section->persistPageDataToSD(READER_FONT_ID, lineCompression, marginTop, marginRight, marginBottom,
|
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());
|
Serial.printf("[%lu] [ERS] Failed to persist page data to SD\n", millis());
|
||||||
section.reset();
|
section.reset();
|
||||||
return;
|
return;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user