From 57623c15ddae10a6b0ba6b206d2502e7951dd705 Mon Sep 17 00:00:00 2001 From: omar Date: Tue, 17 Sep 2019 10:00:28 +0200 Subject: [PATCH] Font: Narrow ellipsis: various minor stylistic tweaks (#2775) --- docs/CHANGELOG.txt | 4 +++ docs/TODO.txt | 1 + imgui.cpp | 61 ++++++++++++++++++++-------------------------- imgui.h | 6 ++--- imgui_demo.cpp | 3 ++- imgui_draw.cpp | 29 ++++++++++------------ 6 files changed, 49 insertions(+), 55 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 6e487ae5..36ba83ba 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -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 diff --git a/docs/TODO.txt b/docs/TODO.txt index 70c64c40..df413b7e 100644 --- a/docs/TODO.txt +++ b/docs/TODO.txt @@ -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 diff --git a/imgui.cpp b/imgui.cpp index 7921b1d1..4a606daa 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -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 { diff --git a/imgui.h b/imgui.h index d4a81c3b..9e875f89 100644 --- a/imgui.h +++ b/imgui.h @@ -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(); diff --git a/imgui_demo.cpp b/imgui_demo.cpp index fd1d85a3..e8e87469 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -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++) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index b4801c26..dd21523e 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -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()