imgui icon indicating copy to clipboard operation
imgui copied to clipboard

Loaded font looks smaller that it should with given size

Open Grantim opened this issue 4 years ago • 15 comments

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();

Grantim avatar Dec 03 '21 14:12 Grantim

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?

ocornut avatar Dec 03 '21 14:12 ocornut

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

Grantim avatar Dec 03 '21 14:12 Grantim

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 avatar Dec 03 '21 14:12 ocornut

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.

ocornut avatar Dec 03 '21 14:12 ocornut

Thank you, I'll have a look!

Grantim avatar Dec 03 '21 14:12 Grantim

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 avatar Dec 03 '21 14:12 Grantim

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

Grantim avatar Dec 03 '21 15:12 Grantim

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

Can reproduce.

etkramer avatar Mar 03 '22 02:03 etkramer

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

  • FT_Request_Size() works with points, not pixels
  • By passing req.horiResolution = 0, it uses 72 dpi
  • Windows uses 96 dpi (default in most systems, but can be different/adjusted).

Suggestions:

  • SetPixelHeight() should be replaced with something like SetPointHeight()
  • SetPointHeight() may need to know what DPI the platform is set to use
  • Text rendering code must not confuse point size and pixel size. For reference: pixel_size = point_size * resolution / 72. See https://freetype.org/freetype2/docs/glyphs/glyphs-2.html

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 avatar Mar 28 '22 14:03 tksuoran

Notes about my experiment:

  • In ImFontConfig I added float SizePoints, also made SizePixels float
  • In AddFontFromMemoryTTF() I added font_cfg.SizePixels = std::round(static_cast<double>(font_cfg.SizePoints) * 96.0 / 72.0)
  • In FontInfo I replaced PixelHeight with float PointHeight
  • I renamed SetPixelHeight() to SetPointHeight(float point_height)
  • In SetPointHeight I forced req.type = FT_SIZE_REQUEST_TYPE_NOMINAL, req.horiResolution = 96 and req.vertResolution = 96

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

tksuoran avatar Mar 28 '22 18:03 tksuoran

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.

etkramer avatar Mar 28 '22 18:03 etkramer

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

ruby3141 avatar May 09 '24 06:05 ruby3141

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

ocornut avatar May 09 '24 07:05 ocornut

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

petrgeorgievsky avatar May 14 '24 10:05 petrgeorgievsky

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.

ruby3141 avatar May 14 '24 16:05 ruby3141