1 /* This is a slightly modified
2 https://github.com/ccxvii/snippets/blob/master/glfont.c by Tor Andersson
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 */
44 signed char lsb
, top
, w
, h
;
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)
70 code
= FT_Init_FreeType(&g_freetype_lib
);
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)
86 unsigned char *zero
= calloc(g_cache_w
, g_cache_h
);
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
);
94 memset(g_table
, 0, sizeof(g_table
));
97 g_cache_row_y
= PADDING
;
98 g_cache_row_x
= PADDING
;
102 static void *filecontents (const char *path
, int *len
)
109 ret
= stat(path
, &st
);
110 if (ret
) err(1, "failed to stat `%s'", path
);
111 if (st
.st_size
> INT_MAX
) errx(1, "font `%s' is too big", path
);
112 res
= malloc(st
.st_size
);
114 err(1, "failed to allocate %llu bytes for `%s'",
115 st
.st_size
+0ull, path
);
117 fd
= open(path
, O_RDONLY
);
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", st
.st_size
+0llu, nread
);
124 *len
= (int) st
.st_size
;
128 static FT_Face
load_font(const char *fontname
)
134 if (g_freetype_lib
== NULL
)
140 base
= filecontents(fontname
, &len
);
141 code
= FT_New_Memory_Face(g_freetype_lib
, base
, len
, 0, &face
);
144 errx(1, "FT_New_Memory_Face for `%s' failed: %d",
148 FT_Select_Charmap(face
, ft_encoding_unicode
);
152 static FT_Face
load_builtin_font(const void *base
, int len
)
157 if (g_freetype_lib
== NULL
)
163 code
= FT_New_Memory_Face(g_freetype_lib
, base
, len
, 0, &face
);
166 errx (1, "failed to load builtin font: %d\n", code
);
170 FT_Select_Charmap(face
, ft_encoding_unicode
);
174 static void UNUSED_ATTR
free_font(FT_Face face
)
180 static unsigned int hashfunc(struct key
*key
)
182 unsigned char *buf
= (unsigned char *)key
;
183 unsigned int len
= sizeof(struct key
);
186 h
= *buf
++ + (h
<< 6) + (h
<< 16) - h
;
190 static unsigned int lookup_table(struct key
*key
)
192 unsigned int pos
= hashfunc(key
) % MAXGLYPHS
;
195 if (!g_table
[pos
].key
.face
) /* empty slot */
197 if (!memcmp(key
, &g_table
[pos
].key
, sizeof(struct key
))) /* matching slot */
199 pos
= (pos
+ 1) % MAXGLYPHS
;
203 static struct glyph
* lookup_glyph(FT_Face face
, int size
, int gid
, int subx
, int suby
)
212 * Look it up in the table
221 pos
= lookup_table(&key
);
222 if (g_table
[pos
].key
.face
)
223 return &g_table
[pos
].glyph
;
235 FT_Set_Transform(face
, NULL
, &subv
);
237 code
= FT_Load_Glyph(face
, gid
, FT_LOAD_NO_BITMAP
| FT_LOAD_NO_HINTING
);
241 code
= FT_Render_Glyph(face
->glyph
, FT_RENDER_MODE_LIGHT
);
245 w
= face
->glyph
->bitmap
.width
;
246 h
= face
->glyph
->bitmap
.rows
;
249 * Find an empty slot in the texture
252 if (g_table_load
== (MAXGLYPHS
* 3) / 4)
254 lprintf("font cache table full, clearing cache");
256 pos
= lookup_table(&key
);
259 if (h
+ PADDING
> g_cache_h
|| w
+ PADDING
> g_cache_w
)
260 errx(1, "rendered glyph exceeds cache dimensions");
262 if (g_cache_row_x
+ w
+ PADDING
> g_cache_w
)
264 g_cache_row_y
+= g_cache_row_h
+ PADDING
;
265 g_cache_row_x
= PADDING
;
268 if (g_cache_row_y
+ h
+ PADDING
> g_cache_h
)
270 lprintf("font cache texture full, clearing cache");
272 pos
= lookup_table(&key
);
276 * Copy bitmap into texture
279 memcpy(&g_table
[pos
].key
, &key
, sizeof(struct key
));
280 g_table
[pos
].glyph
.w
= face
->glyph
->bitmap
.width
;
281 g_table
[pos
].glyph
.h
= face
->glyph
->bitmap
.rows
;
282 g_table
[pos
].glyph
.lsb
= face
->glyph
->bitmap_left
;
283 g_table
[pos
].glyph
.top
= face
->glyph
->bitmap_top
;
284 g_table
[pos
].glyph
.s
= g_cache_row_x
;
285 g_table
[pos
].glyph
.t
= g_cache_row_y
;
286 g_table
[pos
].glyph
.advance
= face
->glyph
->advance
.x
/ 64.0;
289 glPixelStorei(GL_UNPACK_ALIGNMENT
, 1);
290 glPixelStorei(GL_UNPACK_ROW_LENGTH
, face
->glyph
->bitmap
.pitch
);
291 glTexSubImage2D(GL_TEXTURE_2D
, 0, g_cache_row_x
, g_cache_row_y
, w
, h
,
292 GL_ALPHA
, GL_UNSIGNED_BYTE
, face
->glyph
->bitmap
.buffer
);
293 glPixelStorei(GL_UNPACK_ROW_LENGTH
, 0);
298 g_cache_row_x
+= w
+ PADDING
;
299 if (g_cache_row_h
< h
+ PADDING
)
300 g_cache_row_h
= h
+ PADDING
;
302 return &g_table
[pos
].glyph
;
305 static float draw_glyph(FT_Face face
, int size
, int gid
, float x
, float y
)
308 int subx
= (x
- floor(x
)) * XPRECISION
;
309 int suby
= (y
- floor(y
)) * YPRECISION
;
311 GLfloat
*t
= state
.texcoords
;
312 GLfloat
*v
= state
.vertices
;
314 float s0
, t0
, s1
, t1
, xc
, yc
;
316 subx
= (subx
* 64) / XPRECISION
;
317 suby
= (suby
* 64) / YPRECISION
;
319 glyph
= lookup_glyph(face
, size
, gid
, subx
, suby
);
323 s0
= (float) glyph
->s
/ g_cache_w
;
324 t0
= (float) glyph
->t
/ g_cache_h
;
325 s1
= (float) (glyph
->s
+ glyph
->w
) / g_cache_w
;
326 t1
= (float) (glyph
->t
+ glyph
->h
) / g_cache_h
;
327 xc
= floor(x
) + glyph
->lsb
;
328 yc
= floor(y
) - glyph
->top
+ glyph
->h
;
331 t
[0] = s0
; t
[1] = t0
; v
[0] = xc
; v
[1] = yc
- glyph
->h
;
332 t
[2] = s1
; t
[3] = t0
; v
[2] = xc
+ glyph
->w
; v
[3] = yc
- glyph
->h
;
333 t
[4] = s0
; t
[5] = t1
; v
[4] = xc
; v
[5] = yc
;
334 t
[6] = s1
; t
[7] = t1
; v
[6] = xc
+ glyph
->w
; v
[7] = yc
;
336 glDrawArrays(GL_TRIANGLE_STRIP
, 0, 4);
338 glTexCoord2f(s0
, t0
); glVertex2f(xc
, yc
- glyph
->h
);
339 glTexCoord2f(s1
, t0
); glVertex2f(xc
+ glyph
->w
, yc
- glyph
->h
);
340 glTexCoord2f(s1
, t1
); glVertex2f(xc
+ glyph
->w
, yc
);
341 glTexCoord2f(s0
, t1
); glVertex2f(xc
, yc
);
344 return glyph
->advance
;
347 static float measure_string(FT_Face face
, float fsize
, const char *str
)
349 int size
= fsize
* 64;
355 FT_Set_Char_Size(face
, size
, size
, 72, 72);
359 str
+= fz_chartorune(&ucs
, str
);
360 gid
= FT_Get_Char_Index(face
, ucs
);
361 FT_Get_Advance(face
, gid
, FT_LOAD_NO_BITMAP
| FT_LOAD_NO_HINTING
, &advance
);
362 w
+= advance
/ 65536.0;
366 FT_Get_Kerning(face
, left
, gid
, FT_KERNING_UNFITTED
, &kern
);
375 static float draw_string(FT_Face face
, float fsize
, float x
, float y
,
378 int size
= fsize
* 64;
382 FT_Set_Char_Size(face
, size
, size
, 72, 72);
384 glBindTexture(GL_TEXTURE_2D
, g_cache_tex
);
388 glVertexPointer(2, GL_FLOAT
, 0, state
.vertices
);
389 glTexCoordPointer(2, GL_FLOAT
, 0, state
.texcoords
);
393 str
+= fz_chartorune(&ucs
, str
);
394 gid
= FT_Get_Char_Index(face
, ucs
);
395 x
+= draw_glyph(face
, size
, gid
, x
, y
);
399 FT_Get_Kerning(face
, left
, gid
, FT_KERNING_UNFITTED
, &kern
);
413 c-file-style: "linux"