imgui icon indicating copy to clipboard operation
imgui copied to clipboard

Font display does not account for descent and ascent when using ImGuiFreeTypeBuilderFlags_Bitmap

Open pipiwoaini opened this issue 9 months ago • 6 comments

Version/Branch of Dear ImGui:

Version 1.9.5, Branch: docking

Back-ends:

imgui_impl_SDL3.cpp + imgui_impl_SDL3Renderer.cpp

Compiler, OS:

Win22+vs2022

Full config/build information:

No response

Details:

“I’m using fonts created with Freetype and FontBuilderFlags |= ImGuiFreeTypeBuilderFlags_Bitmap. I noticed that in ImGui, the line height in RenderText is directly calculated using the font size, which results in no spacing between lines of text. I modified the line_height to (fabs(Descent) + fabs(Ascent)) * scale, and it displayed correctly. Is this a bug?”

Screenshots/Video:

No response

Minimal, Complete and Verifiable Example code:

void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, float wrap_width, bool cpu_fine_clip) { if (!text_end) text_end = text_begin + strlen(text_begin); // ImGui:: functions generally already provides a valid text_end, so this is merely to handle direct calls.

// Align to be pixel perfect
float x = IM_TRUNC(pos.x);
float y = IM_TRUNC(pos.y);
if (y > clip_rect.w)
    return;

const float scale = size / FontSize;
const float line_height = (fabs(Descent) + fabs(Ascent)) * scale
const float origin_x = x;
const bool word_wrap_enabled = (wrap_width > 0.0f);

pipiwoaini avatar Feb 20 '25 09:02 pipiwoaini

Fonts loaded using ImGuiFreeTypeBuilderFlags_Bitmap are calculating size differently:

// Vuhdo: I'm not sure how to deal with font sizes properly. As far as I understand, currently ImGui assumes that the 'pixel_height'
// is a maximum height of an any given glyph, i.e. it's the sum of font's ascender and descender. Seems strange to me.
// NB: FT_Set_Pixel_Sizes() doesn't seem to get us the same result.
FT_Size_RequestRec req;
req.type = (UserFlags & ImGuiFreeTypeBuilderFlags_Bitmap) ? FT_SIZE_REQUEST_TYPE_NOMINAL : FT_SIZE_REQUEST_TYPE_REAL_DIM;
req.width = 0;
req.height = (uint32_t)(pixel_height * 64 * RasterizationDensity);
req.horiResolution = 0;
req.vertResolution = 0;
FT_Request_Size(Face, &req);

We might have an issue to fix but you could specify which font you are using and provide e.g. a repro in the form of font loading code?

ocornut avatar Feb 20 '25 10:02 ocornut

I’m using the Noto Sans font, and the spacing issue is very easy to reproduce with Chinese characters. Below is my code. ImFontConfig m_font_config; float m_dpi = 1.00f;

m_font_config.FontDataOwnedByAtlas = false;
m_font_config.OversampleH = 1;
m_font_config.OversampleV = 1;
m_font_config.PixelSnapH = true;
m_font_config.FontBuilderFlags |= ImGuiFreeTypeBuilderFlags_Bitmap;
m_font_config.RasterizerDensity = 1.00f;

ImFontAtlas* atlas = ImGui::GetIO().Fonts;
atlas->Clear();

for (auto& iter : g_font.second) {
    vector<uint8_t> m_font_data;
    if (((FONTINFO*)iter)->font_weight == 0) {
        m_font_data = (*g_zip_font)["Regular.ttf"];
    }
    else if (((FONTINFO*)iter)->font_weight == 1) {
        m_font_data = (*g_zip_font)["Medium.ttf"];
    }
    else if (((FONTINFO*)iter)->font_weight == 2) {
        m_font_data = (*g_zip_font)["SemiBold.ttf"];
    }

    ((FONTINFO*)iter)->data = atlas->AddFontFromMemoryTTF(m_font_data.data(), m_font_data.size(), ((FONTINFO*)iter)->font_px, &m_font_config, ((FONTINFO*)iter)->font_range.data());
    if (!((FONTINFO*)iter)->data) {
        printf("Error in %dpx font\n", ((FONTINFO*)iter)->font_px);
    }
    else {
        ((ImFont*)((FONTINFO*)iter)->data)->Scale = m_dpi;
    }
}

atlas->Build();

ImGui_ImplSDLRenderer3_DestroyFontsTexture();
if (ImGui_ImplSDLRenderer3_CreateFontsTexture()) {
    g_breload = false;
}
else {
    g_breload = true;
}

pipiwoaini avatar Feb 20 '25 11:02 pipiwoaini

Could you specify some example characters and show a screenshot? We rarely have time to handle all issues immediately, so any extra reference is helpful when needing to come back at things. Thanks!

ocornut avatar Feb 20 '25 11:02 ocornut

int main(int, char**) { // Setup SDL if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD)) { printf("Error: SDL_Init(): %s\n", SDL_GetError()); return -1; }

// Create window with SDL_Renderer graphics context
Uint32 window_flags = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIDDEN;
SDL_Window* window = SDL_CreateWindow("Dear ImGui SDL3+SDL_Renderer example", 1280, 720, window_flags);
if (window == nullptr)
{
    printf("Error: SDL_CreateWindow(): %s\n", SDL_GetError());
    return -1;
}
SDL_Renderer* renderer = SDL_CreateRenderer(window, nullptr);
SDL_SetRenderVSync(renderer, 1);
if (renderer == nullptr)
{
    SDL_Log("Error: SDL_CreateRenderer(): %s\n", SDL_GetError());
    return -1;
}
SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
SDL_ShowWindow(window);

// Setup Dear ImGui context
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO(); (void)io;
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;     // Enable Keyboard Controls
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;      // Enable Gamepad Controls

// Setup Dear ImGui style
ImGui::StyleColorsDark();
//ImGui::StyleColorsLight();

// Setup Platform/Renderer backends
ImGui_ImplSDL3_InitForSDLRenderer(window, renderer);
ImGui_ImplSDLRenderer3_Init(renderer);

ImFontConfig m_font_config;

m_font_config.FontDataOwnedByAtlas = false;
m_font_config.OversampleH = 1;
m_font_config.OversampleV = 1;
m_font_config.PixelSnapH = true;
m_font_config.FontBuilderFlags |= ImGuiFreeTypeBuilderFlags_Bitmap;
m_font_config.RasterizerDensity = 1;
ImFont* m_font = ImGui::GetIO().Fonts->AddFontFromFileTTF("C:\\Users\\fox\\Desktop\\imgui-master\\Medium.ttf", 13, &m_font_config, ImGui::GetIO().Fonts->GetGlyphRangesChineseSimplifiedCommon());

while (true)
{
    SDL_Event event;
    while (SDL_PollEvent(&event))
    {
        ImGui_ImplSDL3_ProcessEvent(&event);
    }
    if (SDL_GetWindowFlags(window) & SDL_WINDOW_MINIMIZED)
    {
        SDL_Delay(10);
        continue;
    }

    // Start the Dear ImGui frame
    ImGui_ImplSDLRenderer3_NewFrame();
    ImGui_ImplSDL3_NewFrame();
    ImGui::NewFrame();

    ImGui::PushFont(m_font);
    ImGui::ShowDemoWindow();
        ImGui::PopFont();

    // 2. Show a simple window that we create ourselves. We use a Begin/End pair to create a named window.
    {
        static float f = 0.0f;
        static int counter = 0;

        ImGui::Begin("Hello, world!");                          // Create a window called "Hello, world!" and append into it.

        ImGui::PushFont(m_font);
        ImGui::Text("测试文本\n测试文本");
        ImGui::PopFont();

        ImGui::End();
    }


    // Rendering
    ImGui::Render();
    //SDL_RenderSetScale(renderer, io.DisplayFramebufferScale.x, io.DisplayFramebufferScale.y);
    //SDL_SetRenderDrawColorFloat(renderer, clear_color.x, clear_color.y, clear_color.z, clear_color.w);
    SDL_RenderClear(renderer);
    ImGui_ImplSDLRenderer3_RenderDrawData(ImGui::GetDrawData(), renderer);
    SDL_RenderPresent(renderer);
}

#ifdef EMSCRIPTEN EMSCRIPTEN_MAINLOOP_END; #endif

// Cleanup
ImGui_ImplSDLRenderer3_Shutdown();
ImGui_ImplSDL3_Shutdown();
ImGui::DestroyContext();

SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();

return 0;

} This is the test code. It’s very simple, just use FreeType + ImGuiFreeTypeBuilderFlags_Bitmap to reproduce the issue. The font sinking in showDemoWindow is also caused by Descent and Ascent. The Noto Sans font is developed by Google, and you can download it from the internet. If the line height is not changed, the screenshot is as follows. Image If the line height is changed, the spacing between multiple lines of text will be accurate. Our UI engineer has already done a pixel-level comparison. The screenshot is as follows. Image

pipiwoaini avatar Feb 21 '25 03:02 pipiwoaini

This is the font file, you can use it or download it from Google Fonts Medium.ttf.zip

pipiwoaini avatar Feb 21 '25 04:02 pipiwoaini

Thank you. I think it is specifically caused by ImGuiFreeTypeBuilderFlags_Bitmap mode but it'll be a good occasion to rework and standardize how those sizes are specified.

ocornut avatar Feb 21 '25 16:02 ocornut

Linking to #8857 which is related.

ocornut avatar Sep 23 '25 15:09 ocornut