ocornut / imgui

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

Feature Request: Variable font weight support for variable OTF fonts #7477

Open Stehsaer opened 3 months ago

Stehsaer commented 3 months ago

Version/Branch of Dear ImGui:

Version 1.90.2, Branch: master

Back-ends:

imgui_impl_GLFW.cpp + imgui_impl_Vulkan.cpp

Compiler, OS:

Windows 11 + (MinGW's GCC / Clang(MSVC) / Clang(MInGW) / MSVC)

Full config/build information:

Dear ImGui 1.90.2 (19020)
--------------------------------

sizeof(size_t): 8, sizeof(ImDrawIdx): 2, sizeof(ImDrawVert): 20
define: __cplusplus=199711
define: _WIN32
define: _WIN64
define: _MSC_VER=1938
define: _MSVC_LANG=201402
--------------------------------

io.BackendPlatformName: imgui_impl_glfw
io.BackendRendererName: imgui_impl_vulkan
io.ConfigFlags: 0x00000000
io.ConfigInputTextCursorBlink
io.ConfigWindowsResizeFromEdges
io.ConfigMemoryCompactTimer = 60.0
io.BackendFlags: 0x0000000E
 HasMouseCursors
 HasSetMousePos
 RendererHasVtxOffset
--------------------------------

io.Fonts: 1 fonts, Flags: 0x00000000, TexSize: 512,128
io.DisplaySize: 2560.00,1494.00
io.DisplayFramebufferScale: 1.00,1.00
--------------------------------

style.WindowPadding: 12.00,12.00
style.WindowBorderSize: 1.00
style.FramePadding: 6.00,4.00
style.FrameRounding: 0.00
style.FrameBorderSize: 0.00
style.ItemSpacing: 12.00,6.00
style.ItemInnerSpacing: 6.00,6.00

Details:

Feature Request

Hi! I'm loading Catarell font from GNOME FOUNDATION (Link), using imgui_freetype. It was loaded perfectly but I've noticed that the font weight is too thin and it didn't match my expectation. I've tried specifying FontBuilderFlags in ImFontConfig, where ImGuiFreeTypeBuilderFlags_Bold made the font thicker than expectation, and ImGuiFreeTypeBuilderFlags_ForceAutoHint simply didn't affect the font weight.

Since the Catarell font is a VARIABLE Opentype font, as described in the release page, I'm requesting for a feature that adds more control over font weight for the freetype rasterizer.

  1. One option may be to add a separate option of specifying font weight in ImFontConfig, eg. using enum ImFontWeight

    ImFontConfig cfg;
    cfg.FontWeight = ImFontWeight_Bold500;

    (Explanation: it's often the case where regular stands for font weight 400, and bold stands for font weight 500) or

    ImFontConfig cfg;
    cfg.FontWeight = 500;
  2. Another option may be to add separate enum options in ImGuiFreeTypeBuilderFlags in header imgui_freetype.h and add support in the implementation

    enum ImGuiFreeTypeBuilderFlags
    {
    ImGuiFreeTypeBuilderFlags_NoHinting  = 1 << 0,   // Disable hinting. This generally generates 'blurrier' bitmap glyphs when the glyph are rendered in any of the anti-aliased modes.
    ...
    ImGuiFreeTypeBuilderFlags_Bitmap   = 1 << 9,    // Enable FreeType bitmap glyphs
    
    /* Extra Font Weight Options Added After Here*/
    ImGuiFreeTypeBuilderFlags_FontWeight100 = 1 << 10,
    ImGuiFreeTypeBuilderFlags_FontWeight200 = 2 << 10,
    ...
    ImGuiFreeTypeBuilderFlags_FontWeight900 = 9 << 10,
    };

Additional Info

Resolution and size of my monitor: 2560*1600, 16 inches Windows DPI: 150%

Screenshots/Video:

Regular image Thinner than I expected

Bold With ImGuiFreeTypeBuilderFlags_Bold specified in ImFontConfig: image This bolds too much, more than I expected.

The whole idea about this feature request is to have more control over the font weight, if the font supports, so that I can tweak to have effects I expected.

Minimal, Complete and Verifiable Example code:

Regular

io.Fonts->AddFontFromFileTTF("fonts/cantarell.otf", 16 * scale, &cfg);
ImGui::GetStyle().ScaleAllSizes(scale);

Bold

ImFontConfig cfg;
cfg.FontBuilderFlags |= ImGuiFreeTypeBuilderFlags_Bold;

io.Fonts->AddFontFromFileTTF("fonts/cantarell.otf", 16 * scale, &cfg);
ImGui::GetStyle().ScaleAllSizes(scale);

(Explanation: scale stands for content scale acquired from GLFW api)

ocornut commented 3 months ago

I am not sure how this would translate into Freetype directives or if Freetype can handle it. Have you looked into it?

alien-brother commented 3 months ago

As a quick workaround, there are some ready to use fixed versions of Cantarell in the Debian package, e.g., the .deb file here https://ftp.debian.org/debian/pool/main/f/fonts-cantarell/.

That said, variable font support would be nice.

PathogenDavid commented 3 months ago

I am not sure how this would translate into Freetype directives or if Freetype can handle it.

I briefly looked into it and it looks like variable font parameters are controlled via FreeType's Multiple Masters interface. It looks non-trivial to work with due to its flexible nature. (Although maybe a lot of it can be ignored if we narrow the scope to OpenType variable fonts?)


Additional flags would not be a great solution. Variable fonts expose much more than just weight, and a lot of the point of their existence is that the values are continuous rather than discrete. (See the examples at the top of this page.)

Exposing ImFontConfig::FontWeight or something similar also seems not ideal because it'd definitely cause confusion when it doesn't work with non-variant fonts or the stb_truetype implementation. (Ideally we want people to naturally gravitate towards explicitly loading their bold font variant in those scenarios.)

The easiest route might be to just offer a callback for users to further customize the FT_Face at the end of FreeTypeFont::InitFont and offload all the variable font complexity to them. (This should probably be done regardless as a way to allow customizing custom font axes.)

Or if we want to go a step further we could offer in-box customization of the registered OpenType axis tags -- see also MDN. (My assumption is that most variable fonts are OpenType fonts because that's what the web supports.)


The main issue would be how to get this information to the imgui_freetype implementation since the current interface doesn't offer much. My gut instinct is to add a field to ImFontConfig for loader-specific extended config, IE:

struct ImFontConfig
{
//...
    unsigned int    FontBuilderFlags;       // 0        // Settings for custom font builder. THIS IS BUILDER IMPLEMENTATION DEPENDENT. Leave as zero if unsure.
    void*           FontBuilderSettings;    // NULL     // Settings for custom font builder. THIS IS BUILDER IMPLEMENTATION DEPENDENT. THE POINTER NEEDS TO PERSIST AS LONG AS THE FONT IS ALIVE. Leave as NULL if unsure.
//...
};
struct ImGuiFreeTypeBuilderSettings
{
    // Variable font axis values (Only used for variable OpenType fonts!)
    float Weight;      // 400 // wght
    float Width;       // 100 // wdth
    float Slant;       // 0   // slnt
    float Italic;      // 0   // ital
    // opsz is intentionally omitted, my understanding is it's meant to be controlled programatically based on DPI outside of unusual circumstances that advanced users could handle in the callback.

    void (*AdvancedCustomizationCallback)(FT_Face face, const ImFontConfig& cfg, ImGuiFreeTypeBuilderFlags atlas_flags);
};

This has the minor downside of needing to keep the FontBuilderSettings pointer alive as long as the font is alive, but we already require that for ImFontConfig::GlyphRanges so it's maybe not that big of a burden to place on API consumers.