This method is NOT multithreading-safe.
| 568 | |
| 569 | // This method is NOT multithreading-safe. |
| 570 | text::TextMetrics GlyphManager::ShapeText(std::string_view utf8, int fontPixelHeight, hb_language_t const textLang) |
| 571 | { |
| 572 | #ifdef DEBUG |
| 573 | static int const fontSize = fontPixelHeight; |
| 574 | ASSERT_EQUAL(fontSize, fontPixelHeight, |
| 575 | ("Cache relies on the same font height/metrics for each glyph", fontSize, fontPixelHeight)); |
| 576 | #endif |
| 577 | |
| 578 | // A simple cache greatly speeds up text metrics calculation. It has 80+% hit ratio in most scenarios. |
| 579 | if (auto const found = m_impl->m_textMetricsCache.find(utf8); found != m_impl->m_textMetricsCache.end()) |
| 580 | return found->second; |
| 581 | |
| 582 | auto const [text, segments] = harfbuzz_shaping::GetTextSegments(utf8); |
| 583 | |
| 584 | text::TextMetrics allGlyphs; |
| 585 | // For SplitText it's enough to know if the last visual (first logical) segment is RTL. |
| 586 | allGlyphs.m_isRTL = segments.back().m_direction == HB_DIRECTION_RTL; |
| 587 | |
| 588 | allGlyphs.m_glyphs.reserve(strings::CountChar(utf8)); |
| 589 | |
| 590 | for (auto const & substring : segments) |
| 591 | { |
| 592 | hb_buffer_clear_contents(m_impl->m_harfbuzzBuffer); |
| 593 | |
| 594 | // TODO(AB): Some substrings use different fonts. |
| 595 | hb_buffer_add_utf16(m_impl->m_harfbuzzBuffer, reinterpret_cast<uint16_t const *>(text.data()), |
| 596 | static_cast<int>(text.size()), substring.m_start, substring.m_length); |
| 597 | hb_buffer_set_direction(m_impl->m_harfbuzzBuffer, substring.m_direction); |
| 598 | hb_buffer_set_script(m_impl->m_harfbuzzBuffer, substring.m_script); |
| 599 | hb_buffer_set_language(m_impl->m_harfbuzzBuffer, textLang); |
| 600 | |
| 601 | auto u32CharacterIter{text.begin() + substring.m_start}; |
| 602 | auto const end{u32CharacterIter + substring.m_length}; |
| 603 | do |
| 604 | { |
| 605 | auto const u32Character = utf8::unchecked::next16(u32CharacterIter); |
| 606 | // TODO(AB): Mapping characters to fonts can be optimized. |
| 607 | int const fontIndex = GetFontIndex(u32Character); |
| 608 | if (fontIndex < 0) |
| 609 | LOG(LWARNING, ("No font was found for character", NumToHex(u32Character))); |
| 610 | else |
| 611 | { |
| 612 | // TODO(AB): Mapping font only by the first character in a string may fail in theory in some cases. |
| 613 | m_impl->m_fonts[fontIndex]->Shape(m_impl->m_harfbuzzBuffer, fontPixelHeight, fontIndex, allGlyphs); |
| 614 | break; |
| 615 | } |
| 616 | } |
| 617 | while (u32CharacterIter != end); |
| 618 | } |
| 619 | |
| 620 | // Uncomment utf8 printing for debugging if necessary. It crashes JNI with non-modified UTF-8 strings on Android 5 |
| 621 | // and 6. See https://github.com/organicmaps/organicmaps/issues/10685 |
| 622 | if (allGlyphs.m_glyphs.empty()) |
| 623 | LOG(LWARNING, ("No glyphs were found in all fonts for string with characters in warnings above" /*, utf8*/)); |
| 624 | |
| 625 | if (m_impl->m_textMetricsCache.size() > kMaxCacheSize) |
| 626 | { |
| 627 | LOG(LINFO, ("Clearing text metrics cache")); |