Font: Narrow ellipsis: various minor stylistic tweaks (#2775)

This commit is contained in:
omar 2019-09-17 10:00:28 +02:00
parent 45405f0dc9
commit 57623c15dd
6 changed files with 49 additions and 55 deletions

View File

@ -50,6 +50,10 @@ Other Changes:
- SliderScalar: Improved assert when using U32 or U64 types with a large v_max value. (#2765) [@loicmouton]
- DragInt, DragFloat, DragScalar: Using (v_min > v_max) allows locking any edit to the value.
- DragScalar: Fixed dragging of unsigned values on ARM cpu. (#2780) [@dBagrat]
- Font: Better ellipsis drawing implementation. Instead of drawing three pixel-ey dots (which was glaringly
unfitting with many types of fonts) we first attempt to find a standard ellipsis glyphs within the loaded set.
Otherwise we render ellipsis using '.' from the font from where we trim excessive spacing to make it as narrow
as possible. (#2775) [@rokups]
- ImDrawList: clarified the name of many parameters so reading the code is a little easier. (#2740)
- Using offsetof() when available in C++11. Avoids Clang sanitizer complaining about old-style macros. (#94)
- Added a mechanism to compact/free the larger allocations of unused windows (buffers are compacted when

View File

@ -275,6 +275,7 @@ It's mostly a bunch of personal notes, probably incomplete. Feel free to query i
- font: MergeMode: flags to select overwriting or not (this is now very easy with refactored ImFontAtlasBuildWithStbTruetype)
- font: free the Alpha buffer if user only requested RGBA.
!- font: better CalcTextSizeA() API, at least for simple use cases. current one is horrible (perhaps have simple vs extended versions).
- font: for the purpose of RenderTextEllipsis(), it might be useful that CalcTextSizeA() can ignore the trailing padding?
- font: a CalcTextHeight() helper could run faster than CalcTextSize().y
- font: enforce monospace through ImFontConfig (for icons?) + create dual ImFont output from same input, reusing rasterized data but with different glyphs/AdvanceX
- font: finish CustomRectRegister() to allow mapping Unicode codepoint to custom texture data

View File

@ -2506,38 +2506,32 @@ void ImGui::RenderTextEllipsis(ImDrawList* draw_list, const ImVec2& pos_min, con
const ImFont* font = draw_list->_Data->Font;
const float font_size = draw_list->_Data->FontSize;
const char* text_end_ellipsis = NULL;
const ImFontGlyph* glyph;
int ellipsis_char_num = 1;
ImWchar ellipsis_codepoint = font->EllipsisCodePoint;
if (ellipsis_codepoint != (ImWchar)-1)
glyph = font->FindGlyph(ellipsis_codepoint);
else
ImWchar ellipsis_char = font->EllipsisChar;
int ellipsis_char_count = 1;
if (ellipsis_char == (ImWchar)-1)
{
ellipsis_codepoint = (ImWchar)'.';
glyph = font->FindGlyph(ellipsis_codepoint);
ellipsis_char_num = 3;
ellipsis_char = (ImWchar)'.';
ellipsis_char_count = 3;
}
const ImFontGlyph* glyph = font->FindGlyph(ellipsis_char);
float ellipsis_glyph_width = glyph->X1; // Width of the glyph with no padding on either side
float ellipsis_width = ellipsis_glyph_width; // Full width of entire ellipsis
float push_left = 1.f;
float ellipsis_glyph_width = glyph->X1; // Width of the glyph with no padding on either side
float ellipsis_total_width = ellipsis_glyph_width; // Full width of entire ellipsis
float push_left = 1.0f;
if (ellipsis_char_num > 1)
if (ellipsis_char_count > 1)
{
const float spacing_between_dots = 1.f * (draw_list->_Data->FontSize / font->FontSize);
ellipsis_glyph_width = glyph->X1 - glyph->X0 + spacing_between_dots;
// Full ellipsis size without free spacing after it.
ellipsis_width = ellipsis_glyph_width * (float)ellipsis_char_num - spacing_between_dots;
if (glyph->X0 > 1.f)
{
// Pushing ellipsis to the left will be accomplished by rendering the dot (X0).
push_left = 0.f;
}
const float spacing_between_dots = 1.0f * (draw_list->_Data->FontSize / font->FontSize);
ellipsis_glyph_width = glyph->X1 - glyph->X0 + spacing_between_dots;
ellipsis_total_width = ellipsis_glyph_width * (float)ellipsis_char_count - spacing_between_dots;
if (glyph->X0 > 1.0f)
push_left = 0.0f; // Pushing ellipsis to the left will be accomplished by rendering the dot (X0).
}
float text_width = ImMax((pos_max.x - ellipsis_width) - pos_min.x, 1.0f);
float text_size_clipped_x = font->CalcTextSizeA(font_size, text_width, 0.0f, text, text_end_full, &text_end_ellipsis).x;
float text_avail_width = ImMax((pos_max.x - ellipsis_total_width) - pos_min.x, 1.0f);
float text_size_clipped_x = font->CalcTextSizeA(font_size, text_avail_width, 0.0f, text, text_end_full, &text_end_ellipsis).x;
if (text == text_end_ellipsis && text_end_ellipsis < text_end_full)
{
@ -2547,7 +2541,7 @@ void ImGui::RenderTextEllipsis(ImDrawList* draw_list, const ImVec2& pos_min, con
}
while (text_end_ellipsis > text && ImCharIsBlankA(text_end_ellipsis[-1]))
{
// Trim trailing space before ellipsis
// Trim trailing space before ellipsis (FIXME: Supporting non-ascii blanks would be nice, for this we need a function to backtrack in UTF-8 text)
text_end_ellipsis--;
text_size_clipped_x -= font->CalcTextSizeA(font_size, FLT_MAX, 0.0f, text_end_ellipsis, text_end_ellipsis + 1).x; // Ascii blanks are always 1 byte
}
@ -2560,16 +2554,15 @@ void ImGui::RenderTextEllipsis(ImDrawList* draw_list, const ImVec2& pos_min, con
// ||||
// \ \__ extra_spacing when two characters got hidden
// \___ extra_spacing when one character got hidden
unsigned c = 0;
float extra_spacing = 0;
unsigned int c = 0;
float extra_spacing = 0.0f;
const char* text_end_ellipsis_prev = text_end_ellipsis;
text_end_ellipsis += ImTextCharFromUtf8(&c, text_end_ellipsis, text_end_full);
if (c && !ImCharIsBlankW(c))
{
const ImFontGlyph* hidden_glyph = font->FindGlyph(c);
// Free space after first invisible glyph
const ImFontGlyph* hidden_glyph = font->FindGlyph((ImWchar)c);
extra_spacing = hidden_glyph->AdvanceX - hidden_glyph->X1;
c = 0;
text_end_ellipsis += ImTextCharFromUtf8(&c, text_end_ellipsis, text_end_full);
if (c && !ImCharIsBlankW(c))
{
@ -2587,11 +2580,11 @@ void ImGui::RenderTextEllipsis(ImDrawList* draw_list, const ImVec2& pos_min, con
if (extra_spacing > 0)
{
// Repeat calculation hoping that we will get extra character visible
text_width += extra_spacing;
text_avail_width += extra_spacing;
// Text length calculation is essentially an optimized version of this:
// text_size_clipped_x = font->CalcTextSizeA(font_size, text_width, 0.0f, text, text_end_full, &text_end_ellipsis).x;
// It avoids calculating entire width of the string.
text_size_clipped_x += font->CalcTextSizeA(font_size, text_width - text_size_clipped_x, 0.0f, text_end_ellipsis_prev, text_end_full, &text_end_ellipsis).x;
text_size_clipped_x += font->CalcTextSizeA(font_size, text_avail_width - text_size_clipped_x, 0.0f, text_end_ellipsis_prev, text_end_full, &text_end_ellipsis).x;
}
else
text_end_ellipsis = text_end_ellipsis_prev;
@ -2603,14 +2596,12 @@ void ImGui::RenderTextEllipsis(ImDrawList* draw_list, const ImVec2& pos_min, con
// ellipsis character contained in the font. If we render ellipsis manually space is already adequate and extra
// spacing is not needed.
float ellipsis_x = pos_min.x + text_size_clipped_x + push_left;
if (ellipsis_x + ellipsis_width - push_left <= ellipsis_max_x)
{
for (int i = 0; i < ellipsis_char_num; i++)
if (ellipsis_x + ellipsis_total_width - push_left <= ellipsis_max_x)
for (int i = 0; i < ellipsis_char_count; i++)
{
font->RenderChar(draw_list, font_size, ImVec2(ellipsis_x, pos_min.y), GetColorU32(ImGuiCol_Text), ellipsis_codepoint);
font->RenderChar(draw_list, font_size, ImVec2(ellipsis_x, pos_min.y), GetColorU32(ImGuiCol_Text), ellipsis_char);
ellipsis_x += ellipsis_glyph_width;
}
}
}
else
{

View File

@ -2011,7 +2011,7 @@ struct ImFontConfig
bool MergeMode; // false // Merge into previous ImFont, so you can combine multiple inputs font into one ImFont (e.g. ASCII font + icons + Japanese glyphs). You may want to use GlyphOffset.y when merge font of different heights.
unsigned int RasterizerFlags; // 0x00 // Settings for custom font rasterizer (e.g. ImGuiFreeType). Leave as zero if you aren't using one.
float RasterizerMultiply; // 1.0f // Brighten (>1.0f) or darken (<1.0f) font output. Brightening small fonts may be a good workaround to make them more readable.
ImWchar EllipsisCodePoint; // -1 // Explicitly specify unicode codepoint of ellipsis character. When fonts are being merged first specified ellipsis will be used.
ImWchar EllipsisChar; // -1 // Explicitly specify unicode codepoint of ellipsis character. When fonts are being merged first specified ellipsis will be used.
// [Internal]
char Name[40]; // Name (strictly to ease debugging)
@ -2188,12 +2188,12 @@ struct ImFont
ImFontAtlas* ContainerAtlas; // 4-8 // out // // What we has been loaded into
const ImFontConfig* ConfigData; // 4-8 // in // // Pointer within ContainerAtlas->ConfigData
short ConfigDataCount; // 2 // in // ~ 1 // Number of ImFontConfig involved in creating this font. Bigger than 1 when merging multiple font sources into one ImFont.
ImWchar FallbackChar; // 2 // in // = '?' // Replacement glyph if one isn't found. Only set via SetFallbackChar()
ImWchar FallbackChar; // 2 // in // = '?' // Replacement character if a glyph isn't found. Only set via SetFallbackChar()
ImWchar EllipsisChar; // 2 // out // = -1 // Character used for ellipsis rendering.
float Scale; // 4 // in // = 1.f // Base font scale, multiplied by the per-window font scale which you can adjust with SetWindowFontScale()
float Ascent, Descent; // 4+4 // out // // Ascent: distance from top to bottom of e.g. 'A' [0..FontSize]
int MetricsTotalSurface;// 4 // out // // Total surface in pixels to get an idea of the font rasterization/texture cost (not exact, we approximate the cost of padding between glyphs)
bool DirtyLookupTables; // 1 // out //
ImWchar EllipsisCodePoint; // -1 // out // // Override a codepoint used for ellipsis rendering.
// Methods
IMGUI_API ImFont();

View File

@ -3283,7 +3283,8 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref)
ImGui::SameLine(); HelpMarker("Note than the default embedded font is NOT meant to be scaled.\n\nFont are currently rendered into bitmaps at a given size at the time of building the atlas. You may oversample them to get some flexibility with scaling. You can also render at multiple sizes and select which one to use at runtime.\n\n(Glimmer of hope: the atlas system should hopefully be rewritten in the future to make scaling more natural and automatic.)");
ImGui::InputFloat("Font offset", &font->DisplayOffset.y, 1, 1, "%.0f");
ImGui::Text("Ascent: %f, Descent: %f, Height: %f", font->Ascent, font->Descent, font->Ascent - font->Descent);
ImGui::Text("Fallback character: '%c' (%d)", font->FallbackChar, font->FallbackChar);
ImGui::Text("Fallback character: '%c' (U+%04X)", font->FallbackChar, font->FallbackChar);
ImGui::Text("Ellipsis character: '%c' (U+%04X)", font->EllipsisChar);
const float surface_sqrt = sqrtf((float)font->MetricsTotalSurface);
ImGui::Text("Texture surface: %d pixels (approx) ~ %dx%d", font->MetricsTotalSurface, (int)surface_sqrt, (int)surface_sqrt);
for (int config_i = 0; config_i < font->ConfigDataCount; config_i++)

View File

@ -1426,9 +1426,9 @@ ImFontConfig::ImFontConfig()
MergeMode = false;
RasterizerFlags = 0x00;
RasterizerMultiply = 1.0f;
EllipsisChar = (ImWchar)-1;
memset(Name, 0, sizeof(Name));
DstFont = NULL;
EllipsisCodePoint = (ImWchar)-1;
}
//-----------------------------------------------------------------------------
@ -1619,8 +1619,8 @@ ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg)
memcpy(new_font_cfg.FontData, font_cfg->FontData, (size_t)new_font_cfg.FontDataSize);
}
if (new_font_cfg.DstFont->EllipsisCodePoint == (ImWchar)-1)
new_font_cfg.DstFont->EllipsisCodePoint = font_cfg->EllipsisCodePoint;
if (new_font_cfg.DstFont->EllipsisChar == (ImWchar)-1)
new_font_cfg.DstFont->EllipsisChar = font_cfg->EllipsisChar;
// Invalidate texture
ClearTexData();
@ -1656,7 +1656,7 @@ ImFont* ImFontAtlas::AddFontDefault(const ImFontConfig* font_cfg_template)
font_cfg.SizePixels = 13.0f * 1.0f;
if (font_cfg.Name[0] == '\0')
ImFormatString(font_cfg.Name, IM_ARRAYSIZE(font_cfg.Name), "ProggyClean.ttf, %dpx", (int)font_cfg.SizePixels);
font_cfg.EllipsisCodePoint = (ImWchar)0x0085;
font_cfg.EllipsisChar = (ImWchar)0x0085;
const char* ttf_compressed_base85 = GetDefaultCompressedFontDataTTFBase85();
const ImWchar* glyph_ranges = font_cfg.GlyphRanges != NULL ? font_cfg.GlyphRanges : GetGlyphRangesDefault();
@ -2204,22 +2204,19 @@ void ImFontAtlasBuildFinish(ImFontAtlas* atlas)
// Ellipsis character is required for rendering elided text. We prefer using U+2026 (horizontal ellipsis).
// However some old fonts may contain ellipsis at U+0085. Here we auto-detect most suitable ellipsis character.
// FIXME: Also note that 0x2026 is currently seldomly included in our font ranges. Because of this we are more likely to use three individual dots.
for (int i = 0; i < atlas->Fonts.size(); i++)
{
ImFont* font = atlas->Fonts[i];
if (font->EllipsisCodePoint == (ImWchar)-1)
{
const ImWchar ellipsis_variants[] = {(ImWchar)0x2026, (ImWchar)0x0085, (ImWchar)0};
for (int j = 0; ellipsis_variants[j] != (ImWchar) 0; j++)
if (font->EllipsisChar != (ImWchar)-1)
continue;
const ImWchar ellipsis_variants[] = { (ImWchar)0x2026, (ImWchar)0x0085 };
for (int j = 0; j < IM_ARRAYSIZE(ellipsis_variants); j++)
if (font->FindGlyphNoFallback(ellipsis_variants[j]) != NULL) // Verify glyph exists
{
ImWchar ellipsis_codepoint = ellipsis_variants[j];
if (font->FindGlyph(ellipsis_codepoint) != font->FallbackGlyph) // Verify glyph exists
{
font->EllipsisCodePoint = ellipsis_codepoint;
break;
}
font->EllipsisChar = ellipsis_variants[j];
break;
}
}
}
}
@ -2490,6 +2487,7 @@ ImFont::ImFont()
FontSize = 0.0f;
FallbackAdvanceX = 0.0f;
FallbackChar = (ImWchar)'?';
EllipsisChar = (ImWchar)-1;
DisplayOffset = ImVec2(0.0f, 0.0f);
FallbackGlyph = NULL;
ContainerAtlas = NULL;
@ -2499,7 +2497,6 @@ ImFont::ImFont()
Scale = 1.0f;
Ascent = Descent = 0.0f;
MetricsTotalSurface = 0;
EllipsisCodePoint = (ImWchar)-1;
}
ImFont::~ImFont()