Compare commits
2 Commits
c1b8e53138
...
ad282cadfe
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ad282cadfe
|
||
|
|
c8ba4fe973
|
@@ -295,6 +295,9 @@ void CssParser::parseDeclarationIntoStyle(const std::string& decl, CssStyle& sty
|
||||
style.defined.paddingTop = style.defined.paddingRight = style.defined.paddingBottom = style.defined.paddingLeft =
|
||||
1;
|
||||
}
|
||||
} else if (propNameBuf == "height") {
|
||||
style.imageHeight = interpretLength(propValueBuf);
|
||||
style.defined.imageHeight = 1;
|
||||
} else if (propNameBuf == "width") {
|
||||
style.width = interpretLength(propValueBuf);
|
||||
style.defined.width = 1;
|
||||
@@ -565,7 +568,7 @@ CssStyle CssParser::parseInlineStyle(const std::string& styleValue) { return par
|
||||
// Cache serialization
|
||||
|
||||
// Cache format version - increment when format changes
|
||||
constexpr uint8_t CSS_CACHE_VERSION = 2;
|
||||
constexpr uint8_t CSS_CACHE_VERSION = 3;
|
||||
constexpr char rulesCache[] = "/css_rules.cache";
|
||||
|
||||
bool CssParser::hasCache() const { return Storage.exists((cachePath + rulesCache).c_str()); }
|
||||
@@ -616,6 +619,8 @@ bool CssParser::saveToCache() const {
|
||||
writeLength(style.paddingBottom);
|
||||
writeLength(style.paddingLeft);
|
||||
writeLength(style.paddingRight);
|
||||
writeLength(style.imageHeight);
|
||||
writeLength(style.width);
|
||||
|
||||
// Write defined flags as uint16_t
|
||||
uint16_t definedBits = 0;
|
||||
@@ -632,6 +637,8 @@ bool CssParser::saveToCache() const {
|
||||
if (style.defined.paddingBottom) definedBits |= 1 << 10;
|
||||
if (style.defined.paddingLeft) definedBits |= 1 << 11;
|
||||
if (style.defined.paddingRight) definedBits |= 1 << 12;
|
||||
if (style.defined.width) definedBits |= 1 << 13;
|
||||
if (style.defined.imageHeight) definedBits |= 1 << 14;
|
||||
file.write(reinterpret_cast<const uint8_t*>(&definedBits), sizeof(definedBits));
|
||||
}
|
||||
|
||||
@@ -733,7 +740,8 @@ bool CssParser::loadFromCache() {
|
||||
|
||||
if (!readLength(style.textIndent) || !readLength(style.marginTop) || !readLength(style.marginBottom) ||
|
||||
!readLength(style.marginLeft) || !readLength(style.marginRight) || !readLength(style.paddingTop) ||
|
||||
!readLength(style.paddingBottom) || !readLength(style.paddingLeft) || !readLength(style.paddingRight)) {
|
||||
!readLength(style.paddingBottom) || !readLength(style.paddingLeft) || !readLength(style.paddingRight) ||
|
||||
!readLength(style.imageHeight) || !readLength(style.width)) {
|
||||
rulesBySelector_.clear();
|
||||
file.close();
|
||||
return false;
|
||||
@@ -759,6 +767,8 @@ bool CssParser::loadFromCache() {
|
||||
style.defined.paddingBottom = (definedBits & 1 << 10) != 0;
|
||||
style.defined.paddingLeft = (definedBits & 1 << 11) != 0;
|
||||
style.defined.paddingRight = (definedBits & 1 << 12) != 0;
|
||||
style.defined.width = (definedBits & 1 << 13) != 0;
|
||||
style.defined.imageHeight = (definedBits & 1 << 14) != 0;
|
||||
|
||||
rulesBySelector_[selector] = style;
|
||||
}
|
||||
|
||||
@@ -70,6 +70,7 @@ struct CssPropertyFlags {
|
||||
uint16_t paddingLeft : 1;
|
||||
uint16_t paddingRight : 1;
|
||||
uint16_t width : 1;
|
||||
uint16_t imageHeight : 1;
|
||||
|
||||
CssPropertyFlags()
|
||||
: textAlign(0),
|
||||
@@ -85,18 +86,20 @@ struct CssPropertyFlags {
|
||||
paddingBottom(0),
|
||||
paddingLeft(0),
|
||||
paddingRight(0),
|
||||
width(0) {}
|
||||
width(0),
|
||||
imageHeight(0) {}
|
||||
|
||||
[[nodiscard]] bool anySet() const {
|
||||
return textAlign || fontStyle || fontWeight || textDecoration || textIndent || marginTop || marginBottom ||
|
||||
marginLeft || marginRight || paddingTop || paddingBottom || paddingLeft || paddingRight || width;
|
||||
marginLeft || marginRight || paddingTop || paddingBottom || paddingLeft || paddingRight || width ||
|
||||
imageHeight;
|
||||
}
|
||||
|
||||
void clearAll() {
|
||||
textAlign = fontStyle = fontWeight = textDecoration = textIndent = 0;
|
||||
marginTop = marginBottom = marginLeft = marginRight = 0;
|
||||
paddingTop = paddingBottom = paddingLeft = paddingRight = 0;
|
||||
width = 0;
|
||||
width = imageHeight = 0;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -118,7 +121,8 @@ 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 and image sizing)
|
||||
CssLength imageHeight; // Height for img (e.g. 2em) -- width derived from aspect ratio when only height set
|
||||
|
||||
CssPropertyFlags defined; // Tracks which properties were explicitly set
|
||||
|
||||
@@ -181,6 +185,10 @@ struct CssStyle {
|
||||
width = base.width;
|
||||
defined.width = 1;
|
||||
}
|
||||
if (base.hasImageHeight()) {
|
||||
imageHeight = base.imageHeight;
|
||||
defined.imageHeight = 1;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] bool hasTextAlign() const { return defined.textAlign; }
|
||||
@@ -197,6 +205,7 @@ struct CssStyle {
|
||||
[[nodiscard]] bool hasPaddingLeft() const { return defined.paddingLeft; }
|
||||
[[nodiscard]] bool hasPaddingRight() const { return defined.paddingRight; }
|
||||
[[nodiscard]] bool hasWidth() const { return defined.width; }
|
||||
[[nodiscard]] bool hasImageHeight() const { return defined.imageHeight; }
|
||||
|
||||
void reset() {
|
||||
textAlign = CssTextAlign::Left;
|
||||
@@ -207,6 +216,7 @@ struct CssStyle {
|
||||
marginTop = marginBottom = marginLeft = marginRight = CssLength{};
|
||||
paddingTop = paddingBottom = paddingLeft = paddingRight = CssLength{};
|
||||
width = CssLength{};
|
||||
imageHeight = CssLength{};
|
||||
defined.clearAll();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -418,7 +418,47 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char*
|
||||
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 displayWidth = 0;
|
||||
int displayHeight = 0;
|
||||
const float emSize =
|
||||
static_cast<float>(self->renderer.getLineHeight(self->fontId)) * self->lineCompression;
|
||||
CssStyle imgStyle = self->cssParser ? self->cssParser->resolveStyle("img", classAttr) : CssStyle{};
|
||||
if (!styleAttr.empty()) {
|
||||
imgStyle.applyOver(CssParser::parseInlineStyle(styleAttr));
|
||||
}
|
||||
const bool hasCssHeight = imgStyle.hasImageHeight();
|
||||
const bool hasCssWidth = imgStyle.hasWidth();
|
||||
|
||||
if (hasCssHeight && dims.width > 0 && dims.height > 0) {
|
||||
displayHeight = static_cast<int>(
|
||||
imgStyle.imageHeight.toPixels(emSize, static_cast<float>(self->viewportHeight)) + 0.5f);
|
||||
if (displayHeight < 1) displayHeight = 1;
|
||||
displayWidth =
|
||||
static_cast<int>(displayHeight * (static_cast<float>(dims.width) / dims.height) + 0.5f);
|
||||
if (displayWidth > self->viewportWidth) {
|
||||
displayWidth = self->viewportWidth;
|
||||
displayHeight =
|
||||
static_cast<int>(displayWidth * (static_cast<float>(dims.height) / dims.width) + 0.5f);
|
||||
if (displayHeight < 1) displayHeight = 1;
|
||||
}
|
||||
if (displayWidth < 1) displayWidth = 1;
|
||||
LOG_DBG("EHP", "Display size from CSS height: %dx%d", displayWidth, displayHeight);
|
||||
} else if (hasCssWidth && !hasCssHeight && dims.width > 0 && dims.height > 0) {
|
||||
displayWidth = static_cast<int>(
|
||||
imgStyle.width.toPixels(emSize, static_cast<float>(self->viewportWidth)) + 0.5f);
|
||||
if (displayWidth > self->viewportWidth) displayWidth = self->viewportWidth;
|
||||
if (displayWidth < 1) displayWidth = 1;
|
||||
displayHeight =
|
||||
static_cast<int>(displayWidth * (static_cast<float>(dims.height) / dims.width) + 0.5f);
|
||||
if (displayHeight > self->viewportHeight) {
|
||||
displayHeight = self->viewportHeight;
|
||||
displayWidth =
|
||||
static_cast<int>(displayHeight * (static_cast<float>(dims.width) / dims.height) + 0.5f);
|
||||
if (displayWidth < 1) displayWidth = 1;
|
||||
}
|
||||
if (displayHeight < 1) displayHeight = 1;
|
||||
LOG_DBG("EHP", "Display size from CSS width: %dx%d", displayWidth, displayHeight);
|
||||
} else {
|
||||
int maxWidth = self->viewportWidth;
|
||||
int maxHeight = self->viewportHeight;
|
||||
float scaleX = (dims.width > maxWidth) ? (float)maxWidth / dims.width : 1.0f;
|
||||
@@ -426,10 +466,10 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char*
|
||||
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);
|
||||
|
||||
displayWidth = (int)(dims.width * scale);
|
||||
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() &&
|
||||
|
||||
@@ -22,11 +22,6 @@
|
||||
#include "util/BookmarkStore.h"
|
||||
#include "util/Dictionary.h"
|
||||
|
||||
// Image refresh optimization strategy:
|
||||
// 0 = Use double FAST_REFRESH technique (default, feels snappier)
|
||||
// 1 = Use displayWindow() for partial refresh (experimental)
|
||||
#define USE_IMAGE_DOUBLE_FAST_REFRESH 0
|
||||
|
||||
namespace {
|
||||
// pagesPerRefresh now comes from SETTINGS.getRefreshFrequency()
|
||||
constexpr unsigned long skipChapterMs = 700;
|
||||
@@ -1022,13 +1017,8 @@ void EpubReaderActivity::saveProgress(int spineIndex, int currentPage, int pageC
|
||||
void EpubReaderActivity::renderContents(std::unique_ptr<Page> page, const int orientedMarginTop,
|
||||
const int orientedMarginRight, const int orientedMarginBottom,
|
||||
const int orientedMarginLeft) {
|
||||
// Determine if this page needs special image handling
|
||||
bool pageHasImages = page->hasImages();
|
||||
bool useAntiAliasing = SETTINGS.textAntiAliasing;
|
||||
|
||||
// Force half refresh for pages with images when anti-aliasing is on,
|
||||
// as grayscale tones require half refresh to display correctly
|
||||
bool forceFullRefresh = pageHasImages && useAntiAliasing;
|
||||
// Force special handling for pages with images when anti-aliasing is on
|
||||
bool imagePageWithAA = page->hasImages() && SETTINGS.textAntiAliasing;
|
||||
|
||||
page->render(renderer, SETTINGS.getReaderFontId(), orientedMarginLeft, orientedMarginTop);
|
||||
|
||||
@@ -1048,42 +1038,26 @@ void EpubReaderActivity::renderContents(std::unique_ptr<Page> page, const int or
|
||||
}
|
||||
|
||||
renderStatusBar(orientedMarginRight, orientedMarginBottom, orientedMarginLeft);
|
||||
|
||||
// Check if half-refresh is needed (either entering Reader or pages counter reached)
|
||||
if (pagesUntilFullRefresh <= 1) {
|
||||
renderer.displayBuffer(HalDisplay::HALF_REFRESH);
|
||||
pagesUntilFullRefresh = SETTINGS.getRefreshFrequency();
|
||||
} else if (forceFullRefresh) {
|
||||
// OPTIMIZATION: For image pages with anti-aliasing, use fast double-refresh technique
|
||||
// to reduce perceived lag. Only when pagesUntilFullRefresh > 1 (screen already clean).
|
||||
if (imagePageWithAA) {
|
||||
// Double FAST_REFRESH with selective image blanking (pablohc's technique):
|
||||
// HALF_REFRESH sets particles too firmly for the grayscale LUT to adjust.
|
||||
// Instead, blank only the image area and do two fast refreshes.
|
||||
int imgX, imgY, imgW, imgH;
|
||||
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);
|
||||
renderer.fillRect(imgX + orientedMarginLeft, imgY + orientedMarginTop, imgW, imgH, false);
|
||||
renderer.displayBuffer(HalDisplay::FAST_REFRESH);
|
||||
|
||||
#if USE_IMAGE_DOUBLE_FAST_REFRESH == 0
|
||||
// Method A: Fill blank area + two FAST_REFRESH operations
|
||||
renderer.fillRect(screenX, screenY, imgW, imgH, false);
|
||||
renderer.displayBuffer(HalDisplay::FAST_REFRESH);
|
||||
page->render(renderer, SETTINGS.getReaderFontId(), orientedMarginLeft, orientedMarginTop);
|
||||
renderStatusBar(orientedMarginRight, orientedMarginBottom, orientedMarginLeft);
|
||||
renderer.displayBuffer(HalDisplay::FAST_REFRESH);
|
||||
#else
|
||||
// Method B (experimental): Use displayWindow() for partial refresh
|
||||
renderer.displayBuffer(HalDisplay::FAST_REFRESH);
|
||||
page->render(renderer, SETTINGS.getReaderFontId(), orientedMarginLeft, orientedMarginTop);
|
||||
renderStatusBar(orientedMarginRight, orientedMarginBottom, orientedMarginLeft);
|
||||
renderer.displayWindow(screenX, screenY, imgW, imgH, HalDisplay::FAST_REFRESH);
|
||||
#endif
|
||||
} else {
|
||||
LOG_DBG("ERS", "Image page but no bbox, using standard half refresh");
|
||||
renderer.displayBuffer(HalDisplay::HALF_REFRESH);
|
||||
}
|
||||
pagesUntilFullRefresh--;
|
||||
// Double FAST_REFRESH handles ghosting for image pages; don't count toward full refresh cadence
|
||||
} else if (pagesUntilFullRefresh <= 1) {
|
||||
renderer.displayBuffer(HalDisplay::HALF_REFRESH);
|
||||
pagesUntilFullRefresh = SETTINGS.getRefreshFrequency();
|
||||
} else {
|
||||
// Normal page without images, or images without anti-aliasing
|
||||
renderer.displayBuffer();
|
||||
pagesUntilFullRefresh--;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user