Make non-builtin font loading work again
[llpp.git] / glfont.c
blobd78b126e15b5a1cac0a1ae467c78d4d8e48bf5d1
1 /* This is a slightly modified
2 https://github.com/ccxvii/snippets/blob/master/glfont.c by Tor Andersson
3 */
4 /*
5 * A very simple font cache and rasterizer that uses freetype
6 * to draw fonts from a single OpenGL texture. The code uses
7 * a linear-probe hashtable, and writes new glyphs into
8 * the texture using glTexSubImage2D. When the texture fills
9 * up, or the hash table gets too crowded, everything is wiped.
11 * This is designed to be used for horizontal text only,
12 * and draws unhinted text with subpixel accurate metrics
13 * and kerning. As such, you should always call the drawing
14 * function with an identity transform that maps units
15 * to pixels accurately.
17 * If you wish to use it to draw arbitrarily transformed
18 * text, change the min and mag filters to GL_LINEAR and
19 * add a pixel of padding between glyphs and rows, and
20 * make sure to clear the texture when wiping the cache.
23 #include FT_ADVANCES_H
24 typedef int Rune; /* 32 bits */
26 #define PADDING 1 /* set to 0 to save some space but disallow arbitrary transforms */
28 #define MAXGLYPHS 4093 /* prime number for hash table goodness */
29 #define CACHESIZE 256
30 #define XPRECISION 4
31 #define YPRECISION 1
33 struct key
35 FT_Face face;
36 short size;
37 short gid;
38 short subx;
39 short suby;
42 struct glyph
44 signed char lsb, top, w, h;
45 short s, t;
46 float advance;
49 struct table
51 struct key key;
52 struct glyph glyph;
55 static FT_Library g_freetype_lib = NULL;
56 static struct table g_table[MAXGLYPHS];
57 static int g_table_load = 0;
58 static unsigned int g_cache_tex = 0;
59 static int g_cache_w = CACHESIZE;
60 static int g_cache_h = CACHESIZE;
61 static int g_cache_row_y = 0;
62 static int g_cache_row_x = 0;
63 static int g_cache_row_h = 0;
64 static int g_use_kern = 0;
66 static void init_font_cache(void)
68 int code;
70 code = FT_Init_FreeType(&g_freetype_lib);
71 if (code)
72 errx(1, "cannot initialize freetype");
74 glGenTextures(1, &g_cache_tex);
75 glBindTexture(GL_TEXTURE_2D, g_cache_tex);
76 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
77 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
78 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
79 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
80 glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, g_cache_w, g_cache_h, 0, GL_ALPHA, GL_UNSIGNED_BYTE, NULL);
83 static void clear_font_cache(void)
85 #if PADDING > 0
86 unsigned char *zero = calloc(g_cache_w, g_cache_h);
87 if (!zero)
88 err(1, "malloc zero (%u bytes failed)", g_cache_w * g_cache_h);
89 glBindTexture(GL_TEXTURE_2D, g_cache_tex);
90 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, g_cache_w, g_cache_h, GL_ALPHA, GL_UNSIGNED_BYTE, zero);
91 free(zero);
92 #endif
94 memset(g_table, 0, sizeof(g_table));
95 g_table_load = 0;
97 g_cache_row_y = PADDING;
98 g_cache_row_x = PADDING;
99 g_cache_row_h = 0;
102 static void *filecontents (char *path, int *len)
104 int ret, fd;
105 void *res;
106 struct stat st;
107 ssize_t nread;
109 ret = stat(path, &st);
110 if (ret) err (1, "failed to stat `%s'", path);
111 if (st.st_size > INT_MAX) err(1, "font `%s' is too big", path);
112 res = malloc(st.st_size);
113 if (!res)
114 err(1, "failed to allocate %llu bytes for `%s'",
115 st.st_size+0ull, path);
117 fd = open(path, O_RDONLY | O_BINARY);
118 if (fd < 0) err(1, "failed to open `%s'", path);
120 nread = read(fd, res, st.st_size);
121 if (nread - st.st_size)
122 err(1, "read %llu failed, ret=%zd",
123 st.st_size+0llu, nread);
125 *len = (int) st.st_size;
126 return res;
129 static FT_Face load_font(char *fontname)
131 FT_Face face;
132 int code, len;
133 void *base;
135 if (g_freetype_lib == NULL)
137 init_font_cache();
138 clear_font_cache();
141 base = filecontents(fontname, &len);
142 code = FT_New_Memory_Face(g_freetype_lib, base, len, 0, &face);
143 if (code)
145 err(1, "FT_New_Memory_Face for `%s' failed: %d",
146 fontname, code);
149 FT_Select_Charmap(face, ft_encoding_unicode);
150 return face;
153 static FT_Face UNUSED_ATTR load_builtin_font(const void *base, int len)
155 FT_Face face;
156 int code;
158 if (g_freetype_lib == NULL)
160 init_font_cache();
161 clear_font_cache();
164 code = FT_New_Memory_Face(g_freetype_lib, base, len, 0, &face);
165 if (code)
167 errx (1, "failed to load builtin font: %d\n", code);
168 return NULL;
171 FT_Select_Charmap(face, ft_encoding_unicode);
172 return face;
175 static void UNUSED_ATTR free_font(FT_Face face)
177 clear_font_cache();
178 FT_Done_Face(face);
181 static unsigned int hashfunc(struct key *key)
183 unsigned char *buf = (unsigned char *)key;
184 unsigned int len = sizeof(struct key);
185 unsigned int h = 0;
186 while (len--)
187 h = *buf++ + (h << 6) + (h << 16) - h;
188 return h;
191 static unsigned int lookup_table(struct key *key)
193 unsigned int pos = hashfunc(key) % MAXGLYPHS;
194 while (1)
196 if (!g_table[pos].key.face) /* empty slot */
197 return pos;
198 if (!memcmp(key, &g_table[pos].key, sizeof(struct key))) /* matching slot */
199 return pos;
200 pos = (pos + 1) % MAXGLYPHS;
204 static struct glyph * lookup_glyph(FT_Face face, int size, int gid, int subx, int suby)
206 FT_Vector subv;
207 struct key key;
208 unsigned int pos;
209 int code;
210 int w, h;
213 * Look it up in the table
216 key.face = face;
217 key.size = size;
218 key.gid = gid;
219 key.subx = subx;
220 key.suby = suby;
222 pos = lookup_table(&key);
223 if (g_table[pos].key.face)
224 return &g_table[pos].glyph;
227 * Render the bitmap
229 #ifdef FFP
230 glEnd();
231 #endif
233 subv.x = subx;
234 subv.y = suby;
236 FT_Set_Transform(face, NULL, &subv);
238 code = FT_Load_Glyph(face, gid, FT_LOAD_NO_BITMAP | FT_LOAD_NO_HINTING);
239 if (code < 0)
240 return NULL;
242 code = FT_Render_Glyph(face->glyph, FT_RENDER_MODE_LIGHT);
243 if (code < 0)
244 return NULL;
246 w = face->glyph->bitmap.width;
247 h = face->glyph->bitmap.rows;
250 * Find an empty slot in the texture
253 if (g_table_load == (MAXGLYPHS * 3) / 4)
255 lprintf("font cache table full, clearing cache");
256 clear_font_cache();
257 pos = lookup_table(&key);
260 if (h + PADDING > g_cache_h || w + PADDING > g_cache_w)
261 errx(1, "rendered glyph exceeds cache dimensions");
263 if (g_cache_row_x + w + PADDING > g_cache_w)
265 g_cache_row_y += g_cache_row_h + PADDING;
266 g_cache_row_x = PADDING;
267 g_cache_row_h = 0;
269 if (g_cache_row_y + h + PADDING > g_cache_h)
271 lprintf("font cache texture full, clearing cache");
272 clear_font_cache();
273 pos = lookup_table(&key);
277 * Copy bitmap into texture
280 memcpy(&g_table[pos].key, &key, sizeof(struct key));
281 g_table[pos].glyph.w = face->glyph->bitmap.width;
282 g_table[pos].glyph.h = face->glyph->bitmap.rows;
283 g_table[pos].glyph.lsb = face->glyph->bitmap_left;
284 g_table[pos].glyph.top = face->glyph->bitmap_top;
285 g_table[pos].glyph.s = g_cache_row_x;
286 g_table[pos].glyph.t = g_cache_row_y;
287 g_table[pos].glyph.advance = face->glyph->advance.x / 64.0;
288 g_table_load ++;
290 glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
291 glPixelStorei(GL_UNPACK_ROW_LENGTH, face->glyph->bitmap.pitch);
292 glTexSubImage2D(GL_TEXTURE_2D, 0, g_cache_row_x, g_cache_row_y, w, h,
293 GL_ALPHA, GL_UNSIGNED_BYTE, face->glyph->bitmap.buffer);
294 glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
296 #ifdef FFP
297 glBegin(GL_QUADS);
298 #endif
299 g_cache_row_x += w + PADDING;
300 if (g_cache_row_h < h + PADDING)
301 g_cache_row_h = h + PADDING;
303 return &g_table[pos].glyph;
306 static float draw_glyph(FT_Face face, int size, int gid, float x, float y)
308 struct glyph *glyph;
309 int subx = (x - floor(x)) * XPRECISION;
310 int suby = (y - floor(y)) * YPRECISION;
311 #ifndef FFP
312 GLfloat *t = state.texcoords;
313 GLfloat *v = state.vertices;
314 #endif
315 float s0, t0, s1, t1, xc, yc;
317 subx = (subx * 64) / XPRECISION;
318 suby = (suby * 64) / YPRECISION;
320 glyph = lookup_glyph(face, size, gid, subx, suby);
321 if (!glyph)
322 return 0.0;
324 s0 = (float) glyph->s / g_cache_w;
325 t0 = (float) glyph->t / g_cache_h;
326 s1 = (float) (glyph->s + glyph->w) / g_cache_w;
327 t1 = (float) (glyph->t + glyph->h) / g_cache_h;
328 xc = floor(x) + glyph->lsb;
329 yc = floor(y) - glyph->top + glyph->h;
331 #ifndef FFP
332 t[0] = s0; t[1] = t0; v[0] = xc; v[1] = yc - glyph->h;
333 t[2] = s1; t[3] = t0; v[2] = xc + glyph->w; v[3] = yc - glyph->h;
334 t[4] = s0; t[5] = t1; v[4] = xc; v[5] = yc;
335 t[6] = s1; t[7] = t1; v[6] = xc + glyph->w; v[7] = yc;
337 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
338 #else
339 glTexCoord2f(s0, t0); glVertex2f(xc, yc - glyph->h);
340 glTexCoord2f(s1, t0); glVertex2f(xc + glyph->w, yc - glyph->h);
341 glTexCoord2f(s1, t1); glVertex2f(xc + glyph->w, yc);
342 glTexCoord2f(s0, t1); glVertex2f(xc, yc);
343 #endif
345 return glyph->advance;
348 static float measure_string(FT_Face face, float fsize, char *str)
350 int size = fsize * 64;
351 FT_Fixed advance;
352 Rune ucs, gid;
353 float w = 0.0;
354 int left = 0;
356 FT_Set_Char_Size(face, size, size, 72, 72);
358 while (*str)
360 str += fz_chartorune(&ucs, str);
361 gid = FT_Get_Char_Index(face, ucs);
362 FT_Get_Advance(face, gid, FT_LOAD_NO_BITMAP | FT_LOAD_NO_HINTING, &advance);
363 w += advance / 65536.0;
364 if (g_use_kern) {
365 FT_Vector kern;
367 FT_Get_Kerning(face, left, gid, FT_KERNING_UNFITTED, &kern);
368 w += kern.x / 64.0;
370 left = gid;
373 return w;
376 static float draw_string(FT_Face face, float fsize, float x, float y, char *str)
378 int size = fsize * 64;
379 Rune ucs, gid;
380 int left = 0;
382 FT_Set_Char_Size(face, size, size, 72, 72);
384 glBindTexture(GL_TEXTURE_2D, g_cache_tex);
385 #ifdef FFP
386 glBegin(GL_QUADS);
387 #else
388 glVertexPointer(2, GL_FLOAT, 0, state.vertices);
389 glTexCoordPointer(2, GL_FLOAT, 0, state.texcoords);
390 #endif
391 while (*str)
393 str += fz_chartorune(&ucs, str);
394 gid = FT_Get_Char_Index(face, ucs);
395 x += draw_glyph(face, size, gid, x, y);
396 if (g_use_kern) {
397 FT_Vector kern;
399 FT_Get_Kerning(face, left, gid, FT_KERNING_UNFITTED, &kern);
400 x += kern.x / 64.0;
402 left = gid;
405 #ifdef FFP
406 glEnd();
407 #endif
409 return x;
412 Local Variables:
413 c-file-style: "linux"
414 End: