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 |
|
|
|