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
29struct key
30{
31 fz_font *font;
32 float size;
33 short gid;
34 unsigned char subx;
35 unsigned char suby;
36};
37
38struct glyph
39{
40 char lsb, top, w, h;
41 short s, t;
42};
43
44struct table
45{
46 struct key key;
47 struct glyph glyph;
48};
49
50static struct table g_table[MAXGLYPHS];
51static int g_table_load = 0;
52static unsigned int g_cache_tex = 0;
53static int g_cache_w = CACHESIZE;
54static int g_cache_h = CACHESIZE;
55static int g_cache_row_y = 0;
56static int g_cache_row_x = 0;
57static int g_cache_row_h = 0;
58
59static fz_font *g_font = NULL;
60
61static 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
79void 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
100void ui_finish_fonts(void)
101{
102 clear_font_cache();
103 fz_drop_font(ctx, g_font);
104}
105
106static 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
116static 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
129static 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
228static 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
252float 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
259static 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
266static 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
275static void ui_end_text(void)
276{
277 glEnd();
278 glDisable(GL_TEXTURE_2D);
279 glDisable(GL_BLEND);
280}
281
282void 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
294void 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
306void 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
313float 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
325float 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
337int 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
422void 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