@@ -109,52 +109,39 @@ void QtTextRenderer::AddText(const char* text, float x, float y, float z, TextFo
109109 if (text_length == 0 ) {
110110 return ;
111111 }
112- const float width_entire_text = GetStringWidth (text, formatting.font_size );
113112 const float height_entire_text = GetStringHeight (text, formatting.font_size );
114113 Vec2i pen_pos = viewport_->WorldToScreen (Vec2 (x, y));
115114 LayeredVec2 transformed = translations_.TranslateXYZAndFloorXY (
116115 {{static_cast <float >(pen_pos[0 ]), static_cast <float >(pen_pos[1 ])}, z});
117- const float max_width =
118- formatting.max_size == -1 .f ? FLT_MAX : viewport_->WorldToScreen ({formatting.max_size , 0 })[0 ];
119- // Find out how many characters from text can fit into max_width via a binary search.
120- size_t idx_min = 1 ;
121- size_t idx_max = text_length;
122- if (GetStringWidth (text_as_qstring.left (1 ), formatting.font_size ) > max_width) {
123- return ;
124- }
125- if (width_entire_text <= max_width) {
126- idx_min = text_length;
127- }
128- while (idx_max - idx_min > 1 ) {
129- const size_t candidate_idx = (idx_min + idx_max) / 2 ;
130- const QString candidate_string = text_as_qstring.left (candidate_idx);
131- if (GetStringWidth (candidate_string, formatting.font_size ) > max_width) {
132- idx_max = candidate_idx;
133- } else {
134- idx_min = candidate_idx;
135- }
136- }
137- text_as_qstring = text_as_qstring.left (idx_min);
116+ const int max_width = static_cast <int >(viewport_->WorldToScreen ({formatting.max_size , 0 })[0 ]);
117+ QFont font = QFontDatabase::systemFont (QFontDatabase::GeneralFont);
118+ font.setPixelSize (formatting.font_size );
119+ QFontMetrics metrics (font);
138120 float y_offset = GetYOffsetFromAlignment (formatting.valign , height_entire_text);
139121 const float single_line_height = GetStringHeight (" ." , formatting.font_size );
140122 QStringList lines = text_as_qstring.split (" \n " );
123+ float max_line_width = 0 .f ;
141124 for (const auto & line : lines) {
142- const float width = GetStringWidth (line, formatting.font_size );
125+ QString elided_line =
126+ formatting.max_size == -1 .f ? line : metrics.elidedText (line, Qt::ElideRight, max_width);
127+ const float width = GetStringWidth (elided_line, formatting.font_size );
128+ max_line_width = std::max (max_line_width, width);
143129 const float x_offset = GetXOffsetFromAlignment (formatting.halign , width);
144- stored_text_[transformed.z ].emplace_back (
145- line, std::lround (transformed.xy [0 ] + x_offset), std::lround (transformed.xy [1 ] + y_offset),
146- std::lround (width), std::lround (single_line_height), formatting);
130+ stored_text_[transformed.z ].emplace_back (elided_line, std::lround (transformed.xy [0 ] + x_offset),
131+ std::lround (transformed.xy [1 ] + y_offset),
132+ std::lround (width), std::lround (single_line_height),
133+ formatting);
147134 y_offset += single_line_height;
148135 }
149136
150137 if (out_text_pos != nullptr ) {
151138 (*out_text_pos)[0 ] =
152- transformed.xy [0 ] + GetXOffsetFromAlignment (formatting.halign , width_entire_text );
139+ transformed.xy [0 ] + GetXOffsetFromAlignment (formatting.halign , max_line_width );
153140 (*out_text_pos)[1 ] =
154141 transformed.xy [1 ] + GetYOffsetFromAlignment (formatting.valign , height_entire_text);
155142 }
156143 if (out_text_size != nullptr ) {
157- *out_text_size = Vec2 (width_entire_text , height_entire_text);
144+ *out_text_size = Vec2 (max_line_width , height_entire_text);
158145 }
159146}
160147
@@ -176,55 +163,33 @@ float QtTextRenderer::AddTextTrailingCharsPrioritized(const char* text, float x,
176163 }
177164 // Early-out: If we can't fit a single char, there's no use to do all the expensive
178165 // calculations below - this is a major bottleneck in some cases
179- if (formatting.max_size >= 0 && GetStringWidth ( " . " , formatting.font_size ) > formatting.max_size ) {
166+ if (formatting.max_size >= 0 && GetMinimumTextWidth ( formatting.font_size ) > formatting.max_size ) {
180167 return 0 .f ;
181168 }
182169
183- // Test if the entire string fits.
184170 const float max_width =
185171 formatting.max_size == -1 .f ? FLT_MAX : viewport_->WorldToScreen ({formatting.max_size , 0 })[0 ];
186- const float entire_width = GetStringWidth (text, formatting.font_size );
187- if (entire_width <= max_width) {
188- AddText (text, x, y, z, formatting);
189- return entire_width;
190- }
191- // The entire string does not fit. We try to fit something of the form
192- // leading_text + "... " + trailing_text
193- // where leading_text is a variable amount of characters from the beginning of the string and
194- // trailing_text is specified by the trailing_chars_length parameter.
195172 const QString trailing_text = text_as_qstring.right (trailing_chars_length);
196173 const QString leading_text = text_as_qstring.left (text_length - trailing_chars_length);
197- const size_t leading_length = leading_text.length ();
198- const QString ellipsis_plus_trailing_text = QString (" ... " ) + trailing_text;
199- size_t fitting_chars_count = 1 ;
200- QString candidate_string = leading_text.left (fitting_chars_count) + ellipsis_plus_trailing_text;
201-
202- // Test if we can fit the minimal ellipsised string.
203- if (GetStringWidth (candidate_string, formatting.font_size ) > max_width) {
204- // We can't fit any ellipsised string: Even with one leading character the thing becomes too
205- // long. So we let AddText truncate the entire string.
206- Vec2 dims;
207- AddText (text, x, y, z, formatting, nullptr , &dims);
208- return dims[0 ];
209- }
210-
211- // Binary search between 1 and leading_length (we know 1 works and leading_length does not).
212- size_t idx_min = 1 ;
213- size_t idx_max = leading_length;
214- while (idx_max - idx_min > 1 ) {
215- size_t candidate_idx = (idx_min + idx_max) / 2 ;
216- candidate_string = leading_text.left (candidate_idx) + ellipsis_plus_trailing_text;
217- if (GetStringWidth (candidate_string, formatting.font_size ) > max_width) {
218- idx_max = candidate_idx;
219- } else {
220- idx_min = candidate_idx;
174+ QFont font = QFontDatabase::systemFont (QFontDatabase::GeneralFont);
175+ font.setPixelSize (formatting.font_size );
176+ QFontMetrics metrics (font);
177+ const float trailing_text_width = GetStringWidth (trailing_text, formatting.font_size );
178+ QString elided_text;
179+ if (trailing_text_width < max_width) {
180+ elided_text = metrics.elidedText (leading_text, Qt::ElideRight,
181+ static_cast <int >(max_width - trailing_text_width));
182+ }
183+ if (elided_text.isEmpty ()) {
184+ // We can't fit any elided string with the trailing characters preserved so we elide the entire
185+ // string and accept that the trailing characters are truncated.
186+ elided_text = metrics.elidedText (text_as_qstring, Qt::ElideRight, static_cast <int >(max_width));
187+ if (elided_text.isEmpty ()) {
188+ return 0 .f ;
221189 }
190+ return AddFittingSingleLineText (elided_text, x, y, z, formatting);
222191 }
223- fitting_chars_count = idx_min;
224- candidate_string = leading_text.left (fitting_chars_count) + ellipsis_plus_trailing_text;
225- const std::string candiate_as_std_string = candidate_string.toStdString ();
226- AddText (candiate_as_std_string.c_str (), x, y, z, formatting);
227- return GetStringWidth (candidate_string, formatting.font_size );
192+ return AddFittingSingleLineText (elided_text + trailing_text, x, y, z, formatting);
228193}
229194
230195float QtTextRenderer::GetStringWidth (const char * text, uint32_t font_size) {
@@ -256,4 +221,32 @@ float QtTextRenderer::GetStringWidth(const QString& text, uint32_t font_size) {
256221 return GetStringWidth (text_as_string.c_str (), font_size);
257222}
258223
224+ float QtTextRenderer::GetMinimumTextWidth (uint32_t font_size) {
225+ auto minimum_string_width_it = minimum_string_width_cache_.find (font_size);
226+ if (minimum_string_width_it != minimum_string_width_cache_.end ()) {
227+ return minimum_string_width_it->second ;
228+ }
229+ // Only if we can fit one wide (hence the "W") character plus the ellipsis dots we start rendering
230+ // text. Otherwise we leave the space empty.
231+ constexpr char const * kMinimumString = " W..." ;
232+ const float width = GetStringWidth (kMinimumString , font_size);
233+ minimum_string_width_cache_[font_size] = width;
234+ return width;
235+ }
236+
237+ float QtTextRenderer::AddFittingSingleLineText (const QString& text, float x, float y, float z,
238+ TextFormatting formatting) {
239+ const float width = GetStringWidth (text, formatting.font_size );
240+ const float single_line_height = GetStringHeight (" ." , formatting.font_size );
241+ Vec2i pen_pos = viewport_->WorldToScreen (Vec2 (x, y));
242+ LayeredVec2 transformed = translations_.TranslateXYZAndFloorXY (
243+ {{static_cast <float >(pen_pos[0 ]), static_cast <float >(pen_pos[1 ])}, z});
244+ const float x_offset = GetXOffsetFromAlignment (formatting.halign , width);
245+ float y_offset = GetYOffsetFromAlignment (formatting.valign , single_line_height);
246+ stored_text_[transformed.z ].emplace_back (
247+ text, std::lround (transformed.xy [0 ] + x_offset), std::lround (transformed.xy [1 ] + y_offset),
248+ std::lround (width), std::lround (single_line_height), formatting);
249+ return width;
250+ }
251+
259252} // namespace orbit_gl
0 commit comments