add support for zero width line break hints

This commit is contained in:
DeaTh-G 2025-02-17 18:59:55 +01:00
parent 5ad4dbcc39
commit d556bc1a81
3 changed files with 139 additions and 1 deletions

View file

@ -247,6 +247,19 @@ static bool FontBuilder_Build(ImFontAtlas* atlas)
u1 / packer.width, u1 / packer.width,
v0 / packer.height, v0 / packer.height,
advance); advance);
config.DstFont->AddGlyph(
&config,
0x200B,
0.0f,
0.0f,
0.0f,
0.0f,
0.0f,
0.0f,
0.0f,
0.0f,
0.0f);
} }
config.DstFont->BuildLookupTable(); config.DstFont->BuildLookupTable();

View file

@ -464,17 +464,39 @@ std::vector<std::string> Split(const char* strStart, const ImFont* font, float f
const bool wordWrapEnabled = (maxWidth > 0.0f); const bool wordWrapEnabled = (maxWidth > 0.0f);
const char *wordWrapEOL = nullptr; const char *wordWrapEOL = nullptr;
auto IsKanji = [](const char* str, const char* strEnd)
{
const char* tempStr = str;
unsigned int c = (unsigned int)*tempStr;
if (c < 0x80)
tempStr += 1;
else
tempStr += ImTextCharFromUtf8(&c, tempStr, strEnd);
// Basic CJK and CJK Extension A
return (c >= 0x4E00 && c <= 0x9FBF) || (c >= 0x3400 && c <= 0x4DBF);
};
while (*str != 0) while (*str != 0)
{ {
if (wordWrapEnabled) if (wordWrapEnabled)
{ {
if (wordWrapEOL == nullptr) if (wordWrapEOL == nullptr)
{ {
wordWrapEOL = font->CalcWordWrapPositionA(scale, str, strEnd, maxWidth - lineWidth); wordWrapEOL = CalcWordWrapPositionA(font, scale, str, strEnd, maxWidth - lineWidth);
} }
if (str >= wordWrapEOL) if (str >= wordWrapEOL)
{ {
if (IsKanji(str, strEnd))
{
// If the current character is Kanji, move back to prevent splitting Kanji
while (str > lineStart && IsKanji(str - 3, strEnd))
{
str -= 3;
}
}
if (textWidth < lineWidth) if (textWidth < lineWidth)
textWidth = lineWidth; textWidth = lineWidth;
@ -826,3 +848,105 @@ void DrawToggleLight(ImVec2 pos, bool isEnabled, float alpha)
drawList->AddImage(g_texLight.get(), min, max, GET_UV_COORDS(lightOffUVs), lightCol); drawList->AddImage(g_texLight.get(), min, max, GET_UV_COORDS(lightOffUVs), lightCol);
} }
} }
// Taken from ImGui because we need to modify to break for '\u200B\ too
// 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.
// 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* CalcWordWrapPositionA(const ImFont* font, float scale, 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"
float line_width = 0.0f;
float word_width = 0.0f;
float blank_width = 0.0f;
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;
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;
else
next_s = s + ImTextCharFromUtf8(&c, s, text_end);
if (c < 32)
{
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;
}
}
const float char_width = ((int)c < font->IndexAdvanceX.Size ? font->IndexAdvanceX.Data[c] : font->FallbackAdvanceX);
if (ImCharIsBlankW(c) || c == 0x200B)
{
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 != '\"');
}
// 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;
break;
}
s = next_s;
}
// 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 + 1;
return s;
}

View file

@ -81,3 +81,4 @@ ImU32 ColourLerp(ImU32 c0, ImU32 c1, float t);
void DrawVersionString(const ImFont* font, const ImU32 col = IM_COL32(255, 255, 255, 70)); void DrawVersionString(const ImFont* font, const ImU32 col = IM_COL32(255, 255, 255, 70));
void DrawSelectionContainer(ImVec2 min, ImVec2 max, bool fadeTop = false); void DrawSelectionContainer(ImVec2 min, ImVec2 max, bool fadeTop = false);
void DrawToggleLight(ImVec2 pos, bool isEnabled, float alpha = 1.0f); void DrawToggleLight(ImVec2 pos, bool isEnabled, float alpha = 1.0f);
const char* CalcWordWrapPositionA(const ImFont* font, float scale, const char* text, const char* text_end, float wrap_width);