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