ImGuiNET / ImGui.NET

An ImGui wrapper for .NET.
MIT License
1.87k stars 305 forks source link

Problem merging font awesome into default font #460

Open JeffM2501 opened 8 months ago

JeffM2501 commented 8 months ago

Hello. I maintain an ImGui backend for the library Raylib (https://www.raylib.com/), for both C++ and C#.

One feature of the backend is that by default we add font awesome icons to the default font so that people can easily use them in ImGui.

I'm having a problem with the C# version of the code, no mater what I try I can't get the font to merge in anymore. This used to work a while ago, but there have been some library updates and refactors since I tested it last (changing over to events, etc..). I'm in the process of updating the C# backend to the current ImGui.net 1.90.1.1, so I know I'm on current code.

This is my font loading code for C# https://github.com/raylib-extras/rlImGui-cs/blob/23df04bc3ee07c2225594e2e378955fec57662c4/rlImGui/rlImGui.cs#L277

C#

unsafe
{
    ImFontConfig icons_config = new ImFontConfig();
    icons_config.MergeMode = 1;                      // merge the glyph ranges into the default font
    icons_config.PixelSnapH = 1;                     // don't try to render on partial pixels
    icons_config.FontDataOwnedByAtlas = 0;           // the font atlas does not own this font data

    icons_config.GlyphMaxAdvanceX = float.MaxValue;
    icons_config.RasterizerMultiply = 1.0f;
    icons_config.OversampleH = 2;
    icons_config.OversampleV = 1;

    ushort[] IconRanges = new ushort[3];
    IconRanges[0] = IconFonts.FontAwesome6.IconMin;
    IconRanges[1] = IconFonts.FontAwesome6.IconMax;
    IconRanges[2] = 0;

    fixed (ushort* range = &IconRanges[0])
    {
        // this unmanaged memory must remain allocated for the entire run of rlImgui
        IconFonts.FontAwesome6.IconFontRanges = Marshal.AllocHGlobal(6);
        Buffer.MemoryCopy(range, IconFonts.FontAwesome6.IconFontRanges.ToPointer(), 6, 6);
        icons_config.GlyphRanges = (ushort*)IconFonts.FontAwesome6.IconFontRanges.ToPointer();

        byte[] fontDataBuffer = Convert.FromBase64String(IconFonts.FontAwesome6.IconFontData);

        fixed (byte* buffer = fontDataBuffer)
        {
            var fontPtr = ImGui.GetIO().Fonts.AddFontFromMemoryTTF(new IntPtr(buffer), fontDataBuffer.Length, 11, &icons_config, IconFonts.FontAwesome6.IconFontRanges);
        }
    }
}

The font atlas that is generated does not have any of the merged characters in it. image

It never seems to load the font. It does not mater if I use the font from memory or a file on disk, so I don't think it's a data issue.

The code very similar to what I do in the C++ version of the backend, and that code does work. https://github.com/raylib-extras/rlImGui/blob/de2c361b5835e6c2075b4f314bc7d949869666f1/rlImGui.cpp#L252 C++

    ImFontConfig icons_config;
    icons_config.MergeMode = true;
    icons_config.PixelSnapH = true;
    icons_config.FontDataOwnedByAtlas = false;

    icons_config.GlyphRanges = icons_ranges;

    ImGuiIO& io = ImGui::GetIO();

    io.Fonts->AddFontFromMemoryCompressedTTF((void*)fa_solid_900_compressed_data, fa_solid_900_compressed_size, FONT_AWESOME_ICON_SIZE, &icons_config, icons_ranges);

With this code in C++, I do see the icon fonts as part of the font atlas. image

I'm sure there is something I'm missing or doing wrong, C# hasn't been my primary language for a while. Anyone have any idea what more I can do to try and track this down? Any help would be appreciated.

NoahStolk commented 7 months ago

I got this working a while ago, and I believe you must call ImGuiNative.ImFontConfig_ImFontConfig to properly create an ImFontConfig:

ImFontConfig* icons_config = ImGuiNative.ImFontConfig_ImFontConfig();
icons_config->MergeMode = 1;
// etc...

Just cloned rlImGui-cs and tried it myself. The font seems to show up correctly now in the Metrics/Debugger window. Will make a PR.

zaafar commented 7 months ago

Imgui constructors should be optional, looks like a imgui bug and you should report it to imgui repo.

JeffM2501 commented 7 months ago

Imgui constructors should be optional, looks like a imgui bug and you should report it to imgui repo.

The C++ version works just fine, so I don't think it's core ImGui issue. I think the main problem is that I was not allocating a native pointer, but a managed structure and it didn't marshal to the .net C# bindings correctly.

zaafar commented 7 months ago

I see, all good then, I was just pointing to “Constructors/destructors” documentation mentioned in https://github.com/dearimgui/dear_bindings repo.

tingspain commented 1 month ago

Hello @JeffM2501, by any chance, did you find the solution?

I am using the following code to load Font Icons to the Font Atlas and it works well with ImGui.NET 1.89.4. However, It doesn't work with ImGui.NET 1.90.0. I dont know why.

So I was wondering if you manage to make it work.

Thanks in advance.

            var io = ImGui.GetIO();

            // Add the default fonts for the App
            if(lSpace.SDK.Core.Utils.FileExists("Assets/Fonts/Manrope/Manrope-Regular.ttf"))
                io.Fonts.AddFontFromFileTTF("Assets/Fonts/Manrope/Manrope-Regular.ttf", 17.0f);

            // Add alternative bold style for the default font for the app
            if(Core.Utils.FileExists("Assets/Fonts/Manrope/Manrope-Bold.ttf"))
                io.Fonts.AddFontFromFileTTF("Assets/Fonts/Manrope/Manrope-Bold.ttf", 17.0f);

            // Add the App Icons
            if(Core.Utils.FileExists( "Assets/Fonts/appicons.ttf") ){

                    var ranges = new[] { '\ue800', '\uf2db' };

                    var rangesIntPtr = Marshal.UnsafeAddrOfPinnedArrayElement(ranges, 0);

                    ImFontConfig config = new ImFontConfig
                    {
                        MergeMode            = 1,
                        OversampleH          = 3,
                        OversampleV          = 3,
                        GlyphOffset          = System.Numerics.Vector2.Zero,
                        FontDataOwnedByAtlas = 1,
                        PixelSnapH           = 1,
                        GlyphMaxAdvanceX     = float.MaxValue,
                        RasterizerMultiply   = 1.0f
                    };

                    var configHandle = GCHandle.Alloc(config, GCHandleType.Pinned);
                    ImGui.GetIO().Fonts.AddFontFromFileTTF("Assets/Fonts/appicons.ttf", 
                                                        12.5f, 
                                                        configHandle.AddrOfPinnedObject(), 
                                                        rangesIntPtr);
                    configHandle.Free();

            }

            io.ConfigFlags  |= ImGuiConfigFlags.DockingEnable         | ImGuiConfigFlags.ViewportsEnable;
            io.BackendFlags |= ImGuiBackendFlags.RendererHasVtxOffset | ImGuiBackendFlags.PlatformHasViewports;
            io.Fonts.Build();
NoahStolk commented 1 month ago

@tingspain Have you tried calling ImGuiNative.ImFontConfig_ImFontConfig() instead of the ImFontConfig constructor? See my comment and PR for details.

I don't know why this would break in 1.90.0 though.

tingspain commented 4 weeks ago

@NoahStolk, indeed. I am sorry to not post my solution earlier. At the end, I manage to make it works, as you suggested.

Here is my code.

public static void AddIconsFonts(float fontSize, char iconMin, char iconMax, string fontFilePath)
{
      unsafe {

            ushort[]  ranges = { iconMin, iconMax, 0 };

            var rangesIntPtr = Marshal.UnsafeAddrOfPinnedArrayElement(ranges, 0);

            ImFontConfigPtr fontConfig      = ImGuiNative.ImFontConfig_ImFontConfig();
            fontConfig.MergeMode            = true;
            fontConfig.OversampleH          = 3;
            fontConfig.OversampleV          = 3;
            fontConfig.GlyphOffset          = Vector2.Zero;
            fontConfig.FontDataOwnedByAtlas = false;
            fontConfig.PixelSnapH           = true;
            fontConfig.GlyphMaxAdvanceX     = float.MaxValue;
            fontConfig.RasterizerMultiply   = 2.0f;

            ImGui.GetIO().Fonts.AddFontFromFileTTF( fontFilePath, 
                                                      fontSize, 
                                                      fontConfig, 
                                                      rangesIntPtr);

      }
}

(Just in case, it will be useful for someone) And then I use it in my ImGuiController as follow:

var io = ImGui.GetIO();

unsafe {

      // Sanity Check
      if(!Core.Utils.FileExists("Assets/Fonts/Manrope/Manrope-Regular.ttf"))
            throw new System.NullReferenceException("The app can not find the default font");

      // Add the default fonts for the App
      io.Fonts.AddFontFromFileTTF("Assets/Fonts/Manrope/Manrope-Regular.ttf", 17.0f);

      // Add the App Icons
      if(Core.Utils.FileExists( ImGuiExt.AppIcons.FontFile ) )
            ImGuiExt.AddIconsFonts(14,  
                                   ImGuiExt.AppIcons.IconMin,  
                                   ImGuiExt.AppIcons.IconMax,  
                                   ImGuiExt.AppIcons.FontFile);  

}

io.ConfigFlags  |= ImGuiConfigFlags.DockingEnable         | ImGuiConfigFlags.ViewportsEnable | ImGuiConfigFlags.NavEnableKeyboard;
io.BackendFlags |= ImGuiBackendFlags.RendererHasVtxOffset | ImGuiBackendFlags.PlatformHasViewports;
io.Fonts.Build();

It will be more than welcome if anyone has any feedback/suggestion/recommendation. ^___^

Thanks in advance!!!