diff options
Diffstat (limited to 'fontbaker/fontbaker.c')
-rw-r--r-- | fontbaker/fontbaker.c | 303 |
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 | |||
13 | static const FT_Int32 LoadFlags = FT_LOAD_DEFAULT; | ||
14 | |||
15 | static 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 | |||
40 | static 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 | |||
73 | static 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 | |||
101 | cleanup: | ||
102 | if (out != NULL) { | ||
103 | fclose(out); | ||
104 | } | ||
105 | return success; | ||
106 | } | ||
107 | |||
108 | static 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 | |||
114 | static 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 | |||
156 | static 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 | |||
165 | int 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 | |||
292 | cleanup: | ||
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 | } | ||