| 1599 | } |
| 1600 | |
| 1601 | int GfxRenderer::getTextAdvanceX(const int fontId, const char* text, EpdFontFamily::Style style) const { |
| 1602 | // Advance table fast-path for SD card fonts during layout. |
| 1603 | // No kerning/ligature lookup — consistent with previous metadataOnly behavior |
| 1604 | // where kern/lig data was not loaded. |
| 1605 | auto sdIt = sdCardFonts_.find(fontId); |
| 1606 | if (sdIt != sdCardFonts_.end() && sdIt->second->hasAdvanceTable()) { |
| 1607 | int32_t widthFP = 0; |
| 1608 | const bool isSupSub = (style & (EpdFontFamily::SUP | EpdFontFamily::SUB)) != 0; |
| 1609 | const uint8_t styleIdx = resolveSdCardStyle(*sdIt->second, style); |
| 1610 | const auto fontIt = fontMap.find(fontId); |
| 1611 | if (fontIt == fontMap.end()) { |
| 1612 | LOG_ERR("GFX", "Font %d not found", fontId); |
| 1613 | return 0; |
| 1614 | } |
| 1615 | const auto& font = fontIt->second; |
| 1616 | while (uint32_t cp = utf8NextCodepoint(reinterpret_cast<const uint8_t**>(&text))) { |
| 1617 | int32_t advFP = sdIt->second->getAdvance(cp, styleIdx); |
| 1618 | if (advFP == 0 && !utf8IsCombiningMark(cp)) { |
| 1619 | const EpdGlyph* glyph = font.getGlyph(cp, style); |
| 1620 | advFP = glyph ? glyph->advanceX : 0; |
| 1621 | } |
| 1622 | widthFP += isSupSub ? (advFP + 1) / 2 : advFP; |
| 1623 | } |
| 1624 | return fp4::toPixel(widthFP); |
| 1625 | } |
| 1626 | |
| 1627 | const auto fontIt = fontMap.find(fontId); |
| 1628 | if (fontIt == fontMap.end()) { |
| 1629 | LOG_ERR("GFX", "Font %d not found", fontId); |
| 1630 | return 0; |
| 1631 | } |
| 1632 | |
| 1633 | uint32_t cp; |
| 1634 | uint32_t prevCp = 0; |
| 1635 | int widthPx = 0; |
| 1636 | int32_t prevAdvanceFP = 0; // 12.4 fixed-point: prev glyph's advance + next kern for snap |
| 1637 | const auto& font = fontIt->second; |
| 1638 | while ((cp = utf8NextCodepoint(reinterpret_cast<const uint8_t**>(&text)))) { |
| 1639 | if (utf8IsCombiningMark(cp)) { |
| 1640 | continue; |
| 1641 | } |
| 1642 | cp = font.applyLigatures(cp, text, style); |
| 1643 | |
| 1644 | // Differential rounding: snap (previous advance + current kern) together, |
| 1645 | // matching drawText so measurement and rendering agree exactly. |
| 1646 | if (prevCp != 0) { |
| 1647 | const auto kernFP = font.getKerning(prevCp, cp, style); // 4.4 fixed-point kern |
| 1648 | widthPx += fp4::toPixel(prevAdvanceFP + kernFP); // snap 12.4 fixed-point to nearest pixel |
| 1649 | } |
| 1650 | |
| 1651 | const EpdGlyph* glyph = font.getGlyph(cp, style); |
| 1652 | prevAdvanceFP = glyph ? glyph->advanceX : 0; |
| 1653 | if ((style & (EpdFontFamily::SUP | EpdFontFamily::SUB)) != 0) { |
| 1654 | prevAdvanceFP = (prevAdvanceFP + 1) / 2; |
| 1655 | } |
| 1656 | prevCp = cp; |
| 1657 | } |
| 1658 | widthPx += fp4::toPixel(prevAdvanceFP); // final glyph's advance |
no test coverage detected