Open Grantim opened 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?
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
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.
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.
Thank you, I'll have a look!
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!
With ImGuiFreeTypeBuilderFlags_Bitmap
font size become correct but now other problem comes out: each glyph shifted out of its bounds
With ImGuiFreeTypeBuilderFlags_Bitmap font size become correct but now other problem comes out: each glyph shifted out of its bounds
Can reproduce.
I see some issues in FreeTypeFont::SetPixelHeight()
:
FT_Request_Size()
works with points, not pixelsreq.horiResolution = 0
, it uses 72 dpiSuggestions:
SetPixelHeight()
should be replaced with something like SetPointHeight()
SetPointHeight()
may need to know what DPI the platform is set to usepixel_size = point_size * resolution / 72
. See https://freetype.org/freetype2/docs/glyphs/glyphs-2.htmlA 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.
Notes about my experiment:
ImFontConfig
I added float SizePoints
, also made SizePixels
floatAddFontFromMemoryTTF()
I added font_cfg.SizePixels = std::round(static_cast<double>(font_cfg.SizePoints) * 96.0 / 72.0)
FontInfo
I replaced PixelHeight
with float PointHeight
SetPixelHeight()
to SetPointHeight(float point_height)
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: 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:
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.
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,
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
Thanks for the great and detailed comment! I am open to fixing both eventually.
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
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.
Version/Branch of Dear ImGui: brunch:
master
commit hash:78c6435dbb65e84897f22cf8d4a6c5169c3775bc
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 windowsSegoe 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):
Screenshot 2 (text in notepad is much bigger):
Standalone, minimal, complete and verifiable example: I tried it with
IMGUI_ENABLE_FREETYPE
and without, got same result