Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions imgui_demo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3598,7 +3598,10 @@ static void DemoWindowWidgetsText()
ImGui::SliderFloat("Wrap width", &wrap_width, -20, 600, "%.0f");

ImDrawList* draw_list = ImGui::GetWindowDrawList();
for (int n = 0; n < 2; n++)
static char user_input[2048];

ImGui::InputText("Input Any.", user_input, 2048);
for (int n = 0; n < 3; n++)
{
ImGui::Text("Test paragraph %d:", n);
ImVec2 pos = ImGui::GetCursorScreenPos();
Expand All @@ -3607,9 +3610,11 @@ static void DemoWindowWidgetsText()
ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + wrap_width);
if (n == 0)
ImGui::Text("The lazy dog is a good dog. This paragraph should fit within %.0f pixels. Testing a 1 character word. The quick brown fox jumps over the lazy dog.", wrap_width);
else
else if (n == 1)
ImGui::Text("aaaaaaaa bbbbbbbb, c cccccccc,dddddddd. d eeeeeeee ffffffff. gggggggg!hhhhhhhh");

else
ImGui::Text(user_input);

// Draw actual text bounding box, following by marker of our expected limit (should not overlap!)
draw_list->AddRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), IM_COL32(255, 255, 0, 255));
draw_list->AddRectFilled(marker_min, marker_max, IM_COL32(255, 0, 255, 255));
Expand Down
108 changes: 51 additions & 57 deletions imgui_draw.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5353,17 +5353,8 @@ static inline const char* CalcWordWrapNextLineStartA(const char* text, const cha
// 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.)
const char* ImFont::CalcWordWrapPosition(float size, const char* text, const char* text_end, float wrap_width)
{
// For references, possible wrap point marked with ^
// "aaa bbb, ccc,ddd. eee fff. ggg!"
// ^ ^ ^ ^ ^__ ^ ^

// List of hardcoded separators: .,;!?'"

// Skip extra blanks after a line returns (that includes not counting them in width computation)
// e.g. "Hello world" --> "Hello" "World"

// Cut words that cannot possibly fit within one line.
// e.g.: "The tropical fish" with ~5 characters worth of width --> "The tr" "opical" "fish"
// Refactored word wrapping method detects wrapping points by looking for at most 3 consecutive characters.
// (Currently only 2.)

ImFontBaked* baked = GetFontBaked(size);
const float scale = size / baked->Size;
Expand All @@ -5374,87 +5365,91 @@ const char* ImFont::CalcWordWrapPosition(float size, const char* text, const cha
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* prev_s = NULL;
const char* s = NULL;
const char* next_s = text;
unsigned int prev_c = 0;
unsigned int c = 0;
unsigned int next_c;
#define IM_ADVANCE_WORD() \
do {\
word_end = s; \
line_width += word_width + blank_width; \
word_width = blank_width = 0.0f;\
} while (0)
IM_ASSERT(text_end != NULL);
while (s < text_end)
{
unsigned int c = (unsigned int)*s;
const char* next_s;
if (c < 0x80)
next_s = s + 1;
// prev_s is the END of prev_c, which actually points to c
// same for s and next_s.
prev_s = s;
s = next_s;
prev_c = c;
c = next_c;
next_c = (unsigned int)*next_s;
if (next_c < 0x80)
next_s = next_s + 1;
else
next_s = s + ImTextCharFromUtf8(&c, s, text_end);
next_s = next_s + ImTextCharFromUtf8(&next_c, next_s, text_end);
if (next_s > text_end)
next_c = 0;

if (c < 32)
if (prev_s == NULL) {
continue;
}
if (c < 0x20)
{
if (c == '\n')
{
line_width = word_width = blank_width = 0.0f;
inside_word = true;
s = next_s;
continue;
}
if (c == '\r')
{
s = next_s;
continue;
}
}

}
// Optimized inline version of 'float char_width = GetCharAdvance((ImWchar)c);'
float char_width = (c < (unsigned int)baked->IndexAdvanceX.Size) ? baked->IndexAdvanceX.Data[c] : -1.0f;
if (char_width < 0.0f)
char_width = BuildLoadGlyphGetAdvanceOrFallback(baked, c);

if (ImCharIsBlankW(c))
{
if (inside_word)
{
line_width += blank_width;
blank_width = 0.0f;
word_end = s;
}
blank_width += char_width;
inside_word = false;
}
else
{
word_width += char_width;
if (inside_word)
{
word_end = next_s;
}
else
{
prev_word_end = word_end;
line_width += word_width + blank_width;
word_width = blank_width = 0.0f;
}

// Allow wrapping after punctuation.
inside_word = (c != '.' && c != ',' && c != ';' && c != '!' && c != '?' && c != '\"' && c != 0x3001 && c != 0x3002);
word_width += char_width + blank_width;
blank_width = 0.0f;
}

// We ignore blank width at the end of the line (they can be skipped)
if (line_width + word_width > wrap_width)
{
// Words that cannot possibly fit within an entire line will be cut anywhere.
if (word_width < wrap_width)
s = prev_word_end ? prev_word_end : word_end;
s = word_end;
else
s = prev_s;
break;
}

s = next_s;
if (!next_c) {
IM_ADVANCE_WORD();
}
else if (c && next_c)
{
if (prev_c >= '0' && prev_c <= '9' && next_c >= '0' && next_c <= '9' && !ImCharIsLineBreakableW(c))
continue;
if (ImCharIsLineBreakableW(next_c) && !ImCharIsHeadProhibited(next_c) && !ImCharIsTailProhibited(c))
IM_ADVANCE_WORD();
if ((ImCharIsHeadProhibited(c) || ImCharIsLineBreakableW(c)) && !ImCharIsHeadProhibited(next_c))
IM_ADVANCE_WORD();
}
}

#undef IM_ADVANCE_WORD
// Wrap_width is too small to fit anything. Force displaying 1 character to minimize the height discontinuity.
// +1 may not be a character start point in UTF-8 but it's ok because caller loops use (text >= word_wrap_eol).
if (s == text && text < text_end)
return s + ImTextCountUtf8BytesFromChar(s, text_end);
return s;
return s > text_end ? text_end : s;
}

ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, const char* text_begin, const char* text_end, const char** remaining)
Expand All @@ -5480,15 +5475,14 @@ ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, cons
// Calculate how far we can render. Requires two passes on the string data but keeps the code simple and not intrusive for what's essentially an uncommon feature.
if (!word_wrap_eol)
word_wrap_eol = CalcWordWrapPosition(size, s, text_end, wrap_width - line_width);

IM_ASSERT(word_wrap_eol <= text_end);
if (s >= word_wrap_eol)
{
if (text_size.x < line_width)
text_size.x = line_width;
text_size.y += line_height;
line_width = 0.0f;
word_wrap_eol = NULL;
s = CalcWordWrapNextLineStartA(s, text_end); // Wrapping skips upcoming blanks
continue;
}
}
Expand Down
9 changes: 9 additions & 0 deletions imgui_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,15 @@ IM_MSVC_RUNTIME_CHECKS_OFF
inline char ImToUpper(char c) { return (c >= 'a' && c <= 'z') ? c &= ~32 : c; }
inline bool ImCharIsBlankA(char c) { return c == ' ' || c == '\t'; }
inline bool ImCharIsBlankW(unsigned int c) { return c == ' ' || c == '\t' || c == 0x3000; }
inline bool ImCharIsHeadProhibitedA(char c) { return c == ' ' || c == '\t' || c == '}' || c == ')' || c == ']' || c == '?' || c == '!' || c == '|' || c == '/' || c == '&' || c == '.' || c == ',' || c == ':' || c == ';';}
const unsigned int HeadProhibitedW[] = { 0xa2, 0xb0, 0x2032, 0x2033, 0x2030, 0x2103, 0x3001, 0x3002, 0xff61, 0xff64, 0xffe0, 0xff0c, 0xff0e, 0xff1a, 0xff1b, 0xff1f, 0xff01, 0xff05, 0x30fb, 0xff65, 0x309d, 0x309e, 0x30fd, 0x30fe, 0x30fc, 0x30a1, 0x30a3, 0x30a5, 0x30a7, 0x30a9, 0x30c3, 0x30e3, 0x30e5, 0x30e7, 0x30ee, 0x30f5, 0x30f6, 0x3041, 0x3043, 0x3045, 0x3047, 0x3049, 0x3063, 0x3083, 0x3085, 0x3087, 0x308e, 0x3095, 0x3096, 0x31f0, 0x31f1, 0x31f2, 0x31f3, 0x31f4, 0x31f5, 0x31f6, 0x31f7, 0x31f8, 0x31f9, 0x31fa, 0x31fb, 0x31fc, 0x31fd, 0x31fe, 0x31ff, 0x3005, 0x303b, 0xff67, 0xff68, 0xff69, 0xff6a, 0xff6b, 0xff6c, 0xff6d, 0xff6e, 0xff6f, 0xff70, 0x201d, 0x3009, 0x300b, 0x300d, 0x300f, 0x3011, 0x3015, 0xff09, 0xff3d, 0xff5d, 0xff63};
inline bool ImCharIsHeadProhibitedW(unsigned int c) { for (int i = 0; i < IM_ARRAYSIZE(HeadProhibitedW); i++) if (c == HeadProhibitedW[i]) return true; return false;}
inline bool ImCharIsHeadProhibited(unsigned int c) { return (c < 128 && ImCharIsHeadProhibitedA(c)) || ImCharIsHeadProhibitedW(c); }
inline bool ImCharIsTailProhibitedA(unsigned int c) { return c == '(' || c == '[' || c == '{' || c == '+'; }
const unsigned int TailProhibitedW[] = { 0x2018, 0x201c, 0x3008, 0x300a, 0x300c, 0x300e, 0x3010, 0x3014, 0xff08, 0xff3b, 0xff5b, 0xff62, 0xa3, 0xa5, 0xff04, 0xffe1, 0xffe5, 0xff0b };
inline bool ImCharIsTailProhibitedW(unsigned int c) { for (int i = 0; i < IM_ARRAYSIZE(TailProhibitedW); i++) if (c == TailProhibitedW[i]) return true; return false; }
inline bool ImCharIsTailProhibited(unsigned int c) { return (c < 128 && ImCharIsTailProhibitedA(c)) || ImCharIsTailProhibitedW(c); }
inline bool ImCharIsLineBreakableW(unsigned int c) { return (c >= 0x3040 && c <= 0x9fff) || (c >= 0x3400 && c <= 0x4dbf) || (c >= 0x20000 && c <= 0xdffff) || (c >= 0x3040 && c <= 0x30ff) || (c >= 0xac00 && c <= 0xd7ff) || (c >= 0xf900 && c <= 0xfaff) || (c >= 0x1100 && c <= 0x11ff) || (c >= 0x2e80 && c <= 0x2fff); }
inline bool ImCharIsXdigitA(char c) { return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'); }
IM_MSVC_RUNTIME_CHECKS_RESTORE

Expand Down