fix(ui): cut ellipsis truncation on UTF-8 code-point boundaries
TruncateToWidth, the middle "front...back" ellipsis for addresses, pool URLs, paths, labels and the status bar, cut at byte offsets: a multi-byte character at either cut was split into invalid UTF-8 and drawn as replacement characters (e.g. a translated Cyrillic or CJK status). It also tried front lengths from n/2 down until one fit, building and measuring a new string for each: up to O(n^2) per truncated label per frame. util::textfit::middleEllipsis keeps the same contract and is byte-identical for ASCII, but snaps the front cut down and the back cut up to code-point boundaries, and finds the largest fitting front length by bisection into one reused buffer: O(log n) measures for single-line text. Text containing a newline keeps the linear scan, because its measured width (the widest line) does not grow steadily with the cut. text_fit.h also gains largestFittingPrefix, a bisection over code-point boundaries; its first caller is the node-status banner in a following change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
#include "../effects/imgui_acrylic.h"
|
||||
#include "../theme.h"
|
||||
#include "../../util/noise_texture.h"
|
||||
#include "../../util/text_fit.h"
|
||||
#include "../../embedded/IconsMaterialDesign.h"
|
||||
#include "imgui.h"
|
||||
#include "imgui_internal.h"
|
||||
@@ -60,17 +61,14 @@ inline ImU32 ReadableError() {
|
||||
|
||||
// Middle-ellipsis truncation ("front...back", roughly equal halves) so `text` fits within
|
||||
// maxWidth pixels when drawn with `font` at `fontSize`. Returns `text` unchanged if it already
|
||||
// fits (or maxWidth is non-positive). Display-only — never mutate the underlying value with this.
|
||||
// fits (or maxWidth is non-positive). Cuts land on UTF-8 code-point boundaries; single-line text
|
||||
// costs O(log n) measures (util::textfit::middleEllipsis). Display-only — never mutate the
|
||||
// underlying value with this.
|
||||
inline std::string TruncateToWidth(const std::string& text, ImFont* font, float fontSize, float maxWidth) {
|
||||
if (text.empty() || !font || maxWidth <= 0.0f) return text;
|
||||
if (font->CalcTextSizeA(fontSize, FLT_MAX, 0.0f, text.c_str()).x <= maxWidth) return text;
|
||||
const int n = static_cast<int>(text.size());
|
||||
for (int f = n / 2; f >= 3; --f) {
|
||||
const int b = (f - 2 > 3) ? (f - 2) : 3; // keep the two halves roughly equal
|
||||
std::string t = text.substr(0, f) + "..." + text.substr(n - b);
|
||||
if (font->CalcTextSizeA(fontSize, FLT_MAX, 0.0f, t.c_str()).x <= maxWidth) return t;
|
||||
}
|
||||
return n > 6 ? (text.substr(0, 3) + "..." + text.substr(n - 3)) : text;
|
||||
return util::textfit::middleEllipsis(text, maxWidth, [&](const char* b, const char* e) {
|
||||
return font->CalcTextSizeA(fontSize, FLT_MAX, 0.0f, b, e).x;
|
||||
});
|
||||
}
|
||||
|
||||
// Animated "loading" ellipsis: "", ".", "..", "..." cycling on a ~3Hz phase.
|
||||
|
||||
Reference in New Issue
Block a user