chore: post-sync cleanup and clang-format

- Remove stale Lyra3CoversTheme.h (functionality merged into LyraTheme)
- Fix UITheme.cpp to use LyraTheme for LYRA_3_COVERS theme variant
- Update open-x4-sdk submodule to 91e7e2b (drawImageTransparent support)
- Run clang-format on all source files

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
cottongin
2026-02-19 10:46:25 -05:00
parent c1d1e98909
commit 013a738144
38 changed files with 296 additions and 363 deletions

View File

@@ -694,8 +694,8 @@ bool Epub::generateThumbBmp(int height) const {
}
int THUMB_TARGET_WIDTH = height * 0.6;
int THUMB_TARGET_HEIGHT = height;
const bool success =
PngToBmpConverter::pngFileTo1BitBmpStreamWithSize(coverPng, thumbBmp, THUMB_TARGET_WIDTH, THUMB_TARGET_HEIGHT);
const bool success = PngToBmpConverter::pngFileTo1BitBmpStreamWithSize(coverPng, thumbBmp, THUMB_TARGET_WIDTH,
THUMB_TARGET_HEIGHT);
coverPng.close();
thumbBmp.close();
Storage.remove(coverPngTempPath.c_str());

View File

@@ -43,8 +43,8 @@ class PageLine final : public PageElement {
/// Data for a single cell within a PageTableRow.
struct PageTableCellData {
std::vector<std::shared_ptr<TextBlock>> lines; // Laid-out text lines for this cell
uint16_t columnWidth = 0; // Width of this column in pixels
uint16_t xOffset = 0; // X offset of this cell within the row
uint16_t columnWidth = 0; // Width of this column in pixels
uint16_t xOffset = 0; // X offset of this cell within the row
};
/// A table row element that renders cells in a column-aligned grid with borders.

View File

@@ -9,9 +9,9 @@
/// A single cell in a table row.
struct TableCell {
std::unique_ptr<ParsedText> content;
bool isHeader = false; // true for <th>, false for <td>
int colspan = 1; // number of logical columns this cell spans
CssLength widthHint; // width hint from HTML attribute or CSS (if hasWidthHint)
bool isHeader = false; // true for <th>, false for <td>
int colspan = 1; // number of logical columns this cell spans
CssLength widthHint; // width hint from HTML attribute or CSS (if hasWidthHint)
bool hasWidthHint = false;
};

View File

@@ -118,7 +118,7 @@ struct CssStyle {
CssLength paddingBottom; // Padding after
CssLength paddingLeft; // Padding left
CssLength paddingRight; // Padding right
CssLength width; // Element width (used for table columns/cells)
CssLength width; // Element width (used for table columns/cells)
CssPropertyFlags defined; // Tracks which properties were explicitly set

View File

@@ -88,6 +88,4 @@ const LanguageHyphenator* getLanguageHyphenatorForPrimaryTag(const std::string&
return (it != allEntries.end()) ? it->hyphenator : nullptr;
}
LanguageEntryView getLanguageEntries() {
return entries();
}
LanguageEntryView getLanguageEntries() { return entries(); }

View File

@@ -214,7 +214,8 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char*
// (e.g. whitespace between tags) doesn't crash
auto tableBlockStyle = BlockStyle();
tableBlockStyle.alignment = CssTextAlign::Left;
self->currentTextBlock.reset(new ParsedText(self->extraParagraphSpacing, self->hyphenationEnabled, tableBlockStyle));
self->currentTextBlock.reset(
new ParsedText(self->extraParagraphSpacing, self->hyphenationEnabled, tableBlockStyle));
self->depth += 1;
return;
@@ -393,86 +394,86 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char*
// Check format support before any file I/O
ImageToFramebufferDecoder* decoder = ImageDecoderFactory::getDecoder(resolvedPath);
if (decoder) {
// Create a unique filename for the cached image
std::string ext;
size_t extPos = resolvedPath.rfind('.');
if (extPos != std::string::npos) {
ext = resolvedPath.substr(extPos);
}
std::string cachedImagePath = self->imageBasePath + std::to_string(self->imageCounter++) + ext;
// Extract image to cache file
FsFile cachedImageFile;
bool extractSuccess = false;
if (Storage.openFileForWrite("EHP", cachedImagePath, cachedImageFile)) {
extractSuccess = self->epub->readItemContentsToStream(resolvedPath, cachedImageFile, 4096);
cachedImageFile.flush();
cachedImageFile.close();
delay(50); // Give SD card time to sync
}
if (extractSuccess) {
// Get image dimensions
ImageDimensions dims = {0, 0};
if (decoder->getDimensions(cachedImagePath, dims)) {
LOG_DBG("EHP", "Image dimensions: %dx%d", dims.width, dims.height);
// Scale to fit viewport while maintaining aspect ratio
int maxWidth = self->viewportWidth;
int maxHeight = self->viewportHeight;
float scaleX = (dims.width > maxWidth) ? (float)maxWidth / dims.width : 1.0f;
float scaleY = (dims.height > maxHeight) ? (float)maxHeight / dims.height : 1.0f;
float scale = (scaleX < scaleY) ? scaleX : scaleY;
if (scale > 1.0f) scale = 1.0f;
int displayWidth = (int)(dims.width * scale);
int displayHeight = (int)(dims.height * scale);
LOG_DBG("EHP", "Display size: %dx%d (scale %.2f)", displayWidth, displayHeight, scale);
// Create page for image - only break if image won't fit remaining space
if (self->currentPage && !self->currentPage->elements.empty() &&
(self->currentPageNextY + displayHeight > self->viewportHeight)) {
self->completePageFn(std::move(self->currentPage));
self->currentPage.reset(new Page());
if (!self->currentPage) {
LOG_ERR("EHP", "Failed to create new page");
return;
}
self->currentPageNextY = 0;
} else if (!self->currentPage) {
self->currentPage.reset(new Page());
if (!self->currentPage) {
LOG_ERR("EHP", "Failed to create initial page");
return;
}
self->currentPageNextY = 0;
}
// Create ImageBlock and add to page
auto imageBlock = std::make_shared<ImageBlock>(cachedImagePath, displayWidth, displayHeight);
if (!imageBlock) {
LOG_ERR("EHP", "Failed to create ImageBlock");
return;
}
int xPos = (self->viewportWidth - displayWidth) / 2;
auto pageImage = std::make_shared<PageImage>(imageBlock, xPos, self->currentPageNextY);
if (!pageImage) {
LOG_ERR("EHP", "Failed to create PageImage");
return;
}
self->currentPage->elements.push_back(pageImage);
self->currentPageNextY += displayHeight;
self->depth += 1;
return;
} else {
LOG_ERR("EHP", "Failed to get image dimensions");
Storage.remove(cachedImagePath.c_str());
// Create a unique filename for the cached image
std::string ext;
size_t extPos = resolvedPath.rfind('.');
if (extPos != std::string::npos) {
ext = resolvedPath.substr(extPos);
}
std::string cachedImagePath = self->imageBasePath + std::to_string(self->imageCounter++) + ext;
// Extract image to cache file
FsFile cachedImageFile;
bool extractSuccess = false;
if (Storage.openFileForWrite("EHP", cachedImagePath, cachedImageFile)) {
extractSuccess = self->epub->readItemContentsToStream(resolvedPath, cachedImageFile, 4096);
cachedImageFile.flush();
cachedImageFile.close();
delay(50); // Give SD card time to sync
}
if (extractSuccess) {
// Get image dimensions
ImageDimensions dims = {0, 0};
if (decoder->getDimensions(cachedImagePath, dims)) {
LOG_DBG("EHP", "Image dimensions: %dx%d", dims.width, dims.height);
// Scale to fit viewport while maintaining aspect ratio
int maxWidth = self->viewportWidth;
int maxHeight = self->viewportHeight;
float scaleX = (dims.width > maxWidth) ? (float)maxWidth / dims.width : 1.0f;
float scaleY = (dims.height > maxHeight) ? (float)maxHeight / dims.height : 1.0f;
float scale = (scaleX < scaleY) ? scaleX : scaleY;
if (scale > 1.0f) scale = 1.0f;
int displayWidth = (int)(dims.width * scale);
int displayHeight = (int)(dims.height * scale);
LOG_DBG("EHP", "Display size: %dx%d (scale %.2f)", displayWidth, displayHeight, scale);
// Create page for image - only break if image won't fit remaining space
if (self->currentPage && !self->currentPage->elements.empty() &&
(self->currentPageNextY + displayHeight > self->viewportHeight)) {
self->completePageFn(std::move(self->currentPage));
self->currentPage.reset(new Page());
if (!self->currentPage) {
LOG_ERR("EHP", "Failed to create new page");
return;
}
self->currentPageNextY = 0;
} else if (!self->currentPage) {
self->currentPage.reset(new Page());
if (!self->currentPage) {
LOG_ERR("EHP", "Failed to create initial page");
return;
}
self->currentPageNextY = 0;
}
// Create ImageBlock and add to page
auto imageBlock = std::make_shared<ImageBlock>(cachedImagePath, displayWidth, displayHeight);
if (!imageBlock) {
LOG_ERR("EHP", "Failed to create ImageBlock");
return;
}
int xPos = (self->viewportWidth - displayWidth) / 2;
auto pageImage = std::make_shared<PageImage>(imageBlock, xPos, self->currentPageNextY);
if (!pageImage) {
LOG_ERR("EHP", "Failed to create PageImage");
return;
}
self->currentPage->elements.push_back(pageImage);
self->currentPageNextY += displayHeight;
self->depth += 1;
return;
} else {
LOG_ERR("EHP", "Failed to get image dimensions");
Storage.remove(cachedImagePath.c_str());
}
} else {
LOG_ERR("EHP", "Failed to extract image");
}
} else {
LOG_ERR("EHP", "Failed to extract image");
}
} // if (decoder)
}
}
@@ -1353,11 +1354,11 @@ void ChapterHtmlSlimParser::processTable() {
}
// Row height = max lines * lineHeight + top/bottom border + asymmetric vertical padding
const int16_t rowHeight = static_cast<int16_t>(
static_cast<int>(maxLinesInRow) * lh + 2 + TABLE_CELL_PAD_TOP + TABLE_CELL_PAD_BOTTOM);
const int16_t rowHeight =
static_cast<int16_t>(static_cast<int>(maxLinesInRow) * lh + 2 + TABLE_CELL_PAD_TOP + TABLE_CELL_PAD_BOTTOM);
auto pageTableRow = std::make_shared<PageTableRow>(
std::move(cellDataVec), rowHeight, totalTableWidth, static_cast<int16_t>(lh), 0, 0);
auto pageTableRow = std::make_shared<PageTableRow>(std::move(cellDataVec), rowHeight, totalTableWidth,
static_cast<int16_t>(lh), 0, 0);
addTableRowToPage(std::move(pageTableRow));
}

View File

@@ -756,10 +756,8 @@ void GfxRenderer::displayBuffer(const HalDisplay::RefreshMode refreshMode) const
}
// EXPERIMENTAL: Display only a rectangular region with specified refresh mode
void GfxRenderer::displayWindow(int x, int y, int width, int height,
HalDisplay::RefreshMode mode) const {
LOG_DBG("GFX", "Displaying window at (%d,%d) size (%dx%d) with mode %d", x, y, width, height,
static_cast<int>(mode));
void GfxRenderer::displayWindow(int x, int y, int width, int height, HalDisplay::RefreshMode mode) const {
LOG_DBG("GFX", "Displaying window at (%d,%d) size (%dx%d) with mode %d", x, y, width, height, static_cast<int>(mode));
// Validate bounds
if (x < 0 || y < 0 || x + width > getScreenWidth() || y + height > getScreenHeight()) {
@@ -767,9 +765,8 @@ void GfxRenderer::displayWindow(int x, int y, int width, int height,
return;
}
display.displayWindow(static_cast<uint16_t>(x), static_cast<uint16_t>(y),
static_cast<uint16_t>(width), static_cast<uint16_t>(height), mode,
fadingFix);
display.displayWindow(static_cast<uint16_t>(x), static_cast<uint16_t>(y), static_cast<uint16_t>(width),
static_cast<uint16_t>(height), mode, fadingFix);
}
std::string GfxRenderer::truncatedText(const int fontId, const char* text, const int maxWidth,

View File

@@ -6,22 +6,20 @@
static constexpr int BOOK_ICON_WIDTH = 48;
static constexpr int BOOK_ICON_HEIGHT = 48;
static const uint8_t BookIcon[] = {
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00,
0x00, 0x1f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xfc, 0x1f, 0xff, 0xff, 0xff, 0x9f, 0xfc, 0x1f,
0xff, 0xff, 0xff, 0x9f, 0xfc, 0x1f, 0xff, 0xff, 0xff, 0x9f, 0xfc, 0x1f, 0xff, 0xff, 0xff, 0x9f,
0xfc, 0x1f, 0xff, 0xff, 0xff, 0x9f, 0xfc, 0x1f, 0xff, 0xff, 0xff, 0x9f, 0xfc, 0x1f, 0xff, 0xff,
0xff, 0x9f, 0xfc, 0x1f, 0xff, 0xff, 0xff, 0x9f, 0xfc, 0x1c, 0x00, 0x00, 0x01, 0x9f, 0xfc, 0x1f,
0xff, 0xff, 0xff, 0x9f, 0xfc, 0x1f, 0xff, 0xff, 0xff, 0x9f, 0xfc, 0x1f, 0xff, 0xff, 0xff, 0x9f,
0xfc, 0x1f, 0xff, 0xff, 0xff, 0x9f, 0xfc, 0x1f, 0xff, 0xff, 0xff, 0x9f, 0xfc, 0x1c, 0x00, 0x01,
0xff, 0x9f, 0xfc, 0x1f, 0xff, 0xff, 0xff, 0x9f, 0xfc, 0x1f, 0xff, 0xff, 0xff, 0x9f, 0xfc, 0x1f,
0xff, 0xff, 0xff, 0x9f, 0xfc, 0x1f, 0xff, 0xff, 0xff, 0x9f, 0xfc, 0x1f, 0xff, 0xff, 0xff, 0x9f,
0xfc, 0x1c, 0x00, 0x00, 0x1f, 0x9f, 0xfc, 0x1f, 0xff, 0xff, 0xff, 0x9f, 0xfc, 0x1f, 0xff, 0xff,
0xff, 0x9f, 0xfc, 0x1f, 0xff, 0xff, 0xff, 0x9f, 0xfc, 0x1f, 0xff, 0xff, 0xff, 0x9f, 0xfc, 0x1f,
0xff, 0xff, 0xff, 0x9f, 0xfc, 0x1f, 0xff, 0xff, 0xff, 0x9f, 0xfc, 0x1f, 0xff, 0xff, 0xff, 0x9f,
0xfc, 0x1f, 0xff, 0xff, 0xff, 0x9f, 0xfc, 0x1f, 0xff, 0xff, 0xff, 0x9f, 0xfc, 0x1f, 0xff, 0xff,
0xff, 0x9f, 0xfc, 0x1f, 0xff, 0xff, 0xff, 0x9f, 0xfc, 0x1f, 0xff, 0xff, 0xff, 0x9f, 0xfc, 0x1f,
0xff, 0xff, 0xff, 0x9f, 0xfc, 0x1f, 0xff, 0xff, 0xff, 0x9f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x1f,
0xfc, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0x00, 0x00, 0x00,
0x00, 0x3f, 0xff, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x1f,
0xfc, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xfc, 0x1f, 0xff, 0xff, 0xff, 0x9f, 0xfc, 0x1f, 0xff, 0xff, 0xff, 0x9f,
0xfc, 0x1f, 0xff, 0xff, 0xff, 0x9f, 0xfc, 0x1f, 0xff, 0xff, 0xff, 0x9f, 0xfc, 0x1f, 0xff, 0xff, 0xff, 0x9f,
0xfc, 0x1f, 0xff, 0xff, 0xff, 0x9f, 0xfc, 0x1f, 0xff, 0xff, 0xff, 0x9f, 0xfc, 0x1f, 0xff, 0xff, 0xff, 0x9f,
0xfc, 0x1c, 0x00, 0x00, 0x01, 0x9f, 0xfc, 0x1f, 0xff, 0xff, 0xff, 0x9f, 0xfc, 0x1f, 0xff, 0xff, 0xff, 0x9f,
0xfc, 0x1f, 0xff, 0xff, 0xff, 0x9f, 0xfc, 0x1f, 0xff, 0xff, 0xff, 0x9f, 0xfc, 0x1f, 0xff, 0xff, 0xff, 0x9f,
0xfc, 0x1c, 0x00, 0x01, 0xff, 0x9f, 0xfc, 0x1f, 0xff, 0xff, 0xff, 0x9f, 0xfc, 0x1f, 0xff, 0xff, 0xff, 0x9f,
0xfc, 0x1f, 0xff, 0xff, 0xff, 0x9f, 0xfc, 0x1f, 0xff, 0xff, 0xff, 0x9f, 0xfc, 0x1f, 0xff, 0xff, 0xff, 0x9f,
0xfc, 0x1c, 0x00, 0x00, 0x1f, 0x9f, 0xfc, 0x1f, 0xff, 0xff, 0xff, 0x9f, 0xfc, 0x1f, 0xff, 0xff, 0xff, 0x9f,
0xfc, 0x1f, 0xff, 0xff, 0xff, 0x9f, 0xfc, 0x1f, 0xff, 0xff, 0xff, 0x9f, 0xfc, 0x1f, 0xff, 0xff, 0xff, 0x9f,
0xfc, 0x1f, 0xff, 0xff, 0xff, 0x9f, 0xfc, 0x1f, 0xff, 0xff, 0xff, 0x9f, 0xfc, 0x1f, 0xff, 0xff, 0xff, 0x9f,
0xfc, 0x1f, 0xff, 0xff, 0xff, 0x9f, 0xfc, 0x1f, 0xff, 0xff, 0xff, 0x9f, 0xfc, 0x1f, 0xff, 0xff, 0xff, 0x9f,
0xfc, 0x1f, 0xff, 0xff, 0xff, 0x9f, 0xfc, 0x1f, 0xff, 0xff, 0xff, 0x9f, 0xfc, 0x1f, 0xff, 0xff, 0xff, 0x9f,
0xfc, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0x00, 0x00, 0x00, 0x00, 0x3f,
0xff, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0x00, 0x00, 0x00, 0x00, 0x3f,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
};

View File

@@ -123,15 +123,13 @@ class PixelBuffer {
/// Draw a rectangular border in black.
void drawBorder(int x, int y, int w, int h, int thickness) {
fillRect(x, y, w, thickness); // Top
fillRect(x, y + h - thickness, w, thickness); // Bottom
fillRect(x, y, thickness, h); // Left
fillRect(x + w - thickness, y, thickness, h); // Right
fillRect(x, y + h - thickness, w, thickness); // Bottom
fillRect(x, y, thickness, h); // Left
fillRect(x + w - thickness, y, thickness, h); // Right
}
/// Draw a horizontal line in black with configurable thickness.
void drawHLine(int x, int y, int length, int thickness = 1) {
fillRect(x, y, length, thickness);
}
void drawHLine(int x, int y, int length, int thickness = 1) { fillRect(x, y, length, thickness); }
/// Render a single glyph at (cursorX, baselineY) with integer scaling. Returns advance in X (scaled).
int renderGlyph(const EpdFontData* font, uint32_t codepoint, int cursorX, int baselineY, int scale = 1) {
@@ -321,16 +319,15 @@ bool PlaceholderCoverGenerator::generate(const std::string& outputPath, const st
PixelBuffer buf(width, height);
if (!buf.isValid()) {
LOG_ERR("PHC", "Failed to allocate %dx%d pixel buffer (%d bytes)", width, height,
(width + 31) / 32 * 4 * height);
LOG_ERR("PHC", "Failed to allocate %dx%d pixel buffer (%d bytes)", width, height, (width + 31) / 32 * 4 * height);
return false;
}
// Proportional layout constants based on cover dimensions.
// The device bezel covers ~2-3px on each edge, so we pad inward from the edge.
const int edgePadding = std::max(3, width / 48); // ~10px at 480w, ~3px at 136w
const int borderWidth = std::max(2, width / 96); // ~5px at 480w, ~2px at 136w
const int innerPadding = std::max(4, width / 32); // ~15px at 480w, ~4px at 136w
const int edgePadding = std::max(3, width / 48); // ~10px at 480w, ~3px at 136w
const int borderWidth = std::max(2, width / 96); // ~5px at 480w, ~2px at 136w
const int innerPadding = std::max(4, width / 32); // ~15px at 480w, ~4px at 136w
// Text scaling: 2x for full-size covers, 1x for thumbnails
const int titleScale = (height >= 600) ? 2 : 1;
@@ -373,7 +370,7 @@ bool PlaceholderCoverGenerator::generate(const std::string& outputPath, const st
// --- Icon dimensions (needed for title text wrapping) ---
const int iconW = (iconScale > 0) ? BOOK_ICON_WIDTH * iconScale : 0;
const int iconGap = (iconScale > 0) ? std::max(8, width / 40) : 0; // Gap between icon and title text
const int titleTextW = contentW - iconW - iconGap; // Title wraps in narrower area beside icon
const int titleTextW = contentW - iconW - iconGap; // Title wraps in narrower area beside icon
// --- Prepare title text (wraps within the area to the right of the icon) ---
const std::string displayTitle = title.empty() ? "Untitled" : title;
@@ -403,9 +400,8 @@ bool PlaceholderCoverGenerator::generate(const std::string& outputPath, const st
const int numTitleLines = static_cast<int>(titleLines.size());
// Visual height: distance from top of first line to bottom of last line's glyphs.
// Use ascender (not full advanceY) for the last line since trailing line-gap isn't visible.
const int titleVisualH = (numTitleLines > 0)
? (numTitleLines - 1) * titleLineH + titleFont->ascender * titleScale
: 0;
const int titleVisualH =
(numTitleLines > 0) ? (numTitleLines - 1) * titleLineH + titleFont->ascender * titleScale : 0;
const int titleBlockH = std::max(iconH, titleVisualH); // Taller of icon or text
int titleStartY = contentY + (titleZoneH - titleBlockH) / 2;
@@ -416,9 +412,7 @@ bool PlaceholderCoverGenerator::generate(const std::string& outputPath, const st
// If title fits within icon height, center it vertically against the icon.
// Otherwise top-align so extra lines overflow below.
const int iconY = titleStartY;
const int titleTextY = (iconH > 0 && titleVisualH <= iconH)
? titleStartY + (iconH - titleVisualH) / 2
: titleStartY;
const int titleTextY = (iconH > 0 && titleVisualH <= iconH) ? titleStartY + (iconH - titleVisualH) / 2 : titleStartY;
// --- Horizontal centering: measure the widest title line, then center icon+gap+text block ---
int maxTitleLineW = 0;

View File

@@ -38,8 +38,8 @@ void HalDisplay::displayBuffer(HalDisplay::RefreshMode mode, bool turnOffScreen)
}
// EXPERIMENTAL: Display only a rectangular region
void HalDisplay::displayWindow(uint16_t x, uint16_t y, uint16_t w, uint16_t h,
HalDisplay::RefreshMode mode, bool turnOffScreen) {
void HalDisplay::displayWindow(uint16_t x, uint16_t y, uint16_t w, uint16_t h, HalDisplay::RefreshMode mode,
bool turnOffScreen) {
(void)mode; // EInkDisplay::displayWindow does not take mode yet
einkDisplay.displayWindow(x, y, w, h, turnOffScreen);
}

View File

@@ -37,8 +37,8 @@ class HalDisplay {
void refreshDisplay(RefreshMode mode = RefreshMode::FAST_REFRESH, bool turnOffScreen = false);
// EXPERIMENTAL: Display only a rectangular region
void displayWindow(uint16_t x, uint16_t y, uint16_t w, uint16_t h,
RefreshMode mode = RefreshMode::FAST_REFRESH, bool turnOffScreen = false);
void displayWindow(uint16_t x, uint16_t y, uint16_t w, uint16_t h, RefreshMode mode = RefreshMode::FAST_REFRESH,
bool turnOffScreen = false);
// Power management
void deepSleep();

View File

@@ -58,17 +58,16 @@ inline std::vector<SettingInfo> getSettingsList() {
{StrId::STR_NONE_OPT, StrId::STR_FILTER_CONTRAST, StrId::STR_INVERTED},
"sleepScreenCoverFilter", StrId::STR_CAT_DISPLAY),
SettingInfo::Enum(StrId::STR_LETTERBOX_FILL, &CrossPointSettings::sleepScreenLetterboxFill,
{StrId::STR_DITHERED, StrId::STR_SOLID, StrId::STR_NONE_OPT},
"sleepScreenLetterboxFill", StrId::STR_CAT_DISPLAY),
{StrId::STR_DITHERED, StrId::STR_SOLID, StrId::STR_NONE_OPT}, "sleepScreenLetterboxFill",
StrId::STR_CAT_DISPLAY),
SettingInfo::Enum(
StrId::STR_STATUS_BAR, &CrossPointSettings::statusBar,
{StrId::STR_NONE_OPT, StrId::STR_NO_PROGRESS, StrId::STR_STATUS_BAR_FULL_PERCENT,
StrId::STR_STATUS_BAR_FULL_BOOK, StrId::STR_STATUS_BAR_BOOK_ONLY, StrId::STR_STATUS_BAR_FULL_CHAPTER},
"statusBar", StrId::STR_CAT_DISPLAY),
SettingInfo::Enum(
StrId::STR_INDEXING_DISPLAY, &CrossPointSettings::indexingDisplay,
{StrId::STR_INDEXING_POPUP, StrId::STR_INDEXING_STATUS_TEXT, StrId::STR_INDEXING_STATUS_ICON},
"indexingDisplay", StrId::STR_CAT_DISPLAY),
SettingInfo::Enum(StrId::STR_INDEXING_DISPLAY, &CrossPointSettings::indexingDisplay,
{StrId::STR_INDEXING_POPUP, StrId::STR_INDEXING_STATUS_TEXT, StrId::STR_INDEXING_STATUS_ICON},
"indexingDisplay", StrId::STR_CAT_DISPLAY),
SettingInfo::Enum(StrId::STR_HIDE_BATTERY, &CrossPointSettings::hideBatteryPercentage,
{StrId::STR_NEVER, StrId::STR_IN_READER, StrId::STR_ALWAYS}, "hideBatteryPercentage",
StrId::STR_CAT_DISPLAY),
@@ -135,9 +134,7 @@ inline std::vector<SettingInfo> getSettingsList() {
"preferredPortrait", StrId::STR_CAT_READER),
SettingInfo::DynamicEnum(
StrId::STR_PREFERRED_LANDSCAPE, {StrId::STR_LANDSCAPE_CW, StrId::STR_LANDSCAPE_CCW},
[] {
return static_cast<uint8_t>(SETTINGS.preferredLandscape == CrossPointSettings::LANDSCAPE_CCW ? 1 : 0);
},
[] { return static_cast<uint8_t>(SETTINGS.preferredLandscape == CrossPointSettings::LANDSCAPE_CCW ? 1 : 0); },
[](uint8_t idx) {
SETTINGS.preferredLandscape =
(idx == 1) ? CrossPointSettings::LANDSCAPE_CCW : CrossPointSettings::LANDSCAPE_CW;

View File

@@ -15,10 +15,10 @@
#include "CrossPointSettings.h"
#include "CrossPointState.h"
#include "util/BookSettings.h"
#include "components/UITheme.h"
#include "fontIds.h"
#include "images/Logo120.h"
#include "util/BookSettings.h"
#include "util/StringUtils.h"
namespace {
@@ -80,9 +80,7 @@ uint8_t quantizeBayerDither(int gray, int x, int y) {
// creating a high-frequency checkerboard that causes e-ink display crosstalk
// and washes out adjacent content during HALF_REFRESH.
// Gray values 171-254 produce a level-2/level-3 mix via Bayer dithering.
bool bayerCrossesBwBoundary(uint8_t gray) {
return gray > 170 && gray < 255;
}
bool bayerCrossesBwBoundary(uint8_t gray) { return gray > 170 && gray < 255; }
// Hash-based block dithering for BW-boundary gray values (171-254).
// Each blockSize×blockSize pixel block gets a single uniform level (2 or 3),
@@ -301,9 +299,12 @@ void drawLetterboxFill(GfxRenderer& renderer, const LetterboxFillData& data, uin
for (int y = 0; y < data.letterboxA; y++)
for (int x = 0; x < renderer.getScreenWidth(); x++) {
uint8_t lv;
if (isSolid) lv = levelA;
else if (hashA) lv = hashBlockDither(data.avgA, x, y);
else lv = quantizeBayerDither(data.avgA, x, y);
if (isSolid)
lv = levelA;
else if (hashA)
lv = hashBlockDither(data.avgA, x, y);
else
lv = quantizeBayerDither(data.avgA, x, y);
renderer.drawPixelGray(x, y, lv);
}
}
@@ -312,9 +313,12 @@ void drawLetterboxFill(GfxRenderer& renderer, const LetterboxFillData& data, uin
for (int y = start; y < renderer.getScreenHeight(); y++)
for (int x = 0; x < renderer.getScreenWidth(); x++) {
uint8_t lv;
if (isSolid) lv = levelB;
else if (hashB) lv = hashBlockDither(data.avgB, x, y);
else lv = quantizeBayerDither(data.avgB, x, y);
if (isSolid)
lv = levelB;
else if (hashB)
lv = hashBlockDither(data.avgB, x, y);
else
lv = quantizeBayerDither(data.avgB, x, y);
renderer.drawPixelGray(x, y, lv);
}
}
@@ -323,9 +327,12 @@ void drawLetterboxFill(GfxRenderer& renderer, const LetterboxFillData& data, uin
for (int x = 0; x < data.letterboxA; x++)
for (int y = 0; y < renderer.getScreenHeight(); y++) {
uint8_t lv;
if (isSolid) lv = levelA;
else if (hashA) lv = hashBlockDither(data.avgA, x, y);
else lv = quantizeBayerDither(data.avgA, x, y);
if (isSolid)
lv = levelA;
else if (hashA)
lv = hashBlockDither(data.avgA, x, y);
else
lv = quantizeBayerDither(data.avgA, x, y);
renderer.drawPixelGray(x, y, lv);
}
}
@@ -334,9 +341,12 @@ void drawLetterboxFill(GfxRenderer& renderer, const LetterboxFillData& data, uin
for (int x = start; x < renderer.getScreenWidth(); x++)
for (int y = 0; y < renderer.getScreenHeight(); y++) {
uint8_t lv;
if (isSolid) lv = levelB;
else if (hashB) lv = hashBlockDither(data.avgB, x, y);
else lv = quantizeBayerDither(data.avgB, x, y);
if (isSolid)
lv = levelB;
else if (hashB)
lv = hashBlockDither(data.avgB, x, y);
else
lv = quantizeBayerDither(data.avgB, x, y);
renderer.drawPixelGray(x, y, lv);
}
}
@@ -523,9 +533,8 @@ void SleepActivity::renderBitmapSleepScreen(const Bitmap& bitmap, const std::str
}
}
if (fillData.valid) {
LOG_DBG("SLP", "Letterbox fill: %s, horizontal=%d, avgA=%d, avgB=%d, letterboxA=%d, letterboxB=%d",
fillModeName, fillData.horizontal, fillData.avgA, fillData.avgB, fillData.letterboxA,
fillData.letterboxB);
LOG_DBG("SLP", "Letterbox fill: %s, horizontal=%d, avgA=%d, avgB=%d, letterboxA=%d, letterboxB=%d", fillModeName,
fillData.horizontal, fillData.avgA, fillData.avgB, fillData.letterboxA, fillData.letterboxB);
}
}

View File

@@ -5,8 +5,8 @@
#include <GfxRenderer.h>
#include <HalStorage.h>
#include <I18n.h>
#include <Utf8.h>
#include <PlaceholderCoverGenerator.h>
#include <Utf8.h>
#include <Xtc.h>
#include <cstdio>

View File

@@ -17,9 +17,7 @@ void DictionaryDefinitionActivity::onEnter() {
requestUpdate();
}
void DictionaryDefinitionActivity::onExit() {
Activity::onExit();
}
void DictionaryDefinitionActivity::onExit() { Activity::onExit(); }
// ---------------------------------------------------------------------------
// Check if a Unicode codepoint is likely renderable by the e-ink bitmap font.
@@ -27,12 +25,12 @@ void DictionaryDefinitionActivity::onExit() {
// Skips IPA extensions, Greek, Cyrillic, Arabic, CJK, and other non-Latin scripts.
// ---------------------------------------------------------------------------
bool DictionaryDefinitionActivity::isRenderableCodepoint(uint32_t cp) {
if (cp <= 0x024F) return true; // Basic Latin + Latin Extended-A/B
if (cp >= 0x0300 && cp <= 0x036F) return true; // Combining Diacritical Marks
if (cp >= 0x2000 && cp <= 0x206F) return true; // General Punctuation
if (cp >= 0x20A0 && cp <= 0x20CF) return true; // Currency Symbols
if (cp >= 0x2100 && cp <= 0x214F) return true; // Letterlike Symbols
if (cp >= 0x2190 && cp <= 0x21FF) return true; // Arrows
if (cp <= 0x024F) return true; // Basic Latin + Latin Extended-A/B
if (cp >= 0x0300 && cp <= 0x036F) return true; // Combining Diacritical Marks
if (cp >= 0x2000 && cp <= 0x206F) return true; // General Punctuation
if (cp >= 0x20A0 && cp <= 0x20CF) return true; // Currency Symbols
if (cp >= 0x2100 && cp <= 0x214F) return true; // Letterlike Symbols
if (cp >= 0x2190 && cp <= 0x21FF) return true; // Arrows
return false;
}
@@ -48,7 +46,7 @@ std::string DictionaryDefinitionActivity::decodeEntity(const std::string& entity
if (entity == "apos") return "'";
if (entity == "nbsp" || entity == "thinsp" || entity == "ensp" || entity == "emsp") return " ";
if (entity == "ndash") return "\xE2\x80\x93"; // U+2013
if (entity == "mdash") return "\xE2\x80\x94"; // U+2014
if (entity == "mdash") return "\xE2\x80\x94"; // U+2014
if (entity == "lsquo") return "\xE2\x80\x98";
if (entity == "rsquo") return "\xE2\x80\x99";
if (entity == "ldquo") return "\xE2\x80\x9C";
@@ -502,11 +500,11 @@ void DictionaryDefinitionActivity::render(Activity::RenderLock&&) {
const int tw = renderer.getTextWidth(SMALL_FONT_ID, truncated.c_str());
if (useCCW) {
renderer.drawTextRotated90CCW(SMALL_FONT_ID, sideX,
sideButtonY[i] + (sideButtonHeight - tw) / 2, truncated.c_str());
renderer.drawTextRotated90CCW(SMALL_FONT_ID, sideX, sideButtonY[i] + (sideButtonHeight - tw) / 2,
truncated.c_str());
} else {
renderer.drawTextRotated90CW(SMALL_FONT_ID, sideX,
sideButtonY[i] + (sideButtonHeight + tw) / 2, truncated.c_str());
renderer.drawTextRotated90CW(SMALL_FONT_ID, sideX, sideButtonY[i] + (sideButtonHeight + tw) / 2,
truncated.c_str());
}
}

View File

@@ -13,9 +13,7 @@ void DictionarySuggestionsActivity::onEnter() {
requestUpdate();
}
void DictionarySuggestionsActivity::onExit() {
ActivityWithSubactivity::onExit();
}
void DictionarySuggestionsActivity::onExit() { ActivityWithSubactivity::onExit(); }
void DictionarySuggestionsActivity::loop() {
if (subActivity) {
@@ -66,8 +64,8 @@ void DictionarySuggestionsActivity::loop() {
}
enterNewActivity(new DictionaryDefinitionActivity(
renderer, mappedInput, selected, definition, readerFontId, orientation,
[this]() { pendingBackFromDef = true; }, [this]() { pendingExitToReader = true; }));
renderer, mappedInput, selected, definition, readerFontId, orientation, [this]() { pendingBackFromDef = true; },
[this]() { pendingExitToReader = true; }));
return;
}

View File

@@ -25,9 +25,7 @@ void DictionaryWordSelectActivity::onEnter() {
requestUpdate();
}
void DictionaryWordSelectActivity::onExit() {
ActivityWithSubactivity::onExit();
}
void DictionaryWordSelectActivity::onExit() { ActivityWithSubactivity::onExit(); }
bool DictionaryWordSelectActivity::isLandscape() const {
return orientation == CrossPointSettings::ORIENTATION::LANDSCAPE_CW ||
@@ -368,8 +366,8 @@ void DictionaryWordSelectActivity::loop() {
if (!definition.empty()) {
enterNewActivity(new DictionaryDefinitionActivity(
renderer, mappedInput, cleaned, definition, fontId, orientation,
[this]() { pendingBackFromDef = true; }, [this]() { pendingExitToReader = true; }));
renderer, mappedInput, cleaned, definition, fontId, orientation, [this]() { pendingBackFromDef = true; },
[this]() { pendingExitToReader = true; }));
return;
}
@@ -379,8 +377,8 @@ void DictionaryWordSelectActivity::loop() {
std::string stemDef = Dictionary::lookup(stem);
if (!stemDef.empty()) {
enterNewActivity(new DictionaryDefinitionActivity(
renderer, mappedInput, stem, stemDef, fontId, orientation,
[this]() { pendingBackFromDef = true; }, [this]() { pendingExitToReader = true; }));
renderer, mappedInput, stem, stemDef, fontId, orientation, [this]() { pendingBackFromDef = true; },
[this]() { pendingExitToReader = true; }));
return;
}
}
@@ -459,7 +457,7 @@ void DictionaryWordSelectActivity::drawHints() {
renderer.setOrientation(origOrientation);
// Bottom button constants (match LyraTheme::drawButtonHints)
constexpr int buttonHeight = 40; // LyraMetrics::values.buttonHintsHeight
constexpr int buttonHeight = 40; // LyraMetrics::values.buttonHintsHeight
constexpr int buttonWidth = 80;
constexpr int cornerRadius = 6;
constexpr int textYOffset = 7;
@@ -467,35 +465,43 @@ void DictionaryWordSelectActivity::drawHints() {
constexpr int buttonPositions[] = {58, 146, 254, 342};
// Side button constants (match LyraTheme::drawSideButtonHints)
constexpr int sideButtonWidth = 30; // LyraMetrics::values.sideButtonHintsWidth
constexpr int sideButtonWidth = 30; // LyraMetrics::values.sideButtonHintsWidth
constexpr int sideButtonHeight = 78;
constexpr int sideButtonGap = 5;
constexpr int sideTopY = 345; // topHintButtonY
constexpr int sideTopY = 345; // topHintButtonY
const int sideX = portW - sideButtonWidth;
const int sideButtonY[2] = {sideTopY, sideTopY + sideButtonHeight + sideButtonGap};
// Labels for face and side buttons depend on orientation,
// because the physical-to-logical mapping rotates with the screen.
const char* facePrev; // label for physical Left face button
const char* faceNext; // label for physical Right face button
const char* sideTop; // label for physical top side button (PageBack)
const char* sideBottom; // label for physical bottom side button (PageForward)
const char* facePrev; // label for physical Left face button
const char* faceNext; // label for physical Right face button
const char* sideTop; // label for physical top side button (PageBack)
const char* sideBottom; // label for physical bottom side button (PageForward)
const bool landscape = isLandscape();
const bool inverted = isInverted();
if (landscape && orientation == CrossPointSettings::ORIENTATION::LANDSCAPE_CW) {
facePrev = "Line Up"; faceNext = "Line Dn";
sideTop = "Word \xC2\xBB"; sideBottom = "\xC2\xAB Word";
facePrev = "Line Up";
faceNext = "Line Dn";
sideTop = "Word \xC2\xBB";
sideBottom = "\xC2\xAB Word";
} else if (landscape) { // LANDSCAPE_CCW
facePrev = "Line Dn"; faceNext = "Line Up";
sideTop = "\xC2\xAB Word"; sideBottom = "Word \xC2\xBB";
facePrev = "Line Dn";
faceNext = "Line Up";
sideTop = "\xC2\xAB Word";
sideBottom = "Word \xC2\xBB";
} else if (inverted) {
facePrev = "Word \xC2\xBB"; faceNext = "\xC2\xAB Word";
sideTop = "Line Dn"; sideBottom = "Line Up";
facePrev = "Word \xC2\xBB";
faceNext = "\xC2\xAB Word";
sideTop = "Line Dn";
sideBottom = "Line Up";
} else { // Portrait (default)
facePrev = "\xC2\xAB Word"; faceNext = "Word \xC2\xBB";
sideTop = "Line Up"; sideBottom = "Line Dn";
facePrev = "\xC2\xAB Word";
faceNext = "Word \xC2\xBB";
sideTop = "Line Up";
sideBottom = "Line Dn";
}
const auto labels = mappedInput.mapLabels("\xC2\xAB Back", "Select", facePrev, faceNext);
@@ -622,12 +628,12 @@ void DictionaryWordSelectActivity::drawHints() {
if (useCCW) {
// Text reads top-to-bottom (90° CCW rotation): y starts near top of button
renderer.drawTextRotated90CCW(SMALL_FONT_ID, sideX,
sideButtonY[i] + (sideButtonHeight - tw) / 2, truncated.c_str());
renderer.drawTextRotated90CCW(SMALL_FONT_ID, sideX, sideButtonY[i] + (sideButtonHeight - tw) / 2,
truncated.c_str());
} else {
// Text reads bottom-to-top (90° CW rotation): y starts near bottom of button
renderer.drawTextRotated90CW(SMALL_FONT_ID, sideX,
sideButtonY[i] + (sideButtonHeight + tw) / 2, truncated.c_str());
renderer.drawTextRotated90CW(SMALL_FONT_ID, sideX, sideButtonY[i] + (sideButtonHeight + tw) / 2,
truncated.c_str());
}
}

View File

@@ -13,8 +13,7 @@ class DictionaryWordSelectActivity final : public ActivityWithSubactivity {
explicit DictionaryWordSelectActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
std::unique_ptr<Page> page, int fontId, int marginLeft, int marginTop,
const std::string& cachePath, uint8_t orientation,
const std::function<void()>& onBack,
const std::string& nextPageFirstWord = "")
const std::function<void()>& onBack, const std::string& nextPageFirstWord = "")
: ActivityWithSubactivity("DictionaryWordSelect", renderer, mappedInput),
page(std::move(page)),
fontId(fontId),

View File

@@ -6,7 +6,6 @@
#include <HalStorage.h>
#include <I18n.h>
#include <Logging.h>
#include <PlaceholderCoverGenerator.h>
#include "CrossPointSettings.h"
@@ -242,8 +241,7 @@ void EpubReaderActivity::loop() {
}
// Long press CONFIRM opens Table of Contents directly (skip menu)
if (mappedInput.isPressed(MappedInputManager::Button::Confirm) &&
mappedInput.getHeldTime() >= longPressConfirmMs) {
if (mappedInput.isPressed(MappedInputManager::Button::Confirm) && mappedInput.getHeldTime() >= longPressConfirmMs) {
ignoreNextConfirmRelease = true;
if (epub && epub->getTocItemsCount() > 0) {
openChapterSelection(true); // skip the stale release from this long-press
@@ -266,8 +264,8 @@ void EpubReaderActivity::loop() {
}
const int bookProgressPercent = clampPercent(static_cast<int>(bookProgress + 0.5f));
const bool hasDictionary = Dictionary::exists();
const bool isBookmarked = BookmarkStore::hasBookmark(
epub->getCachePath(), currentSpineIndex, section ? section->currentPage : 0);
const bool isBookmarked =
BookmarkStore::hasBookmark(epub->getCachePath(), currentSpineIndex, section ? section->currentPage : 0);
exitActivity();
enterNewActivity(new EpubReaderMenuActivity(
this->renderer, this->mappedInput, epub->getTitle(), currentPage, totalPages, bookProgressPercent,
@@ -935,13 +933,11 @@ void EpubReaderActivity::render(Activity::RenderLock&& lock) {
silentIndexingActive = false;
const bool textOnlyPage = !p->hasImages();
if (textOnlyPage &&
SETTINGS.indexingDisplay != CrossPointSettings::INDEXING_DISPLAY::INDEXING_POPUP &&
if (textOnlyPage && SETTINGS.indexingDisplay != CrossPointSettings::INDEXING_DISPLAY::INDEXING_POPUP &&
section->pageCount >= 1 &&
((section->pageCount == 1 && section->currentPage == 0) ||
(section->pageCount >= 2 && section->currentPage == section->pageCount - 2)) &&
currentSpineIndex + 1 < epub->getSpineItemsCount() &&
preIndexedNextSpine != currentSpineIndex + 1) {
currentSpineIndex + 1 < epub->getSpineItemsCount() && preIndexedNextSpine != currentSpineIndex + 1) {
Section probe(epub, currentSpineIndex + 1, renderer);
if (probe.loadSectionFile(SETTINGS.getReaderFontId(), SETTINGS.getReaderLineCompression(),
SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth,
@@ -971,9 +967,8 @@ bool EpubReaderActivity::silentIndexNextChapterIfNeeded(const uint16_t viewportW
return false;
}
const bool shouldPreIndex =
(section->pageCount == 1 && section->currentPage == 0) ||
(section->pageCount >= 2 && section->currentPage == section->pageCount - 2);
const bool shouldPreIndex = (section->pageCount == 1 && section->currentPage == 0) ||
(section->pageCount >= 2 && section->currentPage == section->pageCount - 2);
if (!epub || !section || !shouldPreIndex) {
silentIndexingActive = false;
return false;
@@ -1065,8 +1060,8 @@ void EpubReaderActivity::renderContents(std::unique_ptr<Page> page, const int or
if (page->getImageBoundingBox(imgX, imgY, imgW, imgH)) {
int screenX = imgX + orientedMarginLeft;
int screenY = imgY + orientedMarginTop;
LOG_DBG("ERS", "Image page: fast double-refresh (page bbox: %d,%d %dx%d, screen: %d,%d %dx%d)",
imgX, imgY, imgW, imgH, screenX, screenY, imgW, imgH);
LOG_DBG("ERS", "Image page: fast double-refresh (page bbox: %d,%d %dx%d, screen: %d,%d %dx%d)", imgX, imgY, imgW,
imgH, screenX, screenY, imgW, imgH);
#if USE_IMAGE_DOUBLE_FAST_REFRESH == 0
// Method A: Fill blank area + two FAST_REFRESH operations
@@ -1233,8 +1228,7 @@ void EpubReaderActivity::renderStatusBar(const int orientedMarginRight, const in
if (SETTINGS.indexingDisplay == CrossPointSettings::INDEXING_DISPLAY::INDEXING_STATUS_TEXT) {
renderer.drawText(SMALL_FONT_ID, indicatorX, textY, tr(STR_INDEXING));
} else if (SETTINGS.indexingDisplay == CrossPointSettings::INDEXING_DISPLAY::INDEXING_STATUS_ICON) {
renderer.drawIcon(kIndexingIcon, indicatorX, textY - kIndexingIconSize + 2, kIndexingIconSize,
kIndexingIconSize);
renderer.drawIcon(kIndexingIcon, indicatorX, textY - kIndexingIconSize + 2, kIndexingIconSize, kIndexingIconSize);
}
}
}

View File

@@ -20,13 +20,13 @@ class EpubReaderActivity final : public ActivityWithSubactivity {
bool pendingPercentJump = false;
// Normalized 0.0-1.0 progress within the target spine item, computed from book percentage.
float pendingSpineProgress = 0.0f;
bool pendingSubactivityExit = false; // Defer subactivity exit to avoid use-after-free
bool pendingGoHome = false; // Defer go home to avoid race condition with display task
bool skipNextButtonCheck = false; // Skip button processing for one frame after subactivity exit
bool ignoreNextConfirmRelease = false; // Suppress short-press after long-press Confirm
volatile bool loadingSection = false; // True during the entire !section block (read from main loop)
bool silentIndexingActive = false; // True while silently pre-indexing the next chapter
int preIndexedNextSpine = -1; // Spine index already pre-indexed (prevents re-render loop)
bool pendingSubactivityExit = false; // Defer subactivity exit to avoid use-after-free
bool pendingGoHome = false; // Defer go home to avoid race condition with display task
bool skipNextButtonCheck = false; // Skip button processing for one frame after subactivity exit
bool ignoreNextConfirmRelease = false; // Suppress short-press after long-press Confirm
volatile bool loadingSection = false; // True during the entire !section block (read from main loop)
bool silentIndexingActive = false; // True while silently pre-indexing the next chapter
int preIndexedNextSpine = -1; // Spine index already pre-indexed (prevents re-render loop)
const std::function<void()> onGoBack;
const std::function<void()> onGoHome;

View File

@@ -47,9 +47,7 @@ void EpubReaderBookmarkSelectionActivity::onEnter() {
requestUpdate();
}
void EpubReaderBookmarkSelectionActivity::onExit() {
ActivityWithSubactivity::onExit();
}
void EpubReaderBookmarkSelectionActivity::onExit() { ActivityWithSubactivity::onExit(); }
void EpubReaderBookmarkSelectionActivity::loop() {
if (subActivity) {
@@ -207,8 +205,8 @@ void EpubReaderBookmarkSelectionActivity::render(Activity::RenderLock&&) {
if (!bookmarks.empty()) {
const char* deleteHint = "Hold select to delete";
const int hintWidth = renderer.getTextWidth(SMALL_FONT_ID, deleteHint);
renderer.drawText(SMALL_FONT_ID, (renderer.getScreenWidth() - hintWidth) / 2,
renderer.getScreenHeight() - 70, deleteHint);
renderer.drawText(SMALL_FONT_ID, (renderer.getScreenWidth() - hintWidth) / 2, renderer.getScreenHeight() - 70,
deleteHint);
}
const auto labels = mappedInput.mapLabels("\xC2\xAB Back", "Select", "Up", "Down");

View File

@@ -26,12 +26,10 @@ class EpubReaderBookmarkSelectionActivity final : public ActivityWithSubactivity
static std::string getPageSuffix(const Bookmark& bookmark);
public:
explicit EpubReaderBookmarkSelectionActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
const std::shared_ptr<Epub>& epub,
std::vector<Bookmark> bookmarks,
const std::string& cachePath,
const std::function<void()>& onGoBack,
const std::function<void(int newSpineIndex, int newPage)>& onSelectBookmark)
explicit EpubReaderBookmarkSelectionActivity(
GfxRenderer& renderer, MappedInputManager& mappedInput, const std::shared_ptr<Epub>& epub,
std::vector<Bookmark> bookmarks, const std::string& cachePath, const std::function<void()>& onGoBack,
const std::function<void(int newSpineIndex, int newPage)>& onSelectBookmark)
: ActivityWithSubactivity("EpubReaderBookmarkSelection", renderer, mappedInput),
epub(epub),
bookmarks(std::move(bookmarks)),

View File

@@ -40,13 +40,13 @@ void EpubReaderMenuActivity::loop() {
return;
}
buttonNavigator.onNext([this] {
orientationSelectIndex = ButtonNavigator::nextIndex(orientationSelectIndex,
static_cast<int>(orientationLabels.size()));
orientationSelectIndex =
ButtonNavigator::nextIndex(orientationSelectIndex, static_cast<int>(orientationLabels.size()));
requestUpdate();
});
buttonNavigator.onPrevious([this] {
orientationSelectIndex = ButtonNavigator::previousIndex(orientationSelectIndex,
static_cast<int>(orientationLabels.size()));
orientationSelectIndex =
ButtonNavigator::previousIndex(orientationSelectIndex, static_cast<int>(orientationLabels.size()));
requestUpdate();
});
return;
@@ -92,8 +92,8 @@ void EpubReaderMenuActivity::loop() {
// Toggle between the two preferred orientations.
// If currently in a portrait-category orientation (Portrait/Inverted), switch to preferredLandscape.
// If currently in a landscape-category orientation (CW/CCW), switch to preferredPortrait.
const bool isCurrentlyPortrait = (pendingOrientation == CrossPointSettings::PORTRAIT ||
pendingOrientation == CrossPointSettings::INVERTED);
const bool isCurrentlyPortrait =
(pendingOrientation == CrossPointSettings::PORTRAIT || pendingOrientation == CrossPointSettings::INVERTED);
if (isCurrentlyPortrait) {
pendingOrientation = SETTINGS.preferredLandscape;
} else {

View File

@@ -33,8 +33,7 @@ class EpubReaderMenuActivity final : public ActivityWithSubactivity {
explicit EpubReaderMenuActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, const std::string& title,
const int currentPage, const int totalPages, const int bookProgressPercent,
const uint8_t currentOrientation, const uint8_t currentFontSize,
const bool hasDictionary, const bool isBookmarked,
const std::string& bookCachePath,
const bool hasDictionary, const bool isBookmarked, const std::string& bookCachePath,
const std::function<void(uint8_t, uint8_t)>& onBack,
const std::function<void(MenuAction)>& onAction)
: ActivityWithSubactivity("EpubReaderMenu", renderer, mappedInput),
@@ -74,14 +73,13 @@ class EpubReaderMenuActivity final : public ActivityWithSubactivity {
uint8_t pendingFontSize = 0;
const std::vector<StrId> orientationLabels = {StrId::STR_PORTRAIT, StrId::STR_LANDSCAPE_CW, StrId::STR_INVERTED,
StrId::STR_LANDSCAPE_CCW};
const std::vector<StrId> fontSizeLabels = {StrId::STR_SMALL, StrId::STR_MEDIUM, StrId::STR_LARGE,
StrId::STR_X_LARGE};
const std::vector<StrId> fontSizeLabels = {StrId::STR_SMALL, StrId::STR_MEDIUM, StrId::STR_LARGE, StrId::STR_X_LARGE};
std::string bookCachePath;
// Letterbox fill override: 0xFF = Default (use global), 0 = Dithered, 1 = Solid, 2 = None
uint8_t pendingLetterboxFill = BookSettings::USE_GLOBAL;
static constexpr int LETTERBOX_FILL_OPTION_COUNT = 4; // Default + 3 modes
const std::vector<StrId> letterboxFillLabels = {StrId::STR_DEFAULT_OPTION, StrId::STR_DITHERED, StrId::STR_SOLID,
StrId::STR_NONE_OPT};
StrId::STR_NONE_OPT};
int currentPage = 0;
int totalPages = 0;
int bookProgressPercent = 0;
@@ -100,7 +98,7 @@ class EpubReaderMenuActivity final : public ActivityWithSubactivity {
// Map the internal override value to an index into letterboxFillLabels.
int letterboxFillToIndex() const {
if (pendingLetterboxFill == BookSettings::USE_GLOBAL) return 0; // "Default"
return pendingLetterboxFill + 1; // 0->1 (Dithered), 1->2 (Solid), 2->3 (None)
return pendingLetterboxFill + 1; // 0->1 (Dithered), 1->2 (Solid), 2->3 (None)
}
// Map an index from letterboxFillLabels back to an override value.
@@ -137,5 +135,4 @@ class EpubReaderMenuActivity final : public ActivityWithSubactivity {
items.push_back({MenuAction::DELETE_CACHE, StrId::STR_DELETE_CACHE});
return items;
}
};

View File

@@ -23,9 +23,7 @@ void LookedUpWordsActivity::onEnter() {
requestUpdate();
}
void LookedUpWordsActivity::onExit() {
ActivityWithSubactivity::onExit();
}
void LookedUpWordsActivity::onExit() { ActivityWithSubactivity::onExit(); }
void LookedUpWordsActivity::loop() {
if (subActivity) {
@@ -83,8 +81,8 @@ void LookedUpWordsActivity::loop() {
// Detect long press on Confirm to trigger delete (only for real word entries, not sentinel)
constexpr unsigned long DELETE_HOLD_MS = 700;
if (selectedIndex != deleteDictCacheIndex &&
mappedInput.isPressed(MappedInputManager::Button::Confirm) && mappedInput.getHeldTime() >= DELETE_HOLD_MS) {
if (selectedIndex != deleteDictCacheIndex && mappedInput.isPressed(MappedInputManager::Button::Confirm) &&
mappedInput.getHeldTime() >= DELETE_HOLD_MS) {
deleteConfirmMode = true;
ignoreNextConfirmRelease = true;
pendingDeleteIndex = selectedIndex;
@@ -150,11 +148,10 @@ void LookedUpWordsActivity::loop() {
Activity::RenderLock lock(*this);
popupLayout = GUI.drawPopup(renderer, "Looking up...");
}
std::string definition = Dictionary::lookup(
headword, [this, &popupLayout](int percent) {
Activity::RenderLock lock(*this);
GUI.fillPopupProgress(renderer, popupLayout, percent);
});
std::string definition = Dictionary::lookup(headword, [this, &popupLayout](int percent) {
Activity::RenderLock lock(*this);
GUI.fillPopupProgress(renderer, popupLayout, percent);
});
if (!definition.empty()) {
enterNewActivity(new DictionaryDefinitionActivity(
@@ -169,8 +166,8 @@ void LookedUpWordsActivity::loop() {
std::string stemDef = Dictionary::lookup(stem);
if (!stemDef.empty()) {
enterNewActivity(new DictionaryDefinitionActivity(
renderer, mappedInput, stem, stemDef, readerFontId, orientation,
[this]() { pendingBackFromDef = true; }, [this]() { pendingExitToReader = true; }));
renderer, mappedInput, stem, stemDef, readerFontId, orientation, [this]() { pendingBackFromDef = true; },
[this]() { pendingExitToReader = true; }));
return;
}
}

View File

@@ -3,11 +3,10 @@
#include <GfxRenderer.h>
#include <HalStorage.h>
#include <I18n.h>
#include <PlaceholderCoverGenerator.h>
#include <Serialization.h>
#include <Utf8.h>
#include <PlaceholderCoverGenerator.h>
#include "CrossPointSettings.h"
#include "CrossPointState.h"
#include "MappedInputManager.h"
@@ -85,7 +84,7 @@ void TxtReaderActivity::onEnter() {
const int thumbHeight = PRERENDER_THUMB_HEIGHTS[i];
const int thumbWidth = static_cast<int>(thumbHeight * 0.6);
PlaceholderCoverGenerator::generate(txt->getThumbBmpPath(thumbHeight), txt->getTitle(), "", thumbWidth,
thumbHeight);
thumbHeight);
updateProgress();
}
}

View File

@@ -11,7 +11,6 @@
#include <GfxRenderer.h>
#include <HalStorage.h>
#include <I18n.h>
#include <PlaceholderCoverGenerator.h>
#include "CrossPointSettings.h"
@@ -73,7 +72,7 @@ void XtcReaderActivity::onEnter() {
const int thumbHeight = PRERENDER_THUMB_HEIGHTS[i];
const int thumbWidth = static_cast<int>(thumbHeight * 0.6);
PlaceholderCoverGenerator::generate(xtc->getThumbBmpPath(thumbHeight), xtc->getTitle(), xtc->getAuthor(),
thumbWidth, thumbHeight);
thumbWidth, thumbHeight);
}
updateProgress();
}

View File

@@ -142,7 +142,6 @@ void CalibreSettingsActivity::render(Activity::RenderLock&&) {
},
true);
const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_SELECT), tr(STR_DIR_UP), tr(STR_DIR_DOWN));
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);

View File

@@ -168,7 +168,6 @@ void KOReaderSettingsActivity::render(Activity::RenderLock&&) {
},
true);
const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_SELECT), tr(STR_DIR_UP), tr(STR_DIR_DOWN));
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);

View File

@@ -2,10 +2,10 @@
#include <GfxRenderer.h>
#include <I18n.h>
#include <sys/time.h>
#include <cstdio>
#include <ctime>
#include <sys/time.h>
#include "CrossPointSettings.h"
#include "MappedInputManager.h"
@@ -106,8 +106,8 @@ void SetTimeActivity::render(Activity::RenderLock&&) {
renderer.fillRoundedRect(startX - highlightPad, timeY - 4, digitWidth + highlightPad * 2, lineHeight12 + 8, 6,
Color::LightGray);
} else {
renderer.fillRoundedRect(startX + digitWidth + colonWidth - highlightPad, timeY - 4,
digitWidth + highlightPad * 2, lineHeight12 + 8, 6, Color::LightGray);
renderer.fillRoundedRect(startX + digitWidth + colonWidth - highlightPad, timeY - 4, digitWidth + highlightPad * 2,
lineHeight12 + 8, 6, Color::LightGray);
}
// Draw the time digits and colon

View File

@@ -6,8 +6,7 @@
class SetTimeActivity final : public Activity {
public:
explicit SetTimeActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
const std::function<void()>& onBack)
explicit SetTimeActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, const std::function<void()>& onBack)
: Activity("SetTime", renderer, mappedInput), onBack(onBack) {}
void onEnter() override;

View File

@@ -8,7 +8,6 @@
#include "MappedInputManager.h"
#include "RecentBooksStore.h"
#include "components/themes/BaseTheme.h"
#include "components/themes/lyra/Lyra3CoversTheme.h"
#include "components/themes/lyra/LyraTheme.h"
#include "util/StringUtils.h"
@@ -42,8 +41,8 @@ void UITheme::setTheme(CrossPointSettings::UI_THEME type) {
break;
case CrossPointSettings::UI_THEME::LYRA_3_COVERS:
LOG_DBG("UI", "Using Lyra 3 Covers theme");
currentTheme = new Lyra3CoversTheme();
currentMetrics = &Lyra3CoversMetrics::values;
currentTheme = std::make_unique<LyraTheme>();
currentMetrics = &LyraMetrics::values;
break;
}
}

View File

@@ -283,8 +283,10 @@ void BaseTheme::drawHeader(const GfxRenderer& renderer, Rect rect, const char* t
snprintf(timeBuf, sizeof(timeBuf), "%d:%02d %s", hour12, t->tm_min, t->tm_hour >= 12 ? "PM" : "AM");
}
int clockFont = SMALL_FONT_ID;
if (SETTINGS.clockSize == CrossPointSettings::CLOCK_SIZE_MEDIUM) clockFont = UI_10_FONT_ID;
else if (SETTINGS.clockSize == CrossPointSettings::CLOCK_SIZE_LARGE) clockFont = UI_12_FONT_ID;
if (SETTINGS.clockSize == CrossPointSettings::CLOCK_SIZE_MEDIUM)
clockFont = UI_10_FONT_ID;
else if (SETTINGS.clockSize == CrossPointSettings::CLOCK_SIZE_LARGE)
clockFont = UI_12_FONT_ID;
renderer.drawText(clockFont, rect.x + 12, rect.y + 5, timeBuf, true);
}
}

View File

@@ -1,41 +0,0 @@
#pragma once
#include "components/themes/lyra/LyraTheme.h"
class GfxRenderer;
// Lyra theme metrics (zero runtime cost)
namespace Lyra3CoversMetrics {
constexpr ThemeMetrics values = {.batteryWidth = 16,
.batteryHeight = 12,
.topPadding = 5,
.batteryBarHeight = 40,
.headerHeight = 84,
.verticalSpacing = 16,
.contentSidePadding = 20,
.listRowHeight = 40,
.listWithSubtitleRowHeight = 60,
.menuRowHeight = 64,
.menuSpacing = 8,
.tabSpacing = 8,
.tabBarHeight = 40,
.scrollBarWidth = 4,
.scrollBarRightOffset = 5,
.homeTopPadding = 56,
.homeCoverHeight = 226,
.homeCoverTileHeight = 287,
.homeRecentBooksCount = 3,
.buttonHintsHeight = 40,
.sideButtonHintsWidth = 30,
.progressBarHeight = 16,
.bookProgressBarHeight = 4};
}
class Lyra3CoversTheme : public LyraTheme {
public:
void drawRecentBookCover(GfxRenderer& renderer, Rect rect, const std::vector<RecentBook>& recentBooks,
const int selectorIndex, bool& coverRendered, bool& coverBufferStored, bool& bufferRestored,
std::function<bool()> storeCoverBuffer) const override;
};

View File

@@ -190,8 +190,10 @@ void LyraTheme::drawHeader(const GfxRenderer& renderer, Rect rect, const char* t
snprintf(timeBuf, sizeof(timeBuf), "%d:%02d %s", hour12, t->tm_min, t->tm_hour >= 12 ? "PM" : "AM");
}
int clockFont = SMALL_FONT_ID;
if (SETTINGS.clockSize == CrossPointSettings::CLOCK_SIZE_MEDIUM) clockFont = UI_10_FONT_ID;
else if (SETTINGS.clockSize == CrossPointSettings::CLOCK_SIZE_LARGE) clockFont = UI_12_FONT_ID;
if (SETTINGS.clockSize == CrossPointSettings::CLOCK_SIZE_MEDIUM)
clockFont = UI_10_FONT_ID;
else if (SETTINGS.clockSize == CrossPointSettings::CLOCK_SIZE_LARGE)
clockFont = UI_12_FONT_ID;
renderer.drawText(clockFont, rect.x + 12, rect.y + 5, timeBuf, true);
}
}
@@ -534,8 +536,7 @@ void LyraTheme::drawRecentBookCover(GfxRenderer& renderer, Rect rect, const std:
if (!coverRendered) {
renderer.drawRect(cardX + hPaddingInSelection, tileY + hPaddingInSelection, coverSlotWidth, coverHeight);
if (!recentBooks[0].coverBmpPath.empty()) {
const std::string coverBmpPath =
UITheme::getCoverThumbPath(recentBooks[0].coverBmpPath, coverHeight);
const std::string coverBmpPath = UITheme::getCoverThumbPath(recentBooks[0].coverBmpPath, coverHeight);
renderCoverBitmap(coverBmpPath, cardX + hPaddingInSelection, tileY + hPaddingInSelection, coverSlotWidth);
}
coverBufferStored = storeCoverBuffer();
@@ -598,8 +599,7 @@ void LyraTheme::drawRecentBookCover(GfxRenderer& renderer, Rect rect, const std:
if (bookSelected) {
renderer.fillRoundedRect(tileX, tileY, tileWidth, hPaddingInSelection, cornerRadius, true, true, false, false,
Color::LightGray);
renderer.fillRectDither(tileX, tileY + hPaddingInSelection, hPaddingInSelection, coverHeight,
Color::LightGray);
renderer.fillRectDither(tileX, tileY + hPaddingInSelection, hPaddingInSelection, coverHeight, Color::LightGray);
renderer.fillRectDither(tileX + tileWidth - hPaddingInSelection, tileY + hPaddingInSelection,
hPaddingInSelection, coverHeight, Color::LightGray);
renderer.fillRoundedRect(tileX, tileY + coverHeight + hPaddingInSelection, tileWidth, bottomSectionHeight,

View File

@@ -138,10 +138,9 @@ bool BookmarkStore::addBookmark(const std::string& cachePath, int spineIndex, in
bool BookmarkStore::removeBookmark(const std::string& cachePath, int spineIndex, int page) {
auto bookmarks = load(cachePath);
auto it = std::remove_if(bookmarks.begin(), bookmarks.end(),
[spineIndex, page](const Bookmark& b) {
return b.spineIndex == spineIndex && b.pageNumber == page;
});
auto it = std::remove_if(bookmarks.begin(), bookmarks.end(), [spineIndex, page](const Bookmark& b) {
return b.spineIndex == spineIndex && b.pageNumber == page;
});
if (it == bookmarks.end()) {
return false; // Not found

View File

@@ -351,7 +351,7 @@ std::vector<std::string> Dictionary::getStemVariants(const std::string& word) {
if (endsWith("ves")) {
add(word.substr(0, len - 3) + "f"); // wolves -> wolf
add(word.substr(0, len - 3) + "fe"); // knives -> knife
add(word.substr(0, len - 1)); // misgives -> misgive
add(word.substr(0, len - 1)); // misgives -> misgive
}
if (endsWith("men")) add(word.substr(0, len - 3) + "man"); // firemen -> fireman
if (endsWith("es") && !endsWith("sses") && !endsWith("ies") && !endsWith("ves")) {
@@ -390,11 +390,11 @@ std::vector<std::string> Dictionary::getStemVariants(const std::string& word) {
// Adverb
if (endsWith("ically")) {
add(word.substr(0, len - 6) + "ic"); // historically -> historic
add(word.substr(0, len - 4)); // basically -> basic
add(word.substr(0, len - 4)); // basically -> basic
}
if (endsWith("ally") && !endsWith("ically")) {
add(word.substr(0, len - 4) + "al"); // accidentally -> accidental
add(word.substr(0, len - 2)); // naturally -> natur... (fallback to -ly strip)
add(word.substr(0, len - 2)); // naturally -> natur... (fallback to -ly strip)
}
if (endsWith("ily") && !endsWith("ally")) {
add(word.substr(0, len - 3) + "y");
@@ -439,18 +439,18 @@ std::vector<std::string> Dictionary::getStemVariants(const std::string& word) {
add(word.substr(0, len - 4) + "e");
}
if (endsWith("ation")) {
add(word.substr(0, len - 5)); // information -> inform
add(word.substr(0, len - 5) + "e"); // exploration -> explore
add(word.substr(0, len - 5) + "ate"); // donation -> donate
add(word.substr(0, len - 5)); // information -> inform
add(word.substr(0, len - 5) + "e"); // exploration -> explore
add(word.substr(0, len - 5) + "ate"); // donation -> donate
}
if (endsWith("tion") && !endsWith("ation")) {
add(word.substr(0, len - 4) + "te"); // completion -> complete
add(word.substr(0, len - 3)); // action -> act
add(word.substr(0, len - 3)); // action -> act
add(word.substr(0, len - 3) + "e"); // reduction -> reduce
}
if (endsWith("ion") && !endsWith("tion")) {
add(word.substr(0, len - 3)); // revision -> revis (-> revise via +e)
add(word.substr(0, len - 3) + "e"); // revision -> revise
add(word.substr(0, len - 3)); // revision -> revis (-> revise via +e)
add(word.substr(0, len - 3) + "e"); // revision -> revise
}
if (endsWith("al") && !endsWith("ial")) {
add(word.substr(0, len - 2));
@@ -461,24 +461,24 @@ std::vector<std::string> Dictionary::getStemVariants(const std::string& word) {
add(word.substr(0, len - 3) + "e");
}
if (endsWith("ous")) {
add(word.substr(0, len - 3)); // dangerous -> danger
add(word.substr(0, len - 3) + "e"); // famous -> fame
add(word.substr(0, len - 3)); // dangerous -> danger
add(word.substr(0, len - 3) + "e"); // famous -> fame
}
if (endsWith("ive")) {
add(word.substr(0, len - 3)); // active -> act
add(word.substr(0, len - 3) + "e"); // creative -> create
add(word.substr(0, len - 3)); // active -> act
add(word.substr(0, len - 3) + "e"); // creative -> create
}
if (endsWith("ize")) {
add(word.substr(0, len - 3)); // modernize -> modern
add(word.substr(0, len - 3)); // modernize -> modern
add(word.substr(0, len - 3) + "e");
}
if (endsWith("ise")) {
add(word.substr(0, len - 3)); // advertise -> advert
add(word.substr(0, len - 3)); // advertise -> advert
add(word.substr(0, len - 3) + "e");
}
if (endsWith("en")) {
add(word.substr(0, len - 2)); // darken -> dark
add(word.substr(0, len - 2) + "e"); // widen -> wide
add(word.substr(0, len - 2)); // darken -> dark
add(word.substr(0, len - 2) + "e"); // widen -> wide
}
// Prefix removal