Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[drape] Integrate Harfbuzz text shaping #8473

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Open

[drape] Integrate Harfbuzz text shaping #8473

wants to merge 13 commits into from

Conversation

biodranik
Copy link
Member

@biodranik biodranik commented Jun 16, 2024

Fixes #4281
Fixes #516
Fixes #1723

What should be done:

  • Split longer lines of text into two

There are many TODOs in the code which can be optimized later. Proper profiling and caching may increase the rendering speed.

@biodranik biodranik requested a review from vng June 16, 2024 22:25
@biodranik biodranik force-pushed the ab-harfbuzz branch 2 times, most recently from b1b21dd to e80bcb9 Compare June 16, 2024 23:28
drape/glyph.hpp Outdated Show resolved Hide resolved
drape_frontend/gui/gui_text.cpp Outdated Show resolved Hide resolved
drape_frontend/gui/gui_text.cpp Outdated Show resolved Hide resolved
delimIndexes.push_back(visibleText.size());
// if (visibleText == text && !forceNoWrap)
// SplitText(visibleText, delimIndexes);
// else
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the problem with keeping an existing SplitText logic? How does it break HB?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does not break, just a bit more complex computation. I will implement it.

@vng can you please review newly added commits?

@biodranik
Copy link
Member Author

Some statistics about the length and count of rendered strings in displayed data after zooming in/out in China and Switzerland (longer strings are NOT split into two):

Dumping stats:
1 13
2 516
3 4145
4 2094
5 2049
6 2140
7 2314
8 1920
9 1723
10 1429
11 1302
12 1258
13 1262
14 1086
15 1139
16 1140
17 1001
18 789
19 679
20 497
21 402
22 297
23 257
24 189
25 188
26 131
27 114
28 139
29 201
30 212
31 170
32 139
33 113
34 81
35 78
36 66
37 66
38 65
39 63
40 45
41 29
42 17
43 15
44 12
45 9
46 11
47 9
48 11
49 12
50 506
51 9
52 1
53 9
54 2
55 5
56 1
57 5
58 2
59 2
60 1
61 2
62 2
64 2
66 3
68 2
69 1
70 2
72 1
74 1
75 1
78 1
84 1
93 1

Conclusion: using buffer_vector with size 50 should cover most of the cases.

Signed-off-by: Alexander Borsuk <[email protected]>
Signed-off-by: Alexander Borsuk <[email protected]>
Signed-off-by: Alexander Borsuk <[email protected]>
Signed-off-by: Alexander Borsuk <[email protected]>
Rendering without a cache is several times slower than without Harfbuzz.

Experiments showed that the hit rate for the same rendered strings is very high.

Signed-off-by: Alexander Borsuk <[email protected]>
#ifdef DEBUG
static int const fontSize = fontPixelHeight;
ASSERT_EQUAL(fontSize, fontPixelHeight,
("Cache relies on the same font height/metrics for each glyph", fontSize, fontPixelHeight));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't get here. If fontPixelHeight should be always the same, why not set it once into GlyphManager and don't pass into ShapeText?

But is it true that font height should always be the same during the app's lifetime?

{
LOG(LINFO, ("Clearing text metrics cache"));
// TODO(AB): Is there a better way? E.g. clear a half of the cache?
m_impl->m_textMetricsCache.clear();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LRU cache.

hb_buffer_set_language(m_impl->m_harfbuzzBuffer, hbLanguage);

auto u32CharacterIter{text.begin() + substring.m_start};
auto const end{text.begin() + substring.m_start + substring.m_length};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you really like this {} notation instead of a natural language wise assignment?
auto const end = u32CharacterIter + substring.m_length;

@@ -34,7 +35,12 @@ struct TextMetrics

void AddGlyphMetrics(int16_t font, uint16_t glyphId, int32_t xOffset, int32_t yOffset, int32_t xAdvance, int32_t height)
{
m_glyphs.push_back({font, glyphId, xOffset, yOffset, xAdvance});
m_glyphs.push_back({{font, glyphId}, xOffset, yOffset, xAdvance});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: emplace_back(GlyphFontAndId(font, glyphId), xOffset, yOffset, xAdvance)

LOG(LWARNING, ("No font was found for character", NumToHex(u32Character)));
else
{
// TODO(AB): Mapping font only by the first character in a string may fail in theory in some cases.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, why TODO then? :)

@@ -26,7 +25,7 @@ class TextGeometryGenerator

void SetPenPosition(glsl::vec2 const & penOffset) {}

void operator() (dp::TextureManager::GlyphRegion const & glyph)
void operator() (dp::TextureManager::GlyphRegion const & glyph, dp::text::GlyphMetrics const & metrics)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dp::text::GlyphMetrics const & because unused?

@@ -107,7 +107,7 @@ class TextOutlinedGeometryGenerator

void SetPenPosition(glsl::vec2 const & penOffset) {}

void operator() (dp::TextureManager::GlyphRegion const & glyph)
void operator() (dp::TextureManager::GlyphRegion const & glyph, dp::text::GlyphMetrics const & metrics)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dp::text::GlyphMetrics const & because unused?


//using TGlyph = std::pair<int16_t /* fontIndex */, uint16_t /* glyphId */>;
// TODO(AB): Measure if 32 is the best value here.
using TGlyphs = buffer_vector<GlyphFontAndId, 32>;
Copy link
Member

@vng vng Jun 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that GetGlyphs functions return by value -> so using move ctor -> the longer in-place buffer -> stranger moving :)

  • Use regular vector, especially with reserve
    or
  • Use GetGlyphs(TGlyphs & res) notation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants