diff --git a/lib/EpdFont/EpdFont.cpp b/lib/EpdFont/EpdFont.cpp index c80a8573..9aedbdb2 100644 --- a/lib/EpdFont/EpdFont.cpp +++ b/lib/EpdFont/EpdFont.cpp @@ -17,9 +17,9 @@ void EpdFont::getTextBounds(const char* string, const int startX, const int star int32_t cursorXFP = fp4::fromPixel(startX); // 12.4 fixed-point accumulator int lastBaseX = startX; - int lastBaseAdvanceFP = 0; // 12.4 fixed-point + int lastBaseLeft = 0; + int lastBaseWidth = 0; int lastBaseTop = 0; - constexpr int MIN_COMBINING_GAP_PX = 1; uint32_t cp; uint32_t prevCp = 0; while ((cp = utf8NextCodepoint(reinterpret_cast(&string)))) { @@ -35,20 +35,16 @@ void EpdFont::getTextBounds(const char* string, const int startX, const int star 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) { cursorXFP += getKerning(prevCp, cp); // 4.4 fixed-point kern } 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 glyphBaseX = + isCombining ? combiningMark::centerOver(lastBaseX, lastBaseLeft, lastBaseWidth, glyph->left, glyph->width) + : cursorXPixels; const int glyphBaseY = startY - raiseBy; *minX = std::min(*minX, glyphBaseX + glyph->left); @@ -58,7 +54,8 @@ void EpdFont::getTextBounds(const char* string, const int startX, const int star if (!isCombining) { lastBaseX = cursorXPixels; - lastBaseAdvanceFP = glyph->advanceX; // 12.4 fixed-point + lastBaseLeft = glyph->left; + lastBaseWidth = glyph->width; lastBaseTop = glyph->top; cursorXFP += glyph->advanceX; // 12.4 fixed-point advance prevCp = cp; diff --git a/lib/EpdFont/EpdFontData.h b/lib/EpdFont/EpdFontData.h index 9f3b691e..8ea5eaed 100644 --- a/lib/EpdFont/EpdFontData.h +++ b/lib/EpdFont/EpdFontData.h @@ -28,6 +28,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 f0fac300..97f0e696 100644 --- a/lib/GfxRenderer/GfxRenderer.cpp +++ b/lib/GfxRenderer/GfxRenderer.cpp @@ -753,7 +753,8 @@ void GfxRenderer::drawText(const int fontId, const int x, const int y, const cha 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 lastBaseLeft = 0; + int lastBaseWidth = 0; int lastBaseTop = 0; // cannot draw a NULL / empty string @@ -772,24 +773,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; } @@ -800,7 +794,8 @@ void GfxRenderer::drawText(const int fontId, const int x, const int y, const cha lastBaseX = fp4::toPixel(xPosFP); // snap 12.4 fixed-point to nearest pixel 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; renderCharImpl(*this, renderMode, font, cp, lastBaseX, yPos, black, style); @@ -1812,25 +1807,20 @@ void GfxRenderer::drawTextRotated90CW(const int fontId, const int x, const int y int32_t yPosFP = fp4::fromPixel(y); // 12.4 fixed-point accumulator int lastBaseY = y; - int lastBaseAdvanceFP = 0; // 12.4 fixed-point + int lastBaseLeft = 0; + int lastBaseWidth = 0; int lastBaseTop = 0; - 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; } @@ -1843,7 +1833,8 @@ void GfxRenderer::drawTextRotated90CW(const int fontId, const int x, const int y 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 + lastBaseLeft = glyph ? glyph->left : 0; + lastBaseWidth = glyph ? glyph->width : 0; lastBaseTop = glyph ? glyph->top : 0; renderCharImpl(*this, renderMode, font, cp, x, lastBaseY, black, style);