| 5 | #include <algorithm> |
| 6 | |
| 7 | void EpdFont::getTextBounds(const char* string, const int startX, const int startY, int* minX, int* minY, int* maxX, |
| 8 | int* maxY) const { |
| 9 | *minX = startX; |
| 10 | *minY = startY; |
| 11 | *maxX = startX; |
| 12 | *maxY = startY; |
| 13 | |
| 14 | if (*string == '\0') { |
| 15 | return; |
| 16 | } |
| 17 | |
| 18 | int lastBaseX = startX; |
| 19 | int lastBaseLeft = 0; |
| 20 | int lastBaseWidth = 0; |
| 21 | int lastBaseTop = 0; |
| 22 | int32_t prevAdvanceFP = 0; // 12.4 fixed-point: prev glyph's advance + next kern for snap |
| 23 | uint32_t cp; |
| 24 | uint32_t prevCp = 0; |
| 25 | while ((cp = utf8NextCodepoint(reinterpret_cast<const uint8_t**>(&string)))) { |
| 26 | const bool isCombining = utf8IsCombiningMark(cp); |
| 27 | |
| 28 | if (!isCombining) { |
| 29 | cp = applyLigatures(cp, string); |
| 30 | } |
| 31 | |
| 32 | const EpdGlyph* glyph = getGlyph(cp); |
| 33 | if (!glyph) { |
| 34 | // Keep cursor movement stable when a base glyph is missing, but don't attach subsequent |
| 35 | // combining marks to stale base metrics. |
| 36 | if (!isCombining) { |
| 37 | lastBaseX += fp4::toPixel(prevAdvanceFP); // flush pending advance before resetting |
| 38 | prevCp = 0; |
| 39 | prevAdvanceFP = 0; |
| 40 | lastBaseLeft = 0; |
| 41 | lastBaseWidth = 0; |
| 42 | lastBaseTop = 0; |
| 43 | } |
| 44 | continue; |
| 45 | } |
| 46 | |
| 47 | const int raiseBy = isCombining ? combiningMark::raiseAboveBase(glyph->top, glyph->height, lastBaseTop) : 0; |
| 48 | |
| 49 | if (!isCombining && prevCp != 0) { |
| 50 | const auto kernFP = getKerning(prevCp, cp); // 4.4 fixed-point kern |
| 51 | lastBaseX += fp4::toPixel(prevAdvanceFP + kernFP); |
| 52 | } |
| 53 | |
| 54 | const int glyphBaseX = |
| 55 | isCombining ? combiningMark::centerOver(lastBaseX, lastBaseLeft, lastBaseWidth, glyph->left, glyph->width) |
| 56 | : lastBaseX; |
| 57 | const int glyphBaseY = startY - raiseBy; |
| 58 | |
| 59 | *minX = std::min(*minX, glyphBaseX + glyph->left); |
| 60 | *maxX = std::max(*maxX, glyphBaseX + glyph->left + glyph->width); |
| 61 | *minY = std::min(*minY, glyphBaseY + glyph->top - glyph->height); |
| 62 | *maxY = std::max(*maxY, glyphBaseY + glyph->top); |
| 63 | |
| 64 | if (!isCombining) { |
nothing calls this directly
no test coverage detected