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