1 | /* |
2 | * A very simple font cache and rasterizer that uses FreeType |
3 | * to draw fonts from a single OpenGL texture. The code uses |
4 | * a linear-probe hashtable, and writes new glyphs into |
5 | * the texture using glTexSubImage2D. When the texture fills |
6 | * up, or the hash table gets too crowded, the cache is emptied. |
7 | * |
8 | * This is designed to be used for horizontal text only, |
9 | * and draws unhinted text with subpixel accurate metrics |
10 | * and kerning. As such, you should always call the drawing |
11 | * function with an orthogonal transform that maps units |
12 | * to pixels accurately. |
13 | */ |
14 | |
15 | #include "gl-app.h" |
16 | |
17 | #include <string.h> |
18 | #include <math.h> |
19 | #include <stdlib.h> |
20 | #include <stdio.h> |
21 | |
22 | #define PADDING 1 /* set to 0 to save some space but disallow arbitrary transforms */ |
23 | |
24 | #define MAXGLYPHS 4093 /* prime number for hash table goodness */ |
25 | #define CACHESIZE 1024 |
26 | #define XPRECISION 4 |
27 | #define YPRECISION 1 |
28 | |
29 | struct key |
30 | { |
31 | fz_font *font; |
32 | float size; |
33 | short gid; |
34 | unsigned char subx; |
35 | unsigned char suby; |
36 | }; |
37 | |
38 | struct glyph |
39 | { |
40 | char lsb, top, w, h; |
41 | short s, t; |
42 | }; |
43 | |
44 | struct table |
45 | { |
46 | struct key key; |
47 | struct glyph glyph; |
48 | }; |
49 | |
50 | static struct table g_table[MAXGLYPHS]; |
51 | static int g_table_load = 0; |
52 | static unsigned int g_cache_tex = 0; |
53 | static int g_cache_w = CACHESIZE; |
54 | static int g_cache_h = CACHESIZE; |
55 | static int g_cache_row_y = 0; |
56 | static int g_cache_row_x = 0; |
57 | static int g_cache_row_h = 0; |
58 | |
59 | static fz_font *g_font = NULL; |
60 | |
61 | static void clear_font_cache(void) |
62 | { |
63 | #if PADDING > 0 |
64 | unsigned char *zero = malloc(g_cache_w * g_cache_h); |
65 | memset(zero, 0, g_cache_w * g_cache_h); |
66 | glBindTexture(GL_TEXTURE_2D, g_cache_tex); |
67 | glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, g_cache_w, g_cache_h, GL_ALPHA, GL_UNSIGNED_BYTE, zero); |
68 | free(zero); |
69 | #endif |
70 | |
71 | memset(g_table, 0, sizeof(g_table)); |
72 | g_table_load = 0; |
73 | |
74 | g_cache_row_y = PADDING; |
75 | g_cache_row_x = PADDING; |
76 | g_cache_row_h = 0; |
77 | } |
78 | |
79 | void ui_init_fonts(void) |
80 | { |
81 | const unsigned char *data; |
82 | int size; |
83 | |
84 | glGenTextures(1, &g_cache_tex); |
85 | glBindTexture(GL_TEXTURE_2D, g_cache_tex); |
86 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
87 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
88 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); |
89 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); |
90 | glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, g_cache_w, g_cache_h, 0, GL_ALPHA, GL_UNSIGNED_BYTE, NULL); |
91 | |
92 | clear_font_cache(); |
93 | |
94 | data = fz_lookup_builtin_font(ctx, "Charis SIL" , 0, 0, &size); |
95 | if (!data) |
96 | data = fz_lookup_builtin_font(ctx, "Times" , 0, 0, &size); |
97 | g_font = fz_new_font_from_memory(ctx, NULL, data, size, 0, 0); |
98 | } |
99 | |
100 | void ui_finish_fonts(void) |
101 | { |
102 | clear_font_cache(); |
103 | fz_drop_font(ctx, g_font); |
104 | } |
105 | |
106 | static unsigned int hashfunc(struct key *key) |
107 | { |
108 | unsigned char *buf = (unsigned char *)key; |
109 | unsigned int len = sizeof(struct key); |
110 | unsigned int h = 0; |
111 | while (len--) |
112 | h = *buf++ + (h << 6) + (h << 16) - h; |
113 | return h; |
114 | } |
115 | |
116 | static unsigned int lookup_table(struct key *key) |
117 | { |
118 | unsigned int pos = hashfunc(key) % MAXGLYPHS; |
119 | while (1) |
120 | { |
121 | if (!g_table[pos].key.font) /* empty slot */ |
122 | return pos; |
123 | if (!memcmp(key, &g_table[pos].key, sizeof(struct key))) /* matching slot */ |
124 | return pos; |
125 | pos = (pos + 1) % MAXGLYPHS; |
126 | } |
127 | } |
128 | |
129 | static struct glyph *lookup_glyph(fz_font *font, float size, int gid, float *xp, float *yp) |
130 | { |
131 | fz_matrix trm, subpix_trm; |
132 | unsigned char subx, suby; |
133 | fz_pixmap *pixmap; |
134 | struct key key; |
135 | unsigned int pos; |
136 | int w, h; |
137 | |
138 | /* match fitz's glyph cache quantization */ |
139 | trm = fz_scale(size, -size); |
140 | trm.e = *xp; |
141 | trm.f = *yp; |
142 | fz_subpixel_adjust(ctx, &trm, &subpix_trm, &subx, &suby); |
143 | *xp = trm.e; |
144 | *yp = trm.f; |
145 | |
146 | /* |
147 | * Look it up in the table |
148 | */ |
149 | |
150 | memset(&key, 0, sizeof key); |
151 | key.font = font; |
152 | key.size = size; |
153 | key.gid = gid; |
154 | key.subx = subx; |
155 | key.suby = suby; |
156 | |
157 | pos = lookup_table(&key); |
158 | if (g_table[pos].key.font) |
159 | return &g_table[pos].glyph; |
160 | |
161 | /* |
162 | * Render the bitmap |
163 | */ |
164 | |
165 | glEnd(); |
166 | |
167 | pixmap = fz_render_glyph_pixmap(ctx, font, gid, &subpix_trm, NULL, 8); |
168 | w = pixmap->w; |
169 | h = pixmap->h; |
170 | |
171 | /* |
172 | * Find an empty slot in the texture |
173 | */ |
174 | |
175 | if (g_table_load == (MAXGLYPHS * 3) / 4) |
176 | { |
177 | puts("font cache table full, clearing cache" ); |
178 | clear_font_cache(); |
179 | pos = lookup_table(&key); |
180 | } |
181 | |
182 | if (h + PADDING > g_cache_h || w + PADDING > g_cache_w) |
183 | return NULL; |
184 | |
185 | if (g_cache_row_x + w + PADDING > g_cache_w) |
186 | { |
187 | g_cache_row_y += g_cache_row_h + PADDING; |
188 | g_cache_row_x = PADDING; |
189 | g_cache_row_h = 0; |
190 | } |
191 | if (g_cache_row_y + h + PADDING > g_cache_h) |
192 | { |
193 | puts("font cache texture full, clearing cache" ); |
194 | clear_font_cache(); |
195 | pos = lookup_table(&key); |
196 | } |
197 | |
198 | /* |
199 | * Copy bitmap into texture |
200 | */ |
201 | |
202 | memcpy(&g_table[pos].key, &key, sizeof(struct key)); |
203 | g_table[pos].glyph.w = pixmap->w; |
204 | g_table[pos].glyph.h = pixmap->h; |
205 | g_table[pos].glyph.lsb = pixmap->x; |
206 | g_table[pos].glyph.top = -pixmap->y; |
207 | g_table[pos].glyph.s = g_cache_row_x; |
208 | g_table[pos].glyph.t = g_cache_row_y; |
209 | g_table_load ++; |
210 | |
211 | glPixelStorei(GL_UNPACK_ALIGNMENT, 1); |
212 | glPixelStorei(GL_UNPACK_ROW_LENGTH, pixmap->w); |
213 | glTexSubImage2D(GL_TEXTURE_2D, 0, g_cache_row_x, g_cache_row_y, w, h, |
214 | GL_ALPHA, GL_UNSIGNED_BYTE, pixmap->samples); |
215 | glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); |
216 | |
217 | fz_drop_pixmap(ctx, pixmap); |
218 | |
219 | glBegin(GL_QUADS); |
220 | |
221 | g_cache_row_x += w + PADDING; |
222 | if (g_cache_row_h < h + PADDING) |
223 | g_cache_row_h = h + PADDING; |
224 | |
225 | return &g_table[pos].glyph; |
226 | } |
227 | |
228 | static float ui_draw_glyph(fz_font *font, float size, int gid, float x, float y) |
229 | { |
230 | struct glyph *glyph; |
231 | float s0, t0, s1, t1, xc, yc; |
232 | |
233 | glyph = lookup_glyph(font, size, gid, &x, &y); |
234 | if (!glyph) |
235 | return 0; |
236 | |
237 | s0 = (float) glyph->s / g_cache_w; |
238 | t0 = (float) glyph->t / g_cache_h; |
239 | s1 = (float) (glyph->s + glyph->w) / g_cache_w; |
240 | t1 = (float) (glyph->t + glyph->h) / g_cache_h; |
241 | xc = floorf(x) + glyph->lsb; |
242 | yc = floorf(y) - glyph->top + glyph->h; |
243 | |
244 | glTexCoord2f(s0, t0); glVertex2f(xc, yc - glyph->h); |
245 | glTexCoord2f(s1, t0); glVertex2f(xc + glyph->w, yc - glyph->h); |
246 | glTexCoord2f(s1, t1); glVertex2f(xc + glyph->w, yc); |
247 | glTexCoord2f(s0, t1); glVertex2f(xc, yc); |
248 | |
249 | return fz_advance_glyph(ctx, font, gid, 0) * size; |
250 | } |
251 | |
252 | float ui_measure_character(int c) |
253 | { |
254 | fz_font *font; |
255 | int gid = fz_encode_character_with_fallback(ctx, g_font, c, 0, 0, &font); |
256 | return fz_advance_glyph(ctx, font, gid, 0) * ui.fontsize; |
257 | } |
258 | |
259 | static float ui_draw_character_imp(float x, float y, int c) |
260 | { |
261 | fz_font *font; |
262 | int gid = fz_encode_character_with_fallback(ctx, g_font, c, 0, 0, &font); |
263 | return ui_draw_glyph(font, ui.fontsize, gid, x, y); |
264 | } |
265 | |
266 | static void ui_begin_text(void) |
267 | { |
268 | glBindTexture(GL_TEXTURE_2D, g_cache_tex); |
269 | glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); |
270 | glEnable(GL_BLEND); |
271 | glEnable(GL_TEXTURE_2D); |
272 | glBegin(GL_QUADS); |
273 | } |
274 | |
275 | static void ui_end_text(void) |
276 | { |
277 | glEnd(); |
278 | glDisable(GL_TEXTURE_2D); |
279 | glDisable(GL_BLEND); |
280 | } |
281 | |
282 | void ui_draw_string(float x, float y, const char *str) |
283 | { |
284 | int c; |
285 | ui_begin_text(); |
286 | while (*str) |
287 | { |
288 | str += fz_chartorune(&c, str); |
289 | x += ui_draw_character_imp(x, y + ui.baseline, c); |
290 | } |
291 | ui_end_text(); |
292 | } |
293 | |
294 | void ui_draw_string_part(float x, float y, const char *s, const char *e) |
295 | { |
296 | int c; |
297 | ui_begin_text(); |
298 | while (s < e) |
299 | { |
300 | s += fz_chartorune(&c, s); |
301 | x += ui_draw_character_imp(x, y + ui.baseline, c); |
302 | } |
303 | ui_end_text(); |
304 | } |
305 | |
306 | void ui_draw_character(float x, float y, int c) |
307 | { |
308 | ui_begin_text(); |
309 | ui_draw_character_imp(x, y + ui.baseline, c); |
310 | ui_end_text(); |
311 | } |
312 | |
313 | float ui_measure_string(const char *str) |
314 | { |
315 | int c; |
316 | float x = 0; |
317 | while (*str) |
318 | { |
319 | str += fz_chartorune(&c, str); |
320 | x += ui_measure_character(c); |
321 | } |
322 | return x; |
323 | } |
324 | |
325 | float ui_measure_string_part(const char *s, const char *e) |
326 | { |
327 | int c; |
328 | float w = 0; |
329 | while (s < e) |
330 | { |
331 | s += fz_chartorune(&c, s); |
332 | w += ui_measure_character(c); |
333 | } |
334 | return w; |
335 | } |
336 | |
337 | int ui_break_lines(char *a, struct line *lines, int maxlines, int width, int *maxwidth) |
338 | { |
339 | char *next, *space = NULL, *b = a; |
340 | int c, n = 0; |
341 | float space_x, x = 0, w = 0; |
342 | |
343 | if (maxwidth) |
344 | *maxwidth = 0; |
345 | |
346 | while (*b) |
347 | { |
348 | next = b + fz_chartorune(&c, b); |
349 | if (c == '\r' || c == '\n') |
350 | { |
351 | if (lines && n < maxlines) |
352 | { |
353 | lines[n].a = a; |
354 | lines[n].b = b; |
355 | } |
356 | ++n; |
357 | if (maxwidth && *maxwidth < x) |
358 | *maxwidth = x; |
359 | a = next; |
360 | x = 0; |
361 | space = NULL; |
362 | } |
363 | else |
364 | { |
365 | if (c == ' ') |
366 | { |
367 | space = b; |
368 | space_x = x; |
369 | } |
370 | |
371 | w = ui_measure_character(c); |
372 | if (x + w > width) |
373 | { |
374 | if (space) |
375 | { |
376 | if (lines && n < maxlines) |
377 | { |
378 | lines[n].a = a; |
379 | lines[n].b = space; |
380 | } |
381 | ++n; |
382 | if (maxwidth && *maxwidth < space_x) |
383 | *maxwidth = space_x; |
384 | a = next = space + 1; |
385 | x = 0; |
386 | space = NULL; |
387 | } |
388 | else |
389 | { |
390 | if (lines && n < maxlines) |
391 | { |
392 | lines[n].a = a; |
393 | lines[n].b = b; |
394 | } |
395 | ++n; |
396 | if (maxwidth && *maxwidth < x) |
397 | *maxwidth = x; |
398 | a = b; |
399 | x = w; |
400 | space = NULL; |
401 | } |
402 | } |
403 | else |
404 | { |
405 | x += w; |
406 | } |
407 | } |
408 | b = next; |
409 | } |
410 | |
411 | if (lines && n < maxlines) |
412 | { |
413 | lines[n].a = a; |
414 | lines[n].b = b; |
415 | } |
416 | ++n; |
417 | if (maxwidth && *maxwidth < x) |
418 | *maxwidth = x; |
419 | return n < maxlines ? n : maxlines; |
420 | } |
421 | |
422 | void ui_draw_lines(float x, float y, struct line *lines, int n) |
423 | { |
424 | int i; |
425 | for (i = 0; i < n; ++i) |
426 | { |
427 | ui_draw_string_part(x, y, lines[i].a, lines[i].b); |
428 | y += ui.lineheight; |
429 | } |
430 | } |
431 | |