summaryrefslogtreecommitdiff
path: root/fontbaker/fontbaker.c
diff options
context:
space:
mode:
author3gg <3gg@shellblade.net>2024-05-04 16:44:28 -0700
committer3gg <3gg@shellblade.net>2024-05-04 16:52:53 -0700
commitaf641426fad35cd857c1f14bda523db3d85a70cd (patch)
tree8a219b03aef0c80cac56cd6b88571a7a6988b35b /fontbaker/fontbaker.c
Initial commit.
Diffstat (limited to 'fontbaker/fontbaker.c')
-rw-r--r--fontbaker/fontbaker.c303
1 files changed, 303 insertions, 0 deletions
diff --git a/fontbaker/fontbaker.c b/fontbaker/fontbaker.c
new file mode 100644
index 0000000..a11aa88
--- /dev/null
+++ b/fontbaker/fontbaker.c
@@ -0,0 +1,303 @@
1#include <font.h>
2
3#include <ft2build.h>
4#include FT_FREETYPE_H
5
6#include <assert.h>
7#include <math.h>
8#include <stdbool.h>
9#include <stdio.h>
10#include <stdlib.h>
11#include <string.h>
12
13static const FT_Int32 LoadFlags = FT_LOAD_DEFAULT;
14
15static void RenderGlyph(
16 unsigned char c, const unsigned char* pixels, int width, int height) {
17 assert(pixels);
18
19 printf("Glyph (%c), %dx%d\n", c, width, height);
20
21 const unsigned char* pixel = pixels;
22 for (int y = 0; y < height; ++y) {
23 printf("%02d ", y);
24 for (int x = 0; x < width; ++x, ++pixel) {
25 unsigned char p = ' ';
26 if (*pixel >= 128) {
27 p = '#';
28 } else if (*pixel >= 32) {
29 p = '*';
30 } else if (*pixel > 0) {
31 p = '.';
32 }
33 printf("%c", p);
34 }
35 printf("\n");
36 }
37 printf("\n");
38}
39
40static void RenderText(const FontAtlas* atlas, const char* text) {
41 assert(text);
42
43 const int glyph_width = atlas->header.glyph_width;
44 const int glyph_height = atlas->header.glyph_height;
45 const int glyph_size = glyph_width * glyph_height;
46
47 for (int y = 0; y < glyph_height; ++y) {
48 printf("%02d ", y);
49
50 for (size_t i = 0; i < strlen(text); ++i) {
51 const char c = text[i];
52 const int glyph_offset = (c - FontGlyphStart) * glyph_size;
53 const int row_offset = (y * glyph_width);
54
55 const unsigned char* pixel = atlas->pixels + glyph_offset + row_offset;
56
57 for (int x = 0; x < glyph_width; ++x, ++pixel) {
58 unsigned char p = ' ';
59 if (*pixel >= 128) {
60 p = '#';
61 } else if (*pixel >= 32) {
62 p = '*';
63 } else if (*pixel > 0) {
64 p = '.';
65 }
66 printf("%c", p);
67 }
68 }
69 printf("\n");
70 }
71}
72
73static bool WriteAtlas(const FontAtlas* atlas, const char* output_path) {
74 assert(atlas);
75 assert(output_path);
76
77 bool success = true;
78
79 FILE* out = NULL;
80
81 const int num_pixels = atlas->header.num_glyphs * atlas->header.glyph_width *
82 atlas->header.glyph_height;
83
84 out = fopen(output_path, "wb");
85 if (out == NULL) {
86 fprintf(stderr, "Failed opening output file: %s\n", output_path);
87 success = false;
88 goto cleanup;
89 }
90 if (fwrite(&atlas->header, sizeof(atlas->header), 1, out) != 1) {
91 fprintf(stderr, "Failed writing atlas header\n");
92 success = false;
93 goto cleanup;
94 }
95 if (fwrite(atlas->pixels, num_pixels, 1, out) != 1) {
96 fprintf(stderr, "Failed writing atlas\n");
97 success = false;
98 goto cleanup;
99 }
100
101cleanup:
102 if (out != NULL) {
103 fclose(out);
104 }
105 return success;
106}
107
108static int GetYOffset(FT_Face face, int font_size) {
109 const int y_offset = font_size - face->glyph->bitmap_top;
110 assert(y_offset >= 0);
111 return y_offset;
112}
113
114static bool GetMaxGlyphDimensions(
115 FT_Library ft, FT_Face face, int font_size, int* width, int* height) {
116 assert(ft);
117 assert(face);
118 assert(width);
119 assert(height);
120
121 FT_Error error = 0;
122
123 int max_width = 0;
124 int max_height = 0;
125
126 for (unsigned char c = FontGlyphStart; c < FontGlyphEnd; ++c) {
127 const FT_UInt glyph_index = FT_Get_Char_Index(face, c);
128 assert(glyph_index > 0);
129
130 error = FT_Load_Glyph(face, glyph_index, LoadFlags);
131 assert(error == 0);
132
133 error = FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL);
134 assert(error == 0);
135
136 const int y_offset = GetYOffset(face, font_size);
137
138 const int xmax = (int)face->glyph->bitmap.width;
139 const int ymax = (int)face->glyph->bitmap.rows + y_offset;
140
141 if (xmax > max_width) {
142 max_width = xmax;
143 }
144
145 if (ymax > max_height) {
146 max_height = ymax;
147 }
148 }
149
150 *width = max_width;
151 *height = max_height;
152
153 return true;
154}
155
156static void GammaCorrect(unsigned char* pixels, int num_pixels) {
157 assert(pixels);
158
159 for (int i = 0; i < num_pixels; ++i) {
160 pixels[i] =
161 (unsigned char)(pow((double)pixels[i] / 255.0, 1.0 / 2.2) * 255.0);
162 }
163}
164
165int main(int argc, const char** argv) {
166 const int font_size = 26;
167
168 bool success = true;
169
170 if (argc < 3) {
171 fprintf(stderr, "Usage: %s <font file> <output file>\n", argv[0]);
172 return 0;
173 }
174
175 const char* font_path = argv[1];
176 const char* output_path = argv[2];
177
178 FT_Library ft = {0};
179 FT_Face face = {0};
180 FT_Error error = 0;
181 FontAtlas* atlas = 0;
182
183 if (FT_Init_FreeType(&ft) != 0) {
184 fprintf(stderr, "FT_Init_FreeType() failed\n");
185 success = false;
186 goto cleanup;
187 }
188
189 if (FT_New_Face(ft, font_path, 0, &face) != 0) {
190 fprintf(stderr, "Failed loading font file: %s\n", font_path);
191 success = false;
192 goto cleanup;
193 }
194
195 if (FT_Set_Pixel_Sizes(face, 0, font_size) != 0) {
196 fprintf(stderr, "Failed to set character size\n");
197 success = false;
198 goto cleanup;
199 }
200
201 // Find the largest glyph dimensions.
202 int glyph_width, glyph_height;
203 GetMaxGlyphDimensions(ft, face, font_size, &glyph_width, &glyph_height);
204
205 const int glyph_size = glyph_width * glyph_height;
206 const int atlas_size = glyph_size * FontAtlasNumGlyphs;
207
208 printf("Glyph: %dx%d (%d bytes)\n", glyph_width, glyph_height, glyph_size);
209 printf(
210 "Atlas: %dx%d (%d bytes)\n", FontAtlasNumGlyphs * glyph_width,
211 glyph_height, atlas_size);
212
213 // -1 because Atlas already includes 1 pixel.
214 atlas = calloc(1, sizeof(FontAtlas) + atlas_size - 1);
215 if (!atlas) {
216 success = false;
217 goto cleanup;
218 }
219 atlas->header = (FontHeader){
220 .glyph_width = glyph_width,
221 .glyph_height = glyph_height,
222 .num_glyphs = FontAtlasNumGlyphs,
223 };
224
225 // Render into atlas in glyph-major order so that glyphs can later be rendered
226 // by scanning memory linearly.
227 for (unsigned char c = FontGlyphStart; c < FontGlyphEnd; ++c) {
228 const FT_UInt glyph_index = FT_Get_Char_Index(face, c);
229 assert(glyph_index > 0);
230
231 error = FT_Load_Glyph(face, glyph_index, LoadFlags);
232 assert(error == 0);
233
234 error = FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL);
235 assert(error == 0);
236
237 const int this_width = (int)face->glyph->bitmap.width;
238 const int this_height = (int)face->glyph->bitmap.rows;
239
240 // For debugging.
241 if ((c == 'A') || (c == 'B') || (c == 'C') || (c == 'e') || (c == '!')) {
242 RenderGlyph(c, face->glyph->bitmap.buffer, this_width, this_height);
243 }
244
245 // Space gets no bitmap allocated, skip rendering.
246 if (c == ' ') {
247 continue;
248 }
249
250 // Render into atlas.
251 const int glyph_offset = (c - FontGlyphStart) * glyph_size;
252 assert(glyph_offset + glyph_size <= atlas_size);
253
254 assert(this_width <= glyph_width);
255 assert(this_height <= glyph_height);
256
257 const int x_offset = (glyph_width - this_width) / 2;
258 const int y_offset = GetYOffset(face, font_size);
259
260 assert(y_offset >= 0);
261 assert(x_offset >= 0);
262 assert(y_offset + this_height <= glyph_height);
263 assert(x_offset + this_width <= glyph_width);
264
265 unsigned char* pAtlas =
266 atlas->pixels + glyph_offset + (y_offset * glyph_width) + x_offset;
267 const unsigned char* pixel = face->glyph->bitmap.buffer;
268
269 for (int y = 0; y < this_height; ++y) {
270 for (int x = 0; x < this_width; ++x, ++pixel, ++pAtlas) {
271 *pAtlas = *pixel;
272 }
273 // Glyph may be narrower than the maximum width.
274 if (glyph_width > this_width) {
275 pAtlas += (glyph_width - this_width);
276 }
277 }
278 }
279
280 // FreeType renders glyphs in linear space. Gamma-correct the atlas so that
281 // applications can directly render to screen.
282 GammaCorrect(atlas->pixels, atlas_size);
283
284 // Quick test.
285 const char* test = "Good Stuff! \"g_g\", Baking Complete.";
286 RenderText(atlas, test);
287
288 if (!WriteAtlas(atlas, output_path)) {
289 goto cleanup;
290 }
291
292cleanup:
293 if (atlas) {
294 free(atlas);
295 }
296 if (face) {
297 FT_Done_Face(face);
298 }
299 if (ft) {
300 FT_Done_FreeType(ft);
301 }
302 return success ? 0 : 1;
303}