2025-12-17 23:32:18 +11:00
|
|
|
#include "SleepActivity.h"
|
2025-12-06 04:20:03 +11:00
|
|
|
|
2025-12-21 18:42:06 +11:00
|
|
|
#include <Epub.h>
|
2025-12-08 22:06:09 +11:00
|
|
|
#include <GfxRenderer.h>
|
2025-12-30 15:09:30 +10:00
|
|
|
#include <SDCardManager.h>
|
2026-01-14 19:36:40 +09:00
|
|
|
#include <Txt.h>
|
2025-12-28 23:56:05 +09:00
|
|
|
#include <Xtc.h>
|
2025-12-06 04:20:03 +11:00
|
|
|
|
2025-12-15 23:17:23 +11:00
|
|
|
#include "CrossPointSettings.h"
|
2025-12-21 18:42:06 +11:00
|
|
|
#include "CrossPointState.h"
|
Aleo, Noto Sans, Open Dyslexic fonts (#163)
## Summary
* Swap out Bookerly font due to licensing issues, replace default font
with Aleo
* I did a bunch of searching around for a nice replacement font, and
this trumped several other like Literata, Merriwether, Vollkorn, etc
* Add Noto Sans, and Open Dyslexic as font options
* They can be selected in the settings screen
* Add font size options (Small, Medium, Large, Extra Large)
* Adjustable in settings
* Swap out uses of reader font in headings and replaced with slightly
larger Ubuntu font
* Replaced PixelArial14 font as it was difficult to track down, replace
with Space Grotesk
* Remove auto formatting on generated font files
* Massively speeds up formatting step now that there is a lot more CPP
font source
* Include fonts with their licenses in the repo
## Additional Context
Line compression setting will follow
| Font | Small | Medium | Large | X Large |
| --- | --- | --- | --- | --- |
| Aleo |

|

|

|

|
| Noto Sans |

|

|

|

|
| Open Dyslexic |

|

|

|

|
2025-12-30 18:21:47 +10:00
|
|
|
#include "fontIds.h"
|
2025-12-08 19:48:49 +11:00
|
|
|
#include "images/CrossLarge.h"
|
2026-01-07 20:07:23 +10:00
|
|
|
#include "util/StringUtils.h"
|
2025-12-28 23:56:05 +09:00
|
|
|
|
2025-12-17 23:32:18 +11:00
|
|
|
void SleepActivity::onEnter() {
|
2025-12-21 21:17:00 +11:00
|
|
|
Activity::onEnter();
|
2025-12-19 14:17:26 +01:00
|
|
|
renderPopup("Entering Sleep...");
|
2025-12-21 18:42:06 +11:00
|
|
|
|
2026-01-05 10:08:39 +01:00
|
|
|
if (SETTINGS.sleepScreen == CrossPointSettings::SLEEP_SCREEN_MODE::BLANK) {
|
|
|
|
|
return renderBlankSleepScreen();
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-21 18:42:06 +11:00
|
|
|
if (SETTINGS.sleepScreen == CrossPointSettings::SLEEP_SCREEN_MODE::CUSTOM) {
|
|
|
|
|
return renderCustomSleepScreen();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (SETTINGS.sleepScreen == CrossPointSettings::SLEEP_SCREEN_MODE::COVER) {
|
|
|
|
|
return renderCoverSleepScreen();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
renderDefaultSleepScreen();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SleepActivity::renderPopup(const char* message) const {
|
2026-01-02 07:55:21 +01:00
|
|
|
const int textWidth = renderer.getTextWidth(UI_12_FONT_ID, message, EpdFontFamily::BOLD);
|
2025-12-21 18:42:06 +11:00
|
|
|
constexpr int margin = 20;
|
|
|
|
|
const int x = (renderer.getScreenWidth() - textWidth - margin * 2) / 2;
|
|
|
|
|
constexpr int y = 117;
|
|
|
|
|
const int w = textWidth + margin * 2;
|
Aleo, Noto Sans, Open Dyslexic fonts (#163)
## Summary
* Swap out Bookerly font due to licensing issues, replace default font
with Aleo
* I did a bunch of searching around for a nice replacement font, and
this trumped several other like Literata, Merriwether, Vollkorn, etc
* Add Noto Sans, and Open Dyslexic as font options
* They can be selected in the settings screen
* Add font size options (Small, Medium, Large, Extra Large)
* Adjustable in settings
* Swap out uses of reader font in headings and replaced with slightly
larger Ubuntu font
* Replaced PixelArial14 font as it was difficult to track down, replace
with Space Grotesk
* Remove auto formatting on generated font files
* Massively speeds up formatting step now that there is a lot more CPP
font source
* Include fonts with their licenses in the repo
## Additional Context
Line compression setting will follow
| Font | Small | Medium | Large | X Large |
| --- | --- | --- | --- | --- |
| Aleo |

|

|

|

|
| Noto Sans |

|

|

|

|
| Open Dyslexic |

|

|

|

|
2025-12-30 18:21:47 +10:00
|
|
|
const int h = renderer.getLineHeight(UI_12_FONT_ID) + margin * 2;
|
2025-12-21 18:42:06 +11:00
|
|
|
// renderer.clearScreen();
|
2026-01-02 07:55:21 +01:00
|
|
|
renderer.fillRect(x - 5, y - 5, w + 10, h + 10, true);
|
2025-12-21 18:42:06 +11:00
|
|
|
renderer.fillRect(x + 5, y + 5, w - 10, h - 10, false);
|
2026-01-02 07:55:21 +01:00
|
|
|
renderer.drawText(UI_12_FONT_ID, x + margin, y + margin, message, true, EpdFontFamily::BOLD);
|
2025-12-21 18:42:06 +11:00
|
|
|
renderer.displayBuffer();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SleepActivity::renderCustomSleepScreen() const {
|
2025-12-19 14:17:26 +01:00
|
|
|
// Check if we have a /sleep directory
|
2025-12-30 15:09:30 +10:00
|
|
|
auto dir = SdMan.open("/sleep");
|
2025-12-19 14:17:26 +01:00
|
|
|
if (dir && dir.isDirectory()) {
|
|
|
|
|
std::vector<std::string> files;
|
2026-01-07 22:43:19 +10:00
|
|
|
char name[500];
|
2025-12-19 14:17:26 +01:00
|
|
|
// collect all valid BMP files
|
2025-12-30 15:09:30 +10:00
|
|
|
for (auto file = dir.openNextFile(); file; file = dir.openNextFile()) {
|
2025-12-19 14:17:26 +01:00
|
|
|
if (file.isDirectory()) {
|
|
|
|
|
file.close();
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2025-12-30 15:09:30 +10:00
|
|
|
file.getName(name, sizeof(name));
|
|
|
|
|
auto filename = std::string(name);
|
2025-12-19 14:17:26 +01:00
|
|
|
if (filename[0] == '.') {
|
|
|
|
|
file.close();
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (filename.substr(filename.length() - 4) != ".bmp") {
|
2025-12-30 15:09:30 +10:00
|
|
|
Serial.printf("[%lu] [SLP] Skipping non-.bmp file name: %s\n", millis(), name);
|
2025-12-19 14:17:26 +01:00
|
|
|
file.close();
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
Bitmap bitmap(file);
|
|
|
|
|
if (bitmap.parseHeaders() != BmpReaderError::Ok) {
|
2025-12-30 15:09:30 +10:00
|
|
|
Serial.printf("[%lu] [SLP] Skipping invalid BMP file: %s\n", millis(), name);
|
2025-12-19 14:17:26 +01:00
|
|
|
file.close();
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
files.emplace_back(filename);
|
|
|
|
|
file.close();
|
|
|
|
|
}
|
2025-12-21 18:42:06 +11:00
|
|
|
const auto numFiles = files.size();
|
2025-12-19 14:17:26 +01:00
|
|
|
if (numFiles > 0) {
|
|
|
|
|
// Generate a random number between 1 and numFiles
|
2026-01-14 05:05:08 -05:00
|
|
|
auto randomFileIndex = random(numFiles);
|
|
|
|
|
// If we picked the same image as last time, reroll
|
|
|
|
|
while (numFiles > 1 && randomFileIndex == APP_STATE.lastSleepImage) {
|
|
|
|
|
randomFileIndex = random(numFiles);
|
|
|
|
|
}
|
|
|
|
|
APP_STATE.lastSleepImage = randomFileIndex;
|
|
|
|
|
APP_STATE.saveToFile();
|
2025-12-21 18:42:06 +11:00
|
|
|
const auto filename = "/sleep/" + files[randomFileIndex];
|
2025-12-30 15:09:30 +10:00
|
|
|
FsFile file;
|
|
|
|
|
if (SdMan.openFileForRead("SLP", filename, file)) {
|
2025-12-21 18:42:06 +11:00
|
|
|
Serial.printf("[%lu] [SLP] Randomly loading: /sleep/%s\n", millis(), files[randomFileIndex].c_str());
|
2025-12-19 14:17:26 +01:00
|
|
|
delay(100);
|
2026-01-12 12:36:19 +01:00
|
|
|
Bitmap bitmap(file, true);
|
2025-12-19 14:17:26 +01:00
|
|
|
if (bitmap.parseHeaders() == BmpReaderError::Ok) {
|
2025-12-21 18:42:06 +11:00
|
|
|
renderBitmapSleepScreen(bitmap);
|
2025-12-19 14:17:26 +01:00
|
|
|
dir.close();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (dir) dir.close();
|
|
|
|
|
|
2025-12-19 08:45:14 +11:00
|
|
|
// Look for sleep.bmp on the root of the sd card to determine if we should
|
|
|
|
|
// render a custom sleep screen instead of the default.
|
2025-12-30 15:09:30 +10:00
|
|
|
FsFile file;
|
|
|
|
|
if (SdMan.openFileForRead("SLP", "/sleep.bmp", file)) {
|
2026-01-12 12:36:19 +01:00
|
|
|
Bitmap bitmap(file, true);
|
2025-12-19 08:45:14 +11:00
|
|
|
if (bitmap.parseHeaders() == BmpReaderError::Ok) {
|
2025-12-21 18:42:06 +11:00
|
|
|
Serial.printf("[%lu] [SLP] Loading: /sleep.bmp\n", millis());
|
|
|
|
|
renderBitmapSleepScreen(bitmap);
|
2025-12-19 08:45:14 +11:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
renderDefaultSleepScreen();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SleepActivity::renderDefaultSleepScreen() const {
|
2025-12-21 18:42:06 +11:00
|
|
|
const auto pageWidth = renderer.getScreenWidth();
|
|
|
|
|
const auto pageHeight = renderer.getScreenHeight();
|
2025-12-08 19:48:49 +11:00
|
|
|
|
|
|
|
|
renderer.clearScreen();
|
fix: rotate origin in drawImage (#557)
## Summary
This was originally a comment in #499, but I'm making it its own PR,
because it doesn't depend on anything there and then I can base that PR
on this one.
Currently, `drawBitmap` is used for covers and sleep wallpaper, and
`drawImage` is used for the boot logo. `drawBitmap` goes row by row and
pixel by pixel, so it respects the renderer orientation. `drawImage`
just calls the `EInkDisplay`'s `drawImage`, which works in the eink
panel's native display orientation.
`drawImage` rotates the x,y coordinates where it's going to draw the
image, but doesn't account for the fact that the northwest corner in
portrait orientation becomes, the southwest corner of the image
rectangle in the native orientation. The boot and sleep activities
currently work around this by calculating the north*east* corner of
where the image should go, which becomes the northwest corner after
`rotateCoordinates`.
I think this wasn't really apparent because the CrossPoint logo is
rotationally symmetrical. The `EInkDisplay` `drawImage` always draws the
image in native orientation, but that looks the same for the "X" image.
If we rotate the origin coordinate in `GfxRenderer`'s `drawImage`, we
can use a much clearer northwest corner coordinate in the boot and sleep
activities. (And then, in #499, we can actually rotate the boot screen
to the user's preferred orientation).
This does *not* yet rotate the actual bits in the image; it's still
displayed in native orientation. This doesn't affect the
rotationally-symmetric logo, but if it's ever changed, we will probably
want to allocate a new `u8int[]` and transpose rows and columns if
necessary.
## Additional Context
I've created an additional branch on top of this to demonstrate by
replacing the logo with a non-rotationally-symmetrical image:
<img width="128" height="128" alt="Cat-in-a-pan-128-bw"
src="https://github.com/user-attachments/assets/d0b239bc-fe75-4ec8-bc02-9cf9436ca65f"
/>
https://github.com/crosspoint-reader/crosspoint-reader/compare/master...maeveynot:rotated-cat
(many thanks to https://notisrac.github.io/FileToCArray/)
As you can see, it is always drawn in native orientation, which makes it
sideways (turned clockwise) in portrait.
---
### AI Usage
No
Co-authored-by: Maeve Andrews <maeve@git.mail.maeveandrews.com>
2026-01-27 05:59:41 -06:00
|
|
|
renderer.drawImage(CrossLarge, (pageWidth - 128) / 2, (pageHeight - 128) / 2, 128, 128);
|
2025-12-31 12:11:36 +10:00
|
|
|
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 + 70, "CrossPoint", true, EpdFontFamily::BOLD);
|
2025-12-08 22:52:19 +11:00
|
|
|
renderer.drawCenteredText(SMALL_FONT_ID, pageHeight / 2 + 95, "SLEEPING");
|
2025-12-15 13:16:46 +01:00
|
|
|
|
2025-12-21 18:42:06 +11:00
|
|
|
// Make sleep screen dark unless light is selected in settings
|
|
|
|
|
if (SETTINGS.sleepScreen != CrossPointSettings::SLEEP_SCREEN_MODE::LIGHT) {
|
2025-12-15 13:16:46 +01:00
|
|
|
renderer.invertScreen();
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-08 22:06:09 +11:00
|
|
|
renderer.displayBuffer(EInkDisplay::HALF_REFRESH);
|
2025-12-08 19:48:49 +11:00
|
|
|
}
|
2025-12-19 08:45:14 +11:00
|
|
|
|
2025-12-21 18:42:06 +11:00
|
|
|
void SleepActivity::renderBitmapSleepScreen(const Bitmap& bitmap) const {
|
2025-12-19 08:45:14 +11:00
|
|
|
int x, y;
|
2025-12-21 18:42:06 +11:00
|
|
|
const auto pageWidth = renderer.getScreenWidth();
|
|
|
|
|
const auto pageHeight = renderer.getScreenHeight();
|
2026-01-05 11:07:27 +01:00
|
|
|
float cropX = 0, cropY = 0;
|
2025-12-19 08:45:14 +11:00
|
|
|
|
2026-01-05 11:07:27 +01:00
|
|
|
Serial.printf("[%lu] [SLP] bitmap %d x %d, screen %d x %d\n", millis(), bitmap.getWidth(), bitmap.getHeight(),
|
|
|
|
|
pageWidth, pageHeight);
|
2025-12-19 08:45:14 +11:00
|
|
|
if (bitmap.getWidth() > pageWidth || bitmap.getHeight() > pageHeight) {
|
|
|
|
|
// image will scale, make sure placement is right
|
2026-01-05 11:07:27 +01:00
|
|
|
float ratio = static_cast<float>(bitmap.getWidth()) / static_cast<float>(bitmap.getHeight());
|
2025-12-19 08:45:14 +11:00
|
|
|
const float screenRatio = static_cast<float>(pageWidth) / static_cast<float>(pageHeight);
|
|
|
|
|
|
2026-01-05 11:07:27 +01:00
|
|
|
Serial.printf("[%lu] [SLP] bitmap ratio: %f, screen ratio: %f\n", millis(), ratio, screenRatio);
|
2025-12-19 08:45:14 +11:00
|
|
|
if (ratio > screenRatio) {
|
|
|
|
|
// image wider than viewport ratio, scaled down image needs to be centered vertically
|
2026-01-05 11:07:27 +01:00
|
|
|
if (SETTINGS.sleepScreenCoverMode == CrossPointSettings::SLEEP_SCREEN_COVER_MODE::CROP) {
|
|
|
|
|
cropX = 1.0f - (screenRatio / ratio);
|
|
|
|
|
Serial.printf("[%lu] [SLP] Cropping bitmap x: %f\n", millis(), cropX);
|
|
|
|
|
ratio = (1.0f - cropX) * static_cast<float>(bitmap.getWidth()) / static_cast<float>(bitmap.getHeight());
|
|
|
|
|
}
|
2025-12-19 08:45:14 +11:00
|
|
|
x = 0;
|
2026-01-05 11:07:27 +01:00
|
|
|
y = std::round((static_cast<float>(pageHeight) - static_cast<float>(pageWidth) / ratio) / 2);
|
|
|
|
|
Serial.printf("[%lu] [SLP] Centering with ratio %f to y=%d\n", millis(), ratio, y);
|
2025-12-19 08:45:14 +11:00
|
|
|
} else {
|
|
|
|
|
// image taller than viewport ratio, scaled down image needs to be centered horizontally
|
2026-01-05 11:07:27 +01:00
|
|
|
if (SETTINGS.sleepScreenCoverMode == CrossPointSettings::SLEEP_SCREEN_COVER_MODE::CROP) {
|
|
|
|
|
cropY = 1.0f - (ratio / screenRatio);
|
|
|
|
|
Serial.printf("[%lu] [SLP] Cropping bitmap y: %f\n", millis(), cropY);
|
|
|
|
|
ratio = static_cast<float>(bitmap.getWidth()) / ((1.0f - cropY) * static_cast<float>(bitmap.getHeight()));
|
|
|
|
|
}
|
2026-01-21 13:27:41 +01:00
|
|
|
x = std::round((static_cast<float>(pageWidth) - static_cast<float>(pageHeight) * ratio) / 2);
|
2025-12-19 08:45:14 +11:00
|
|
|
y = 0;
|
2026-01-05 11:07:27 +01:00
|
|
|
Serial.printf("[%lu] [SLP] Centering with ratio %f to x=%d\n", millis(), ratio, x);
|
2025-12-19 08:45:14 +11:00
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// center the image
|
|
|
|
|
x = (pageWidth - bitmap.getWidth()) / 2;
|
|
|
|
|
y = (pageHeight - bitmap.getHeight()) / 2;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-05 11:07:27 +01:00
|
|
|
Serial.printf("[%lu] [SLP] drawing to %d x %d\n", millis(), x, y);
|
2025-12-19 08:45:14 +11:00
|
|
|
renderer.clearScreen();
|
2026-01-27 13:21:59 +00:00
|
|
|
|
|
|
|
|
const bool hasGreyscale = bitmap.hasGreyscale() &&
|
|
|
|
|
SETTINGS.sleepScreenCoverFilter == CrossPointSettings::SLEEP_SCREEN_COVER_FILTER::NO_FILTER;
|
|
|
|
|
|
2026-01-05 11:07:27 +01:00
|
|
|
renderer.drawBitmap(bitmap, x, y, pageWidth, pageHeight, cropX, cropY);
|
2026-01-27 13:21:59 +00:00
|
|
|
|
|
|
|
|
if (SETTINGS.sleepScreenCoverFilter == CrossPointSettings::SLEEP_SCREEN_COVER_FILTER::INVERTED_BLACK_AND_WHITE) {
|
|
|
|
|
renderer.invertScreen();
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-19 08:45:14 +11:00
|
|
|
renderer.displayBuffer(EInkDisplay::HALF_REFRESH);
|
|
|
|
|
|
2026-01-27 13:21:59 +00:00
|
|
|
if (hasGreyscale) {
|
2025-12-19 08:45:14 +11:00
|
|
|
bitmap.rewindToData();
|
|
|
|
|
renderer.clearScreen(0x00);
|
|
|
|
|
renderer.setRenderMode(GfxRenderer::GRAYSCALE_LSB);
|
2026-01-05 11:07:27 +01:00
|
|
|
renderer.drawBitmap(bitmap, x, y, pageWidth, pageHeight, cropX, cropY);
|
2025-12-19 08:45:14 +11:00
|
|
|
renderer.copyGrayscaleLsbBuffers();
|
|
|
|
|
|
|
|
|
|
bitmap.rewindToData();
|
|
|
|
|
renderer.clearScreen(0x00);
|
|
|
|
|
renderer.setRenderMode(GfxRenderer::GRAYSCALE_MSB);
|
2026-01-05 11:07:27 +01:00
|
|
|
renderer.drawBitmap(bitmap, x, y, pageWidth, pageHeight, cropX, cropY);
|
2025-12-19 08:45:14 +11:00
|
|
|
renderer.copyGrayscaleMsbBuffers();
|
|
|
|
|
|
|
|
|
|
renderer.displayGrayBuffer();
|
|
|
|
|
renderer.setRenderMode(GfxRenderer::BW);
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-12-21 18:42:06 +11:00
|
|
|
|
|
|
|
|
void SleepActivity::renderCoverSleepScreen() const {
|
2025-12-21 21:17:00 +11:00
|
|
|
if (APP_STATE.openEpubPath.empty()) {
|
|
|
|
|
return renderDefaultSleepScreen();
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-28 23:56:05 +09:00
|
|
|
std::string coverBmpPath;
|
2026-01-12 10:55:47 +01:00
|
|
|
bool cropped = SETTINGS.sleepScreenCoverMode == CrossPointSettings::SLEEP_SCREEN_COVER_MODE::CROP;
|
2025-12-21 21:17:00 +11:00
|
|
|
|
2026-01-14 19:36:40 +09:00
|
|
|
// Check if the current book is XTC, TXT, or EPUB
|
2026-01-07 20:07:23 +10:00
|
|
|
if (StringUtils::checkFileExtension(APP_STATE.openEpubPath, ".xtc") ||
|
|
|
|
|
StringUtils::checkFileExtension(APP_STATE.openEpubPath, ".xtch")) {
|
2025-12-28 23:56:05 +09:00
|
|
|
// Handle XTC file
|
|
|
|
|
Xtc lastXtc(APP_STATE.openEpubPath, "/.crosspoint");
|
|
|
|
|
if (!lastXtc.load()) {
|
|
|
|
|
Serial.println("[SLP] Failed to load last XTC");
|
|
|
|
|
return renderDefaultSleepScreen();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!lastXtc.generateCoverBmp()) {
|
|
|
|
|
Serial.println("[SLP] Failed to generate XTC cover bmp");
|
|
|
|
|
return renderDefaultSleepScreen();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
coverBmpPath = lastXtc.getCoverBmpPath();
|
2026-01-14 19:36:40 +09:00
|
|
|
} else if (StringUtils::checkFileExtension(APP_STATE.openEpubPath, ".txt")) {
|
|
|
|
|
// Handle TXT file - looks for cover image in the same folder
|
|
|
|
|
Txt lastTxt(APP_STATE.openEpubPath, "/.crosspoint");
|
|
|
|
|
if (!lastTxt.load()) {
|
|
|
|
|
Serial.println("[SLP] Failed to load last TXT");
|
|
|
|
|
return renderDefaultSleepScreen();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!lastTxt.generateCoverBmp()) {
|
|
|
|
|
Serial.println("[SLP] No cover image found for TXT file");
|
|
|
|
|
return renderDefaultSleepScreen();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
coverBmpPath = lastTxt.getCoverBmpPath();
|
2026-01-07 20:07:23 +10:00
|
|
|
} else if (StringUtils::checkFileExtension(APP_STATE.openEpubPath, ".epub")) {
|
2025-12-28 23:56:05 +09:00
|
|
|
// Handle EPUB file
|
|
|
|
|
Epub lastEpub(APP_STATE.openEpubPath, "/.crosspoint");
|
|
|
|
|
if (!lastEpub.load()) {
|
|
|
|
|
Serial.println("[SLP] Failed to load last epub");
|
|
|
|
|
return renderDefaultSleepScreen();
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-12 10:55:47 +01:00
|
|
|
if (!lastEpub.generateCoverBmp(cropped)) {
|
2025-12-28 23:56:05 +09:00
|
|
|
Serial.println("[SLP] Failed to generate cover bmp");
|
|
|
|
|
return renderDefaultSleepScreen();
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-12 10:55:47 +01:00
|
|
|
coverBmpPath = lastEpub.getCoverBmpPath(cropped);
|
2026-01-07 20:07:23 +10:00
|
|
|
} else {
|
|
|
|
|
return renderDefaultSleepScreen();
|
2025-12-21 18:42:06 +11:00
|
|
|
}
|
|
|
|
|
|
2025-12-30 15:09:30 +10:00
|
|
|
FsFile file;
|
|
|
|
|
if (SdMan.openFileForRead("SLP", coverBmpPath, file)) {
|
2025-12-21 18:42:06 +11:00
|
|
|
Bitmap bitmap(file);
|
|
|
|
|
if (bitmap.parseHeaders() == BmpReaderError::Ok) {
|
2026-01-27 10:21:15 +01:00
|
|
|
Serial.printf("[SLP] Rendering sleep cover: %s\n", coverBmpPath);
|
2025-12-21 18:42:06 +11:00
|
|
|
renderBitmapSleepScreen(bitmap);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
renderDefaultSleepScreen();
|
|
|
|
|
}
|
2026-01-05 10:08:39 +01:00
|
|
|
|
|
|
|
|
void SleepActivity::renderBlankSleepScreen() const {
|
|
|
|
|
renderer.clearScreen();
|
|
|
|
|
renderer.displayBuffer(EInkDisplay::HALF_REFRESH);
|
|
|
|
|
}
|