1/*
2libdrawtext - a simple library for fast text rendering in OpenGL
3Copyright (C) 2011-2014 John Tsiombikas <nuclear@member.fsf.org>
4
5This program is free software: you can redistribute it and/or modify
6it under the terms of the GNU Lesser General Public License as published by
7the Free Software Foundation, either version 3 of the License, or
8(at your option) any later version.
9
10This program is distributed in the hope that it will be useful,
11but WITHOUT ANY WARRANTY; without even the implied warranty of
12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13GNU Lesser General Public License for more details.
14
15You should have received a copy of the GNU Lesser General Public License
16along with this program. If not, see <http://www.gnu.org/licenses/>.
17*/
18#ifndef NO_FREETYPE
19#define USE_FREETYPE
20#endif
21
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
31#ifdef USE_FREETYPE
32#include <ft2build.h>
33#include FT_FREETYPE_H
34#endif
35#include "drawtext.h"
36#include "drawtext_impl.h"
37
38#define FTSZ_TO_PIXELS(x) ((x) / 64)
39#define MAX_IMG_WIDTH 4096
40
41
42#ifdef USE_FREETYPE
43static int init_freetype(void);
44static void cleanup(void);
45
46static void calc_best_size(int total_width, int max_gwidth, int max_gheight,
47 int padding, int pow2, int *imgw, int *imgh);
48static int next_pow2(int x);
49
50static FT_Library ft;
51
52
53static int init_done;
54
55static int init_freetype(void)
56{
57 if(!init_done) {
58 if(FT_Init_FreeType(&ft) != 0) {
59 return -1;
60 }
61 atexit(cleanup);
62 init_done = 1;
63 }
64 return 0;
65}
66
67static void cleanup(void)
68{
69 if(init_done) {
70 FT_Done_FreeType(ft);
71 }
72}
73#endif /* USE_FREETYPE */
74
75struct dtx_font *dtx_open_font(const char *fname, int sz)
76{
77 struct dtx_font *fnt = 0;
78
79#ifdef USE_FREETYPE
80 init_freetype();
81
82 if(!(fnt = calloc(1, sizeof *fnt))) {
83 fperror("failed to allocate font structure");
84 return 0;
85 }
86
87 if(FT_New_Face(ft, fname, 0, (FT_Face*)&fnt->face) != 0) {
88 fprintf(stderr, "failed to open font file: %s\n", fname);
89 return 0;
90 }
91
92 /* pre-create the extended ASCII range glyphmap */
93 if(sz) {
94 dtx_prepare_range(fnt, sz, 0, 256);
95
96 if(!dtx_font) {
97 dtx_use_font(fnt, sz);
98 }
99 }
100#else
101 fprintf(stderr, "ignoring call to dtx_open_font: not compiled with freetype support!\n");
102#endif
103
104 return fnt;
105}
106
107struct dtx_font *dtx_open_font_glyphmap(const char *fname)
108{
109 struct dtx_font *fnt;
110 struct dtx_glyphmap *gmap;
111
112 if(!(fnt = calloc(1, sizeof *fnt))) {
113 fperror("failed to allocate font structure");
114 return 0;
115 }
116
117 if(fname) {
118 if(!(gmap = dtx_load_glyphmap(fname))) {
119 free(fnt);
120 return 0;
121 }
122
123 dtx_add_glyphmap(fnt, gmap);
124 }
125 return fnt;
126}
127
128void dtx_close_font(struct dtx_font *fnt)
129{
130 if(!fnt) return;
131
132#ifdef USE_FREETYPE
133 FT_Done_Face(fnt->face);
134#endif
135
136 /* destroy the glyphmaps */
137 while(fnt->gmaps) {
138 void *tmp = fnt->gmaps;
139 fnt->gmaps = fnt->gmaps->next;
140 dtx_free_glyphmap(tmp);
141 }
142
143 free(fnt);
144}
145
146void dtx_prepare(struct dtx_font *fnt, int sz)
147{
148 if(!dtx_get_font_glyphmap_range(fnt, sz, 0, 256)) {
149 fprintf(stderr, "%s: failed (sz: %d, range: 0-255 [ascii])\n", __FUNCTION__, sz);
150 }
151}
152
153void dtx_prepare_range(struct dtx_font *fnt, int sz, int cstart, int cend)
154{
155 if(!dtx_get_font_glyphmap_range(fnt, sz, cstart, cend)) {
156 fprintf(stderr, "%s: failed (sz: %d, range: %d-%d)\n", __FUNCTION__, sz, cstart, cend);
157 }
158}
159
160struct dtx_glyphmap *dtx_get_font_glyphmap(struct dtx_font *fnt, int sz, int code)
161{
162 struct dtx_glyphmap *gm;
163
164 /* check to see if the last we've given out fits the bill */
165 if(fnt->last_gmap && code >= fnt->last_gmap->cstart && code < fnt->last_gmap->cend && fnt->last_gmap->ptsize == sz) {
166 return fnt->last_gmap;
167 }
168
169 /* otherwise search for the appropriate glyphmap */
170 gm = fnt->gmaps;
171 while(gm) {
172 if(code >= gm->cstart && code < gm->cend && sz == gm->ptsize) {
173 fnt->last_gmap = gm;
174 return gm;
175 }
176 gm = gm->next;
177 }
178 return 0;
179}
180
181struct dtx_glyphmap *dtx_get_font_glyphmap_range(struct dtx_font *fnt, int sz, int cstart, int cend)
182{
183 struct dtx_glyphmap *gm;
184
185 /* search the available glyphmaps to see if we've got one that includes
186 * the requested range
187 */
188 gm = fnt->gmaps;
189 while(gm) {
190 if(gm->cstart <= cstart && gm->cend >= cend && gm->ptsize == sz) {
191 return gm;
192 }
193 gm = gm->next;
194 }
195
196 /* not found, create one and add it to the list */
197 if(!(gm = dtx_create_glyphmap_range(fnt, sz, cstart, cend))) {
198 return 0;
199 }
200 return gm;
201}
202
203struct dtx_glyphmap *dtx_create_glyphmap_range(struct dtx_font *fnt, int sz, int cstart, int cend)
204{
205 struct dtx_glyphmap *gmap = 0;
206
207#ifdef USE_FREETYPE
208 FT_Face face = fnt->face;
209 int i, j;
210 int gx, gy;
211 int padding = 4;
212 int total_width, max_width, max_height;
213
214 FT_Set_Char_Size(fnt->face, 0, sz * 64, 72, 72);
215
216 if(!(gmap = calloc(1, sizeof *gmap))) {
217 return 0;
218 }
219
220 gmap->ptsize = sz;
221 gmap->cstart = cstart;
222 gmap->cend = cend;
223 gmap->crange = cend - cstart;
224 gmap->line_advance = FTSZ_TO_PIXELS((float)face->size->metrics.height);
225
226 if(!(gmap->glyphs = malloc(gmap->crange * sizeof *gmap->glyphs))) {
227 free(gmap);
228 return 0;
229 }
230
231 total_width = padding;
232 max_width = max_height = 0;
233
234 for(i=0; i<gmap->crange; i++) {
235 int w, h;
236
237 FT_Load_Char(face, i + cstart, 0);
238 w = FTSZ_TO_PIXELS(face->glyph->metrics.width);
239 h = FTSZ_TO_PIXELS(face->glyph->metrics.height);
240
241 if(w > max_width) max_width = w;
242 if(h > max_height) max_height = h;
243
244 total_width += w + padding;
245 }
246
247 calc_best_size(total_width, max_width, max_height, padding, 1, &gmap->xsz, &gmap->ysz);
248
249 if(!(gmap->pixels = malloc(gmap->xsz * gmap->ysz))) {
250 free(gmap->glyphs);
251 free(gmap);
252 return 0;
253 }
254 memset(gmap->pixels, 0, gmap->xsz * gmap->ysz);
255
256 gx = padding;
257 gy = padding;
258
259 for(i=0; i<gmap->crange; i++) {
260 float gwidth, gheight;
261 unsigned char *src, *dst;
262 FT_GlyphSlot glyph;
263
264 FT_Load_Char(face, i + cstart, FT_LOAD_RENDER);
265 glyph = face->glyph;
266 gwidth = FTSZ_TO_PIXELS((float)glyph->metrics.width);
267 gheight = FTSZ_TO_PIXELS((float)glyph->metrics.height);
268
269 if(gx > gmap->xsz - gwidth - padding) {
270 gx = padding;
271 gy += max_height + padding;
272 }
273
274 src = glyph->bitmap.buffer;
275 dst = gmap->pixels + gy * gmap->xsz + gx;
276
277 for(j=0; j<glyph->bitmap.rows; j++) {
278 memcpy(dst, src, glyph->bitmap.width);
279 dst += gmap->xsz;
280 src += glyph->bitmap.pitch;
281 }
282
283 gmap->glyphs[i].code = i;
284 gmap->glyphs[i].x = (float)(gx - 1);
285 gmap->glyphs[i].y = (float)(gy - 1);
286 gmap->glyphs[i].width = gwidth + 2;
287 gmap->glyphs[i].height = gheight + 2;
288 gmap->glyphs[i].orig_x = -FTSZ_TO_PIXELS((float)glyph->metrics.horiBearingX) + 1;
289 gmap->glyphs[i].orig_y = FTSZ_TO_PIXELS((float)glyph->metrics.height - glyph->metrics.horiBearingY) + 1;
290 gmap->glyphs[i].advance = FTSZ_TO_PIXELS((float)glyph->metrics.horiAdvance);
291 /* also precalc normalized */
292 gmap->glyphs[i].nx = (float)gmap->glyphs[i].x / (float)gmap->xsz;
293 gmap->glyphs[i].ny = (float)gmap->glyphs[i].y / (float)gmap->ysz;
294 gmap->glyphs[i].nwidth = (float)gmap->glyphs[i].width / (float)gmap->xsz;
295 gmap->glyphs[i].nheight = (float)gmap->glyphs[i].height / (float)gmap->ysz;
296
297 gx += (int)(gwidth + padding);
298 }
299
300 /* add it to the glyphmaps list of the font */
301 dtx_add_glyphmap(fnt, gmap);
302#endif /* USE_FREETYPE */
303
304 return gmap;
305}
306
307void dtx_free_glyphmap(struct dtx_glyphmap *gmap)
308{
309 if(gmap) {
310 gmap->tex = 0;
311 free(gmap->pixels);
312 free(gmap->glyphs);
313 free(gmap);
314 }
315}
316
317unsigned char *dtx_get_glyphmap_pixels(struct dtx_glyphmap *gmap)
318{
319 return gmap->pixels;
320}
321
322int dtx_get_glyphmap_width(struct dtx_glyphmap *gmap)
323{
324 return gmap->xsz;
325}
326
327int dtx_get_glyphmap_height(struct dtx_glyphmap *gmap)
328{
329 return gmap->ysz;
330}
331
332struct dtx_glyphmap *dtx_load_glyphmap(const char *fname)
333{
334 FILE *fp;
335 struct dtx_glyphmap *gmap;
336
337 if(!(fp = fopen(fname, "r"))) {
338 return 0;
339 }
340 gmap = dtx_load_glyphmap_stream(fp);
341 fclose(fp);
342 return gmap;
343}
344
345struct dtx_glyphmap *dtx_load_glyphmap_stream(FILE *fp)
346{
347 char buf[512];
348 int hdr_lines = 0;
349 struct dtx_glyphmap *gmap;
350 struct glyph *glyphs = 0;
351 struct glyph *g;
352 int min_code = INT_MAX;
353 int max_code = INT_MIN;
354 int i;
355 int max_pixval = 0;
356 int num_pixels;
357
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;
364
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 }
371
372 while(isspace(*line)) {
373 line++;
374 }
375
376 if(line[0] == '#') {
377 int c, res;
378 float x, y, xsz, ysz, orig_x, orig_y, adv, line_adv;
379 int ptsize;
380
381 if((res = sscanf(line + 1, " size: %d\n", &ptsize)) == 1) {
382 gmap->ptsize = ptsize;
383
384 } else if((res = sscanf(line + 1, " advance: %f\n", &line_adv)) == 1) {
385 gmap->line_advance = line_adv;
386
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 */
402
403 g->next = glyphs;
404 glyphs = g;
405
406 if(c < min_code) {
407 min_code = c;
408 }
409 if(c > max_code) {
410 max_code = c;
411 }
412
413 } else {
414 fprintf(stderr, "%s: invalid glyph info line\n", __FUNCTION__);
415 goto err;
416 }
417
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;
426
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;
433
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;
444
445 default:
446 break; /* can't happen */
447 }
448 hdr_lines++;
449 }
450 }
451
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 }
456
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 }
466
467 num_pixels = gmap->xsz * gmap->ysz;
468 if(!(gmap->pixels = malloc(num_pixels))) {
469 fperror("failed to allocate pixels");
470 goto err;
471 }
472
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] = (unsigned char)(255 * c / max_pixval);
480 fseek(fp, 2, SEEK_CUR);
481 }
482
483 gmap->cstart = min_code;
484 gmap->cend = max_code + 1;
485 gmap->crange = gmap->cend - gmap->cstart;
486
487 if(!(gmap->glyphs = calloc(gmap->crange, sizeof *gmap->glyphs))) {
488 fperror("failed to allocate glyph info");
489 goto err;
490 }
491
492 while(glyphs) {
493 struct glyph *g = glyphs;
494 glyphs = glyphs->next;
495
496 gmap->glyphs[g->code - gmap->cstart] = *g;
497 free(g);
498 }
499 return gmap;
500
501err:
502 dtx_free_glyphmap(gmap);
503 while(glyphs) {
504 void *tmp = glyphs;
505 glyphs = glyphs->next;
506 free(tmp);
507 }
508 return 0;
509}
510
511int dtx_save_glyphmap(const char *fname, const struct dtx_glyphmap *gmap)
512{
513 FILE *fp;
514 int res;
515
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}
524
525int dtx_save_glyphmap_stream(FILE *fp, const struct dtx_glyphmap *gmap)
526{
527 int i, num_pixels;
528 struct glyph *g = gmap->glyphs;
529
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");
539
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}
549
550void dtx_add_glyphmap(struct dtx_font *fnt, struct dtx_glyphmap *gmap)
551{
552 gmap->next = fnt->gmaps;
553 fnt->gmaps = gmap;
554}
555
556void dtx_use_interpolation(int interpolation)
557{
558 dtx_interpolation = interpolation;
559 //gmap->tex = 0;
560 //dtx_update_texture_interpolation();
561}
562
563void dtx_use_font(struct dtx_font *fnt, int sz)
564{
565 dtx_font = fnt;
566 dtx_font_sz = sz;
567}
568
569float dtx_line_height(void)
570{
571 struct dtx_glyphmap *gmap = dtx_get_font_glyphmap(dtx_font, dtx_font_sz, '\n');
572
573 return gmap->line_advance;
574}
575
576void dtx_glyph_box(int code, struct dtx_box *box)
577{
578 int cidx;
579 struct dtx_glyphmap *gmap;
580 gmap = dtx_get_font_glyphmap(dtx_font, dtx_font_sz, code);
581
582 cidx = code - gmap->cstart;
583
584 box->x = gmap->glyphs[cidx].orig_x;
585 box->y = gmap->glyphs[cidx].orig_y;
586 box->width = gmap->glyphs[cidx].width;
587 box->height = gmap->glyphs[cidx].height;
588}
589
590float dtx_glyph_width(int code)
591{
592 struct dtx_box box;
593 dtx_glyph_box(code, &box);
594 return box.width;
595}
596
597float dtx_glyph_height(int code)
598{
599 struct dtx_box box;
600 dtx_glyph_box(code, &box);
601 return box.height;
602}
603
604void dtx_string_box(const char *str, struct dtx_box *box)
605{
606 struct dtx_glyphmap *gmap = dtx_get_font_glyphmap(dtx_font, dtx_font_sz, *str);
607
608 int code;
609 float pos_x = 0.0f, pos_y = 0.0f;
610 struct glyph *g = 0;
611 float x0, y0, x1, y1;
612
613 x0 = y0 = FLT_MAX;
614 x1 = y1 = -FLT_MAX;
615
616 while(*str) {
617 float px, py;
618
619 // get the next character in the string and then advance to the next string character
620 code = *str;//dtx_utf8_char_code(str);
621 str++;// = dtx_utf8_next_char((char*)str);
622
623 px = pos_x;
624 py = pos_y;
625
626 // process special characters
627 if(code == '\n')
628 {
629 pos_y -= gmap->line_advance;
630 pos_x = 0.0;
631 }
632 else if(code == '\t') pos_x = (float)((fmod(pos_x, 4.0) + 4.0) * gmap->glyphs[0].advance);
633 else if(code == '\r') pos_x = 0.0f;
634 else
635 {
636 // advance the cursor normally and add the character
637 pos_x += gmap->glyphs[code - gmap->cstart].advance;
638 g = gmap->glyphs + code - gmap->cstart;
639 }
640
641 if(px + g->orig_x < x0) {
642 x0 = px + g->orig_x;
643 }
644 if(py - g->orig_y < y0) {
645 y0 = py - g->orig_y;
646 }
647 if(px + g->orig_x + g->width > x1) {
648 x1 = px + g->orig_x + g->width;
649 }
650 if(py - g->orig_y + g->height > y1) {
651 y1 = py - g->orig_y + g->height;
652 }
653 }
654
655 box->x = x0;
656 box->y = y0;
657 box->width = x1 - x0;
658 box->height = y1 - y0;
659}
660
661float dtx_string_width(const char *str)
662{
663 struct dtx_box box;
664
665 dtx_string_box(str, &box);
666 return box.width;
667}
668
669float dtx_string_height(const char *str)
670{
671 struct dtx_box box;
672
673 dtx_string_box(str, &box);
674 return box.height;
675}
676
677float dtx_char_pos(const char *str, int n)
678{
679 int i;
680 float pos = 0.0;
681 struct dtx_glyphmap *gmap;
682
683 for(i=0; i<n; i++) {
684 int code = dtx_utf8_char_code(str);
685 str = dtx_utf8_next_char((char*)str);
686
687 gmap = dtx_get_font_glyphmap(dtx_font, dtx_font_sz, code);
688 pos += gmap->glyphs[i].advance;
689 }
690 return pos;
691}
692
693int dtx_char_at_pt(const char *str, float pt)
694{
695 int i;
696 float prev_pos = 0.0f, pos = 0.0f;
697 struct dtx_glyphmap *gmap;
698
699 for(i=0; *str; i++) {
700 int code = dtx_utf8_char_code(str);
701 str = dtx_utf8_next_char((char*)str);
702
703 gmap = dtx_get_font_glyphmap(dtx_font, dtx_font_sz, code);
704 pos += gmap->glyphs[i].advance;
705
706 if(fabs(pt - prev_pos) < fabs(pt - pos)) {
707 break;
708 }
709 prev_pos = pos;
710 }
711 return i;
712}
713
714#ifdef USE_FREETYPE
715static void calc_best_size(int total_width, int max_gwidth, int max_gheight, int padding, int pow2, int *imgw, int *imgh)
716{
717 int xsz, ysz, num_rows;
718 float aspect;
719
720 for(xsz=2; xsz<=MAX_IMG_WIDTH; xsz *= 2) {
721 num_rows = total_width / xsz + 1;
722
723 /* assume worst case, all last glyphs will float to the next line
724 * so let's add extra rows for that. */
725 num_rows += (padding + (max_gwidth + padding) * num_rows + xsz - 1) / xsz;
726
727 ysz = num_rows * (max_gheight + padding) + padding;
728 if(pow2) {
729 ysz = next_pow2(ysz);
730 }
731 aspect = (float)xsz / (float)ysz;
732
733 if(aspect >= 1.0) {
734 break;
735 }
736 }
737
738 if(xsz > MAX_IMG_WIDTH) {
739 xsz = MAX_IMG_WIDTH;
740 }
741
742 *imgw = xsz;
743 *imgh = ysz;
744}
745
746
747static int next_pow2(int x)
748{
749 x--;
750 x = (x >> 1) | x;
751 x = (x >> 2) | x;
752 x = (x >> 4) | x;
753 x = (x >> 8) | x;
754 x = (x >> 16) | x;
755 return x + 1;
756}
757#endif
758