Support transparent PNG
This commit is contained in:
@@ -829,3 +829,65 @@ void EpubReaderActivity::restoreSavedPosition() {
|
||||
}
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
bool EpubReaderActivity::drawCurrentPageToBuffer(const std::string& filePath, GfxRenderer& renderer) {
|
||||
auto epub = std::make_shared<Epub>(filePath, "/.crosspoint");
|
||||
// skip CSS (second arg) since we only need layout/section data
|
||||
if (!epub->load(true, false)) {
|
||||
LOG_DBG("SLP", "EPUB: failed to load %s", filePath.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
epub->setupCacheDir();
|
||||
|
||||
// Load saved spine index and page number
|
||||
int spineIndex = 0, pageNumber = 0;
|
||||
FsFile f;
|
||||
if (Storage.openFileForRead("SLP", epub->getCachePath() + "/progress.bin", f)) {
|
||||
uint8_t data[6];
|
||||
if (f.read(data, 6) == 6) {
|
||||
spineIndex = data[0] | (data[1] << 8);
|
||||
pageNumber = data[2] | (data[3] << 8);
|
||||
}
|
||||
f.close();
|
||||
}
|
||||
if (spineIndex < 0 || spineIndex >= epub->getSpineItemsCount()) spineIndex = 0;
|
||||
|
||||
// Apply the reader orientation so margins match what the reader would produce
|
||||
applyReaderOrientation(renderer, SETTINGS.orientation);
|
||||
|
||||
// Compute margins exactly as render() does
|
||||
int marginTop, marginRight, marginBottom, marginLeft;
|
||||
renderer.getOrientedViewableTRBL(&marginTop, &marginRight, &marginBottom, &marginLeft);
|
||||
marginTop += SETTINGS.screenMargin;
|
||||
marginLeft += SETTINGS.screenMargin;
|
||||
marginRight += SETTINGS.screenMargin;
|
||||
const uint8_t statusBarHeight = UITheme::getInstance().getStatusBarHeight();
|
||||
marginBottom += std::max(SETTINGS.screenMargin, statusBarHeight);
|
||||
|
||||
const uint16_t viewportWidth = renderer.getScreenWidth() - marginLeft - marginRight;
|
||||
const uint16_t viewportHeight = renderer.getScreenHeight() - marginTop - marginBottom;
|
||||
|
||||
// Load the cached section file (won't rebuild if cache missing — too slow for sleep)
|
||||
auto section = std::unique_ptr<Section>(new Section(epub, spineIndex, renderer));
|
||||
if (!section->loadSectionFile(SETTINGS.getReaderFontId(), SETTINGS.getReaderLineCompression(),
|
||||
SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth,
|
||||
viewportHeight, SETTINGS.hyphenationEnabled, SETTINGS.embeddedStyle)) {
|
||||
LOG_DBG("SLP", "EPUB: section cache not found for spine %d", spineIndex);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pageNumber < 0 || pageNumber >= section->pageCount) pageNumber = 0;
|
||||
section->currentPage = pageNumber;
|
||||
|
||||
auto page = section->loadPageFromSectionFile();
|
||||
if (!page) {
|
||||
LOG_DBG("SLP", "EPUB: failed to load page %d", pageNumber);
|
||||
return false;
|
||||
}
|
||||
|
||||
renderer.clearScreen();
|
||||
page->render(renderer, SETTINGS.getReaderFontId(), marginLeft, marginTop);
|
||||
// No displayBuffer call — caller (SleepActivity) handles that after compositing the overlay
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -58,4 +58,9 @@ class EpubReaderActivity final : public Activity {
|
||||
void loop() override;
|
||||
void render(RenderLock&& lock) override;
|
||||
bool isReaderActivity() const override { return true; }
|
||||
|
||||
// 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);
|
||||
};
|
||||
|
||||
@@ -600,3 +600,188 @@ void TxtReaderActivity::savePageIndexCache() const {
|
||||
f.close();
|
||||
LOG_DBG("TRS", "Saved page index cache: %d pages", totalPages);
|
||||
}
|
||||
|
||||
bool TxtReaderActivity::drawCurrentPageToBuffer(const std::string& filePath, GfxRenderer& renderer) {
|
||||
Txt txt(filePath, "/.crosspoint");
|
||||
if (!txt.load()) {
|
||||
LOG_DBG("SLP", "TXT: failed to load %s", filePath.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Compute layout values that match what initializeReader() produces
|
||||
const int fontId = SETTINGS.getReaderFontId();
|
||||
const uint8_t screenMargin = SETTINGS.screenMargin;
|
||||
const uint8_t paragraphAlignment = SETTINGS.paragraphAlignment;
|
||||
|
||||
int marginTop, marginRight, marginBottom, marginLeft;
|
||||
renderer.getOrientedViewableTRBL(&marginTop, &marginRight, &marginBottom, &marginLeft);
|
||||
marginTop += screenMargin;
|
||||
marginLeft += screenMargin;
|
||||
marginRight += screenMargin;
|
||||
marginBottom += std::max(screenMargin, static_cast<uint8_t>(UITheme::getInstance().getStatusBarHeight()));
|
||||
|
||||
const int vw = renderer.getScreenWidth() - marginLeft - marginRight;
|
||||
const int vh = renderer.getScreenHeight() - marginTop - marginBottom;
|
||||
const int lineHeight = renderer.getLineHeight(fontId);
|
||||
const int linesPerPage = std::max(1, vh / lineHeight);
|
||||
|
||||
// Load the page offset index from cache (must already exist from normal reading)
|
||||
std::string cachePath = txt.getCachePath() + "/index.bin";
|
||||
FsFile cacheFile;
|
||||
if (!Storage.openFileForRead("SLP", cachePath, cacheFile)) {
|
||||
LOG_DBG("SLP", "TXT: no page index cache");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t magic;
|
||||
serialization::readPod(cacheFile, magic);
|
||||
if (magic != CACHE_MAGIC) {
|
||||
cacheFile.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t version;
|
||||
serialization::readPod(cacheFile, version);
|
||||
if (version != CACHE_VERSION) {
|
||||
cacheFile.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t cachedFileSize;
|
||||
serialization::readPod(cacheFile, cachedFileSize);
|
||||
if (cachedFileSize != txt.getFileSize()) {
|
||||
cacheFile.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
int32_t cachedVw, cachedLpp, cachedFontId, cachedMargin;
|
||||
serialization::readPod(cacheFile, cachedVw);
|
||||
serialization::readPod(cacheFile, cachedLpp);
|
||||
serialization::readPod(cacheFile, cachedFontId);
|
||||
serialization::readPod(cacheFile, cachedMargin);
|
||||
if (cachedVw != vw || cachedLpp != linesPerPage || cachedFontId != fontId || cachedMargin != screenMargin) {
|
||||
LOG_DBG("SLP", "TXT: cache invalid (settings changed)");
|
||||
cacheFile.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t cachedAlignment;
|
||||
serialization::readPod(cacheFile, cachedAlignment);
|
||||
if (cachedAlignment != paragraphAlignment) {
|
||||
cacheFile.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t numPages;
|
||||
serialization::readPod(cacheFile, numPages);
|
||||
if (numPages == 0) {
|
||||
cacheFile.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<size_t> pageOffsets;
|
||||
pageOffsets.reserve(numPages);
|
||||
for (uint32_t i = 0; i < numPages; i++) {
|
||||
uint32_t offset;
|
||||
serialization::readPod(cacheFile, offset);
|
||||
pageOffsets.push_back(offset);
|
||||
}
|
||||
cacheFile.close();
|
||||
|
||||
// Load saved page number from progress file
|
||||
int savedPage = 0;
|
||||
FsFile progFile;
|
||||
if (Storage.openFileForRead("SLP", txt.getCachePath() + "/progress.bin", progFile)) {
|
||||
uint8_t data[4];
|
||||
if (progFile.read(data, 4) == 4) {
|
||||
savedPage = data[0] + (data[1] << 8);
|
||||
}
|
||||
progFile.close();
|
||||
}
|
||||
if (savedPage < 0 || savedPage >= static_cast<int>(numPages)) savedPage = 0;
|
||||
|
||||
// Load the page lines from file
|
||||
std::vector<std::string> pageLines;
|
||||
const size_t fileSize = txt.getFileSize();
|
||||
size_t offset = pageOffsets[savedPage];
|
||||
if (offset >= fileSize) {
|
||||
LOG_DBG("SLP", "TXT: page offset out of bounds");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Replicate loadPageAtOffset() logic with local layout variables
|
||||
size_t chunkSize = std::min(CHUNK_SIZE, fileSize - offset);
|
||||
auto* buffer = static_cast<uint8_t*>(malloc(chunkSize + 1));
|
||||
if (!buffer) return false;
|
||||
|
||||
if (!txt.readContent(buffer, offset, chunkSize)) {
|
||||
free(buffer);
|
||||
return false;
|
||||
}
|
||||
buffer[chunkSize] = '\0';
|
||||
|
||||
size_t pos = 0;
|
||||
while (pos < chunkSize && static_cast<int>(pageLines.size()) < linesPerPage) {
|
||||
size_t lineEnd = pos;
|
||||
while (lineEnd < chunkSize && buffer[lineEnd] != '\n') lineEnd++;
|
||||
bool lineComplete = (lineEnd < chunkSize) || (offset + lineEnd >= fileSize);
|
||||
if (!lineComplete && !pageLines.empty()) break;
|
||||
|
||||
size_t lineContentLen = lineEnd - pos;
|
||||
bool hasCR = (lineContentLen > 0 && buffer[pos + lineContentLen - 1] == '\r');
|
||||
size_t displayLen = hasCR ? lineContentLen - 1 : lineContentLen;
|
||||
std::string line(reinterpret_cast<char*>(buffer + pos), displayLen);
|
||||
size_t lineBytePos = 0;
|
||||
|
||||
while (!line.empty() && static_cast<int>(pageLines.size()) < linesPerPage) {
|
||||
if (renderer.getTextWidth(fontId, line.c_str()) <= vw) {
|
||||
pageLines.push_back(line);
|
||||
lineBytePos = displayLen;
|
||||
line.clear();
|
||||
break;
|
||||
}
|
||||
size_t breakPos = line.length();
|
||||
while (breakPos > 0 && renderer.getTextWidth(fontId, line.substr(0, breakPos).c_str()) > vw) {
|
||||
size_t spacePos = line.rfind(' ', breakPos - 1);
|
||||
if (spacePos != std::string::npos && spacePos > 0) {
|
||||
breakPos = spacePos;
|
||||
} else {
|
||||
breakPos--;
|
||||
while (breakPos > 0 && (line[breakPos] & 0xC0) == 0x80) breakPos--;
|
||||
}
|
||||
}
|
||||
if (breakPos == 0) breakPos = 1;
|
||||
pageLines.push_back(line.substr(0, breakPos));
|
||||
size_t skipChars = breakPos;
|
||||
if (breakPos < line.length() && line[breakPos] == ' ') skipChars++;
|
||||
lineBytePos += skipChars;
|
||||
line = line.substr(skipChars);
|
||||
}
|
||||
pos = line.empty() ? lineEnd + 1 : pos + lineBytePos;
|
||||
}
|
||||
free(buffer);
|
||||
|
||||
if (pageLines.empty()) return false;
|
||||
|
||||
// Render lines to frame buffer (no displayBuffer call)
|
||||
renderer.clearScreen();
|
||||
int y = marginTop;
|
||||
for (const auto& line : pageLines) {
|
||||
if (!line.empty()) {
|
||||
int x = marginLeft;
|
||||
switch (paragraphAlignment) {
|
||||
case CrossPointSettings::CENTER_ALIGN:
|
||||
x = marginLeft + (vw - renderer.getTextWidth(fontId, line.c_str())) / 2;
|
||||
break;
|
||||
case CrossPointSettings::RIGHT_ALIGN:
|
||||
x = marginLeft + vw - renderer.getTextWidth(fontId, line.c_str());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
renderer.drawText(fontId, x, y, line.c_str());
|
||||
}
|
||||
y += lineHeight;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -49,4 +49,9 @@ class TxtReaderActivity final : public Activity {
|
||||
void loop() override;
|
||||
void render(RenderLock&&) override;
|
||||
bool isReaderActivity() const override { return true; }
|
||||
|
||||
// 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);
|
||||
};
|
||||
|
||||
@@ -351,3 +351,78 @@ void XtcReaderActivity::loadProgress() {
|
||||
f.close();
|
||||
}
|
||||
}
|
||||
|
||||
bool XtcReaderActivity::drawCurrentPageToBuffer(const std::string& filePath, GfxRenderer& renderer) {
|
||||
Xtc xtc(filePath, "/.crosspoint");
|
||||
if (!xtc.load()) {
|
||||
LOG_DBG("SLP", "XTC: failed to load %s", filePath.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Load saved page number
|
||||
uint32_t savedPage = 0;
|
||||
FsFile f;
|
||||
if (Storage.openFileForRead("SLP", xtc.getCachePath() + "/progress.bin", f)) {
|
||||
uint8_t data[4];
|
||||
if (f.read(data, 4) == 4) {
|
||||
savedPage = data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24);
|
||||
}
|
||||
f.close();
|
||||
}
|
||||
if (savedPage >= xtc.getPageCount()) savedPage = 0;
|
||||
|
||||
const uint16_t pageWidth = xtc.getPageWidth();
|
||||
const uint16_t pageHeight = xtc.getPageHeight();
|
||||
const uint8_t bitDepth = xtc.getBitDepth();
|
||||
|
||||
// Only use the 1-bit BW path; grayscale is not needed as a background under the overlay
|
||||
const size_t pageBufferSize = (bitDepth == 2) ? ((static_cast<size_t>(pageWidth) * pageHeight + 7) / 8) * 2
|
||||
: ((pageWidth + 7) / 8) * pageHeight;
|
||||
|
||||
uint8_t* pageBuffer = static_cast<uint8_t*>(malloc(pageBufferSize));
|
||||
if (!pageBuffer) {
|
||||
LOG_ERR("SLP", "XTC: failed to allocate page buffer");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (xtc.loadPage(savedPage, pageBuffer, pageBufferSize) == 0) {
|
||||
LOG_ERR("SLP", "XTC: failed to load page %lu", savedPage);
|
||||
free(pageBuffer);
|
||||
return false;
|
||||
}
|
||||
|
||||
renderer.clearScreen();
|
||||
|
||||
if (bitDepth == 2) {
|
||||
// 2-bit XTH: draw all non-white pixels as black (BW pass only)
|
||||
const size_t planeSize = (static_cast<size_t>(pageWidth) * pageHeight + 7) / 8;
|
||||
const uint8_t* plane1 = pageBuffer;
|
||||
const uint8_t* plane2 = pageBuffer + planeSize;
|
||||
const size_t colBytes = (pageHeight + 7) / 8;
|
||||
for (uint16_t y = 0; y < pageHeight; y++) {
|
||||
for (uint16_t x = 0; x < pageWidth; x++) {
|
||||
const size_t colIndex = pageWidth - 1 - x;
|
||||
const size_t byteInCol = y / 8;
|
||||
const size_t bitInByte = 7 - (y % 8);
|
||||
const size_t byteOffset = colIndex * colBytes + byteInCol;
|
||||
const uint8_t bit1 = (plane1[byteOffset] >> bitInByte) & 1;
|
||||
const uint8_t bit2 = (plane2[byteOffset] >> bitInByte) & 1;
|
||||
if ((bit1 << 1) | bit2) {
|
||||
renderer.drawPixel(x, y, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 1-bit XTG: draw black pixels
|
||||
const size_t srcRowBytes = (pageWidth + 7) / 8;
|
||||
for (uint16_t srcY = 0; srcY < pageHeight; srcY++) {
|
||||
for (uint16_t srcX = 0; srcX < pageWidth; srcX++) {
|
||||
const bool isBlack = !((pageBuffer[srcY * srcRowBytes + srcX / 8] >> (7 - srcX % 8)) & 1);
|
||||
if (isBlack) renderer.drawPixel(srcX, srcY, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
free(pageBuffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -29,4 +29,9 @@ class XtcReaderActivity final : public Activity {
|
||||
void loop() override;
|
||||
void render(RenderLock&&) override;
|
||||
bool isReaderActivity() const override { return true; }
|
||||
|
||||
// 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);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user