Text: rewrite word-wrapping logic. (#8990, #3237, #8503, #8139, #8439, #9094, #3002, #9066, #8838)

This commit is contained in:
ocornut
2025-12-18 00:12:32 +01:00
parent 683f9160b9
commit 22ffa3d6d3
3 changed files with 59 additions and 34 deletions

View File

@@ -93,7 +93,12 @@ Other Changes:
- Added ImGuiSliderFlags_ColorMarkers to opt-in adding R/G/B/A color markers - Added ImGuiSliderFlags_ColorMarkers to opt-in adding R/G/B/A color markers
next to each components, in multi-components functions. next to each components, in multi-components functions.
- Added a way to select a specific marker color. - Added a way to select a specific marker color.
- Text: - Text, InputText:
- Reworked word-wrapping logic:
- Try to not wrap in the middle of contiguous punctuations. (#8139, #8439, #9094)
- Try to not wrap between a punctuation and a digit. (#8503)
- Inside InputTextMultiline() with _WordWrap: prefer keeping blanks at the
end of a line rather than at the beginning of next line. (#8990, #3237)
- Fixed low-level word-wrapping function reading from *text_end when passed - Fixed low-level word-wrapping function reading from *text_end when passed
a string range. (#9107) [@achabense] a string range. (#9107) [@achabense]
- Scrollbar: fixed a codepath leading to a divide-by-zero (which would not be - Scrollbar: fixed a codepath leading to a divide-by-zero (which would not be

View File

@@ -30,7 +30,7 @@
// Library Version // Library Version
// (Integer encoded as XYYZZ for use in #if preprocessor conditionals, e.g. '#if IMGUI_VERSION_NUM >= 12345') // (Integer encoded as XYYZZ for use in #if preprocessor conditionals, e.g. '#if IMGUI_VERSION_NUM >= 12345')
#define IMGUI_VERSION "1.92.6 WIP" #define IMGUI_VERSION "1.92.6 WIP"
#define IMGUI_VERSION_NUM 19256 #define IMGUI_VERSION_NUM 19257
#define IMGUI_HAS_TABLE // Added BeginTable() - from IMGUI_VERSION_NUM >= 18000 #define IMGUI_HAS_TABLE // Added BeginTable() - from IMGUI_VERSION_NUM >= 18000
#define IMGUI_HAS_TEXTURES // Added ImGuiBackendFlags_RendererHasTextures - from IMGUI_VERSION_NUM >= 19198 #define IMGUI_HAS_TEXTURES // Added ImGuiBackendFlags_RendererHasTextures - from IMGUI_VERSION_NUM >= 19198

View File

@@ -17,7 +17,7 @@ Index of this file:
// [SECTION] ImFontAtlas: backend for stb_truetype // [SECTION] ImFontAtlas: backend for stb_truetype
// [SECTION] ImFontAtlas: glyph ranges helpers // [SECTION] ImFontAtlas: glyph ranges helpers
// [SECTION] ImFontGlyphRangesBuilder // [SECTION] ImFontGlyphRangesBuilder
// [SECTION] ImFont // [SECTION] ImFontBaked, ImFont
// [SECTION] ImGui Internal Render Helpers // [SECTION] ImGui Internal Render Helpers
// [SECTION] Decompression code // [SECTION] Decompression code
// [SECTION] Default font data (ProggyClean.ttf) // [SECTION] Default font data (ProggyClean.ttf)
@@ -5068,7 +5068,7 @@ void ImFontGlyphRangesBuilder::BuildRanges(ImVector<ImWchar>* out_ranges)
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// [SECTION] ImFont // [SECTION] ImFontBaked, ImFont
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
ImFontBaked::ImFontBaked() ImFontBaked::ImFontBaked()
@@ -5371,6 +5371,12 @@ const char* ImTextCalcWordWrapNextLineStart(const char* text, const char* text_e
return text; return text;
} }
// Character classification for word-wrapping logic
enum
{
ImWcharClass_Blank, ImWcharClass_Punct, ImWcharClass_Other
};
// Simple word-wrapping for English, not full-featured. Please submit failing cases! // Simple word-wrapping for English, not full-featured. Please submit failing cases!
// This will return the next location to wrap from. If no wrapping if necessary, this will fast-forward to e.g. text_end. // This will return the next location to wrap from. If no wrapping if necessary, this will fast-forward to e.g. text_end.
// FIXME: Much possible improvements (don't cut things like "word !", "word!!!" but cut within "word,,,,", more sensible support for punctuations, support for Unicode punctuations, etc.) // FIXME: Much possible improvements (don't cut things like "word !", "word!!!" but cut within "word,,,,", more sensible support for punctuations, support for Unicode punctuations, etc.)
@@ -5392,16 +5398,20 @@ const char* ImFontCalcWordWrapPositionEx(ImFont* font, float size, const char* t
const float scale = size / baked->Size; const float scale = size / baked->Size;
float line_width = 0.0f; float line_width = 0.0f;
float word_width = 0.0f;
float blank_width = 0.0f; float blank_width = 0.0f;
wrap_width /= scale; // We work with unscaled widths to avoid scaling every characters wrap_width /= scale; // We work with unscaled widths to avoid scaling every characters
const char* word_end = text;
const char* prev_word_end = NULL;
bool inside_word = true;
const char* s = text; const char* s = text;
IM_ASSERT(text_end != NULL); IM_ASSERT(text_end != NULL);
int prev_type = ImWcharClass_Other;
const bool keep_blanks = (flags & ImDrawTextFlags_WrapKeepBlanks) != 0;
// Find next wrapping point
//const char* span_begin = s;
const char* span_end = s;
float span_width = 0.0f;
while (s < text_end) while (s < text_end)
{ {
unsigned int c = (unsigned int)*s; unsigned int c = (unsigned int)*s;
@@ -5417,7 +5427,7 @@ const char* ImFontCalcWordWrapPositionEx(ImFont* font, float size, const char* t
return s; // Direct return, skip "Wrap_width is too small to fit anything" path. return s; // Direct return, skip "Wrap_width is too small to fit anything" path.
if (c == '\r') if (c == '\r')
{ {
s = next_s; s = next_s; // Fast-skip
continue; continue;
} }
} }
@@ -5427,46 +5437,56 @@ const char* ImFontCalcWordWrapPositionEx(ImFont* font, float size, const char* t
if (char_width < 0.0f) if (char_width < 0.0f)
char_width = BuildLoadGlyphGetAdvanceOrFallback(baked, c); char_width = BuildLoadGlyphGetAdvanceOrFallback(baked, c);
if (ImCharIsBlankW(c)) // Classify current character
int curr_type;
if (c == ' ' || c == '\t' || c == 0x3000) // Inline version of ImCharIsBlankW(c)
curr_type = ImWcharClass_Blank;
else if (c == '.' || c == ',' || c == ';' || c == '!' || c == '?' || c == '\"' || c == 0x3001 || c == 0x3002)
curr_type = ImWcharClass_Punct;
else
curr_type = ImWcharClass_Other;
if (curr_type == ImWcharClass_Blank)
{ {
if (inside_word) // End span: 'A ' or '. '
if (prev_type != ImWcharClass_Blank && !keep_blanks)
{ {
line_width += blank_width; span_end = s;
blank_width = 0.0f; line_width += span_width;
word_end = s; span_width = 0.0f;
} }
blank_width += char_width; blank_width += char_width;
inside_word = false;
} }
else else
{ {
word_width += char_width; // End span: '.X' unless X is a digit
if (inside_word) if (prev_type == ImWcharClass_Punct && curr_type != ImWcharClass_Punct && !(c >= '0' && c <= '9'))
{ {
word_end = next_s; span_end = s;
line_width += span_width + blank_width;
span_width = blank_width = 0.0f;
} }
else // End span: 'A ' or '. '
else if (prev_type == ImWcharClass_Blank && keep_blanks)
{ {
prev_word_end = word_end; span_end = s;
line_width += word_width + blank_width; line_width += span_width + blank_width;
if ((flags & ImDrawTextFlags_WrapKeepBlanks) && line_width <= wrap_width) span_width = blank_width = 0.0f;
prev_word_end = s;
word_width = blank_width = 0.0f;
} }
span_width += char_width;
// Allow wrapping after punctuation.
inside_word = (c != '.' && c != ',' && c != ';' && c != '!' && c != '?' && c != '\"' && c != 0x3001 && c != 0x3002);
} }
// We ignore blank width at the end of the line (they can be skipped) if (span_width + blank_width + line_width > wrap_width)
if (line_width + word_width > wrap_width)
{ {
// Words that cannot possibly fit within an entire line will be cut anywhere. if (span_width + blank_width > wrap_width)
if (word_width < wrap_width) break;
s = prev_word_end ? prev_word_end : word_end; // FIXME: Narrow wrapping e.g. "A quick brown" -> "Quic|k br|own", would require knowing if span is going to be longer than wrap_width.
break; //if (span_width > wrap_width && !is_blank && !was_blank)
// return s;
return span_end;
} }
prev_type = curr_type;
s = next_s; s = next_s;
} }