libglui / glui

GLUI is a GLUT-based C++ user interface library which provides controls such as buttons, checkboxes, radio buttons, and spinners to OpenGL applications. It is window-system independent, using GLUT or FreeGLUT.
Other
194 stars 82 forks source link

Font dimensions, etc. #95

Open m-7761 opened 5 years ago

m-7761 commented 5 years ago

Tonight I was looking at the font subroutines.

I noticed GLUT has a glutBitmapLength method that could replace the cache of widths currently stored (in a really zany way) in every single control.

In my code I reduced the control cache to 128 char sizers. Part of the reason trying to do anything for GLUI is miserable is back in 2017 even suggesting insane code like this required patching to be taken seriously was met with intense resistance!

    int hash_index = c%CHAR_WIDTH_HASH_SIZE;
    if(c!=char_widths[hash_index][0])
    {
        char_widths[hash_index][0] = c;
        char_widths[hash_index][1] = glutBitmapWidth(get_font(),c);
    }
    return char_widths[hash_index][1];

The char_widths field is int char_widths[128][2]. Yeah, every control stores 1KB of useless memory, or more. 100 controls and you exceed the comment in glui.cpp that says:

/**
 Note: moving this routine here from glui_add_controls.cpp prevents the linker
 from touching glui_add_controls.o in non-deprecated programs, which
 descreases the linked size of small GLUI programs substantially (100K+). (OSL 2006/06)
*/

Anyway, I've made the 256 char cache one per GLUT font, instead of per control. Assuming that's better (or at least simpler at this point) than glutBitmapLength. But also, the textbox control has a fixed height for fonts, set at 15 pixels. I'm now wondering what edittext uses.

So I dug this out from the "freeglut" source code:

            //glutBitmapHeight
            //These come from fg_font_data.c
            //14: GLUT_BITMAP_8_BY_13 
            //16: GLUT_BITMAP_9_BY_15
            //14: GLUT_BITMAP_HELVETICA_10
            //16: GLUT_BITMAP_HELVETICA_12
            //23: GLUT_BITMAP_HELVETICA_18
            //14: GLUT_BITMAP_TIMES_ROMAN_10
            //29: GLUT_BITMAP_TIMES_ROMAN_24

Assuming it holds for other versions of GLUT, this is the height of each of these fonts.

One problem that remains, is the controls fall back onto the GLUI object's font if their font is zeroed. To fix this somewhat I had the controls initialize their font stuff when added to the GLUI object. But it's not foolproof because the GLUI object's font pointer is exposed, and the control's is too. But at least controls have a set_font method.

Anyway, that's the state of fonts. I just wanted to share the height information. And remind how sloppy things are, ahead of providing a major service in auditing/rewriting GLUI's code next week.

nigels-com commented 5 years ago

I'd be happy to consider a pull request focused on this particular aspect of GLUI.

But if it has obvious side-effects, limitations or breaks compatibility, I would have reservations.

Have you pushed code to a repo where we can go peruse the details? Or you'd rather build the suspense?

m-7761 commented 5 years ago

Here is what I came up with today:

    class Font
    {
    public:

        int height; 

        void *font;

        const char(*width)[256];

    public: //Compatibility layer

        inline const Font &operator=(void *font)const
        {           
            Control *p = (Control*)this;

            (char*&)p-=(char*)&p->font-(char*)p; 

            p->set_font(font); return *this;
        }

        inline operator void*()const{ return font; }
    };
    static const Font _glut_fonts[7];

It's worth noting that char_width had taken a char type. Therefore it could not exceed 128, and had it been negative would have accessed out-of-bounds memory. So the whole, hash-map like think is not even achieving anything. I had to double-check this morning that CHAR_WIDTH_HASH_SIZE is 128 in the original code.

The GLUT documentations says "Character to render (not confined to 8 bits). " however I believe freeglut is limited to 256. And I'm unsure what code-page it uses for its 8-bit values. So, I don't know why int would have been chosen for that array, quadrupling its size, other than theoretically GLUT claims to support Unicode or something.

Here Font replaces the void *font member. The sizing methods are made inline and the _glut_fonts sizing caches are initialized the first time their font is selected.

It dawned on me last night that the default font semantics can be straightforward if set_font locks in the font. To do that get_font just returns the control's font pointer instead of looking for the default. And set_font looks for a default, and finding none uses _glut_fonts[0]. So Control::Control just needs to call set_font(NULL). The downside is that [0] always gets initialized. (I think that's unavoidable without adding a method to change which is [0] and so change which font GLUI::GLUI selects for itself.)

/*************** GLUI_Control::set_font() **********/

static char glui_control_char_widths[7][256];

const UI::Control::Font UI::Control::_glut_fonts[7] = 
{
    //glutBitmapHeight 
    //These hail from fg_font_data.c
    {16,GLUT_BITMAP_HELVETICA_12},
    {14,GLUT_BITMAP_HELVETICA_10},
    {23,GLUT_BITMAP_HELVETICA_18},
    {14,GLUT_BITMAP_TIMES_ROMAN_10},
    {29,GLUT_BITMAP_TIMES_ROMAN_24},
    {14,GLUT_BITMAP_8_BY_13},
    {16,GLUT_BITMAP_9_BY_15},
};

void UI::Control::set_font(void *new_font)
{
    int i; 

    if(!new_font) if(ui&&ui->font)
    {
        new_font = ui->font;
    }
    else goto use_default_font;

    for(i=0;i<7;i++)
    if(new_font==_glut_fonts[i].font)
    {   
        break;
    }
    if(i==7) use_default_font:
    {
        i = 0; new_font = _glut_fonts[0].font;
    }

    if(!_glut_fonts[i].width)
    {   
        char *w = glui_control_char_widths[i];
        (void*&)_glut_fonts[i].width = w;
        for(int i=0;i<256;i++)      
        w[i] = 0xFF&glutBitmapWidth(new_font,i);
    }

    const_cast<Font&>(font) = _glut_fonts[i]; redraw();
}
m-7761 commented 5 years ago

P.S. I'm going to have to really rethink the constructors. I think it will make sense to inherit the font from the parent in most cases. That will inherit the GLUI container, and it will, I think, avoid initializing [0] as discussed too. The parent just has to be passed to the Control class's constructor.

The "main_panel" control may require a special, internal constructor to pull it off. The constructors will need to be inline methods. Some of them that take a lot of arguments might be simplified with templates somehow, to avoid error-prone code. Mainly to deal with the live-variable type stuff.

m-7761 commented 5 years ago

Part of the reason for the following quote is likely that glui_textbox.cpp abuses the hell out of substring_width as if it is akin to magic.

/* Hash table for faster character width lookups - JVK
       Speeds up the textbox a little bit.
  */

It's going to take some work to rewrite it. But basically in like 20 or 30 places there are loops that measure a string, add 1 character to the end, measure the whole string again, and so on, rather than just measuring the added character, and adding that to the total length. Whether that is significant work or not (it's hard to believe processing text could've caused detectable slowdown under any circumstances) it is definitely domestic abuse of your home computer.

nigels-com commented 5 years ago

You'd want to look the whole string to support Kerning, for example.

m-7761 commented 5 years ago

You'd want to look the whole string to support Kerning, for example.

The pattern is not sizing the string. It's sizing letter 1. Sizing letter 1,2. Next 1,2,3. And so on, up to the length of each touched line. It accounts for tabs. (I assume you know, but GLUT doesn't do kerning since it doesn't have a string rendering API, in case someone reading this gets the impression otherwise.)