libdrawtext
view src/font.c @ 47:00f98842608f
dtx_vprintf
author | John Tsiombikas <nuclear@member.fsf.org> |
---|---|
date | Fri, 18 Sep 2015 06:40:58 +0300 |
parents | f067608d8a7c |
children | 670d0affc08f |
line source
1 /*
2 libdrawtext - a simple library for fast text rendering in OpenGL
3 Copyright (C) 2011-2014 John Tsiombikas <nuclear@member.fsf.org>
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18 #ifndef NO_FREETYPE
19 #define USE_FREETYPE
20 #endif
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <math.h>
26 #include <limits.h>
27 #include <ctype.h>
28 #include <float.h>
29 #include <errno.h>
30 #ifdef USE_FREETYPE
31 #include <ft2build.h>
32 #include FT_FREETYPE_H
33 #endif
34 #include "drawtext.h"
35 #include "drawtext_impl.h"
37 #define FTSZ_TO_PIXELS(x) ((x) / 64)
38 #define MAX_IMG_WIDTH 4096
41 #ifdef USE_FREETYPE
42 static int init_freetype(void);
43 static void cleanup(void);
45 static void calc_best_size(int total_width, int max_gwidth, int max_gheight,
46 int padding, int pow2, int *imgw, int *imgh);
47 static int next_pow2(int x);
49 static FT_Library ft;
52 static int init_done;
54 static int init_freetype(void)
55 {
56 if(!init_done) {
57 if(FT_Init_FreeType(&ft) != 0) {
58 return -1;
59 }
60 atexit(cleanup);
61 init_done = 1;
62 }
63 return 0;
64 }
66 static void cleanup(void)
67 {
68 if(init_done) {
69 FT_Done_FreeType(ft);
70 }
71 }
72 #endif /* USE_FREETYPE */
74 struct dtx_font *dtx_open_font(const char *fname, int sz)
75 {
76 struct dtx_font *fnt = 0;
78 #ifdef USE_FREETYPE
79 init_freetype();
81 if(!(fnt = calloc(1, sizeof *fnt))) {
82 fperror("failed to allocate font structure");
83 return 0;
84 }
86 if(FT_New_Face(ft, fname, 0, (FT_Face*)&fnt->face) != 0) {
87 fprintf(stderr, "failed to open font file: %s\n", fname);
88 return 0;
89 }
91 /* pre-create the extended ASCII range glyphmap */
92 if(sz) {
93 dtx_prepare_range(fnt, sz, 0, 256);
95 if(!dtx_font) {
96 dtx_use_font(fnt, sz);
97 }
98 }
99 #else
100 fprintf(stderr, "ignoring call to dtx_open_font: not compiled with freetype support!\n");
101 #endif
103 return fnt;
104 }
106 struct dtx_font *dtx_open_font_glyphmap(const char *fname)
107 {
108 struct dtx_font *fnt;
109 struct dtx_glyphmap *gmap;
111 if(!(fnt = calloc(1, sizeof *fnt))) {
112 fperror("failed to allocate font structure");
113 return 0;
114 }
116 if(fname) {
117 if(!(gmap = dtx_load_glyphmap(fname))) {
118 free(fnt);
119 return 0;
120 }
122 dtx_add_glyphmap(fnt, gmap);
124 if(!dtx_font) {
125 dtx_use_font(fnt, gmap->ptsize);
126 }
127 }
128 return fnt;
129 }
131 void dtx_close_font(struct dtx_font *fnt)
132 {
133 if(!fnt) return;
135 #ifdef USE_FREETYPE
136 FT_Done_Face(fnt->face);
137 #endif
139 /* destroy the glyphmaps */
140 while(fnt->gmaps) {
141 void *tmp = fnt->gmaps;
142 fnt->gmaps = fnt->gmaps->next;
143 dtx_free_glyphmap(tmp);
144 }
146 free(fnt);
147 }
149 void dtx_prepare(struct dtx_font *fnt, int sz)
150 {
151 if(!dtx_get_font_glyphmap_range(fnt, sz, 0, 256)) {
152 fprintf(stderr, "%s: failed (sz: %d, range: 0-255 [ascii])\n", __FUNCTION__, sz);
153 }
154 }
156 void dtx_prepare_range(struct dtx_font *fnt, int sz, int cstart, int cend)
157 {
158 if(!dtx_get_font_glyphmap_range(fnt, sz, cstart, cend)) {
159 fprintf(stderr, "%s: failed (sz: %d, range: %d-%d)\n", __FUNCTION__, sz, cstart, cend);
160 }
161 }
163 struct dtx_glyphmap *dtx_get_font_glyphmap(struct dtx_font *fnt, int sz, int code)
164 {
165 struct dtx_glyphmap *gm;
167 /* check to see if the last we've given out fits the bill */
168 if(fnt->last_gmap && code >= fnt->last_gmap->cstart && code < fnt->last_gmap->cend && fnt->last_gmap->ptsize == sz) {
169 return fnt->last_gmap;
170 }
172 /* otherwise search for the appropriate glyphmap */
173 gm = fnt->gmaps;
174 while(gm) {
175 if(code >= gm->cstart && code < gm->cend && sz == gm->ptsize) {
176 fnt->last_gmap = gm;
177 return gm;
178 }
179 gm = gm->next;
180 }
181 return 0;
182 }
184 struct dtx_glyphmap *dtx_get_font_glyphmap_range(struct dtx_font *fnt, int sz, int cstart, int cend)
185 {
186 struct dtx_glyphmap *gm;
188 /* search the available glyphmaps to see if we've got one that includes
189 * the requested range
190 */
191 gm = fnt->gmaps;
192 while(gm) {
193 if(gm->cstart <= cstart && gm->cend >= cend && gm->ptsize == sz) {
194 return gm;
195 }
196 gm = gm->next;
197 }
199 /* not found, create one and add it to the list */
200 if(!(gm = dtx_create_glyphmap_range(fnt, sz, cstart, cend))) {
201 return 0;
202 }
203 return gm;
204 }
206 struct dtx_glyphmap *dtx_create_glyphmap_range(struct dtx_font *fnt, int sz, int cstart, int cend)
207 {
208 struct dtx_glyphmap *gmap = 0;
210 #ifdef USE_FREETYPE
211 FT_Face face = fnt->face;
212 int i, j;
213 int gx, gy;
214 int padding = 4;
215 int total_width, max_width, max_height;
217 FT_Set_Char_Size(fnt->face, 0, sz * 64, 72, 72);
219 if(!(gmap = calloc(1, sizeof *gmap))) {
220 return 0;
221 }
223 gmap->ptsize = sz;
224 gmap->cstart = cstart;
225 gmap->cend = cend;
226 gmap->crange = cend - cstart;
227 gmap->line_advance = FTSZ_TO_PIXELS((float)face->size->metrics.height);
229 if(!(gmap->glyphs = malloc(gmap->crange * sizeof *gmap->glyphs))) {
230 free(gmap);
231 return 0;
232 }
234 total_width = padding;
235 max_width = max_height = 0;
237 for(i=0; i<gmap->crange; i++) {
238 int w, h;
240 FT_Load_Char(face, i + cstart, 0);
241 w = FTSZ_TO_PIXELS(face->glyph->metrics.width);
242 h = FTSZ_TO_PIXELS(face->glyph->metrics.height);
244 if(w > max_width) max_width = w;
245 if(h > max_height) max_height = h;
247 total_width += w + padding;
248 }
250 calc_best_size(total_width, max_width, max_height, padding, 1, &gmap->xsz, &gmap->ysz);
252 if(!(gmap->pixels = malloc(gmap->xsz * gmap->ysz))) {
253 free(gmap->glyphs);
254 free(gmap);
255 return 0;
256 }
257 memset(gmap->pixels, 0, gmap->xsz * gmap->ysz);
259 gx = padding;
260 gy = padding;
262 for(i=0; i<gmap->crange; i++) {
263 float gwidth, gheight;
264 unsigned char *src, *dst;
265 FT_GlyphSlot glyph;
267 FT_Load_Char(face, i + cstart, FT_LOAD_RENDER);
268 glyph = face->glyph;
269 gwidth = FTSZ_TO_PIXELS((float)glyph->metrics.width);
270 gheight = FTSZ_TO_PIXELS((float)glyph->metrics.height);
272 if(gx > gmap->xsz - gwidth - padding) {
273 gx = padding;
274 gy += max_height + padding;
275 }
277 src = glyph->bitmap.buffer;
278 dst = gmap->pixels + gy * gmap->xsz + gx;
280 for(j=0; j<glyph->bitmap.rows; j++) {
281 memcpy(dst, src, glyph->bitmap.width);
282 dst += gmap->xsz;
283 src += glyph->bitmap.pitch;
284 }
286 gmap->glyphs[i].code = i;
287 gmap->glyphs[i].x = gx - 1;
288 gmap->glyphs[i].y = gy - 1;
289 gmap->glyphs[i].width = gwidth + 2;
290 gmap->glyphs[i].height = gheight + 2;
291 gmap->glyphs[i].orig_x = -FTSZ_TO_PIXELS((float)glyph->metrics.horiBearingX) + 1;
292 gmap->glyphs[i].orig_y = FTSZ_TO_PIXELS((float)glyph->metrics.height - glyph->metrics.horiBearingY) + 1;
293 gmap->glyphs[i].advance = FTSZ_TO_PIXELS((float)glyph->metrics.horiAdvance);
294 /* also precalc normalized */
295 gmap->glyphs[i].nx = (float)gmap->glyphs[i].x / (float)gmap->xsz;
296 gmap->glyphs[i].ny = (float)gmap->glyphs[i].y / (float)gmap->ysz;
297 gmap->glyphs[i].nwidth = (float)gmap->glyphs[i].width / (float)gmap->xsz;
298 gmap->glyphs[i].nheight = (float)gmap->glyphs[i].height / (float)gmap->ysz;
300 gx += gwidth + padding;
301 }
303 /* add it to the glyphmaps list of the font */
304 dtx_add_glyphmap(fnt, gmap);
305 #endif /* USE_FREETYPE */
307 return gmap;
308 }
310 void dtx_free_glyphmap(struct dtx_glyphmap *gmap)
311 {
312 if(gmap) {
313 free(gmap->pixels);
314 free(gmap->glyphs);
315 free(gmap);
316 }
317 }
319 unsigned char *dtx_get_glyphmap_pixels(struct dtx_glyphmap *gmap)
320 {
321 return gmap->pixels;
322 }
324 int dtx_get_glyphmap_width(struct dtx_glyphmap *gmap)
325 {
326 return gmap->xsz;
327 }
329 int dtx_get_glyphmap_height(struct dtx_glyphmap *gmap)
330 {
331 return gmap->ysz;
332 }
334 struct dtx_glyphmap *dtx_load_glyphmap(const char *fname)
335 {
336 FILE *fp;
337 struct dtx_glyphmap *gmap;
339 if(!(fp = fopen(fname, "rb"))) {
340 return 0;
341 }
342 gmap = dtx_load_glyphmap_stream(fp);
343 fclose(fp);
344 return gmap;
345 }
347 struct dtx_glyphmap *dtx_load_glyphmap_stream(FILE *fp)
348 {
349 char buf[512];
350 int hdr_lines = 0;
351 struct dtx_glyphmap *gmap;
352 struct glyph *glyphs = 0;
353 struct glyph *g;
354 int min_code = INT_MAX;
355 int max_code = INT_MIN;
356 int i, max_pixval, num_pixels;
358 if(!(gmap = calloc(1, sizeof *gmap))) {
359 fperror("failed to allocate glyphmap");
360 return 0;
361 }
362 gmap->ptsize = -1;
363 gmap->line_advance = FLT_MIN;
365 while(hdr_lines < 3) {
366 char *line = buf;
367 if(!fgets(buf, sizeof buf, fp)) {
368 fperror("unexpected end of file");
369 goto err;
370 }
372 while(isspace(*line)) {
373 line++;
374 }
376 if(line[0] == '#') {
377 int c, res;
378 float x, y, xsz, ysz, orig_x, orig_y, adv, line_adv;
379 int ptsize;
381 if((res = sscanf(line + 1, " size: %d\n", &ptsize)) == 1) {
382 gmap->ptsize = ptsize;
384 } else if((res = sscanf(line + 1, " advance: %f\n", &line_adv)) == 1) {
385 gmap->line_advance = line_adv;
387 } else if((res = sscanf(line + 1, " %d: %fx%f+%f+%f o:%f,%f adv:%f\n",
388 &c, &xsz, &ysz, &x, &y, &orig_x, &orig_y, &adv)) == 8) {
389 if(!(g = malloc(sizeof *g))) {
390 fperror("failed to allocate glyph");
391 goto err;
392 }
393 g->code = c;
394 g->x = x;
395 g->y = y;
396 g->width = xsz;
397 g->height = ysz;
398 g->orig_x = orig_x;
399 g->orig_y = orig_y;
400 g->advance = adv;
401 /* normalized coordinates will be precalculated after everything is loaded */
403 g->next = glyphs;
404 glyphs = g;
406 if(c < min_code) {
407 min_code = c;
408 }
409 if(c > max_code) {
410 max_code = c;
411 }
413 } else {
414 fprintf(stderr, "%s: invalid glyph info line\n", __FUNCTION__);
415 goto err;
416 }
418 } else {
419 switch(hdr_lines) {
420 case 0:
421 if(0[line] != 'P' || 1[line] != '6') {
422 fprintf(stderr, "%s: invalid file format (magic)\n", __FUNCTION__);
423 goto err;
424 }
425 break;
427 case 1:
428 if(sscanf(line, "%d %d", &gmap->xsz, &gmap->ysz) != 2) {
429 fprintf(stderr, "%s: invalid file format (dim)\n", __FUNCTION__);
430 goto err;
431 }
432 break;
434 case 2:
435 {
436 char *endp;
437 max_pixval = strtol(line, &endp, 10);
438 if(endp == line) {
439 fprintf(stderr, "%s: invalid file format (maxval)\n", __FUNCTION__);
440 goto err;
441 }
442 }
443 break;
445 default:
446 break; /* can't happen */
447 }
448 hdr_lines++;
449 }
450 }
452 if(gmap->ptsize == -1 || gmap->line_advance == FLT_MIN) {
453 fprintf(stderr, "%s: invalid glyphmap, insufficient information in ppm comments\n", __FUNCTION__);
454 goto err;
455 }
457 /* precalculate normalized glyph coordinates */
458 g = glyphs;
459 while(g) {
460 g->nx = g->x / gmap->xsz;
461 g->ny = g->y / gmap->ysz;
462 g->nwidth = g->width / gmap->xsz;
463 g->nheight = g->height / gmap->ysz;
464 g = g->next;
465 }
467 num_pixels = gmap->xsz * gmap->ysz;
468 if(!(gmap->pixels = malloc(num_pixels))) {
469 fperror("failed to allocate pixels");
470 goto err;
471 }
473 for(i=0; i<num_pixels; i++) {
474 long c = fgetc(fp);
475 if(c == -1) {
476 fprintf(stderr, "unexpected end of file while reading pixels\n");
477 goto err;
478 }
479 gmap->pixels[i] = 255 * c / max_pixval;
480 fseek(fp, 2, SEEK_CUR);
481 }
483 gmap->cstart = min_code;
484 gmap->cend = max_code + 1;
485 gmap->crange = gmap->cend - gmap->cstart;
487 if(!(gmap->glyphs = calloc(gmap->crange, sizeof *gmap->glyphs))) {
488 fperror("failed to allocate glyph info");
489 goto err;
490 }
492 while(glyphs) {
493 struct glyph *g = glyphs;
494 glyphs = glyphs->next;
496 gmap->glyphs[g->code - gmap->cstart] = *g;
497 free(g);
498 }
499 return gmap;
501 err:
502 dtx_free_glyphmap(gmap);
503 while(glyphs) {
504 void *tmp = glyphs;
505 glyphs = glyphs->next;
506 free(tmp);
507 }
508 return 0;
509 }
511 int dtx_save_glyphmap(const char *fname, const struct dtx_glyphmap *gmap)
512 {
513 FILE *fp;
514 int res;
516 if(!(fp = fopen(fname, "wb"))) {
517 fprintf(stderr, "%s: failed to open file: %s: %s\n", __FUNCTION__, fname, strerror(errno));
518 return -1;
519 }
520 res = dtx_save_glyphmap_stream(fp, gmap);
521 fclose(fp);
522 return res;
523 }
525 int dtx_save_glyphmap_stream(FILE *fp, const struct dtx_glyphmap *gmap)
526 {
527 int i, num_pixels;
528 struct glyph *g = gmap->glyphs;
530 fprintf(fp, "P6\n%d %d\n", gmap->xsz, gmap->ysz);
531 fprintf(fp, "# size: %d\n", gmap->ptsize);
532 fprintf(fp, "# advance: %g\n", gmap->line_advance);
533 for(i=0; i<gmap->crange; i++) {
534 fprintf(fp, "# %d: %gx%g+%g+%g o:%g,%g adv:%g\n", g->code, g->width, g->height, g->x, g->y,
535 g->orig_x, g->orig_y, g->advance);
536 g++;
537 }
538 fprintf(fp, "255\n");
540 num_pixels = gmap->xsz * gmap->ysz;
541 for(i=0; i<num_pixels; i++) {
542 int c = gmap->pixels[i];
543 fputc(c, fp);
544 fputc(c, fp);
545 fputc(c, fp);
546 }
547 return 0;
548 }
550 void dtx_add_glyphmap(struct dtx_font *fnt, struct dtx_glyphmap *gmap)
551 {
552 gmap->next = fnt->gmaps;
553 fnt->gmaps = gmap;
554 }
557 void dtx_use_font(struct dtx_font *fnt, int sz)
558 {
559 dtx_gl_init();
561 dtx_font = fnt;
562 dtx_font_sz = sz;
563 }
565 float dtx_line_height(void)
566 {
567 struct dtx_glyphmap *gmap = dtx_get_font_glyphmap(dtx_font, dtx_font_sz, '\n');
569 return gmap->line_advance;
570 }
572 void dtx_glyph_box(int code, struct dtx_box *box)
573 {
574 int cidx;
575 struct dtx_glyphmap *gmap;
576 gmap = dtx_get_font_glyphmap(dtx_font, dtx_font_sz, code);
578 cidx = code - gmap->cstart;
580 box->x = gmap->glyphs[cidx].orig_x;
581 box->y = gmap->glyphs[cidx].orig_y;
582 box->width = gmap->glyphs[cidx].width;
583 box->height = gmap->glyphs[cidx].height;
584 }
586 float dtx_glyph_width(int code)
587 {
588 struct dtx_box box;
589 dtx_glyph_box(code, &box);
590 return box.width;
591 }
593 float dtx_glyph_height(int code)
594 {
595 struct dtx_box box;
596 dtx_glyph_box(code, &box);
597 return box.height;
598 }
600 void dtx_string_box(const char *str, struct dtx_box *box)
601 {
602 int code;
603 float pos_x = 0.0f, pos_y = 0.0f;
604 struct glyph *g = 0;
605 float x0, y0, x1, y1;
607 x0 = y0 = FLT_MAX;
608 x1 = y1 = -FLT_MAX;
610 while(*str) {
611 float px, py;
612 struct dtx_glyphmap *gmap;
614 code = dtx_utf8_char_code(str);
615 str = dtx_utf8_next_char((char*)str);
617 px = pos_x;
618 py = pos_y;
620 if((gmap = dtx_proc_char(code, &pos_x, &pos_y))) {
621 g = gmap->glyphs + code - gmap->cstart;
623 if(px + g->orig_x < x0) {
624 x0 = px + g->orig_x;
625 }
626 if(py - g->orig_y < y0) {
627 y0 = py - g->orig_y;
628 }
629 if(px + g->orig_x + g->width > x1) {
630 x1 = px + g->orig_x + g->width;
631 }
632 if(py - g->orig_y + g->height > y1) {
633 y1 = py - g->orig_y + g->height;
634 }
635 }
636 }
638 box->x = x0;
639 box->y = y0;
640 box->width = x1 - x0;
641 box->height = y1 - y0;
642 }
644 float dtx_string_width(const char *str)
645 {
646 struct dtx_box box;
648 dtx_string_box(str, &box);
649 return box.width;
650 }
652 float dtx_string_height(const char *str)
653 {
654 struct dtx_box box;
656 dtx_string_box(str, &box);
657 return box.height;
658 }
660 float dtx_char_pos(const char *str, int n)
661 {
662 int i;
663 float pos = 0.0;
664 struct dtx_glyphmap *gmap;
666 for(i=0; i<n; i++) {
667 int code = dtx_utf8_char_code(str);
668 str = dtx_utf8_next_char((char*)str);
670 gmap = dtx_get_font_glyphmap(dtx_font, dtx_font_sz, code);
671 pos += gmap->glyphs[i].advance;
672 }
673 return pos;
674 }
676 int dtx_char_at_pt(const char *str, float pt)
677 {
678 int i;
679 float prev_pos = 0.0f, pos = 0.0f;
680 struct dtx_glyphmap *gmap;
682 for(i=0; *str; i++) {
683 int code = dtx_utf8_char_code(str);
684 str = dtx_utf8_next_char((char*)str);
686 gmap = dtx_get_font_glyphmap(dtx_font, dtx_font_sz, code);
687 pos += gmap->glyphs[i].advance;
689 if(fabs(pt - prev_pos) < fabs(pt - pos)) {
690 break;
691 }
692 prev_pos = pos;
693 }
694 return i;
695 }
697 struct dtx_glyphmap *dtx_proc_char(int code, float *xpos, float *ypos)
698 {
699 struct dtx_glyphmap *gmap;
700 gmap = dtx_get_font_glyphmap(dtx_font, dtx_font_sz, code);
702 switch(code) {
703 case '\n':
704 *xpos = 0.0;
705 if(gmap) {
706 *ypos -= gmap->line_advance;
707 }
708 return 0;
710 case '\t':
711 if(gmap) {
712 *xpos = (fmod(*xpos, 4.0) + 4.0) * gmap->glyphs[0].advance;
713 }
714 return 0;
716 case '\r':
717 *xpos = 0.0;
718 return 0;
720 default:
721 break;
722 }
724 if(gmap) {
725 *xpos += gmap->glyphs[code - gmap->cstart].advance;
726 }
727 return gmap;
728 }
730 #ifdef USE_FREETYPE
731 static void calc_best_size(int total_width, int max_gwidth, int max_gheight, int padding, int pow2, int *imgw, int *imgh)
732 {
733 int xsz, ysz, num_rows;
734 float aspect;
736 for(xsz=2; xsz<=MAX_IMG_WIDTH; xsz *= 2) {
737 num_rows = total_width / xsz + 1;
739 /* assume worst case, all last glyphs will float to the next line
740 * so let's add extra rows for that. */
741 num_rows += (padding + (max_gwidth + padding) * num_rows + xsz - 1) / xsz;
743 ysz = num_rows * (max_gheight + padding) + padding;
744 if(pow2) {
745 ysz = next_pow2(ysz);
746 }
747 aspect = (float)xsz / (float)ysz;
749 if(aspect >= 1.0) {
750 break;
751 }
752 }
754 if(xsz > MAX_IMG_WIDTH) {
755 xsz = MAX_IMG_WIDTH;
756 }
758 *imgw = xsz;
759 *imgh = ysz;
760 }
763 static int next_pow2(int x)
764 {
765 x--;
766 x = (x >> 1) | x;
767 x = (x >> 2) | x;
768 x = (x >> 4) | x;
769 x = (x >> 8) | x;
770 x = (x >> 16) | x;
771 return x + 1;
772 }
773 #endif