Compare commits

..

2 Commits

Author SHA1 Message Date
Dave Allie 77a964e83a Style fixes 2026-04-06 15:35:36 +10:00
Dave Allie e20f4550ce Render image inside existing block style margins 2026-04-06 15:18:38 +10:00
60 changed files with 775 additions and 1260 deletions
+6 -7
View File
@@ -15,10 +15,10 @@ void EpdFont::getTextBounds(const char* string, const int startX, const int star
return;
}
int32_t cursorXFP = fp4::fromPixel(startX); // 12.4 fixed-point accumulator
int lastBaseX = startX;
int lastBaseAdvanceFP = 0; // 12.4 fixed-point
int lastBaseTop = 0;
int32_t prevAdvanceFP = 0; // 12.4 fixed-point: prev glyph's advance + next kern for snap
constexpr int MIN_COMBINING_GAP_PX = 1;
uint32_t cp;
uint32_t prevCp = 0;
@@ -31,9 +31,7 @@ void EpdFont::getTextBounds(const char* string, const int startX, const int star
const EpdGlyph* glyph = getGlyph(cp);
if (!glyph) {
lastBaseX += fp4::toPixel(prevAdvanceFP); // flush pending advance before resetting
prevCp = 0;
prevAdvanceFP = 0;
continue;
}
@@ -46,11 +44,11 @@ void EpdFont::getTextBounds(const char* string, const int startX, const int star
}
if (!isCombining && prevCp != 0) {
const auto kernFP = getKerning(prevCp, cp); // 4.4 fixed-point kern
lastBaseX += fp4::toPixel(prevAdvanceFP + kernFP);
cursorXFP += getKerning(prevCp, cp); // 4.4 fixed-point kern
}
const int glyphBaseX = isCombining ? (lastBaseX + fp4::toPixel(lastBaseAdvanceFP / 2)) : lastBaseX;
const int cursorXPixels = fp4::toPixel(cursorXFP); // snap 12.4 fixed-point to nearest pixel
const int glyphBaseX = isCombining ? (lastBaseX + fp4::toPixel(lastBaseAdvanceFP / 2)) : cursorXPixels;
const int glyphBaseY = startY - raiseBy;
*minX = std::min(*minX, glyphBaseX + glyph->left);
@@ -59,9 +57,10 @@ void EpdFont::getTextBounds(const char* string, const int startX, const int star
*maxY = std::max(*maxY, glyphBaseY + glyph->top);
if (!isCombining) {
lastBaseX = cursorXPixels;
lastBaseAdvanceFP = glyph->advanceX; // 12.4 fixed-point
lastBaseTop = glyph->top;
prevAdvanceFP = lastBaseAdvanceFP;
cursorXFP += glyph->advanceX; // 12.4 fixed-point advance
prevCp = cp;
}
}
+4 -6
View File
@@ -7,12 +7,10 @@
/// Font metrics use "fixed-point 4" (4 fractional bits, i.e. 1/16-pixel
/// resolution). Both the 12.4 glyph advances (uint16_t) and the 4.4 kern
/// values (int8_t) share the same 4 fractional bits, so they can be freely
/// added before snapping to whole pixels.
///
/// Rendering and measurement use "differential rounding": each glyph step
/// (previous advance + current kern) is combined in fixed-point and snapped
/// to a pixel as one unit. This guarantees identical character pairs always
/// produce the same pixel spacing, regardless of position on the line.
/// added into a single int32_t accumulator during text layout. The
/// accumulator is snapped to the nearest whole pixel only at render time,
/// which avoids the per-character rounding errors that plagued integer-only
/// layout.
///
/// The helpers below eliminate the raw bit-shifts that would otherwise be
/// scattered across every layout / measurement call site.
+1 -1
View File
@@ -10,7 +10,7 @@
#include "parsers/ChapterHtmlSlimParser.h"
namespace {
constexpr uint8_t SECTION_FILE_VERSION = 19;
constexpr uint8_t SECTION_FILE_VERSION = 18;
constexpr uint32_t HEADER_SIZE = sizeof(uint8_t) + sizeof(int) + sizeof(float) + sizeof(bool) + sizeof(uint8_t) +
sizeof(uint16_t) + sizeof(uint16_t) + sizeof(uint16_t) + sizeof(bool) + sizeof(bool) +
sizeof(uint8_t) + sizeof(uint32_t) + sizeof(uint32_t);
+8 -13
View File
@@ -15,7 +15,6 @@
struct DirectPixelWriter {
uint8_t* fb;
GfxRenderer::RenderMode mode;
uint16_t displayWidthBytes; // Runtime framebuffer stride (X4: 100, X3: 99)
// Orientation is collapsed into a linear transform:
// phyX = phyXBase + x * phyXStepX + y * phyXStepY
@@ -30,33 +29,29 @@ struct DirectPixelWriter {
void init(GfxRenderer& renderer) {
fb = renderer.getFrameBuffer();
mode = renderer.getRenderMode();
displayWidthBytes = renderer.getDisplayWidthBytes();
const int phyW = renderer.getDisplayWidth();
const int phyH = renderer.getDisplayHeight();
switch (renderer.getOrientation()) {
case GfxRenderer::Portrait:
// phyX = y, phyY = (phyH-1) - x
// phyX = y, phyY = (DISPLAY_HEIGHT-1) - x
phyXBase = 0;
phyYBase = phyH - 1;
phyYBase = HalDisplay::DISPLAY_HEIGHT - 1;
phyXStepX = 0;
phyYStepX = -1;
phyXStepY = 1;
phyYStepY = 0;
break;
case GfxRenderer::LandscapeClockwise:
// phyX = (phyW-1) - x, phyY = (phyH-1) - y
phyXBase = phyW - 1;
phyYBase = phyH - 1;
// phyX = (DISPLAY_WIDTH-1) - x, phyY = (DISPLAY_HEIGHT-1) - y
phyXBase = HalDisplay::DISPLAY_WIDTH - 1;
phyYBase = HalDisplay::DISPLAY_HEIGHT - 1;
phyXStepX = -1;
phyYStepX = 0;
phyXStepY = 0;
phyYStepY = -1;
break;
case GfxRenderer::PortraitInverted:
// phyX = (phyW-1) - y, phyY = x
phyXBase = phyW - 1;
// phyX = (DISPLAY_WIDTH-1) - y, phyY = x
phyXBase = HalDisplay::DISPLAY_WIDTH - 1;
phyYBase = 0;
phyXStepX = 0;
phyYStepX = 1;
@@ -120,7 +115,7 @@ struct DirectPixelWriter {
const int phyX = rowPhyXBase + logicalX * phyXStepX;
const int phyY = rowPhyYBase + logicalX * phyYStepX;
const uint16_t byteIndex = phyY * displayWidthBytes + (phyX >> 3);
const uint16_t byteIndex = phyY * HalDisplay::DISPLAY_WIDTH_BYTES + (phyX >> 3);
const uint8_t bitMask = 1 << (7 - (phyX & 7));
if (state) {
@@ -19,25 +19,38 @@ namespace {
// The draw callback receives this via pDraw->pUser (set by setUserPointer()).
// The file I/O callbacks receive the FsFile* via pFile->fHandle (set by jpegOpen()).
struct JpegContext {
GfxRenderer* renderer{nullptr};
const RenderConfig* config{nullptr};
int screenWidth{0};
int screenHeight{0};
GfxRenderer* renderer;
const RenderConfig* config;
int screenWidth;
int screenHeight;
// Source dimensions after JPEGDEC's built-in scaling
int scaledSrcWidth{0};
int scaledSrcHeight{0};
int scaledSrcWidth;
int scaledSrcHeight;
// Final output dimensions
int dstWidth{0};
int dstHeight{0};
int dstWidth;
int dstHeight;
// Fine scale in 16.16 fixed-point (ESP32-C3 has no FPU)
int32_t fineScaleFP{1 << 16}; // src -> dst mapping
int32_t invScaleFP{1 << 16}; // dst -> src mapping
int32_t fineScaleFP; // src -> dst mapping
int32_t invScaleFP; // dst -> src mapping
PixelCache cache;
bool caching{false};
bool caching;
JpegContext()
: renderer(nullptr),
config(nullptr),
screenWidth(0),
screenHeight(0),
scaledSrcWidth(0),
scaledSrcHeight(0),
dstWidth(0),
dstHeight(0),
fineScaleFP(1 << 16),
invScaleFP(1 << 16),
caching(false) {}
};
// File I/O callbacks use pFile->fHandle to access the FsFile*,
@@ -19,23 +19,37 @@ namespace {
// The draw callback receives this via pDraw->pUser (set by png.decode()).
// The file I/O callbacks receive the FsFile* via pFile->fHandle (set by pngOpen()).
struct PngContext {
GfxRenderer* renderer{nullptr};
const RenderConfig* config{nullptr};
int screenWidth{0};
int screenHeight{0};
GfxRenderer* renderer;
const RenderConfig* config;
int screenWidth;
int screenHeight;
// Scaling state
float scale{1.f};
int srcWidth{0};
int srcHeight{0};
int dstWidth{0};
int dstHeight{0};
int lastDstY{-1}; // Track last rendered destination Y to avoid duplicates
float scale;
int srcWidth;
int srcHeight;
int dstWidth;
int dstHeight;
int lastDstY; // Track last rendered destination Y to avoid duplicates
PixelCache cache;
bool caching{false};
bool caching;
uint8_t* grayLineBuffer{nullptr};
uint8_t* grayLineBuffer;
PngContext()
: renderer(nullptr),
config(nullptr),
screenWidth(0),
screenHeight(0),
scale(1.0f),
srcWidth(0),
srcHeight(0),
dstWidth(0),
dstHeight(0),
lastDstY(-1),
caching(false),
grayLineBuffer(nullptr) {}
};
// File I/O callbacks use pFile->fHandle to access the FsFile*,
+2 -23
View File
@@ -12,24 +12,11 @@ const LanguageHyphenator* Hyphenator::cachedHyphenator_ = nullptr;
namespace {
// Normalize ISO 639-2 (three-letter) codes to ISO 639-1 (two-letter) codes used by the
// hyphenation registry. EPUBs may use either form in their dc:language metadata (e.g.
// "eng" instead of "en"). Both the bibliographic ("fre"/"ger") and terminological
// ("fra"/"deu") ISO 639-2 variants are mapped.
struct Iso639Mapping {
const char* iso639_2;
const char* iso639_1;
};
static constexpr Iso639Mapping kIso639Mappings[] = {
{"eng", "en"}, {"fra", "fr"}, {"fre", "fr"}, {"deu", "de"}, {"ger", "de"},
{"rus", "ru"}, {"spa", "es"}, {"ita", "it"}, {"ukr", "uk"},
};
// Maps a BCP-47 or ISO 639-2 language tag to a language-specific hyphenator.
// Maps a BCP-47 language tag to a language-specific hyphenator.
const LanguageHyphenator* hyphenatorForLanguage(const std::string& langTag) {
if (langTag.empty()) return nullptr;
// Extract primary subtag and normalize to lowercase (e.g., "en-US" -> "en", "ENG" -> "en").
// Extract primary subtag and normalize to lowercase (e.g., "en-US" -> "en").
std::string primary;
primary.reserve(langTag.size());
for (char c : langTag) {
@@ -39,14 +26,6 @@ const LanguageHyphenator* hyphenatorForLanguage(const std::string& langTag) {
}
if (primary.empty()) return nullptr;
// Normalize ISO 639-2 three-letter codes to two-letter equivalents.
for (const auto& mapping : kIso639Mappings) {
if (primary == mapping.iso639_2) {
primary = mapping.iso639_1;
break;
}
}
return getLanguageHyphenatorForPrimaryTag(primary);
}
+25 -14
View File
@@ -339,18 +339,29 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char*
const bool hasCssHeight = imgStyle.hasImageHeight();
const bool hasCssWidth = imgStyle.hasImageWidth();
// Compute effective container width for percentage-based image sizes.
// If the image is inside a block with horizontal margins/padding (e.g.
// <div style="margin: 1em 40%">), percentage widths like width:100%
// should resolve against the container width, not the full viewport.
int containerWidth = self->viewportWidth;
if (self->currentTextBlock) {
const int inset = self->currentTextBlock->getBlockStyle().totalHorizontalInset();
if (inset > 0 && inset < self->viewportWidth) {
containerWidth = self->viewportWidth - inset;
}
}
if (hasCssHeight && hasCssWidth && dims.width > 0 && dims.height > 0) {
// Both CSS height and width set: resolve both, then clamp to viewport preserving requested ratio
displayHeight = static_cast<int>(
imgStyle.imageHeight.toPixels(emSize, static_cast<float>(self->viewportHeight)) + 0.5f);
displayWidth = static_cast<int>(
imgStyle.imageWidth.toPixels(emSize, static_cast<float>(self->viewportWidth)) + 0.5f);
displayWidth =
static_cast<int>(imgStyle.imageWidth.toPixels(emSize, static_cast<float>(containerWidth)) + 0.5f);
if (displayHeight < 1) displayHeight = 1;
if (displayWidth < 1) displayWidth = 1;
if (displayWidth > self->viewportWidth || displayHeight > self->viewportHeight) {
float scaleX = (displayWidth > self->viewportWidth)
? static_cast<float>(self->viewportWidth) / displayWidth
: 1.0f;
if (displayWidth > containerWidth || displayHeight > self->viewportHeight) {
float scaleX =
(displayWidth > containerWidth) ? static_cast<float>(containerWidth) / displayWidth : 1.0f;
float scaleY = (displayHeight > self->viewportHeight)
? static_cast<float>(self->viewportHeight) / displayHeight
: 1.0f;
@@ -375,8 +386,8 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char*
static_cast<int>(displayHeight * (static_cast<float>(dims.width) / dims.height) + 0.5f);
if (displayWidth < 1) displayWidth = 1;
}
if (displayWidth > self->viewportWidth) {
displayWidth = self->viewportWidth;
if (displayWidth > containerWidth) {
displayWidth = containerWidth;
// Rescale height to preserve aspect ratio when width is clamped
displayHeight =
static_cast<int>(displayWidth * (static_cast<float>(dims.height) / dims.width) + 0.5f);
@@ -385,10 +396,10 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char*
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) {
// Use CSS width (resolve % against viewport width) and derive height from aspect ratio
displayWidth = static_cast<int>(
imgStyle.imageWidth.toPixels(emSize, static_cast<float>(self->viewportWidth)) + 0.5f);
if (displayWidth > self->viewportWidth) displayWidth = self->viewportWidth;
// Use CSS width (resolve % against container width) and derive height from aspect ratio
displayWidth =
static_cast<int>(imgStyle.imageWidth.toPixels(emSize, static_cast<float>(containerWidth)) + 0.5f);
if (displayWidth > containerWidth) displayWidth = containerWidth;
if (displayWidth < 1) displayWidth = 1;
displayHeight =
static_cast<int>(displayWidth * (static_cast<float>(dims.height) / dims.width) + 0.5f);
@@ -402,8 +413,8 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char*
if (displayHeight < 1) displayHeight = 1;
LOG_DBG("EHP", "Display size from CSS width: %dx%d", displayWidth, displayHeight);
} else {
// Scale to fit viewport while maintaining aspect ratio
int maxWidth = self->viewportWidth;
// Scale to fit container while maintaining aspect ratio
int maxWidth = containerWidth;
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;
-8
View File
@@ -78,12 +78,4 @@ bool hasTxtExtension(std::string_view fileName) { return checkFileExtension(file
bool hasMarkdownExtension(std::string_view fileName) { return checkFileExtension(fileName, ".md"); }
std::string extractFolderPath(const std::string& filePath) {
const auto lastSlash = filePath.find_last_of('/');
if (lastSlash == std::string::npos || lastSlash == 0) {
return "/";
}
return filePath.substr(0, lastSlash);
}
} // namespace FsHelpers
-2
View File
@@ -55,6 +55,4 @@ inline bool hasTxtExtension(const String& fileName) {
// Check for .md extension (case-insensitive)
bool hasMarkdownExtension(std::string_view fileName);
std::string extractFolderPath(const std::string& filePath);
} // namespace FsHelpers
+6 -6
View File
@@ -108,7 +108,7 @@ uint8_t quantize1bit(int gray, int x, int y) {
return (gray >= adjustedThreshold) ? 1 : 0;
}
void createBmpHeader(BmpHeader* bmpHeader, int width, int height, BmpRowOrder rowOrder) {
void createBmpHeader(BmpHeader* bmpHeader, int width, int height) {
if (!bmpHeader) return;
// Zero out the memory to ensure no garbage data if called on uninitialized stack memory
@@ -126,15 +126,15 @@ void createBmpHeader(BmpHeader* bmpHeader, int width, int height, BmpRowOrder ro
bmpHeader->infoHeader.biSize = sizeof(bmpHeader->infoHeader);
bmpHeader->infoHeader.biWidth = width;
bmpHeader->infoHeader.biHeight = (rowOrder == BmpRowOrder::TopDown) ? -height : height;
bmpHeader->infoHeader.biHeight = height;
bmpHeader->infoHeader.biPlanes = 1;
bmpHeader->infoHeader.biBitCount = 1;
bmpHeader->infoHeader.biCompression = 0;
bmpHeader->infoHeader.biSizeImage = imageSize;
bmpHeader->infoHeader.biXPelsPerMeter = 2835; // 72 DPI
bmpHeader->infoHeader.biYPelsPerMeter = 2835; // 72 DPI
bmpHeader->infoHeader.biClrUsed = 2;
bmpHeader->infoHeader.biClrImportant = 2;
bmpHeader->infoHeader.biXPelsPerMeter = 0;
bmpHeader->infoHeader.biYPelsPerMeter = 0;
bmpHeader->infoHeader.biClrUsed = 0;
bmpHeader->infoHeader.biClrImportant = 0;
// Color 0 (black)
bmpHeader->colors[0].rgbBlue = 0;
+1 -3
View File
@@ -11,10 +11,8 @@ uint8_t quantizeSimple(int gray);
uint8_t quantize1bit(int gray, int x, int y);
int adjustPixel(int gray);
enum class BmpRowOrder { BottomUp, TopDown };
// Populates a 1-bit BMP header in the provided memory.
void createBmpHeader(BmpHeader* bmpHeader, int width, int height, BmpRowOrder rowOrder);
void createBmpHeader(BmpHeader* bmpHeader, int width, int height);
// 1-bit Atkinson dithering - better quality than noise dithering for thumbnails
// Error distribution pattern (same as 2-bit but quantizes to 2 levels):
+20 -31
View File
@@ -131,9 +131,9 @@ static void renderCharImpl(const GfxRenderer& renderer, GfxRenderer::RenderMode
if (renderMode == GfxRenderer::BW && bmpVal < 3) {
// Black (also paints over the grays in BW mode)
renderer.drawPixel(screenX, screenY, pixelState);
} else if (renderMode == GfxRenderer::GRAYSCALE_MSB && (bmpVal == 1 || bmpVal == 2)) {
} else if (renderMode == GfxRenderer::GRAYSCALE_MSB && (bmpVal == 1 || (gpio.deviceIsX4() && bmpVal == 2))) {
// Light gray (also mark the MSB if it's going to be a dark gray too)
// Dedicated X3 gray LUTs now provide proper 4-level gray on both devices
// X3 AA tuning: keep only the darker antialias level to avoid washed text
// We have to flag pixels in reverse for the gray buffers, as 0 leave alone, 1 update
renderer.drawPixel(screenX, screenY, false);
} else if (renderMode == GfxRenderer::GRAYSCALE_LSB && bmpVal == 1) {
@@ -215,10 +215,10 @@ void GfxRenderer::drawCenteredText(const int fontId, const int y, const char* te
void GfxRenderer::drawText(const int fontId, const int x, const int y, const char* text, const bool black,
const EpdFontFamily::Style style) const {
const int yPos = y + getFontAscenderSize(fontId);
int32_t xPosFP = fp4::fromPixel(x); // 12.4 fixed-point accumulator
int lastBaseX = x;
int lastBaseAdvanceFP = 0; // 12.4 fixed-point
int lastBaseTop = 0;
int32_t prevAdvanceFP = 0; // 12.4 fixed-point: prev glyph's advance + next kern for snap
// cannot draw a NULL / empty string
if (text == nullptr || *text == '\0') {
@@ -258,22 +258,19 @@ void GfxRenderer::drawText(const int fontId, const int x, const int y, const cha
}
cp = font.applyLigatures(cp, text, style);
const int kernFP = (prevCp != 0) ? font.getKerning(prevCp, cp, style) : 0; // 4.4 fixed-point kern
xPosFP += kernFP;
// Differential rounding: snap (previous advance + current kern) as one unit so
// identical character pairs always produce the same pixel step regardless of
// where they fall on the line.
if (prevCp != 0) {
const auto kernFP = font.getKerning(prevCp, cp, style); // 4.4 fixed-point kern
lastBaseX += fp4::toPixel(prevAdvanceFP + kernFP); // snap 12.4 fixed-point to nearest pixel
}
lastBaseX = fp4::toPixel(xPosFP); // snap 12.4 fixed-point to nearest pixel
const EpdGlyph* glyph = font.getGlyph(cp, style);
lastBaseAdvanceFP = glyph ? glyph->advanceX : 0;
lastBaseTop = glyph ? glyph->top : 0;
prevAdvanceFP = lastBaseAdvanceFP;
renderCharImpl<TextRotation::None>(*this, renderMode, font, cp, lastBaseX, yPos, black, style);
if (glyph) {
xPosFP += glyph->advanceX; // 12.4 fixed-point advance
}
prevCp = cp;
}
}
@@ -686,7 +683,7 @@ void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, con
if (renderMode == BW && val < 3) {
drawPixel(screenX, screenY);
} else if (renderMode == GRAYSCALE_MSB && (val == 1 || val == 2)) {
} else if (renderMode == GRAYSCALE_MSB && (val == 1 || (gpio.deviceIsX4() && val == 2))) {
drawPixel(screenX, screenY, false);
} else if (renderMode == GRAYSCALE_LSB && val == 1) {
drawPixel(screenX, screenY, false);
@@ -1010,28 +1007,21 @@ int GfxRenderer::getTextAdvanceX(const int fontId, const char* text, EpdFontFami
uint32_t cp;
uint32_t prevCp = 0;
int widthPx = 0;
int32_t prevAdvanceFP = 0; // 12.4 fixed-point: prev glyph's advance + next kern for snap
int32_t widthFP = 0; // 12.4 fixed-point accumulator
const auto& font = fontIt->second;
while ((cp = utf8NextCodepoint(reinterpret_cast<const uint8_t**>(&text)))) {
if (utf8IsCombiningMark(cp)) {
continue;
}
cp = font.applyLigatures(cp, text, style);
// Differential rounding: snap (previous advance + current kern) together,
// matching drawText so measurement and rendering agree exactly.
if (prevCp != 0) {
const auto kernFP = font.getKerning(prevCp, cp, style); // 4.4 fixed-point kern
widthPx += fp4::toPixel(prevAdvanceFP + kernFP); // snap 12.4 fixed-point to nearest pixel
widthFP += font.getKerning(prevCp, cp, style); // 4.4 fixed-point kern
}
const EpdGlyph* glyph = font.getGlyph(cp, style);
prevAdvanceFP = glyph ? glyph->advanceX : 0;
if (glyph) widthFP += glyph->advanceX; // 12.4 fixed-point advance
prevCp = cp;
}
widthPx += fp4::toPixel(prevAdvanceFP); // final glyph's advance
return widthPx;
return fp4::toPixel(widthFP); // snap 12.4 fixed-point to nearest pixel
}
int GfxRenderer::getFontAscenderSize(const int fontId) const {
@@ -1078,10 +1068,10 @@ void GfxRenderer::drawTextRotated90CW(const int fontId, const int x, const int y
const auto& font = fontIt->second;
int32_t yPosFP = fp4::fromPixel(y); // 12.4 fixed-point accumulator
int lastBaseY = y;
int lastBaseAdvanceFP = 0; // 12.4 fixed-point
int lastBaseTop = 0;
int32_t prevAdvanceFP = 0; // 12.4 fixed-point: prev glyph's advance + next kern for snap
constexpr int MIN_COMBINING_GAP_PX = 1;
uint32_t cp;
@@ -1104,21 +1094,20 @@ void GfxRenderer::drawTextRotated90CW(const int fontId, const int x, const int y
}
cp = font.applyLigatures(cp, text, style);
// Differential rounding: snap (previous advance + current kern) as one unit,
// subtracting for the rotated coordinate direction.
if (prevCp != 0) {
const auto kernFP = font.getKerning(prevCp, cp, style); // 4.4 fixed-point kern
lastBaseY -= fp4::toPixel(prevAdvanceFP + kernFP); // snap 12.4 fixed-point to nearest pixel
yPosFP -= font.getKerning(prevCp, cp, style); // 4.4 fixed-point kern (subtract for rotated)
}
lastBaseY = fp4::toPixel(yPosFP); // snap 12.4 fixed-point to nearest pixel
const EpdGlyph* glyph = font.getGlyph(cp, style);
lastBaseAdvanceFP = glyph ? glyph->advanceX : 0; // 12.4 fixed-point
lastBaseTop = glyph ? glyph->top : 0;
prevAdvanceFP = lastBaseAdvanceFP;
renderCharImpl<TextRotation::Rotated90CW>(*this, renderMode, font, cp, x, lastBaseY, black, style);
if (glyph) {
yPosFP -= glyph->advanceX; // 12.4 fixed-point advance (subtract for rotated)
}
prevCp = cp;
}
}
-3
View File
@@ -157,7 +157,4 @@ class GfxRenderer {
// Low level functions
uint8_t* getFrameBuffer() const;
size_t getBufferSize() const;
uint16_t getDisplayWidth() const { return panelWidth; }
uint16_t getDisplayHeight() const { return panelHeight; }
uint16_t getDisplayWidthBytes() const { return panelWidthBytes; }
};
+1 -1
View File
@@ -45,7 +45,7 @@ STR_OPEN_URL_HINT: "Адкрыйце гэты адрас у браўзеры"
STR_OR_HTTP_PREFIX: "або http://"
STR_SCAN_QR_HINT: "або адсканіруйце QR-код:"
STR_CALIBRE_WIRELESS: "Calibre па Wi-Fi"
STR_CALIBRE_WEB_URL: "OPDS URL"
STR_CALIBRE_WEB_URL: "Вэб-адрас Calibre"
STR_NETWORK_LEGEND: "* = Абаронена | + = Захавана"
STR_MAC_ADDRESS: "MAC-адрас:"
STR_CHECKING_WIFI: "Праверка Wi-Fi..."
+1 -1
View File
@@ -45,7 +45,7 @@ STR_OPEN_URL_HINT: "Obriu aquest URL al navegador"
STR_OR_HTTP_PREFIX: "o http://"
STR_SCAN_QR_HINT: "o escanegeu el codi QR amb el telèfon:"
STR_CALIBRE_WIRELESS: "Calibre sense fils"
STR_CALIBRE_WEB_URL: "OPDS URL"
STR_CALIBRE_WEB_URL: "URL web del Calibre"
STR_NETWORK_LEGEND: "* = Encriptat | + = Desat"
STR_MAC_ADDRESS: "Adreça MAC:"
STR_CHECKING_WIFI: "S'està comprovant el WiFi..."
+1 -1
View File
@@ -45,7 +45,7 @@ STR_OPEN_URL_HINT: "Otevřete tuto URL ve svém prohlížeči"
STR_OR_HTTP_PREFIX: "nebo http://"
STR_SCAN_QR_HINT: "nebo naskenujte QR kód telefonem:"
STR_CALIBRE_WIRELESS: "Calibre Wireless"
STR_CALIBRE_WEB_URL: "OPDS URL"
STR_CALIBRE_WEB_URL: "URL webu Calibre"
STR_NETWORK_LEGEND: "* = Šifrováno | + = Uloženo"
STR_MAC_ADDRESS: "MAC adresa:"
STR_CHECKING_WIFI: "Kontrola WiFi..."
+1 -1
View File
@@ -45,7 +45,7 @@ STR_OPEN_URL_HINT: "Åbn denne URL i din browser"
STR_OR_HTTP_PREFIX: "eller http://"
STR_SCAN_QR_HINT: "eller scan QR-kode med din telefon:"
STR_CALIBRE_WIRELESS: "Calibre Wireless"
STR_CALIBRE_WEB_URL: "OPDS URL"
STR_CALIBRE_WEB_URL: "Calibre Web URL"
STR_NETWORK_LEGEND: "* = Krypteret | + = Gemt"
STR_MAC_ADDRESS: "MAC-adresse:"
STR_CHECKING_WIFI: "Tjekker WiFi..."
+1 -1
View File
@@ -45,7 +45,7 @@ STR_OPEN_URL_HINT: "Open deze URL in je browser"
STR_OR_HTTP_PREFIX: "of http://"
STR_SCAN_QR_HINT: "of scan de QR-code met je telefoon:"
STR_CALIBRE_WIRELESS: "Calibre Wireless"
STR_CALIBRE_WEB_URL: "OPDS URL"
STR_CALIBRE_WEB_URL: "Calibre Web URL"
STR_NETWORK_LEGEND: "* = Beveiligd | + = Opgeslagen"
STR_MAC_ADDRESS: "MAC-adres:"
STR_CHECKING_WIFI: "Wifi controleren..."
+1 -4
View File
@@ -45,7 +45,7 @@ STR_OPEN_URL_HINT: "Open this URL in your browser"
STR_OR_HTTP_PREFIX: "or http://"
STR_SCAN_QR_HINT: "or scan QR code with your phone:"
STR_CALIBRE_WIRELESS: "Calibre Wireless"
STR_CALIBRE_WEB_URL: "OPDS URL"
STR_CALIBRE_WEB_URL: "Calibre Web URL"
STR_NETWORK_LEGEND: "* = Encrypted | + = Saved"
STR_MAC_ADDRESS: "MAC address:"
STR_CHECKING_WIFI: "Checking WiFi..."
@@ -175,8 +175,6 @@ STR_UNNAMED: "Unnamed"
STR_NO_SERVER_URL: "No server URL configured"
STR_FETCH_FEED_FAILED: "Failed to fetch feed"
STR_PARSE_FEED_FAILED: "Failed to parse feed"
STR_NEXT_PAGE: "Next Page »"
STR_PREV_PAGE: "« Previous Page"
STR_NETWORK_PREFIX: "Network: "
STR_IP_ADDRESS_PREFIX: "IP Address: "
STR_ERROR_GENERAL_FAILURE: "Error: General failure"
@@ -231,7 +229,6 @@ STR_THEME_LYRA_EXTENDED: "Lyra Extended"
STR_SUNLIGHT_FADING_FIX: "Sunlight Fading Fix"
STR_REMAP_FRONT_BUTTONS: "Remap Front Buttons"
STR_OPDS_BROWSER: "OPDS Browser"
STR_SEARCH: "Search"
STR_COVER_CUSTOM: "Cover + Custom"
STR_MENU_RECENT_BOOKS: "Recent Books"
STR_NO_RECENT_BOOKS: "No recent books"

Some files were not shown because too many files have changed in this diff Show More