FL_KEYBOARD and utf-8 international characters

19 views
Skip to first unread message

Gonzalo Garramuno

unread,
Apr 17, 2022, 10:35:29 PM4/17/22
to fltkg...@googlegroups.com
In my application, I would like to have a text input mode, where the text is input and drawn directly on the OpenGL window. I would like to know if catching FL_KEYBOARD events and appending Fl::event_text(), and later sending the caught text to gl_draw(), is enough to handle utf8 input (like inputting Japanese characters for example).



Gonzalo Garramuno
ggar...@gmail.com




Ian MacArthur

unread,
Apr 18, 2022, 5:00:01 AM4/18/22
to fltk.general
On Monday, 18 April 2022 at 03:35:29 UTC+1  Gonzalo  wrote:
In my application, I would like to have a text input mode, where the text is input and drawn directly on the OpenGL window. I would like to know if catching FL_KEYBOARD events and appending Fl::event_text(), and later sending the caught text to gl_draw(), is enough to handle utf8 input (like inputting Japanese characters for example).



I think there are two parts to this question really, being:

1: Can you render UTF8 strings (including, for example, Japanese glyphs) to a GL scene? Answer: yes - I'll paste a working example below.

2: Can you figure out the input text just by catching keyboard events? Answer: well, I suppose, but it may be tricky - the key being handling the various input methods typically used to input (for example) CJK text on "western style" keyboards. It's tricky. FLTK itself has some support for this that more or less works on X11, macOS and WIN32, but if you grab the key events and try to do it yourself it could get messy very quickly. Better to find some way to let FLTK compose the glyphs into strings and read that, rather than trying to parse the raw input yourself, I would suggest.

Anyway, here's the render sample code: this is basically just Greg's OpenGL 2D Text on 3D Object example but bodged up to display UTF8 strings from his UTF8 Japanese Font Test example...

fltk-config --use-gl --compile test.cxx

Tested (on Win10) with fltk-1.3.5 and fltk-1.4.0. 
Both worked OK, though minor font handling differences (due to fltk-1.4 using GDI+ possibly?) but correct glyphs displayed in both cases.


// Start here //
//
// OpenGL example showing text on a rotating 3D object.
// erco 03/03/06
//
#include <FL/Fl.H>
#include <FL/Fl_Gl_Window.H>
#include <FL/gl.h>
#include <GL/glu.h>
#include <string.h>
#include <stdio.h>

// These strings are from the Wikipedia page on Japanese
// under the "Book of Song" heading.
//
const char *songnames[] = {                 // (Following comments look right in a UTF8 editor)
    "1: \xe9\xa0\x86\xe5\xb8\x9d\xe6\x98\x87\xe6\x98\x8e"   // 順帝昇明二年
    "\xe4\xba\x8c\xe5\xb9\xb4",
    "2: \xe9\x81\xa3\xe4\xbd\xbf\xe4\xb8\x8a\xe8\xa1\xa8"   // 遣使上表曰
    "\xe6\x9b\xb0",
    "3: \xe4\xbd\x9c\xe8\x97\xa9\xe4\xba\x8e\xe5\xa4\x96",  // 作藩于外
    "4: \xe8\x87\xaa\xe6\x98\x94\xe7\xa5\x96\xe7\xa6\xb0",  // 自昔祖禰
    "5: \xe8\xba\xac\xe6\x93\x90\xe7\x94\xb2\xe5\x86\x91",  // 躬擐甲冑
    "6: \xe8\xb7\x8b\xe6\xb8\x89\xe5\xb1\xb1\xe5\xb7\x9d",  // 跋渉山川
    "7: \xe4\xb8\x8d\xe9\x81\x91\xe5\xaf\xa7\xe8\x99\x95"   // 不遑寧處。東征毛人五十國
    "\xe3\x80\x82\xe6\x9d\xb1\xe5\xbe\x81\xe6\xaf\x9b"
    "\xe4\xba\xba\xe4\xba\x94\xe5\x8d\x81\xe5\x9c\x8b",
    "8: \xe8\xa5\xbf\xe6\x9c\x8d\xe8\xa1\x86\xe5\xa4\xb7"   // etc..
    "\xe5\x85\xad\xe5\x8d\x81\xe5\x85\xad\xe5\x9c\x8b",
    "9: \xe6\xb8\xa1\xe5\xb9\xb3\xe6\xb5\xb7\xe5\x8c\x97"
    "\xe4\xb9\x9d\xe5\x8d\x81\xe4\xba\x94\xe5\x9c\x8b",
    "10: \xe7\x8e\x8b\xe9\x81\x93\xe8\x9e\x8d\xe6\xb3\xb0",
    "11: \xe5\xbb\x93\xe5\x9c\x9f\xe9\x81\x90\xe7\x95\xbf",
    "12: \xe7\xb4\xaf\xe8\x91\x89\xe6\x9c\x9d\xe5\xae\x97",
    "13: \xe4\xb8\x8d\xe6\x84\x86\xe4\xba\x8e\xe6\xad\xb3"
    "\xe3\x80\x82\xe8\x87\xa3\xe9\x9b\x96\xe4\xb8\x8b"
    "\xe6\x84\x9a",
    "14: \xe5\xbf\x9d\xe8\x83\xa4\xe5\x85\x88\xe7\xb7\x92",
    "15: \xe9\xa9\x85\xe7\x8e\x87\xe6\x89\x80\xe7\xb5\xb1",
    "16: \xe6\xad\xb8\xe5\xb4\x87\xe5\xa4\xa9\xe6\xa5\xb5",
    0
};

// Tetrahedron points
#define TOP    0,  1,  0
#define RIGHT  1, -1,  1
#define LEFT  -1, -1,  1
#define BACK   0, -1, -1
class MyGlWindow : public Fl_Gl_Window {
    float rotangle;
    void draw() {
        // First time? init viewport, etc.
        if (!valid()) {
            valid(1);
            // Initialize GL
            glClearColor(0.0, 0.0, 0.0, 0.0);
            glClearDepth(1.0);
            glDepthFunc(GL_LESS);
            glEnable(GL_DEPTH_TEST);
            glShadeModel(GL_FLAT);
        }
        glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
        // Position camera/viewport init
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        glViewport(0,0,w(),h());
        gluPerspective(45.0, (float)w()/(float)h(), 1.0, 10.0);
        glTranslatef(0.0, 0.0, -5.0);
        // Position object
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
        glRotatef(rotangle, 1, 0, 1);
        glRotatef(rotangle, 0, 1, 0);
        glRotatef(rotangle, 1, 1, 1);
        // Draw tetrahedron
        glColor3f(1.0, 0.0, 0.0); glBegin(GL_POLYGON); glVertex3f(TOP);   glVertex3f(RIGHT);  glVertex3f(LEFT);  glEnd();
        glColor3f(0.0, 1.0, 0.0); glBegin(GL_POLYGON); glVertex3f(TOP);   glVertex3f(BACK);   glVertex3f(RIGHT); glEnd();
        glColor3f(0.0, 0.0, 1.0); glBegin(GL_POLYGON); glVertex3f(TOP);   glVertex3f(LEFT);   glVertex3f(BACK);  glEnd();
        glColor3f(0.5, 0.5, 0.5); glBegin(GL_POLYGON); glVertex3f(RIGHT); glVertex3f(BACK);   glVertex3f(LEFT);  glEnd();
        // Print tetrahedron's points on object
        //     Disable depth buffer while drawing text,
        //     so text draws /over/ object.
        //
        glDisable(GL_DEPTH_TEST);
        {
            const char *p;
            gl_font(FL_HELVETICA, 15);
            glColor3f(1.0, 1.0, 1.0);
            glRasterPos3f(TOP);   p = songnames[0];   gl_draw(p, strlen(p));
            glRasterPos3f(LEFT);  p = songnames[1];  gl_draw(p, strlen(p));
            glRasterPos3f(RIGHT); p = songnames[2]; gl_draw(p, strlen(p));
            glRasterPos3f(BACK);  p = songnames[3];  gl_draw(p, strlen(p));
        }
        glEnable(GL_DEPTH_TEST);
        // Print rotangle value at fixed position at lower left
        char s[40];
        sprintf(s, "ROT=%.2f", rotangle);
        glLoadIdentity(); glRasterPos2f(-3,-2); gl_draw(s, strlen(s));
    }
    static void Timer_CB(void *userdata) {
        MyGlWindow *o = (MyGlWindow*)userdata;
        o->rotangle += 1.0;
        o->redraw();
        Fl::repeat_timeout(1.0/24.0, Timer_CB, userdata);       // 24fps
    }
public:
    // CONSTRUCTOR
    MyGlWindow(int X,int Y,int W,int H,const char*L=0) : Fl_Gl_Window(X,Y,W,H,L) {
        rotangle = 0;
        Fl::add_timeout(3.0, Timer_CB, (void*)this);       // wait 3 secs before animation begins
    }
};
// MAIN
int main() {
     Fl_Window win(500, 300);
     MyGlWindow mygl(10, 10, win.w()-20, win.h()-20);
     win.show();
     return(Fl::run());
}

// end of file //




 

Matthias Melcher

unread,
Apr 18, 2022, 2:48:08 PM4/18/22
to fltk.general
Starting with the current GitHub version of FLTK, you can simply add an Fl_Input widget inside the Fl_GL_Window. See the cube example.

Gonzalo Garramuno

unread,
Apr 18, 2022, 7:20:18 PM4/18/22
to fltkg...@googlegroups.com


> El 18 abr. 2022, a las 15:48, 'Matthias Melcher' via fltk.general <fltkg...@googlegroups.com> escribió:
>
> Starting with the current GitHub version of FLTK, you can simply add an Fl_Input widget inside the Fl_GL_Window. See the cube example.

Yes, that seems to be the best solution (with a custom Fl_Input widget).
I was previously cutting and pasting the fltk code for the text editor, with partial success. I got it mostly working except for multi character input ( like Spanish accents ).

I’ll start all over now. I only need to figure out how to make the Fl_Input widget appear, resize and disappear when the user adds the text or closes the (custom) Fl_Input widget.


Gonzalo Garramuno
ggar...@gmail.com




Ian MacArthur

unread,
Apr 19, 2022, 7:04:42 AM4/19/22
to fltk.general
On Monday, 18 April 2022 at 19:48:08 UTC+1 Matthias wrote:
Starting with the current GitHub version of FLTK, you can simply add an Fl_Input widget inside the Fl_GL_Window. See the cube example.

Matt, Gonzalo,

This seemed like an "easy" tweak to test , so I bodged the previous example - see below.

So, this was meant to show a basic Fl_Input widget at the mouse position, each time I click in the GL window area. 
Then type some text and hit enter, the Fl_Input is dismissed.

Well... that sort of works, but the Fl_Input always appears at the starting position, never at the mouse coordinates.

I assume there's some sort of (re-)initialization needed for the repositioned widget, but... dunno...

Code as follows:

//
// OpenGL example showing text on a rotating 3D object.
// erco 03/03/06
//
#include <FL/Fl.H>
#include <FL/Fl_Gl_Window.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Input.H>
#define SPARE_COLOUR (FL_FREE_COLOR + 1)

static Fl_Window *main_win = NULL;

static void inp_cb(Fl_Widget *w, void*);


class MyGlWindow : public Fl_Gl_Window {
    float rotangle;
    static int txt_idx;
    static int framecount;
            int txt_set = txt_idx / 4;
            txt_set = txt_set * 4;
            glRasterPos3i(TOP);   p = songnames[(txt_set + 0) & 0x0F];  gl_draw(p, strlen(p));
            glRasterPos3i(LEFT);  p = songnames[(txt_set + 1) & 0x0F];  gl_draw(p, strlen(p));
            glRasterPos3i(RIGHT); p = songnames[(txt_set + 2) & 0x0F];  gl_draw(p, strlen(p));
            glRasterPos3i(BACK);  p = songnames[(txt_set + 3) & 0x0F];  gl_draw(p, strlen(p));

        }
        glEnable(GL_DEPTH_TEST);

        // Print rotangle value at fixed position at lower left
        glLoadIdentity();
        glRasterPos2f(-2.5,-2);

        char s[128];
        sprintf(s, "ROT=%6.2f --- %s", rotangle, songnames[txt_idx]);
        gl_draw(s, strlen(s));

        glRasterPos2f(0, 0);
        gl_draw("+", 1);


        // if an OpenGL graphics driver is installed, give it a chance
        // to draw additional graphics
        Fl_Gl_Window::draw();
    } // draw

    int handle (int ev)
    {
        int res = Fl_Gl_Window::handle(ev);
        switch (ev)
        {
        case FL_ENTER:
        case FL_LEAVE:
            res = 1;
            break;

        case FL_PUSH:
            if (Fl::event_button() == 1)
            {
                res = 1;
            }
            break;

        case FL_RELEASE:
            if (Fl::event_button() == 1)
            {
                res = 1;
                int xb = Fl::event_x();
                int yb = Fl::event_y();
                inp->position (xb, yb);
                init_sizes(); // though this might help - but makes No Difference...
                inp->show ();

                printf ("click (%d, %d)\n", xb, yb); fflush(stdout);
            }
            break;

        default:
            break;
        }
        return res;
    } // handle


    static void Timer_CB(void *userdata)
    {
        MyGlWindow *o = (MyGlWindow*)userdata;
        o->rotangle += 0.1;
        o->redraw();
        Fl::repeat_timeout(1.0/6.0, Timer_CB, userdata);
        framecount++;
        if (framecount >= 9)
        {
            framecount = 0;
            txt_idx = (txt_idx + 1) & 0x0F;
        }
    } // Timer_CB

public:
    Fl_Input *inp;


    // CONSTRUCTOR
    MyGlWindow(int X,int Y,int W,int H,const char*L=0) : Fl_Gl_Window(X,Y,W,H,L)
    {
        rotangle = 0;
        txt_idx = 0;
        framecount = 0;

        Fl::add_timeout(1.2, Timer_CB, (void*)this); // wait 1.2 secs before animation begins
    } // CONSTRUCTOR
} ; // MyGlWindow

int MyGlWindow::txt_idx = 0;
int MyGlWindow::framecount = 0;

static void inp_cb(Fl_Widget *w, void*)
{
    Fl_Input *inp = (Fl_Input *)w;
    if (strlen(inp->value()) > 0)
    {
        printf ("Got : %s\n", inp->value());
        fflush (stdout);
    }
    inp->hide();
    main_win->cursor(FL_CURSOR_DEFAULT); // resotre the default cursor
}

// MAIN
int main(int argc, char **argv)
{
    Fl::use_high_res_GL(1);
    Fl::set_color(SPARE_COLOUR, 255, 255, 255, 75);

    Fl_Window win(640, 480, "GL Text handling test");
    main_win = &win;
    win.begin();

    MyGlWindow mygl(4, 4, win.w()-8, win.h()-8);
    mygl.begin();

    mygl.inp = new Fl_Input (10, 10, 120, 30);
    mygl.inp->box (FL_FLAT_BOX);
    mygl.inp->color(SPARE_COLOUR);
    mygl.inp->callback (inp_cb);
    mygl.inp->when (FL_WHEN_ENTER_KEY | FL_WHEN_NOT_CHANGED);
    mygl.inp->hide();

    mygl.end();
    win.end();

    win.show(argc, argv);

    return Fl::run();

Albrecht Schlosser

unread,
Apr 19, 2022, 8:03:16 AM4/19/22
to fltkg...@googlegroups.com
On 4/19/22 13:04 Ian MacArthur wrote:
This seemed like an "easy" tweak to test , so I bodged the previous example - see below.

So, this was meant to show a basic Fl_Input widget at the mouse position, each time I click in the GL window area. 
Then type some text and hit enter, the Fl_Input is dismissed.

Well... that sort of works, but the Fl_Input always appears at the starting position, never at the mouse coordinates.

I assume there's some sort of (re-)initialization needed for the repositioned widget, but... dunno...

Code as follows:

//
// OpenGL example showing text on a rotating 3D object.
// erco 03/03/06
//

[...]


        case FL_RELEASE:
            if (Fl::event_button() == 1)
            {
                res = 1;
                int xb = Fl::event_x();
                int yb = Fl::event_y();

// inp->position (xb, yb); // sets cursor pos. and mark // inp->Fl_Widget::position(xb, yb); // use either this or ...
inp->resize(xb, yb, inp->w(), inp->h());
                init_sizes(); // though this might help - but makes No Difference...
                inp->show ();


Unfortunately Fl_Input::position(int, int) is one of these unexpected overloads of Fl_Widget methods because it changes semantics. You can either use the fully qualified name or resize(..).

Using init_sizes() in this place is generally useful because it prevents the input widget from "jumping back" to its previous position if the window (group) is resized later. However, here it's not important because the window is not resizable.

Ian MacArthur

unread,
Apr 19, 2022, 9:02:59 AM4/19/22
to fltk.general
On Tuesday, 19 April 2022 at 13:03:16 UTC+1 Albrecht wrote:


Unfortunately Fl_Input::position(int, int) is one of these unexpected overloads of Fl_Widget methods because it changes semantics. You can either use the fully qualified name or resize(..).


Doh!
Thanks Albrecht - that got it. 
I went with Fl_Widget::position(xb, yb) ;  for my test - worked like a charm.

 
Reply all
Reply to author
Forward
0 new messages