INSTINCT Code Coverage Report


Directory: src/
File: internal/gui/widgets/TextAnsiColored.cpp
Date: 2025-02-07 16:54:41
Exec Total Coverage
Lines: 0 346 0.0%
Functions: 0 10 0.0%
Branches: 0 265 0.0%

Line Branch Exec Source
1 // This file is part of INSTINCT, the INS Toolkit for Integrated
2 // Navigation Concepts and Training by the Institute of Navigation of
3 // the University of Stuttgart, Germany.
4 //
5 // This Source Code Form is subject to the terms of the Mozilla Public
6 // License, v. 2.0. If a copy of the MPL was not distributed with this
7 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
8
9 #include "TextAnsiColored.hpp"
10
11 #include <array>
12
13 namespace
14 {
15
16 constexpr int kMaxChar = 10000;
17 std::array<char, kMaxChar> char_buf;
18 std::array<ImU32, kMaxChar> col_buf;
19 std::array<bool, kMaxChar> char_skip;
20
21 } // namespace
22
23 namespace jet
24 {
25 namespace
26 {
27 std::vector<std::string> split(const std::string& str, const std::string& delim = " ")
28 {
29 std::vector<std::string> res;
30 std::size_t previous = 0;
31 std::size_t current = str.find(delim);
32 while (current != std::string::npos)
33 {
34 res.push_back(str.substr(previous, current - previous));
35 previous = current + delim.length();
36 current = str.find(delim, previous);
37 }
38 res.push_back(str.substr(previous, current - previous));
39 return res;
40 }
41
42 } // namespace
43 } // namespace jet
44
45 namespace ImGui
46 {
47
48 namespace
49 {
50 bool ParseColor(const char* s, ImU32* col, int* skipChars)
51 {
52 if (s[0] != '\033' || s[1] != '[')
53 {
54 return false;
55 }
56
57 if (s[2] == 'm')
58 {
59 *col = ImGui::ColorConvertFloat4ToU32(ImGui::GetStyleColorVec4(ImGuiCol_Text));
60 *skipChars = 3;
61 return true;
62 }
63
64 if (s[2] == '0' && s[3] == 'm')
65 {
66 *col = ImGui::ColorConvertFloat4ToU32(ImGui::GetStyleColorVec4(ImGuiCol_Text));
67 *skipChars = 4;
68 return true;
69 }
70
71 const char* seqEnd = &s[2];
72 while (*seqEnd != 'm')
73 {
74 seqEnd++;
75 }
76
77 std::string seq{ &s[2], seqEnd };
78 std::string colorStr;
79 for (const auto& el : jet::split(seq, ";"))
80 {
81 if (el[0] == '3' && el.size() == 2)
82 {
83 colorStr = el;
84 break;
85 }
86 }
87
88 if (!colorStr.empty())
89 {
90 switch (colorStr[1])
91 {
92 case '0':
93 *col = 0xffcccccc;
94 break;
95 case '1':
96 *col = 0xff7a77f2;
97 break;
98 case '2':
99 *col = 0xff99cc99;
100 break;
101 case '3':
102 *col = 0xff66ccff;
103 break;
104 case '4':
105 *col = 0xffcc9966;
106 break;
107 case '5':
108 *col = 0xffcc99cc;
109 break;
110 case '6':
111 *col = 0xffcccc66;
112 break;
113 case '7':
114 *col = 0xff2d2d2d;
115 break;
116 default:
117 return false;
118 }
119 }
120
121 *skipChars = static_cast<int>(seqEnd - s + 1);
122 return true;
123 }
124
125 void ImFont_RenderAnsiText(const ImFont* font,
126 ImDrawList* draw_list,
127 float size,
128 ImVec2 pos,
129 ImU32 col,
130 const ImVec4& clip_rect,
131 const char* text_begin,
132 const char* text_end,
133 float wrap_width = 0.0F,
134 bool cpu_fine_clip = false)
135 {
136 if (!text_end)
137 {
138 // ImGui functions generally already provides a valid text_end,
139 // so this is merely to handle direct calls.
140 text_end = text_begin + strlen(text_begin);
141 }
142
143 // Align to be pixel perfect
144 // Align to be pixel perfect
145 pos.x = IM_FLOOR(pos.x);
146 pos.y = IM_FLOOR(pos.y);
147 float x = pos.x;
148 float y = pos.y;
149 if (y > clip_rect.w)
150 {
151 return;
152 }
153
154 const float scale = size / font->FontSize;
155 const float line_height = font->FontSize * scale;
156 const bool word_wrap_enabled = (wrap_width > 0.0F);
157 const char* word_wrap_eol = nullptr;
158
159 // Fast-forward to first visible line
160 const char* s = text_begin;
161 if (y + line_height < clip_rect.y && !word_wrap_enabled)
162 {
163 while (y + line_height < clip_rect.y && s < text_end)
164 {
165 s = static_cast<const char*>(memchr(s, '\n', static_cast<size_t>(text_end - s)));
166 s = s ? s + 1 : text_end;
167 y += line_height;
168 }
169 }
170
171 // For large text, scan for the last visible line in order to avoid over-reserving in the call to PrimReserve()
172 // Note that very large horizontal line will still be affected by the issue (e.g. a one megabyte string buffer
173 // without a newline will likely crash atm)
174 if (text_end - s > 10000 && !word_wrap_enabled)
175 {
176 const char* s_end = s;
177 float y_end = y;
178 while (y_end < clip_rect.w && s_end < text_end)
179 {
180 s_end = static_cast<const char*>(memchr(s_end, '\n', static_cast<size_t>(text_end - s_end)));
181 s = s ? s + 1 : text_end;
182 y_end += line_height;
183 }
184 text_end = s_end;
185 }
186 if (s == text_end)
187 {
188 return;
189 }
190
191 // Reserve vertices for remaining worse case (over-reserving is useful and easily amortized)
192 const int vtx_count_max = static_cast<int>(text_end - s) * 4;
193 const int idx_count_max = static_cast<int>(text_end - s) * 6;
194 const int idx_expected_size = draw_list->IdxBuffer.Size + idx_count_max;
195 draw_list->PrimReserve(idx_count_max, vtx_count_max);
196
197 ImDrawVert* vtx_write = draw_list->_VtxWritePtr;
198 ImDrawIdx* idx_write = draw_list->_IdxWritePtr;
199 unsigned int vtx_current_idx = draw_list->_VtxCurrentIdx;
200
201 {
202 for (size_t i = 0; i < static_cast<size_t>(text_end - text_begin); i++)
203 {
204 char_skip.at(i) = false;
205 }
206 size_t index = 0;
207 int skipChars = 0;
208 const char* sLocal = s;
209 ImU32 temp_col = col;
210 while (sLocal < text_end)
211 {
212 if (sLocal < text_end - 4 && ParseColor(sLocal, &temp_col, &skipChars))
213 {
214 sLocal += skipChars;
215 for (size_t i = 0; i < static_cast<size_t>(skipChars); i++)
216 {
217 char_skip.at(index + i) = true;
218 }
219 index += static_cast<size_t>(skipChars);
220 }
221 else
222 {
223 char_buf.at(index) = *sLocal;
224 col_buf.at(index) = temp_col;
225 char_skip.at(index) = false;
226 ++index;
227 ++sLocal;
228 }
229 }
230 }
231
232 const char* s1 = s;
233 while (s < text_end)
234 {
235 if (char_skip.at(static_cast<size_t>(s - s1)))
236 {
237 s++;
238 continue;
239 }
240 if (word_wrap_enabled)
241 {
242 // Calculate how far we can render. Requires two passes on the string data but keeps the code simple and
243 // not intrusive for what's essentially an uncommon feature.
244 if (!word_wrap_eol)
245 {
246 word_wrap_eol = font->CalcWordWrapPositionA(scale, s, text_end, wrap_width - (x - pos.x));
247 if (word_wrap_eol == s) // Wrap_width is too small to fit anything. Force displaying 1 character to minimize the height discontinuity.
248 { // +1 may not be a character start point in UTF-8 but it's ok because we use s >= word_wrap_eol below
249 word_wrap_eol++;
250 }
251 }
252
253 if (s >= word_wrap_eol)
254 {
255 x = pos.x;
256 y += line_height;
257 word_wrap_eol = nullptr;
258
259 // Wrapping skips upcoming blanks
260 while (s < text_end)
261 {
262 const char c = *s;
263 if (ImCharIsBlankA(c))
264 {
265 s++;
266 }
267 else if (c == '\n')
268 {
269 s++;
270 break;
271 }
272 else
273 {
274 break;
275 }
276 }
277 continue;
278 }
279 }
280
281 // Decode and advance source
282 auto c = static_cast<unsigned int>(static_cast<unsigned char>(*s));
283 if (c < 0x80)
284 {
285 s += 1;
286 }
287 else
288 {
289 s += ImTextCharFromUtf8(&c, s, text_end);
290 if (c == 0) // Malformed UTF-8?
291 {
292 break;
293 }
294 }
295
296 if (c < 32)
297 {
298 if (c == '\n')
299 {
300 x = pos.x;
301 y += line_height;
302 if (y > clip_rect.w)
303 {
304 break; // break out of main loop
305 }
306 continue;
307 }
308 if (c == '\r')
309 {
310 continue;
311 }
312 }
313
314 float char_width = 0.0F;
315 if (const ImFontGlyph* glyph = font->FindGlyph(static_cast<ImWchar>(c)))
316 {
317 char_width = glyph->AdvanceX * scale;
318
319 // Arbitrarily assume that both space and tabs are empty glyphs as an optimization
320 if (c != ' ' && c != '\t')
321 {
322 // We don't do a second finer clipping test on the Y axis as we've already skipped anything before
323 // clip_rect.y and exit once we pass clip_rect.w
324 float x1 = x + glyph->X0 * scale;
325 float x2 = x + glyph->X1 * scale;
326 float y1 = y + glyph->Y0 * scale;
327 float y2 = y + glyph->Y1 * scale;
328 if (x1 <= clip_rect.z && x2 >= clip_rect.x)
329 {
330 // Render a character
331 float u1 = glyph->U0;
332 float v1 = glyph->V0;
333 float u2 = glyph->U1;
334 float v2 = glyph->V1;
335
336 // CPU side clipping used to fit text in their frame when the frame is too small. Only does
337 // clipping for axis aligned quads.
338 if (cpu_fine_clip)
339 {
340 if (x1 < clip_rect.x)
341 {
342 u1 = u1 + (1.0F - (x2 - clip_rect.x) / (x2 - x1)) * (u2 - u1);
343 x1 = clip_rect.x;
344 }
345 if (y1 < clip_rect.y)
346 {
347 v1 = v1 + (1.0F - (y2 - clip_rect.y) / (y2 - y1)) * (v2 - v1);
348 y1 = clip_rect.y;
349 }
350 if (x2 > clip_rect.z)
351 {
352 u2 = u1 + ((clip_rect.z - x1) / (x2 - x1)) * (u2 - u1);
353 x2 = clip_rect.z;
354 }
355 if (y2 > clip_rect.w)
356 {
357 v2 = v1 + ((clip_rect.w - y1) / (y2 - y1)) * (v2 - v1);
358 y2 = clip_rect.w;
359 }
360 if (y1 >= y2)
361 {
362 x += char_width;
363 continue;
364 }
365 }
366
367 // We are NOT calling PrimRectUV() here because non-inlined causes too much overhead in a debug
368 // builds. Inlined here:
369 ImU32 temp_col = col_buf.at(static_cast<size_t>(s - text_begin - 1));
370 {
371 idx_write[0] = vtx_current_idx;
372 idx_write[1] = vtx_current_idx + 1;
373 idx_write[2] = vtx_current_idx + 2;
374 idx_write[3] = vtx_current_idx;
375 idx_write[4] = vtx_current_idx + 2;
376 idx_write[5] = vtx_current_idx + 3;
377 vtx_write[0].pos.x = x1;
378 vtx_write[0].pos.y = y1;
379 vtx_write[0].col = temp_col;
380 vtx_write[0].uv.x = u1;
381 vtx_write[0].uv.y = v1;
382 vtx_write[1].pos.x = x2;
383 vtx_write[1].pos.y = y1;
384 vtx_write[1].col = temp_col;
385 vtx_write[1].uv.x = u2;
386 vtx_write[1].uv.y = v1;
387 vtx_write[2].pos.x = x2;
388 vtx_write[2].pos.y = y2;
389 vtx_write[2].col = temp_col;
390 vtx_write[2].uv.x = u2;
391 vtx_write[2].uv.y = v2;
392 vtx_write[3].pos.x = x1;
393 vtx_write[3].pos.y = y2;
394 vtx_write[3].col = temp_col;
395 vtx_write[3].uv.x = u1;
396 vtx_write[3].uv.y = v2;
397 vtx_write += 4;
398 vtx_current_idx += 4;
399 idx_write += 6;
400 }
401 }
402 }
403 }
404
405 x += char_width;
406 }
407
408 // Give back unused vertices
409 draw_list->VtxBuffer.resize(static_cast<int>(vtx_write - draw_list->VtxBuffer.Data));
410 draw_list->IdxBuffer.resize(static_cast<int>(idx_write - draw_list->IdxBuffer.Data));
411 draw_list->CmdBuffer[draw_list->CmdBuffer.Size - 1].ElemCount -= static_cast<unsigned int>(idx_expected_size - draw_list->IdxBuffer.Size);
412 draw_list->_VtxWritePtr = vtx_write;
413 draw_list->_IdxWritePtr = idx_write;
414 draw_list->_VtxCurrentIdx = static_cast<unsigned int>(draw_list->VtxBuffer.Size);
415 }
416
417 void ImDrawList_AddAnsiText(ImDrawList* drawList,
418 const ImFont* font,
419 float font_size,
420 const ImVec2& pos,
421 ImU32 col,
422 const char* text_begin,
423 const char* text_end = nullptr,
424 float wrap_width = 0.0F,
425 const ImVec4* cpu_fine_clip_rect = nullptr)
426 {
427 if ((col & IM_COL32_A_MASK) == 0)
428 {
429 return;
430 }
431
432 if (text_end == nullptr)
433 {
434 text_end = text_begin + strlen(text_begin);
435 }
436 if (text_begin == text_end)
437 {
438 return;
439 }
440
441 // Pull default font/size from the shared ImDrawListSharedData instance
442 if (font == nullptr)
443 {
444 font = drawList->_Data->Font;
445 }
446 if (font_size == 0.0F)
447 {
448 font_size = drawList->_Data->FontSize;
449 }
450
451 IM_ASSERT(font->ContainerAtlas->TexID == drawList->_TextureIdStack.back()); // Use high-level ImGui::PushFont()
452 // or low-level
453 // ImDrawList::PushTextureId() to
454 // change font.
455
456 ImVec4 clip_rect = drawList->_ClipRectStack.back();
457 if (cpu_fine_clip_rect)
458 {
459 clip_rect.x = ImMax(clip_rect.x, cpu_fine_clip_rect->x);
460 clip_rect.y = ImMax(clip_rect.y, cpu_fine_clip_rect->y);
461 clip_rect.z = ImMin(clip_rect.z, cpu_fine_clip_rect->z);
462 clip_rect.w = ImMin(clip_rect.w, cpu_fine_clip_rect->w);
463 }
464 ImFont_RenderAnsiText(font,
465 drawList,
466 font_size,
467 pos,
468 col,
469 clip_rect,
470 text_begin,
471 text_end,
472 wrap_width,
473 cpu_fine_clip_rect != nullptr);
474 }
475
476 void RenderAnsiText(ImVec2 pos, const char* text, const char* text_end, bool hide_text_after_hash)
477 {
478 ImGuiContext& g = *GImGui;
479 ImGuiWindow* window = g.CurrentWindow;
480
481 // Hide anything after a '##' string
482 const char* text_display_end = nullptr;
483 if (hide_text_after_hash)
484 {
485 text_display_end = FindRenderedTextEnd(text, text_end);
486 }
487 else
488 {
489 if (!text_end)
490 {
491 text_end = text + strlen(text);
492 }
493 text_display_end = text_end;
494 }
495
496 if (text != text_display_end)
497 {
498 ImDrawList_AddAnsiText(window->DrawList, g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_display_end);
499 if (g.LogEnabled)
500 {
501 LogRenderedText(&pos, text, text_display_end);
502 }
503 }
504 }
505
506 void RenderAnsiTextWrapped(ImVec2 pos, const char* text, const char* text_end, float wrap_width)
507 {
508 ImGuiContext& g = *GImGui;
509 ImGuiWindow* window = g.CurrentWindow;
510
511 if (!text_end)
512 {
513 text_end = text + strlen(text);
514 }
515
516 if (text != text_end)
517 {
518 ImDrawList_AddAnsiText(window->DrawList, g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_end, wrap_width);
519 if (g.LogEnabled)
520 {
521 LogRenderedText(&pos, text, text_end);
522 }
523 }
524 }
525
526 } // namespace
527 } // namespace ImGui
528
529 void ImGui::TextAnsiUnformatted(const char* text, const char* text_end)
530 {
531 ImGuiWindow* window = GetCurrentWindow();
532 if (window->SkipItems)
533 {
534 return;
535 }
536
537 ImGuiContext& g = *GImGui;
538 IM_ASSERT(text != nullptr);
539 const char* text_begin = text;
540 if (text_end == nullptr)
541 {
542 text_end = text + strlen(text); // NOLINT(clang-analyzer-core.NonNullParamChecker) - false positive, as checked by IM_ASSERT
543 }
544
545 const ImVec2 text_pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset);
546 const float wrap_pos_x = window->DC.TextWrapPos;
547 const bool wrap_enabled = wrap_pos_x >= 0.0F;
548 if (text_end - text > 2000 && !wrap_enabled)
549 {
550 // Long text!
551 // Perform manual coarse clipping to optimize for long multi-line text
552 // - From this point we will only compute the width of lines that are visible. Optimization only available
553 // when word-wrapping is disabled.
554 // - We also don't vertically center the text within the line full height, which is unlikely to matter
555 // because we are likely the biggest and only item on the line.
556 // - We use memchr(), pay attention that well optimized versions of those str/mem functions are much faster
557 // than a casually written loop.
558 const char* line = text;
559 const float line_height = GetTextLineHeight();
560 const ImRect clip_rect = window->ClipRect;
561 ImVec2 text_size(0, 0);
562
563 if (text_pos.y <= clip_rect.Max.y)
564 {
565 ImVec2 pos = text_pos;
566
567 // Lines to skip (can't skip when logging text)
568 if (!g.LogEnabled)
569 {
570 int lines_skippable = static_cast<int>((clip_rect.Min.y - text_pos.y) / line_height);
571 if (lines_skippable > 0)
572 {
573 int lines_skipped = 0;
574 while (line < text_end && lines_skipped < lines_skippable)
575 {
576 const char* line_end = static_cast<const char*>(memchr(line, '\n', static_cast<size_t>(text_end - line)));
577 if (!line_end)
578 {
579 line_end = text_end;
580 }
581 line = line_end + 1;
582 lines_skipped++;
583 }
584 pos.y += static_cast<float>(lines_skipped) * line_height;
585 }
586 }
587
588 // Lines to render
589 if (line < text_end)
590 {
591 ImRect line_rect(pos, pos + ImVec2(FLT_MAX, line_height));
592 while (line < text_end)
593 {
594 if (IsClippedEx(line_rect, 0))
595 {
596 break;
597 }
598
599 const char* line_end = static_cast<const char*>(memchr(line, '\n', static_cast<size_t>(text_end - line)));
600 if (!line_end)
601 {
602 line_end = text_end;
603 }
604 const ImVec2 line_size = CalcTextSize(line, line_end, false);
605 text_size.x = ImMax(text_size.x, line_size.x);
606 RenderAnsiText(pos, line, line_end, false);
607 line = line_end + 1;
608 line_rect.Min.y += line_height;
609 line_rect.Max.y += line_height;
610 pos.y += line_height;
611 }
612
613 // Count remaining lines
614 int lines_skipped = 0;
615 while (line < text_end)
616 {
617 const char* line_end = static_cast<const char*>(memchr(line, '\n', static_cast<size_t>(text_end - line)));
618 if (!line_end)
619 {
620 line_end = text_end;
621 }
622 line = line_end + 1;
623 lines_skipped++;
624 }
625 pos.y += static_cast<float>(lines_skipped) * line_height;
626 }
627
628 text_size.y += (pos - text_pos).y;
629 }
630
631 ImRect bb(text_pos, text_pos + text_size);
632 ItemSize(bb);
633 ItemAdd(bb, 0);
634 }
635 else
636 {
637 const float wrap_width = wrap_enabled ? CalcWrapWidthForPos(window->DC.CursorPos, wrap_pos_x) : 0.0F;
638 const ImVec2 text_size = CalcTextSize(text_begin, text_end, false, wrap_width);
639
640 // Account of baseline offset
641 ImRect bb(text_pos, text_pos + text_size);
642 ItemSize(text_size);
643 if (!ItemAdd(bb, 0))
644 {
645 return;
646 }
647
648 // Render (we don't hide text after ## in this end-user function)
649 RenderAnsiTextWrapped(bb.Min, text_begin, text_end, wrap_width);
650 }
651 }
652
653 void ImGui::TextAnsiV(const char* fmt, va_list args)
654 {
655 ImGuiWindow* window = GetCurrentWindow();
656 if (window->SkipItems)
657 {
658 return;
659 }
660
661 ImGuiContext& g = *GImGui;
662 #if defined(__GNUC__) || defined(__clang__)
663 #pragma GCC diagnostic push
664 #pragma GCC diagnostic ignored "-Wformat-nonliteral"
665 #endif
666 const char* text_end = g.TempBuffer.Data + ImFormatStringV(g.TempBuffer.Data, strlen(g.TempBuffer.Data), fmt, args); // NOLINT(clang-diagnostic-format-nonliteral)
667 #if defined(__GNUC__) || defined(__clang__)
668 #pragma GCC diagnostic pop
669 #endif
670 TextAnsiUnformatted(g.TempBuffer.Data, text_end);
671 }
672
673 void ImGui::TextAnsiColoredV(const ImVec4& col, const char* fmt, va_list args)
674 {
675 PushStyleColor(ImGuiCol_Text, col);
676 TextAnsiV(fmt, args);
677 PopStyleColor();
678 }
679
680 void ImGui::TextAnsiColored(const ImVec4& col, const char* fmt, ...) // NOLINT(cert-dcl50-cpp)
681 {
682 va_list args;
683 va_start(args, fmt);
684 TextAnsiColoredV(col, fmt, args);
685 va_end(args);
686 }
687