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