src/text.c
  1#include <X11/extensions/render.h>
  2
  3#include "alba.h"
  4#include "internal.h"
  5
  6#include "freetype/ftglyph.h"
  7
  8extern char _binary_Roboto_Regular_ttf_start[];
  9extern char _binary_Roboto_Regular_ttf_end[];
 10
 11FT_Library freetype = NULL;
 12FT_Face roboto = NULL;
 13Atlas atlas;
 14pthread_mutex_t freetype_mutex = PTHREAD_MUTEX_INITIALIZER;
 15
 16void ensure_freetype_initialized()
 17{
 18    // already initialized
 19    if (freetype != NULL) return;
 20
 21    pthread_mutex_lock(&freetype_mutex);
 22    if (FT_Init_FreeType(&freetype))
 23    {
 24        fprintf(stderr, "error: initializeing FreeType failed");
 25        pthread_mutex_unlock(&freetype_mutex);
 26        exit(1);
 27    }
 28
 29    FT_Error error = FT_New_Memory_Face(
 30        freetype,
 31        _binary_Roboto_Regular_ttf_start,
 32        _binary_Roboto_Regular_ttf_end - _binary_Roboto_Regular_ttf_start,
 33        0, // face index
 34        &roboto
 35    );
 36    if (error)
 37    {
 38        fprintf(stderr, "error: cound not load built in Roboto font");
 39    }
 40
 41    atlas = create_atlas(256);
 42
 43    pthread_mutex_unlock(&freetype_mutex);
 44}
 45
 46void atlas_add_chars(Atlas* atlas, const AlbaString string, const AlbaTextStyle style)
 47{
 48    const char* text = alba_string_to_char(&string);
 49    for (uint64_t i = 0; i < string.length; i++)
 50    {
 51        atlas_append(atlas, roboto, text[i], style); // TODO: unicode
 52    }
 53}
 54
 55uint64_t hash_glyph2(const void* item, const uint64_t seed0, const uint64_t seed1)
 56{
 57    return hashmap_murmur((FT_Long*)item, sizeof(FT_Long), seed0, seed1);
 58}
 59
 60void atlas_generate_text_geometry(
 61    const Atlas* atlas,
 62    const Text* text,
 63    const AlbaWindow* window,
 64    DrawCall* draw_call
 65)
 66{
 67    const AlbaString string = text->string;
 68    const AlbaTextStyle style = text->style;
 69    AlbaArray* vertices = &draw_call->new_vertices;
 70    AlbaArray* attributes = &draw_call->new_attributes;
 71    AlbaArray* indices = &draw_call->new_indices;
 72
 73    alba_array_reserve(vertices, vertices->length + string.length * 4);
 74    alba_array_reserve(attributes, attributes->length + string.length * 4);
 75    alba_array_reserve(indices, indices->length + string.length * 6);
 76
 77    const char* string_char = alba_string_to_char(&string);
 78    AlbaVector current = text->position;
 79    for (uint64_t i = 0; i < string.length; i++)
 80    {
 81        // TODO: mutex?
 82        const FT_Long character = string_char[i]; // TODO: unicode
 83        const AlbaGlyph* glyph = hashmap_get(
 84            atlas->glyphs,
 85            &(AlbaGlyph){
 86                .character = character,
 87                .style = style
 88            }
 89        );
 90        if (glyph == NULL)
 91        {
 92            fprintf(stderr, "error: unable to find glyph for character '%s'\n", (char*)&character);
 93            exit(1);
 94        }
 95        const FT_Glyph_Metrics metrics = glyph->metrics;
 96
 97        // All these / 64 are because FreeType reports metrics in 1/64th of pixel
 98        const AlbaVector origin = {
 99            current.x + metrics.horiBearingX / 64 / window->scale.x,
100            current.y - metrics.horiBearingY / 64 / window->scale.y,
101        };
102        // Divide by scaling to correct the scaling applied by the shader.
103        // We do not want scaling text geometry to avoid making text blurry.
104        const float width = metrics.width / 64 / window->scale.x;
105        const float height = metrics.height / 64 / window->scale.y;
106        // Calculate before adding new vertices
107        const uint32_t vertex_count = vertices->length;
108        alba_array_extend(
109            vertices, 4, (AlbaVector[]){
110                origin,
111                origin.x, origin.y + height,
112                origin.x + width, origin.y + height,
113                origin.x + width, origin.y,
114            });
115
116        alba_array_extend(
117            attributes, 4, (AlbaAttribute[]){
118                {style.color, glyph->start},
119                {style.color, (AlbaVector){glyph->start.x, glyph->end.y}},
120                {style.color, glyph->end},
121                {style.color, (AlbaVector){glyph->end.x, glyph->start.y}},
122            });
123
124        alba_array_extend(
125            indices, 6, (uint32_t[]){
126                // first triangle
127                vertex_count,
128                vertex_count + 1,
129                vertex_count + 2,
130                // second triangle
131                vertex_count,
132                vertex_count + 2,
133                vertex_count + 3
134            });
135
136        current.x += metrics.horiAdvance / 64 / window->scale.x;
137    }
138}
139
140void draw_call_generate_text(const AlbaWindow* window, DrawCall* draw_call)
141{
142    for (uint64_t i = 0; i < draw_call->new_texts.length; i++)
143    {
144        const Text* text = draw_call->new_texts.data + i * draw_call->new_texts.element_size;
145        atlas_generate_text_geometry(&atlas, text, window, draw_call);
146    }
147}
148
149void alba_draw_text(
150    AlbaWindow* window,
151    const AlbaVector pos,
152    const AlbaString string,
153    AlbaTextStyle style
154)
155{
156    ensure_freetype_initialized();
157
158    if (style.font_size == 0) style.font_size = 16;
159    if (alba_color_is_transparent(&style.color)) style.color = (AlbaColor)WHITE;
160
161    pthread_mutex_lock(&freetype_mutex);
162    const FT_Error error = FT_Set_Pixel_Sizes(
163        roboto,
164        style.font_size * window->scale.x,
165        style.font_size * window->scale.y
166    );
167    if (error)
168    {
169        fprintf(stderr, "error: cound not set Roboto face size");
170    }
171
172    atlas_add_chars(&atlas, string, style);
173    pthread_mutex_unlock(&freetype_mutex);
174
175    const Text text_obj = {
176        .string = string,
177        .position = pos,
178        .face = roboto,
179        .style = style,
180    };
181
182    DrawCall* draw_call = alba_array_last(&window->draw_calls);
183    if (draw_call->texture_set)
184    {
185        draw_call = window_create_draw_call(window);
186    }
187    alba_array_append(&draw_call->new_texts, &text_obj);
188    draw_call->text_dirty = 1;
189}
190