Skip to content

Commit 22ffa3d

Browse files
committed
Text: rewrite word-wrapping logic. (#8990, #3237, #8503, #8139, #8439, #9094, #3002, #9066, #8838)
1 parent 683f916 commit 22ffa3d

File tree

3 files changed

+59
-34
lines changed

3 files changed

+59
-34
lines changed

docs/CHANGELOG.txt

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

imgui.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
// Library Version
3131
// (Integer encoded as XYYZZ for use in #if preprocessor conditionals, e.g. '#if IMGUI_VERSION_NUM >= 12345')
3232
#define IMGUI_VERSION "1.92.6 WIP"
33-
#define IMGUI_VERSION_NUM 19256
33+
#define IMGUI_VERSION_NUM 19257
3434
#define IMGUI_HAS_TABLE // Added BeginTable() - from IMGUI_VERSION_NUM >= 18000
3535
#define IMGUI_HAS_TEXTURES // Added ImGuiBackendFlags_RendererHasTextures - from IMGUI_VERSION_NUM >= 19198
3636

imgui_draw.cpp

Lines changed: 52 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ Index of this file:
1717
// [SECTION] ImFontAtlas: backend for stb_truetype
1818
// [SECTION] ImFontAtlas: glyph ranges helpers
1919
// [SECTION] ImFontGlyphRangesBuilder
20-
// [SECTION] ImFont
20+
// [SECTION] ImFontBaked, ImFont
2121
// [SECTION] ImGui Internal Render Helpers
2222
// [SECTION] Decompression code
2323
// [SECTION] Default font data (ProggyClean.ttf)
@@ -5068,7 +5068,7 @@ void ImFontGlyphRangesBuilder::BuildRanges(ImVector<ImWchar>* out_ranges)
50685068
}
50695069

50705070
//-----------------------------------------------------------------------------
5071-
// [SECTION] ImFont
5071+
// [SECTION] ImFontBaked, ImFont
50725072
//-----------------------------------------------------------------------------
50735073

50745074
ImFontBaked::ImFontBaked()
@@ -5371,6 +5371,12 @@ const char* ImTextCalcWordWrapNextLineStart(const char* text, const char* text_e
53715371
return text;
53725372
}
53735373

5374+
// Character classification for word-wrapping logic
5375+
enum
5376+
{
5377+
ImWcharClass_Blank, ImWcharClass_Punct, ImWcharClass_Other
5378+
};
5379+
53745380
// Simple word-wrapping for English, not full-featured. Please submit failing cases!
53755381
// This will return the next location to wrap from. If no wrapping if necessary, this will fast-forward to e.g. text_end.
53765382
// 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
53925398
const float scale = size / baked->Size;
53935399

53945400
float line_width = 0.0f;
5395-
float word_width = 0.0f;
53965401
float blank_width = 0.0f;
53975402
wrap_width /= scale; // We work with unscaled widths to avoid scaling every characters
53985403

5399-
const char* word_end = text;
5400-
const char* prev_word_end = NULL;
5401-
bool inside_word = true;
5402-
54035404
const char* s = text;
54045405
IM_ASSERT(text_end != NULL);
5406+
5407+
int prev_type = ImWcharClass_Other;
5408+
const bool keep_blanks = (flags & ImDrawTextFlags_WrapKeepBlanks) != 0;
5409+
5410+
// Find next wrapping point
5411+
//const char* span_begin = s;
5412+
const char* span_end = s;
5413+
float span_width = 0.0f;
5414+
54055415
while (s < text_end)
54065416
{
54075417
unsigned int c = (unsigned int)*s;
@@ -5417,7 +5427,7 @@ const char* ImFontCalcWordWrapPositionEx(ImFont* font, float size, const char* t
54175427
return s; // Direct return, skip "Wrap_width is too small to fit anything" path.
54185428
if (c == '\r')
54195429
{
5420-
s = next_s;
5430+
s = next_s; // Fast-skip
54215431
continue;
54225432
}
54235433
}
@@ -5427,46 +5437,56 @@ const char* ImFontCalcWordWrapPositionEx(ImFont* font, float size, const char* t
54275437
if (char_width < 0.0f)
54285438
char_width = BuildLoadGlyphGetAdvanceOrFallback(baked, c);
54295439

5430-
if (ImCharIsBlankW(c))
5440+
// Classify current character
5441+
int curr_type;
5442+
if (c == ' ' || c == '\t' || c == 0x3000) // Inline version of ImCharIsBlankW(c)
5443+
curr_type = ImWcharClass_Blank;
5444+
else if (c == '.' || c == ',' || c == ';' || c == '!' || c == '?' || c == '\"' || c == 0x3001 || c == 0x3002)
5445+
curr_type = ImWcharClass_Punct;
5446+
else
5447+
curr_type = ImWcharClass_Other;
5448+
5449+
if (curr_type == ImWcharClass_Blank)
54315450
{
5432-
if (inside_word)
5451+
// End span: 'A ' or '. '
5452+
if (prev_type != ImWcharClass_Blank && !keep_blanks)
54335453
{
5434-
line_width += blank_width;
5435-
blank_width = 0.0f;
5436-
word_end = s;
5454+
span_end = s;
5455+
line_width += span_width;
5456+
span_width = 0.0f;
54375457
}
54385458
blank_width += char_width;
5439-
inside_word = false;
54405459
}
54415460
else
54425461
{
5443-
word_width += char_width;
5444-
if (inside_word)
5462+
// End span: '.X' unless X is a digit
5463+
if (prev_type == ImWcharClass_Punct && curr_type != ImWcharClass_Punct && !(c >= '0' && c <= '9'))
54455464
{
5446-
word_end = next_s;
5465+
span_end = s;
5466+
line_width += span_width + blank_width;
5467+
span_width = blank_width = 0.0f;
54475468
}
5448-
else
5469+
// End span: 'A ' or '. '
5470+
else if (prev_type == ImWcharClass_Blank && keep_blanks)
54495471
{
5450-
prev_word_end = word_end;
5451-
line_width += word_width + blank_width;
5452-
if ((flags & ImDrawTextFlags_WrapKeepBlanks) && line_width <= wrap_width)
5453-
prev_word_end = s;
5454-
word_width = blank_width = 0.0f;
5472+
span_end = s;
5473+
line_width += span_width + blank_width;
5474+
span_width = blank_width = 0.0f;
54555475
}
5456-
5457-
// Allow wrapping after punctuation.
5458-
inside_word = (c != '.' && c != ',' && c != ';' && c != '!' && c != '?' && c != '\"' && c != 0x3001 && c != 0x3002);
5476+
span_width += char_width;
54595477
}
54605478

5461-
// We ignore blank width at the end of the line (they can be skipped)
5462-
if (line_width + word_width > wrap_width)
5479+
if (span_width + blank_width + line_width > wrap_width)
54635480
{
5464-
// Words that cannot possibly fit within an entire line will be cut anywhere.
5465-
if (word_width < wrap_width)
5466-
s = prev_word_end ? prev_word_end : word_end;
5467-
break;
5481+
if (span_width + blank_width > wrap_width)
5482+
break;
5483+
// 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.
5484+
//if (span_width > wrap_width && !is_blank && !was_blank)
5485+
// return s;
5486+
return span_end;
54685487
}
54695488

5489+
prev_type = curr_type;
54705490
s = next_s;
54715491
}
54725492

0 commit comments

Comments
 (0)