imgui icon indicating copy to clipboard operation
imgui copied to clipboard

Merged Fonts vertical alignment

Open rickyviking opened this issue 4 years ago • 10 comments

This issue is related to https://github.com/ocornut/imgui/issues/2857

When merging an icon font (Google Material Icons) to the Roboto fonts, both loaded at the same size (20px), I'm seeing a vertical misalignment (4px), as shown by the following images:

  1. merged fonts without using vertical offset => misalignment
  2. merged fonts adding a vertical offset => OK, fonts aligned
  3. loading the Google Material fonts separately without merging, the fonts are aligned correctly.

The misalignment is introduced only when merging the 2 fonts, probably due to the different value of Descent (-1 and -5) computed for the 2 fonts independently - not sure how this value is computed.

Using the vertical offset as in point 2) above solves the issue, not sure if this is an issue or an expected behavior. Dear ImGui Version 1.81

  1. [merged font, no offset] imgui_icons_misaligned

  2. [merged font, 4px vertical offset] imgui_icons_offset

  3. [fonts loaded independently, no merge] imgui_icons_no_merge

rickyviking avatar May 11 '21 12:05 rickyviking

I think I made a mistake in 9da53bcecdc2273fef86f032ad55c36393c38ebd should have used current font's ascent value instead of dst_font->Ascent. I believe changing it to ascent would be a move in the good direction and may break lots of hardcoded GlyphOffset value.

ocornut avatar May 24 '21 13:05 ocornut

It's actually trickier than that. Current code ensure that all glyphs are sharing the same baseline, which is desirable e.g. when merging two text fonts (for western + asian language). I would argue it is probably similarly desirable when merging a text font + an icon font. image (This is a weird example but in reality it would make sense when combining asian + non-asian glyphs)

Using destination font ascent (current code) = same baseline

  • Inconsistency of offset when a font is merged vs not merged (your bug)
  • Multiple text fonts are naturally on same baseline
[00000] src 0 size 20.00 -> dst 0: src_ascent/descent 16.00,-5.00 dst_ascent/descent 16.00/-5.00 -> font_off_y 16.00
[00000] src 1 size 20.00 -> dst 0: src_ascent/descent 21.00,-1.00 dst_ascent/descent 16.00/-5.00 -> font_off_y 16.00
[00000] src 2 size 15.00 -> dst 1: src_ascent/descent 12.00,-4.00 dst_ascent/descent 12.00/-4.00 -> font_off_y 12.00
[00000] src 3 size 20.00 -> dst 1: src_ascent/descent 21.00,-1.00 dst_ascent/descent 12.00/-4.00 -> font_off_y 12.00
[00000] src 4 size 20.00 -> dst 2: src_ascent/descent 16.00,-5.00 dst_ascent/descent 16.00/-5.00 -> font_off_y 16.00
[00000] src 5 size 15.00 -> dst 2: src_ascent/descent 16.00,-1.00 dst_ascent/descent 16.00/-5.00 -> font_off_y 16.00

image

Using source font ascent:

  • Avoid the inconsistency of offset when a font is merged vs not merged
  • Require more manual offsetting when font are different sizes (e.g. slightly bigger icons)
[00000] src 0 size 20.00 -> dst 0: src_ascent/descent 16.00,-5.00 dst_ascent/descent 16.00/-5.00 -> font_off_y 16.00
[00000] src 1 size 20.00 -> dst 0: src_ascent/descent 21.00,-1.00 dst_ascent/descent 16.00/-5.00 -> font_off_y 21.00
[00000] src 2 size 15.00 -> dst 1: src_ascent/descent 12.00,-4.00 dst_ascent/descent 12.00/-4.00 -> font_off_y 12.00
[00000] src 3 size 20.00 -> dst 1: src_ascent/descent 21.00,-1.00 dst_ascent/descent 12.00/-4.00 -> font_off_y 21.00
[00000] src 4 size 20.00 -> dst 2: src_ascent/descent 16.00,-5.00 dst_ascent/descent 16.00/-5.00 -> font_off_y 16.00
[00000] src 5 size 15.00 -> dst 2: src_ascent/descent 16.00,-1.00 dst_ascent/descent 16.00/-5.00 -> font_off_y 16.00

image

The code before 9da53bcecdc2273fef86f032ad55c36393c38ebd attempted perform centering (perhaps incorrectly) and removed it in favor of explicit GlyphOffset values.

I want to say current behavior is actually better BUT creates a change depending on whether the icon font is merged or not, which is not ideal. Genuine question: your post state "I'm seeing a vertical misalignment" but what constitute a "misalignment" is entirely depending on your expectation. How do you use that icon font in the first place? How/why did you even notice the misalignment?

As we are aiming to facilitate use of mixed fonts at any size (as we expect to make resizing automatic), we may want to find a solution for this where user indication (if any) can be suitably scaled, but I don't yet know how to design that solution.

ocornut avatar May 24 '21 15:05 ocornut

Genuine answer :) I wanted to add some icons besides text, for instance to show a informative sign in a tooltip: icon_baseline_02 or to indicate that a tree node is a folder in an asset browser: icon_baseline

Following the example to merge an icon font with the main text font, I expected that their baseline would be aligned, being the 2 fonts loaded at the same resolution (20px in my example). This is what the example shows as a result.
Say that I'd expect fonts (text or icon) merged with the same size would be aligned (as it happens to be when they're not merged). If that's not the case, I'd just add this info in the Fonts document.

rickyviking avatar May 24 '21 15:05 rickyviking

The point is that their baseline ARE aligned in your two screenshots.

ocornut avatar May 24 '21 15:05 ocornut

eheh right, because I am manually adding a 4px vertical alignment - case 2) in my original post.

My code to obtain the screenshots above is:

   // load custom font
   std::string fontFile = "path/to/RobotoCondensed-Regular.ttf";
   ImFont* assetTreeFont = ImGui::GetIO().Fonts->AddFontFromFileTTF(fontFile.c_str(), 20);

   // merge google icons font
   ImFontConfig imFontConfig;
   imFontConfig.MergeMode = true;
   imFontConfig.GlyphOffset = { 0.f, 4.f };
   imFontConfig.GlyphMinAdvanceX = 20.0f; // Use if you want to make the icon monospaced
   
   ImVector<ImWchar> icon_ranges;
   ImFontGlyphRangesBuilder builder;

   builder.AddChar(0xe2c7); // ICON_MD_FOLDER
   // [...]   
   builder.BuildRanges(&icon_ranges);    // Build the final result (ordered ranges with all the unique characters submitted)

   fontFile = "path/to/MaterialIconsOutlined-Regular.otf";
   ImGui::GetIO().Fonts->AddFontFromFileTTF(fontFile.c_str(), 20.f, &imFontConfig, icon_ranges.Data);

rickyviking avatar May 24 '21 16:05 rickyviking

eheh right, because I am manually adding a 4px vertical alignment - case 2) in my original post.

Oops, right!

ocornut avatar May 24 '21 16:05 ocornut

I think replacing

const float font_off_x = cfg.GlyphOffset.x;
const float font_off_y = cfg.GlyphOffset.y + IM_ROUND(dst_font->Ascent);

with

const float merged_off_y = cfg.MergeMode ? (descent - dst_font->Descent) * (cfg.SizePixels / dst_font->FontSize) : 0.0f;
const float font_off_x = cfg.GlyphOffset.x;
const float font_off_y = cfg.GlyphOffset.y + IM_ROUND(dst_font->Ascent + merged_off_y);

Might be good. EDIT No it's not, doesn't work as well when merging two text fonts in other tests.

image

ocornut avatar May 24 '21 17:05 ocornut

The more I dig the more think there is no magic solution other than exposing a way to specify vertical alignment (for merged fonts) on a per font basis. This would be loosely equivalent to specifying the +4 here but in a way that would more naturally fit for scaling.

The equation above is merely a hack which happens to work for this font, but would fail at others. A good test case is to merge two copies for the same font but with different glyph sizes.

For now I would suggest keeping that GlyphOffset but this is a topic we will need to explore further in the future.

ocornut avatar May 24 '21 18:05 ocornut

Didn't expect to create such a turmoil. I agree that leaving things as they are works just fine, I'd only suggest to mention the fact in the Fonts doc, as at the beginning I thought I was doing something wrong.

rickyviking avatar May 24 '21 21:05 rickyviking

In case anyone else is trying to align their icon fonts, here's a working solution for imgui 1.92. This keeps the icon font (pretty near) centered vertically, regardless of font_size_pixels and icon_scaling. The linear coefficients, 0.5 and 0.3, that specify the GlyphOffset, are specific to the icon font; I am using the Lucide icons here. If you want baseline alignment (for some reason) you can pick different coefficients.

// Icon font symbol header from juliettef
#include "IconsLucide.h"

// ...

float font_size_pixels = 18.0;  // Or whatever you'd like.
float icon_scaling = 1.0f;  // Or whatever you'd like.

ImFont* text_font = io->Fonts->AddFontFromFileTTF(text_font_filename, font_size_pixels, &config, extra_ranges);

// ...

ImFontConfig config;
config.MergeMode = true;
// Align vertically; the coefficients are specific to the particular icon font we're using.
config.GlyphOffset = { 0.0f, font_size_pixels * (0.5f*icon_scaling - 0.3f) };
// Make the icons monospaced. (Unnecessary for Lucide Icons.)
//config.GlyphMinAdvanceX = font_size_pixels * 1.0f;
static const ImWchar icon_ranges[] = { ICON_MIN_LC, ICON_MAX_LC, 0 };
ImFont* icon_font = io->Fonts->AddFontFromFileTTF(icon_font_filename, font_size_pixels * icon_scaling, &config, icon_ranges);

yshklarov avatar Apr 17 '25 22:04 yshklarov