src/text.c
  1#include "alba.h"
  2#include "internal.h"
  3
  4#include <pthread.h>
  5
  6#include "freetype/ftglyph.h"
  7
  8extern char _binary_Roboto_Regular_ttf_start[];
  9extern char _binary_Roboto_Regular_ttf_end[];
 10
 11static FT_Library freetype = NULL;
 12static FT_Face roboto = NULL;
 13AlbaAtlas atlas;
 14pthread_mutex_t freetype_mutex = PTHREAD_MUTEX_INITIALIZER;
 15
 16void initialize_freetype()
 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    error = FT_Set_Pixel_Sizes(roboto, 0 /* width, same as height */, 18);
 41    if (error)
 42    {
 43        fprintf(stderr, "error: cound not set Roboto face size");
 44    }
 45
 46    atlas = atlas_new(1024);
 47
 48    pthread_mutex_unlock(&freetype_mutex);
 49}
 50
 51void atlas_add_chars(AlbaAtlas* atlas, uint64_t length, const char* text)
 52{
 53    for (uint64_t i = 0; i < length; i++)
 54    {
 55        atlas_append(atlas, roboto, text[i]); // TODO: unicode
 56    }
 57}
 58
 59uint64_t hash_glyph2(const void* item, const uint64_t seed0, const uint64_t seed1)
 60{
 61    return hashmap_murmur((FT_Long*)item, sizeof(FT_Long), seed0, seed1);
 62}
 63
 64void atlas_generate_text_geometry(
 65    const AlbaAtlas* atlas,
 66    const AlbaVector pos, const AlbaColor color,
 67    uint64_t length, const char* text,
 68    AlbaDynArray* vertices, AlbaDynArray* attributes, AlbaDynArray* indices
 69)
 70{
 71    AlbaVector current = pos;
 72    for (uint64_t i = 0; i < length; i++)
 73    {
 74        // TODO: mutex?
 75        const FT_Long character = text[i]; // TODO: unicode
 76        const AlbaGlyph* glyph = hashmap_get(atlas->glyphs, &character);
 77        if (glyph == NULL)
 78        {
 79            fprintf(stderr, "error: unable to find glyph for character '%s'\n", (char*)&character);
 80            exit(1);
 81        }
 82        const FT_Glyph_Metrics metrics = glyph->metrics;
 83
 84        const AlbaVector origin = {
 85            current.x + metrics.horiBearingX / 64,
 86            current.y - metrics.horiBearingY / 64,
 87        };
 88        const uint32_t vertex_count = vertices->length;
 89        alba_dynarray_extend(
 90            vertices, 4, (AlbaVector[]){
 91                origin,
 92                origin.x, origin.y + metrics.height / 64,
 93                origin.x + metrics.width / 64, origin.y + metrics.height / 64,
 94                origin.x + metrics.width / 64, origin.y,
 95            });
 96
 97        alba_dynarray_extend(
 98            attributes, 4, (AlbaAttribute[]){
 99                {color, glyph->start},
100                {color, (AlbaVector){glyph->start.x, glyph->end.y}},
101                {color, glyph->end},
102                {color, (AlbaVector){glyph->end.x, glyph->start.y}},
103            });
104
105        alba_dynarray_extend(
106            indices, 6, (uint32_t[]){
107                // first triangle
108                vertex_count,
109                vertex_count + 1,
110                vertex_count + 2,
111                // second triangle
112                vertex_count,
113                vertex_count + 2,
114                vertex_count + 3
115            });
116
117        current.x += metrics.horiAdvance / 64;
118        // current_y += metrics.vertAdvance / 64;
119    }
120}
121
122void alba_draw_text(
123    AlbaWindow* window,
124    const AlbaVector pos, const AlbaColor color,
125    uint64_t length, const char* text
126)
127{
128    initialize_freetype();
129
130    if (length == 0)
131    {
132        length = strlen(text);
133    }
134
135    pthread_mutex_lock(&freetype_mutex);
136    atlas_add_chars(&atlas, length, text);
137    atlas_generate_image(&atlas);
138    pthread_mutex_unlock(&freetype_mutex);
139
140    // TODO: do this like buffers lazily
141    window->text.texture = create_texture(
142        window,
143        WGPUTextureUsage_TextureBinding | WGPUTextureUsage_CopyDst,
144        atlas.size,
145        WGPUTextureFormat_BGRA8UnormSrgb, 1, // TODO: do not assume format
146        atlas.image
147    );
148    window->text.dirty = 1;
149
150    AlbaDynArray vertices = dynarray_new(sizeof(AlbaVector), length * 4);
151    AlbaDynArray attributes = dynarray_new(sizeof(AlbaAttribute), length * 4);
152    AlbaDynArray indices = dynarray_new(sizeof(uint32_t), length * 6);
153
154    atlas_generate_text_geometry(
155        &atlas,
156        pos, color, length, text,
157        &vertices, &attributes, &indices
158    );
159
160    draw_triangles_indexed_render_pass(
161        &window->text,
162        length * 4, vertices.data, attributes.data,
163        length * 6, indices.data
164    );
165}