| 386 | } |
| 387 | |
| 388 | void GfxRenderer::drawText(const int fontId, const int x, const int y, const char* text, const bool black, |
| 389 | const EpdFontFamily::Style style, const BidiUtils::BidiBaseDir baseDir) const { |
| 390 | // cannot draw a NULL / empty string |
| 391 | if (text == nullptr || *text == '\0') { |
| 392 | return; |
| 393 | } |
| 394 | |
| 395 | std::string visual; |
| 396 | const char* renderedText = resolveVisualText(text, visual, baseDir); |
| 397 | |
| 398 | const int yPos = y + getFontAscenderSize(fontId); |
| 399 | int lastBaseX = x; |
| 400 | int lastBaseLeft = 0; |
| 401 | int lastBaseWidth = 0; |
| 402 | int lastBaseTop = 0; |
| 403 | int32_t prevAdvanceFP = 0; // 12.4 fixed-point: prev glyph's advance + next kern for snap |
| 404 | |
| 405 | if (fontCacheManager_ && fontCacheManager_->isScanning()) { |
| 406 | fontCacheManager_->recordText(renderedText, fontId, style); |
| 407 | return; |
| 408 | } |
| 409 | |
| 410 | const auto fontIt = fontMap.find(fontId); |
| 411 | if (fontIt == fontMap.end()) { |
| 412 | LOG_ERR("GFX", "Font %d not found", fontId); |
| 413 | return; |
| 414 | } |
| 415 | const auto& font = fontIt->second; |
| 416 | |
| 417 | const char* textCursor = renderedText; |
| 418 | uint32_t cp; |
| 419 | uint32_t prevCp = 0; |
| 420 | while ((cp = utf8NextCodepoint(reinterpret_cast<const uint8_t**>(&textCursor)))) { |
| 421 | // Skip Hebrew Niqqud (vowel marks) |
| 422 | // Temporary: avoid adding Niqqud to built-in fonts. Remove when custom fonts are supported. |
| 423 | if (cp >= 0x0591 && cp <= 0x05C7) { |
| 424 | continue; |
| 425 | } |
| 426 | |
| 427 | if (utf8IsCombiningMark(cp)) { |
| 428 | const EpdGlyph* combiningGlyph = font.getGlyph(cp, style); |
| 429 | if (!combiningGlyph) continue; |
| 430 | const int raiseBy = combiningMark::raiseAboveBase(combiningGlyph->top, combiningGlyph->height, lastBaseTop); |
| 431 | const int combiningX = combiningMark::centerOver(lastBaseX, lastBaseLeft, lastBaseWidth, combiningGlyph->left, |
| 432 | combiningGlyph->width); |
| 433 | renderCharImpl<TextRotation::None>(*this, renderMode, font, cp, combiningX, yPos - raiseBy, black, style); |
| 434 | continue; |
| 435 | } |
| 436 | |
| 437 | cp = font.applyLigatures(cp, textCursor, style); |
| 438 | |
| 439 | // Differential rounding: snap (previous advance + current kern) as one unit so |
| 440 | // identical character pairs always produce the same pixel step regardless of |
| 441 | // where they fall on the line. |
| 442 | if (prevCp != 0) { |
| 443 | const auto kernFP = font.getKerning(prevCp, cp, style); // 4.4 fixed-point kern |
| 444 | lastBaseX += fp4::toPixel(prevAdvanceFP + kernFP); // snap 12.4 fixed-point to nearest pixel |
| 445 | } |
no test coverage detected