ocornut / imgui

Dear ImGui: Bloat-free Graphical User interface for C++ with minimal dependencies
MIT License
57.68k stars 9.9k forks source link

Loaded font looks smaller that it should with given size #4780

Open Grantim opened 2 years ago

Grantim commented 2 years ago

Version/Branch of Dear ImGui: brunch: master commit hash: 78c6435dbb65e84897f22cf8d4a6c5169c3775bc

Dear ImGui 1.86 WIP (18513)
--------------------------------
sizeof(size_t): 8, sizeof(ImDrawIdx): 4, sizeof(ImDrawVert): 20
define: __cplusplus=199711
define: _WIN32
define: _WIN64
define: _MSC_VER=1929
define: _MSVC_LANG=202004
--------------------------------
io.BackendPlatformName: imgui_impl_glfw
io.BackendRendererName: imgui_impl_opengl3
io.ConfigFlags: 0x00000000
io.ConfigInputTextCursorBlink
io.ConfigWindowsResizeFromEdges
io.ConfigMemoryCompactTimer = 60.0
io.BackendFlags: 0x00000006
 HasMouseCursors
 HasSetMousePos
--------------------------------
io.Fonts: 2 fonts, Flags: 0x00000000, TexSize: 512,1024
io.DisplaySize: 1280.00,800.00
io.DisplayFramebufferScale: 1.00,1.00
--------------------------------
style.WindowPadding: 8.00,8.00
style.WindowBorderSize: 1.00
style.FramePadding: 4.00,3.00
style.FrameRounding: 5.00
style.FrameBorderSize: 0.00
style.ItemSpacing: 8.00,4.00
style.ItemInnerSpacing: 4.00,4.00

My Issue/Question:

I am trying to change default font in my application but it seems that some fonts loading works unexpected: font size doest match to given value. For examle I changed default font to windows Consola (C:\Windows\Fonts\Consola.ttf) and it looks fine (screenshot 1) And then I tried windows Segoe UI (C:\Windows\Fonts\segoeui.ttf) with same size and it looks smaller then I expect (screenshot 2)

Screenshots/Video There is notepad with "Debug" in the same font and font size as Dear ImGui in these screenshots

Screenshot 1 (text in notepad is a bit bigger): image

Screenshot 2 (text in notepad is much bigger): image

Standalone, minimal, complete and verifiable example: I tried it with IMGUI_ENABLE_FREETYPE and without, got same result

    std::filesystem::path fontPath = "C:\\Windows\\Fonts\\segoeui.ttf";
    ImGuiIO& io = ImGui::GetIO();
    io.Fonts->AddFontFromFileTTF( fontPath.string().c_str(), 14, nullptr, nullptr);
    io.Fonts->Build();
ocornut commented 2 years ago

Hello,

I don't think there is a proper standard for it, even stb_truetype and Freetype AFAIK can give slightly different result.

Also with DPI scaling involved at the OS level some things can be more confusing.

What is actually your problem? Why would you need fonts to precisely match the size of another application? Can't you just increase the size to get toward a size that's good for your use, or expose that setting to the user?

Grantim commented 2 years ago

I can workaround it with giving other size to font Segoe UI but it is a little confusing that changing font leads to chages in font size

ocornut commented 2 years ago

I'm not sure what to say about this. I suspect OS function have higher-level systems trying to normalize fonts based on other metrics than the raw metrics provided in the TTF/OTF data.

The stb_truetype renderer uses:

const float scale = (cfg.SizePixels > 0) ? stbtt_ScaleForPixelHeight(&src_tmp.FontInfo, cfg.SizePixels) : stbtt_ScaleForMappingEmToPixels(&src_tmp.FontInfo, -cfg.SizePixels);

The freetype renderer uses:

void FreeTypeFont::SetPixelHeight(int pixel_height)
{
    // 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;
    req.horiResolution = 0;
    req.vertResolution = 0;
    FT_Request_Size(Face, &req);

    // Update font info
    FT_Size_Metrics metrics = Face->size->metrics;
    Info.PixelHeight = (uint32_t)pixel_height;
    Info.Ascender = (float)FT_CEIL(metrics.ascender);
    Info.Descender = (float)FT_CEIL(metrics.descender);
    Info.LineSpacing = (float)FT_CEIL(metrics.height);
    Info.LineGap = (float)FT_CEIL(metrics.height - metrics.ascender + metrics.descender);
    Info.MaxAdvanceWidth = (float)FT_CEIL(metrics.max_advance);
}

If you feel like investigating this it would be good.

ocornut commented 2 years ago

Addenum: it is possible that imgui_freetype forcefully tried to mimick what the stb_truetype renderer did for scale, and both may need to be changed.

Grantim commented 2 years ago

Thank you, I'll have a look!

Grantim commented 2 years ago

I just tried

    std::filesystem::path fontPath = "C:\\Windows\\Fonts\\segoeui.ttf";
    ImGuiIO& io = ImGui::GetIO();
    ImFontConfig config;
    config.FontBuilderFlags = ImGuiFreeTypeBuilderFlags_Bitmap;
    io.Fonts->AddFontFromFileTTF( fontPath.string().c_str(), 14, &config, nullptr);
    io.Fonts->Build();

and it helped, thank you!

Grantim commented 2 years ago

With ImGuiFreeTypeBuilderFlags_Bitmap font size become correct but now other problem comes out: each glyph shifted out of its bounds image

etkramer commented 2 years ago

With ImGuiFreeTypeBuilderFlags_Bitmap font size become correct but now other problem comes out: each glyph shifted out of its bounds

Can reproduce.

tksuoran commented 2 years ago

I see some issues in FreeTypeFont::SetPixelHeight():

Suggestions:

A super quick test hacking horiResolution and vertResolution to 96 produces pixel size that is probably closer to what notepad shows. But the text layout code in ImGui is now off, because of pixel height vs. point height confusion.

tksuoran commented 2 years ago

Notes about my experiment:

This is not quite enough to get font rendering right. One should not use fixed DPI value 96, instead value should be queried from platform / OS.

Changing the font API so that it uses points instead of pixels is a breaking change. This could still be handled as a branch or possibly compile time define. Or there could be separate font creating functions for creating fonts using old style (OS DPI ignorant) and new (proposed) style (OS DPI aware). This is hypothetical though, as my quick experiment did not produce perfect rendering.

Rendering comparison against Notepad: image That was rendered with my custom (erhe) backend. I can see that the font looks too fat, so here is style edited to look more like Notepad: image

etkramer commented 2 years ago

It's probably worth noting that Win10 Notepad (as that seems to be) uses GDI and therefore ClearType for it's text rendering. Win11 Notepad, being a XAML app, uses grayscale AA instead which should be far closer to what ImGui looks like with FreeType.

ruby3141 commented 1 month ago

That's because Imgui uses TrueType hhea ascent/descent difference rather than em(like almost everyone does) as font size. https://github.com/ocornut/imgui/blob/6ccc561a2ab497ad4ae6ee1dbd3b992ffada35cb/imgui_draw.cpp#L2892 https://github.com/ocornut/imgui/blob/6ccc561a2ab497ad4ae6ee1dbd3b992ffada35cb/imstb_truetype.h#L763-L769 https://github.com/ocornut/imgui/blob/6ccc561a2ab497ad4ae6ee1dbd3b992ffada35cb/imstb_truetype.h#L2665-L2669 (At first glance it seems you can toss negative size to use em sizing, but negative font size is prohibited).

Here's an article about how CSS handles font metrics, image

And here's how Winform handles font size.

imgui_freetype uses FT_SIZE_REQUEST_TYPE_NOMINAL(Which FreeType officially recommends to use for scaling) only when ImGuiFreeTypeBuilderFlags_Bitmap is enabled. \ If not enabled, probably to mimic imstb's behavior, it uses FT_SIZE_REQUEST_TYPE_REAL_DIM, which is also based on TrueType hhea ascent/descent. https://github.com/ocornut/imgui/blob/6ccc561a2ab497ad4ae6ee1dbd3b992ffada35cb/misc/freetype/imgui_freetype.cpp#L232-L238

ocornut commented 1 month ago

Thanks for the great and detailed comment! I am open to fixing both eventually.

petrgeorgievsky commented 1 month ago

Not sure if that's the correct way to fix the issue with incorrect font size when using ImGuiFreeTypeBuilderFlags_Bitmap. The issue seem to be caused by setting ImFont::FontSize to value the font was configured, but when FT_SIZE_REQUEST_TYPE_NOMINAL is enabled it seems that fonts should instead use metrics.height as an metric for line height. font_scale.patch

ruby3141 commented 1 month ago

Actually what I mean was only ImGuiFreeTypeBuilderFlags_Bitmap has glyphs scaled "correctly"-ish.

The reason why I tried not to use word "correct" or any kind of word implying rightfulness, is scaling glyph based on units_per_em is "de-facto standard by convention", not "standard defined behavior" as far as I know.

TrueType hhea ascent/descent is for representing font designers' intention about line height when using the font, so scaling glyph based on them is perfectly normal if you want to align "document" with lined notebook or something.

But imgui is a user interface library, not a word processor. Scaling glyphs based on font-defined line height can cause size inconsistency between texts and widgets. Using units_per_em for scaling basis gives similar sized glyphs regardless of what font you use, and it would help making texts and widgets look uniform.