ImGui.NET icon indicating copy to clipboard operation
ImGui.NET copied to clipboard

Font.FindGlyph and font.Glyphs return wrong data

Open codingus-g opened this issue 5 years ago • 6 comments

Font ProggySmall.ttf everything renders and works correctly. However if you print the contents of each glyph from ImGui.GetFont() you get uvs that dont make sense, AdvanceX that doesnt make sense etc. I tried to find the issue to the best of my knowledge, set structs to sequential in hopes that there is some layout mismatch, converted chars to utf8, the result is always the same - wrong data is provided. I am not sure what is going on there.

here is the case where i found out the problem


static bool test = false;

    /// Draws vertical text. The position is the bottom left of the text rect.
    public static unsafe void AddTextVertical(string text, Color text_color, Vector2? position = null)
    {
        if (string.IsNullOrEmpty(text))
        {
            text = "Null";
        }

        Vector2 pos = position.HasValue ? position.Value + ImGui.GetWindowPos() : ImGui.GetCursorPos() + ImGui.GetWindowPos();
        ImDrawListPtr DrawList = ImGui.GetWindowDrawList();
        pos.x = Mathf.Round(pos.x);
        pos.y = Mathf.Round(pos.y);
        ImFontPtr font = ImGui.GetFont();

        if (!test)
        {
            var glyphs = font.Glyphs;
            for (int i = 0; i < glyphs.Size; i++)
            {
                var g = glyphs[i];
                Debug.Log("g: " + g.Codepoint + " a:" + g.AdvanceX + " v0:" + g.V0 + " v1:" + g.V1);
            }
            test = true;
        }

        byte[] bytes = Encoding.Default.GetBytes(text);
        text = Encoding.UTF8.GetString(bytes);

        ImFontGlyphPtr glyph;
        char c;
        //ImGuiContext g = *GImGui;
        Vector2 text_size = ImGui.CalcTextSize(text);

        float textX = text_size.x / text.Length;
        for (int i = 0; i < text.Length; i++)
        {
            c = text[i];

            glyph = font.FindGlyph(c);

            if (glyph.NativePtr == null) continue;

            DrawList.PrimReserve(6, 4);
            DrawList.PrimQuadUV(
                    pos + new Vector2(glyph.Y0, -glyph.X0),
                    pos + new Vector2(glyph.Y0, -glyph.X1),
                    pos + new Vector2(glyph.Y1, -glyph.X1),
                    pos + new Vector2(glyph.Y1, -glyph.X0),

                    new Vector2(glyph.U0, glyph.V0),
                    new Vector2(glyph.U1, glyph.V0),
                    new Vector2(glyph.U1, glyph.V1),
                    new Vector2(glyph.U0, glyph.V1),
                    text_color.ToUint());
            
            pos.y -= textX ;
        }

        ImGui.SetCursorPosX(text_size.y);
    }

codingus-g avatar Oct 21 '20 18:10 codingus-g

Without really knowing too much about how these low-level font details work, it's hard to say if this is expected. Are you able to get the same or similar code working with the native library directly?

mellinoe avatar Nov 08 '20 03:11 mellinoe

If you just want to sanity check the results from FindGlyph you could just have a test case like Debug.Assert(fontptr.FindGlyph('B').AdvanceX == fontptr.GetCharAdvance('B'))

ncatlin avatar Jan 26 '21 22:01 ncatlin

This is a problem with the sizes of the first two entries in ImFontGlyph. https://github.com/ocornut/imgui/blob/3867c6c5f0eace22ce2c59ac299bae285bb84ec4/imgui.h Lists 'codepoint' and 'visible' as unsigned ints, but the compiled library seems to have 2 byte ints (?) so we need to have unsigned shorts here.

Changing in ImFontGlyph.gen.cs (or structs_and_enums.json I guess)

    public unsafe partial struct ImFontGlyph
    {
        public ushort Codepoint;  // <---
        public ushort Visible;       //  <---
        public float AdvanceX;
        public float X0;
        ---snip---
    }

makes the problem go away

ncatlin avatar Jan 27 '21 02:01 ncatlin

struct ImFontGlyph
{
    unsigned int Codepoint : 31;
    unsigned int Visible : 1;
    float AdvanceX;
    float X0, Y0, X1, Y1;
    float U0, V0, U1, V1;
};

Codepoint and Visible are bitfields, both are in the same 32bit int, Codepoint fills the first 31 bits and Visible the last 1 bit. So AdvanceX offset is corrected with two ushort declarations but Codepoint and Visible wont be correctly accesible.

How C# manages bitfields? A quick and dirty solution would be to declare one field only CodepointVisible of size u32 and use the relevant bits for Codepoint or Visible.

sonoro1234 avatar Jan 27 '21 09:01 sonoro1234

Ahhh, that makes sense now - I didn't recognise the bitfield syntax.

ncatlin avatar Jan 27 '21 12:01 ncatlin

Yeah, bitfields are not handled at all right now. C# has no concept of a bitfield, so this will require a lot of custom handling to make these parts work. Luckily, they are very few and far between (although it seems the Tables API has added a few new instances).

mellinoe avatar Mar 25 '21 08:03 mellinoe