diff --git a/lib/EpdFont/EpdFont.cpp b/lib/EpdFont/EpdFont.cpp index bfe746eff..fbcc32990 100644 --- a/lib/EpdFont/EpdFont.cpp +++ b/lib/EpdFont/EpdFont.cpp @@ -16,10 +16,10 @@ void EpdFont::getTextBounds(const char* string, const int startX, const int star } int lastBaseX = startX; - int lastBaseAdvanceFP = 0; // 12.4 fixed-point + int lastBaseLeft = 0; + int lastBaseWidth = 0; 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; while ((cp = utf8NextCodepoint(reinterpret_cast(&string)))) { @@ -31,26 +31,29 @@ 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; + // Keep cursor movement stable when a base glyph is missing, but don't attach subsequent + // combining marks to stale base metrics. + if (!isCombining) { + lastBaseX += fp4::toPixel(prevAdvanceFP); // flush pending advance before resetting + prevCp = 0; + prevAdvanceFP = 0; + lastBaseLeft = 0; + lastBaseWidth = 0; + lastBaseTop = 0; + } continue; } - int raiseBy = 0; - if (isCombining) { - const int currentGap = glyph->top - glyph->height - lastBaseTop; - if (currentGap < MIN_COMBINING_GAP_PX) { - raiseBy = MIN_COMBINING_GAP_PX - currentGap; - } - } + const int raiseBy = isCombining ? combiningMark::raiseAboveBase(glyph->top, glyph->height, lastBaseTop) : 0; if (!isCombining && prevCp != 0) { const auto kernFP = getKerning(prevCp, cp); // 4.4 fixed-point kern lastBaseX += fp4::toPixel(prevAdvanceFP + kernFP); } - const int glyphBaseX = isCombining ? (lastBaseX + fp4::toPixel(lastBaseAdvanceFP / 2)) : lastBaseX; + const int glyphBaseX = + isCombining ? combiningMark::centerOver(lastBaseX, lastBaseLeft, lastBaseWidth, glyph->left, glyph->width) + : lastBaseX; const int glyphBaseY = startY - raiseBy; *minX = std::min(*minX, glyphBaseX + glyph->left); @@ -59,9 +62,10 @@ void EpdFont::getTextBounds(const char* string, const int startX, const int star *maxY = std::max(*maxY, glyphBaseY + glyph->top); if (!isCombining) { - lastBaseAdvanceFP = glyph->advanceX; // 12.4 fixed-point + lastBaseLeft = glyph->left; + lastBaseWidth = glyph->width; lastBaseTop = glyph->top; - prevAdvanceFP = lastBaseAdvanceFP; + prevAdvanceFP = glyph->advanceX; // 12.4 fixed-point prevCp = cp; } } diff --git a/lib/EpdFont/EpdFontData.h b/lib/EpdFont/EpdFontData.h index 0b70e57fd..380c5733d 100644 --- a/lib/EpdFont/EpdFontData.h +++ b/lib/EpdFont/EpdFontData.h @@ -30,6 +30,37 @@ constexpr int toPixel(int32_t fp) { return static_cast((fp + HALF) >> FRAC_ constexpr float toFloat(int32_t fp) { return fp / static_cast(1 << FRAC_BITS); } } // namespace fp4 +/// Helpers for positioning Unicode combining marks (U+0300 ff.) over a +/// preceding base glyph without GPOS anchor tables. +namespace combiningMark { + +constexpr int MIN_GAP_PX = 1; + +/// Compute the cursor-X at which to render a combining mark so its bitmap +/// is visually centered over the base glyph's bitmap. +constexpr int centerOver(int baseCursorPos, int baseLeft, int baseWidth, int markLeft, int markWidth) { + return baseCursorPos + baseLeft + baseWidth / 2 - markWidth / 2 - markLeft; +} + +/// Rotated-90CW variant of centerOver. In the rotated coordinate system +/// renderCharImpl uses (cursorY - left) instead of (cursorX + left), so +/// every left/width term inverts sign. +constexpr int centerOverRotated90CW(int baseCursorPos, int baseLeft, int baseWidth, int markLeft, int markWidth) { + return baseCursorPos - baseLeft - baseWidth / 2 + markWidth / 2 + markLeft; +} + +/// For combining marks that sit entirely above the baseline, compute how many +/// pixels to raise the mark so there is at least MIN_GAP_PX between its bottom +/// edge and the top of the base glyph. Returns 0 for marks that extend to or +/// below the baseline (e.g. cedilla, dot-below, ogonek). +constexpr int raiseAboveBase(int markTop, int markHeight, int baseTop) { + if (markTop - markHeight <= 0) return 0; + const int gap = markTop - markHeight - baseTop; + return (gap < MIN_GAP_PX) ? (MIN_GAP_PX - gap) : 0; +} + +} // namespace combiningMark + /// Fixed-point conventions used by EpdGlyph and EpdFontData: /// advanceX: 12.4 unsigned fixed-point in uint16_t (use fp4::toPixel) /// kernMatrix: 4.4 signed fixed-point in int8_t (use fp4::toPixel) diff --git a/lib/GfxRenderer/GfxRenderer.cpp b/lib/GfxRenderer/GfxRenderer.cpp index c1a4b869f..2d685e5fe 100644 --- a/lib/GfxRenderer/GfxRenderer.cpp +++ b/lib/GfxRenderer/GfxRenderer.cpp @@ -216,7 +216,8 @@ void GfxRenderer::drawText(const int fontId, const int x, const int y, const cha const EpdFontFamily::Style style) const { const int yPos = y + getFontAscenderSize(fontId); int lastBaseX = x; - int lastBaseAdvanceFP = 0; // 12.4 fixed-point + int lastBaseLeft = 0; + int lastBaseWidth = 0; int lastBaseTop = 0; int32_t prevAdvanceFP = 0; // 12.4 fixed-point: prev glyph's advance + next kern for snap @@ -236,24 +237,17 @@ void GfxRenderer::drawText(const int fontId, const int x, const int y, const cha return; } const auto& font = fontIt->second; - constexpr int MIN_COMBINING_GAP_PX = 1; uint32_t cp; uint32_t prevCp = 0; while ((cp = utf8NextCodepoint(reinterpret_cast(&text)))) { if (utf8IsCombiningMark(cp)) { const EpdGlyph* combiningGlyph = font.getGlyph(cp, style); - int raiseBy = 0; - if (combiningGlyph) { - const int currentGap = combiningGlyph->top - combiningGlyph->height - lastBaseTop; - if (currentGap < MIN_COMBINING_GAP_PX) { - raiseBy = MIN_COMBINING_GAP_PX - currentGap; - } - } - - const int combiningX = lastBaseX + fp4::toPixel(lastBaseAdvanceFP / 2); - const int combiningY = yPos - raiseBy; - renderCharImpl(*this, renderMode, font, cp, combiningX, combiningY, black, style); + if (!combiningGlyph) continue; + const int raiseBy = combiningMark::raiseAboveBase(combiningGlyph->top, combiningGlyph->height, lastBaseTop); + const int combiningX = combiningMark::centerOver(lastBaseX, lastBaseLeft, lastBaseWidth, combiningGlyph->left, + combiningGlyph->width); + renderCharImpl(*this, renderMode, font, cp, combiningX, yPos - raiseBy, black, style); continue; } @@ -269,9 +263,10 @@ void GfxRenderer::drawText(const int fontId, const int x, const int y, const cha const EpdGlyph* glyph = font.getGlyph(cp, style); - lastBaseAdvanceFP = glyph ? glyph->advanceX : 0; + lastBaseLeft = glyph ? glyph->left : 0; + lastBaseWidth = glyph ? glyph->width : 0; lastBaseTop = glyph ? glyph->top : 0; - prevAdvanceFP = lastBaseAdvanceFP; + prevAdvanceFP = glyph ? glyph->advanceX : 0; // 12.4 fixed-point renderCharImpl(*this, renderMode, font, cp, lastBaseX, yPos, black, style); prevCp = cp; @@ -1079,26 +1074,21 @@ void GfxRenderer::drawTextRotated90CW(const int fontId, const int x, const int y const auto& font = fontIt->second; int lastBaseY = y; - int lastBaseAdvanceFP = 0; // 12.4 fixed-point + int lastBaseLeft = 0; + int lastBaseWidth = 0; 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; while ((cp = utf8NextCodepoint(reinterpret_cast(&text)))) { if (utf8IsCombiningMark(cp)) { const EpdGlyph* combiningGlyph = font.getGlyph(cp, style); - int raiseBy = 0; - if (combiningGlyph) { - const int currentGap = combiningGlyph->top - combiningGlyph->height - lastBaseTop; - if (currentGap < MIN_COMBINING_GAP_PX) { - raiseBy = MIN_COMBINING_GAP_PX - currentGap; - } - } - + if (!combiningGlyph) continue; + const int raiseBy = combiningMark::raiseAboveBase(combiningGlyph->top, combiningGlyph->height, lastBaseTop); const int combiningX = x - raiseBy; - const int combiningY = lastBaseY - fp4::toPixel(lastBaseAdvanceFP / 2); + const int combiningY = combiningMark::centerOverRotated90CW(lastBaseY, lastBaseLeft, lastBaseWidth, + combiningGlyph->left, combiningGlyph->width); renderCharImpl(*this, renderMode, font, cp, combiningX, combiningY, black, style); continue; } @@ -1114,9 +1104,10 @@ void GfxRenderer::drawTextRotated90CW(const int fontId, const int x, const int y const EpdGlyph* glyph = font.getGlyph(cp, style); - lastBaseAdvanceFP = glyph ? glyph->advanceX : 0; // 12.4 fixed-point + lastBaseLeft = glyph ? glyph->left : 0; + lastBaseWidth = glyph ? glyph->width : 0; lastBaseTop = glyph ? glyph->top : 0; - prevAdvanceFP = lastBaseAdvanceFP; + prevAdvanceFP = glyph ? glyph->advanceX : 0; // 12.4 fixed-point renderCharImpl(*this, renderMode, font, cp, x, lastBaseY, black, style); prevCp = cp;