| 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 | |