| 8 | #include <cstring> |
| 9 | |
| 10 | void TextBlock::render(const GfxRenderer& renderer, const int fontId, const int x, const int y) const { |
| 11 | // Focus annotations are optional: empty vectors mean no word in this block has a split. |
| 12 | // When present, they must be sized in lockstep with words[]. |
| 13 | const bool hasFocus = !wordFocusBoundary.empty(); |
| 14 | if (words.size() != wordXpos.size() || words.size() != wordStyles.size() || |
| 15 | (hasFocus && (words.size() != wordFocusBoundary.size() || words.size() != wordFocusSuffixX.size()))) { |
| 16 | LOG_ERR("TXB", "Render skipped: size mismatch (words=%u, xpos=%u, styles=%u, boundary=%u, suffixX=%u)\n", |
| 17 | (uint32_t)words.size(), (uint32_t)wordXpos.size(), (uint32_t)wordStyles.size(), |
| 18 | (uint32_t)wordFocusBoundary.size(), (uint32_t)wordFocusSuffixX.size()); |
| 19 | return; |
| 20 | } |
| 21 | |
| 22 | const bool scanning = renderer.isFontCacheScanning(); |
| 23 | const int ascender = renderer.getFontAscenderSize(fontId); |
| 24 | for (size_t i = 0; i < words.size(); i++) { |
| 25 | const int wordX = wordXpos[i] + x; |
| 26 | const EpdFontFamily::Style currentStyle = wordStyles[i]; |
| 27 | const auto baseDir = static_cast<BidiUtils::BidiBaseDir>( |
| 28 | BidiUtils::detectParagraphLevel(words[i].c_str(), blockStyle.isRtl ? 1 : 0)); |
| 29 | const uint8_t boundary = hasFocus ? wordFocusBoundary[i] : 0; |
| 30 | |
| 31 | // SUP/SUB shift the baseline passed to drawText; the glyph is also scaled 50% inside |
| 32 | // drawText, so these offsets are chosen relative to the full-size ascender: |
| 33 | // SUP: raise by 40% of ascender — sits clearly above the cap-height |
| 34 | // SUB: lower by 25% of ascender — descends below baseline without clashing with ascenders below |
| 35 | int wordY = y; |
| 36 | if ((currentStyle & EpdFontFamily::SUP) != 0) { |
| 37 | wordY -= ascender * 2 / 5; |
| 38 | } else if ((currentStyle & EpdFontFamily::SUB) != 0) { |
| 39 | wordY += ascender / 4; |
| 40 | } |
| 41 | |
| 42 | if (boundary > 0) { |
| 43 | // Focus split: draw bold prefix, then the regular suffix at a pre-computed x offset. |
| 44 | // The bold prefix is bounded to 9 codepoints by the clamp on targetBoldChars in |
| 45 | // ParsedText::addWord; 9 UTF-8 codepoints occupy at most 9 * 4 = 36 bytes, +1 for null = 37. |
| 46 | // suffixX is computed at cache-creation time to avoid font metric lookups at render time. |
| 47 | static constexpr size_t MAX_FOCUS_PREFIX_BYTES = 9 * 4 + 1; |
| 48 | char boldBuf[40]; |
| 49 | static_assert(sizeof(boldBuf) >= MAX_FOCUS_PREFIX_BYTES, |
| 50 | "boldBuf too small for max focus prefix (9 codepoints * 4 UTF-8 bytes + null)"); |
| 51 | const auto boldStyle = static_cast<EpdFontFamily::Style>(currentStyle | EpdFontFamily::BOLD); |
| 52 | const size_t boldLen = std::min<size_t>({static_cast<size_t>(boundary), words[i].size(), sizeof(boldBuf) - 1}); |
| 53 | memcpy(boldBuf, words[i].c_str(), boldLen); |
| 54 | boldBuf[boldLen] = '\0'; |
| 55 | renderer.drawText(fontId, wordX, wordY, boldBuf, true, boldStyle, baseDir); |
| 56 | const int suffixX = wordX + wordFocusSuffixX[i]; |
| 57 | renderer.drawText(fontId, suffixX, wordY, words[i].c_str() + boldLen, true, currentStyle, baseDir); |
| 58 | } else { |
| 59 | renderer.drawText(fontId, wordX, wordY, words[i].c_str(), true, currentStyle, baseDir); |
| 60 | } |
| 61 | |
| 62 | if (!scanning && (currentStyle & EpdFontFamily::UNDERLINE) != 0) { |
| 63 | const std::string& w = words[i]; |
| 64 | int underlineWidth = renderer.getTextWidth(fontId, w.c_str(), currentStyle, baseDir); |
| 65 | const int underlineY = wordY + ascender + 2; |
| 66 | |
| 67 | if ((currentStyle & (EpdFontFamily::SUP | EpdFontFamily::SUB)) != 0) { |
nothing calls this directly
no test coverage detected