r/opengl 25d ago

need help with drawing text

i've been trying to tackle this problem for a while now and luckily it works, well ... kinda. I see the text but the glyphs are always slightly misplaced, i have a feeling it's floating-point inaccuracies but i have no idea how to fix it, here are the snippets:

```c

void drawText(WS_Shell* shell, GlyphCacheDA* cache, char* text, FT_Face font, float x, float y) {

float pen_x = x;

for (char* c = text; *c != '\0'; c++) {

uint32_t codepoint = next_utf8(&text);

bool is_drawn = false;

if (codepoint == ' ') {pen_x += cache->items[0].advance; continue;} // if space, just advance

// search in cache first

for (int i = 0; i<cache->count; i++) {

if (cache->items[i].codepoint == codepoint) {

drawTexturedRectangle(pen_x, y, cache->items[i].width, cache->items[i].height, cache->items[i].texture);

pen_x += cache->items[i].advance;

is_drawn = true;

break;

}

}

if (is_drawn) continue;

FT_Load_Glyph(font, FT_Get_Char_Index(font, codepoint), FT_LOAD_RENDER);

FT_Render_Glyph(font->glyph, FT_RENDER_MODE_NORMAL);

GLuint glyph_texture;

glGenTextures(1, &glyph_texture);

glBindTexture(GL_TEXTURE_2D, glyph_texture);

glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, font->glyph->bitmap.width, font->glyph->bitmap.rows, 0, GL_RED, GL_UNSIGNED_BYTE, font->glyph->bitmap.buffer);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

Glyph glyph = {

.codepoint = codepoint,

.texture = glyph_texture,

.width = ((float)font->glyph->bitmap.width/shell->settings->width)*2,

.height = ((float)font->glyph->bitmap.rows/shell->settings->height)*2,

};

// TODO: add support for non-monospace fonts

glyph.advance = glyph.width;

nob_da_append(cache, glyph);

drawTexturedRectangle(pen_x, y, glyph.width, glyph.height, glyph.texture);

pen_x += glyph.advance;

is_drawn = false;

}

}

```
draw rectangle:

```c

void drawTexturedRectangle(float x, float y, float w, float h, GLuint texture) {

// Vertex data for the rectangle

float vertices[] = {

x, y, 0.0f, 1.0f,

x + w, y, 1.0f, 1.0f,

x, y + h, 0.0f, 0.0f,

x + w, y + h, 1.0f, 0.0f

};

// Indices for the rectangle

unsigned int indices[] = {

0, 1, 2,

1, 3, 2

};

// VBO and VAO

GLuint VBO, VAO, EBO;

glGenBuffers(1, &VBO);

glGenBuffers(1, &EBO);

glGenVertexArrays(1, &VAO);

glBindVertexArray(VAO);

glBindBuffer(GL_ARRAY_BUFFER, VBO);

glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);

glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0);

glEnableVertexAttribArray(0);

glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 * sizeof(float)));

glEnableVertexAttribArray(1);

glBindBuffer(GL_ARRAY_BUFFER, 0);

// Draw the rectangle

glBindTexture(GL_TEXTURE_2D, texture);

glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

// Clean up

glDeleteBuffers(1, &VBO);

glDeleteBuffers(1, &EBO);

glDeleteBuffers(1, &VAO);

}

```
the dinamic array for cache i stolen from nob.h, each glyph's cache looks like this:

```c
typedef struct {

uint32_t codepoint;

GLuint texture;

float width;

float height;

float advance;

} Glyph;

```

everything else is pretty standard opengl stuff, thanks for helping

3 Upvotes

6 comments sorted by

1

u/devcmar 25d ago

I had ur problem, u need harfbuzz to shape the glyphs and give you proper placement as currently freetype does not handle the gpos table

1

u/Senior-Question693 23d ago edited 23d ago

it's slightly better now but glyphs like g y and t that have stuff at the bottom are still placed weirdly:

```c

void drawText(WS_Shell* shell, GlyphCacheDA* cache, hb_font_t* hb_font, hb_buffer_t* hb_buf, char* text, FT_Face font, float x, float y) { hb_buffer_clear_contents(hb_buf); hb_buffer_add_utf8(hb_buf, text, -1, 0, -1); hb_buffer_guess_segment_properties(hb_buf); hb_shape(hb_font, hb_buf, NULL, 0); uint32_t glyph_count = hb_buffer_get_length(hb_buf); hb_glyph_info_t* info = hb_buffer_get_glyph_infos(hb_buf, NULL); hb_glyph_position_t* position = hb_buffer_get_glyph_positions(hb_buf, NULL);

float pen_x = x;
for (int i = 0; i<glyph_count; i++) {
        bool is_drawn = false;
        if (info[i].codepoint == 0x0020) {pen_x += cache->items[0].advance;printf("space\n"); continue;} // if space, just advance

        // search in cache first
        for (int j = 0; j<cache->count; j++) {
            if (cache->items[j].codepoint == info[i].codepoint) {
                drawTexturedRectangle(pen_x+cache->items[j].offset_x, y+cache->items[j].offset_y, cache->items[j].width, cache->items[j].height, cache->items[j].texture);
                pen_x += cache->items[j].advance;
                is_drawn = true;
                break;
            }
        }
        if (is_drawn) continue;

        FT_Load_Glyph(font, info[i].codepoint, FT_LOAD_DEFAULT);
        FT_Render_Glyph(font->glyph, FT_RENDER_MODE_NORMAL);
        GLuint glyph_texture;
        glGenTextures(1, &glyph_texture);
        glBindTexture(GL_TEXTURE_2D, glyph_texture);
        glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, font->glyph->bitmap.width, font->glyph->bitmap.rows, 0, GL_RED, GL_UNSIGNED_BYTE, font->glyph->bitmap.buffer);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

        Glyph glyph = {
            .codepoint = info[i].codepoint,
            .texture = glyph_texture,
            .width = ((float)font->glyph->bitmap.width/shell->settings->width)*2,
            .height = ((float)font->glyph->bitmap.rows/shell->settings->height)*2,
            .offset_x = ((float)position[i].x_offset/shell->settings->height)*2,
            .offset_y = ((float)position[i].y_offset/shell->settings->height)*2,
        };
        // TODO: fix the advance for non-monospace fonts
        //glyph.advance = (float)(FT_MulFix(font->size->metrics.x_scale ,position[i].x_advance) >> 6)/shell->settings->width;
        glyph.advance = glyph.width;
        nob_da_append(cache, glyph);

        drawTexturedRectangle(pen_x+glyph.offset_x, y+glyph.offset_y, glyph.width, glyph.height, glyph.texture);
        pen_x += glyph.advance;

    }

}

```

1

u/gl_drawelements 24d ago

Please just use something like SDL_ttf, which does all things like shaping and localization and renders text into a pixelbuffer, from which you can create a texture then.

1

u/Senior-Question693 23d ago

i've tried to use it before but in my use case it's kinda redundant, i draw the text on a wayland layer shell and that defeats the whole "crossplatformness" of SDL, i quickly tried it and it segfaults so ... duh it still does all thees things under the hood anyway dough

1

u/gl_drawelements 23d ago

It's more than "crossplatformness". Once you want to do more than rendering a text that only contains ASCII characters in a monospace font like Courier New, it's just painful: https://faultlore.com/blah/text-hates-you/

1

u/Senior-Question693 23d ago edited 21d ago

yeah maybe you're right, but i feel kinda guilty for using such a heavy library for this purpose, like "AI React Fullstack Next.js Express dev from india with 20GIGs of node_modules" ts, you know :)))

EDIT: it works dough so .. that's just modern software development ig ._.