1 #define ALLEGRO_INTERNAL_UNSTABLE
2 
3 #include "allegro5/allegro.h"
4 #ifdef ALLEGRO_CFG_OPENGL
5 #include "allegro5/allegro_opengl.h"
6 #endif
7 #include "allegro5/internal/aintern.h"
8 #include "allegro5/internal/aintern_vector.h"
9 
10 #include "allegro5/allegro_ttf.h"
11 #include "allegro5/internal/aintern_font.h"
12 #include "allegro5/internal/aintern_ttf_cfg.h"
13 #include "allegro5/internal/aintern_dtor.h"
14 #include "allegro5/internal/aintern_system.h"
15 #include "allegro5/internal/aintern_exitfunc.h"
16 
17 #include <ft2build.h>
18 #include FT_FREETYPE_H
19 
20 #include <stdlib.h>
21 
22 ALLEGRO_DEBUG_CHANNEL("font")
23 
24 
25 /* Some low-end drivers enable automatic S3TC compression, which
26  * requires glTexSubImage2D to only work on multiples of aligned
27  * 4x4 pixel blocks with some buggy OpenGL drivers.
28  * There's not much we can do about that in general - if the user
29  * locks a portion of a bitmap not conformin to this it will fail
30  * with such a driver.
31  *
32  * However in many programs this is no problem at all safe for rendering
33  * glyphs and simply aligning to 4 pixels here fixes it.
34  */
35 #define ALIGN_TO_4_PIXEL
36 
37 
38 #define RANGE_SIZE   128
39 
40 
41 typedef struct REGION
42 {
43    short x;
44    short y;
45    short w;
46    short h;
47 } REGION;
48 
49 
50 typedef struct ALLEGRO_TTF_GLYPH_DATA
51 {
52    ALLEGRO_BITMAP *page_bitmap;
53    REGION region;
54    short offset_x;
55    short offset_y;
56    short advance;
57 } ALLEGRO_TTF_GLYPH_DATA;
58 
59 
60 typedef struct ALLEGRO_TTF_GLYPH_RANGE
61 {
62    int32_t range_start;
63    ALLEGRO_TTF_GLYPH_DATA *glyphs;  /* [RANGE_SIZE] */
64 } ALLEGRO_TTF_GLYPH_RANGE;
65 
66 
67 typedef struct ALLEGRO_TTF_FONT_DATA
68 {
69    FT_Face face;
70    int flags;
71    _AL_VECTOR glyph_ranges;  /* sorted array of of ALLEGRO_TTF_GLYPH_RANGE */
72 
73    _AL_VECTOR page_bitmaps;  /* of ALLEGRO_BITMAP pointers */
74    int page_pos_x;
75    int page_pos_y;
76    int page_line_height;
77    ALLEGRO_LOCKED_REGION *page_lr;
78 
79    FT_StreamRec stream;
80    ALLEGRO_FILE *file;
81    unsigned long base_offset;
82    unsigned long offset;
83 
84    int bitmap_format;
85    int bitmap_flags;
86 
87    int min_page_size;
88    int max_page_size;
89 
90    bool skip_cache_misses;
91 } ALLEGRO_TTF_FONT_DATA;
92 
93 
94 /* globals */
95 static bool ttf_inited;
96 static FT_Library ft;
97 static ALLEGRO_FONT_VTABLE vt;
98 
99 
align4(int x)100 static INLINE int align4(int x)
101 {
102 #ifdef ALIGN_TO_4_PIXEL
103    return (x + 3) & ~3;
104 #else
105    return x;
106 #endif
107 }
108 
109 
110 /* Returns false if the glyph is invalid.
111  */
get_glyph(ALLEGRO_TTF_FONT_DATA * data,int ft_index,ALLEGRO_TTF_GLYPH_DATA ** glyph)112 static bool get_glyph(ALLEGRO_TTF_FONT_DATA *data,
113    int ft_index, ALLEGRO_TTF_GLYPH_DATA **glyph)
114 {
115    ALLEGRO_TTF_GLYPH_RANGE *range;
116    int32_t range_start;
117    int lo, hi, mid;
118    ASSERT(glyph);
119 
120    range_start = ft_index - (ft_index % RANGE_SIZE);
121 
122    /* Binary search for the range. */
123    lo = 0;
124    hi = _al_vector_size(&data->glyph_ranges);
125    mid = (hi + lo)/2;
126    range = NULL;
127 
128    while (lo < hi) {
129       ALLEGRO_TTF_GLYPH_RANGE *r = _al_vector_ref(&data->glyph_ranges, mid);
130       if (r->range_start == range_start) {
131          range = r;
132          break;
133       }
134       else if (r->range_start < range_start) {
135          lo = mid + 1;
136       }
137       else {
138          hi = mid;
139       }
140       mid = (hi + lo)/2;
141    }
142 
143    if (!range) {
144       range = _al_vector_alloc_mid(&data->glyph_ranges, mid);
145       range->range_start = range_start;
146       range->glyphs = al_calloc(RANGE_SIZE, sizeof(ALLEGRO_TTF_GLYPH_DATA));
147    }
148 
149    *glyph = &range->glyphs[ft_index - range_start];
150 
151    /* If we're skipping cache misses and it isn't already cached, return it as invalid. */
152    if (data->skip_cache_misses && !(*glyph)->page_bitmap && (*glyph)->region.x >= 0) {
153       return false;
154    }
155 
156    return ft_index != 0;
157 }
158 
159 
unlock_current_page(ALLEGRO_TTF_FONT_DATA * data)160 static void unlock_current_page(ALLEGRO_TTF_FONT_DATA *data)
161 {
162    if (data->page_lr) {
163       ALLEGRO_BITMAP **back = _al_vector_ref_back(&data->page_bitmaps);
164       ASSERT(al_is_bitmap_locked(*back));
165       al_unlock_bitmap(*back);
166       data->page_lr = NULL;
167       ALLEGRO_DEBUG("Unlocking page: %p\n", *back);
168    }
169 }
170 
171 
push_new_page(ALLEGRO_TTF_FONT_DATA * data,int glyph_size)172 static ALLEGRO_BITMAP *push_new_page(ALLEGRO_TTF_FONT_DATA *data, int glyph_size)
173 {
174     ALLEGRO_BITMAP **back;
175     ALLEGRO_BITMAP *page;
176     ALLEGRO_STATE state;
177     int page_size = 1;
178     /* 16 seems to work well. A particular problem are fixed width fonts which
179      * take an inordinate amount of space. */
180     while (page_size < 16 * glyph_size) {
181       page_size *= 2;
182     }
183     if (page_size < data->min_page_size) {
184       page_size = data->min_page_size;
185     }
186     if (page_size > data->max_page_size) {
187       page_size = data->max_page_size;
188     }
189     if (glyph_size > page_size) {
190       ALLEGRO_ERROR("Unable create new page, glyph too large: %d > %d\n",
191          glyph_size, page_size);
192       return NULL;
193     }
194 
195     unlock_current_page(data);
196 
197     /* The bitmap will be destroyed when the parent font is destroyed so
198      * it is not safe to register a destructor for it.
199      */
200     _al_push_destructor_owner();
201     al_store_state(&state, ALLEGRO_STATE_NEW_BITMAP_PARAMETERS);
202     al_set_new_bitmap_format(data->bitmap_format);
203     al_set_new_bitmap_flags(data->bitmap_flags);
204     page = al_create_bitmap(page_size, page_size);
205     al_restore_state(&state);
206     _al_pop_destructor_owner();
207 
208     if (page) {
209        back = _al_vector_alloc_back(&data->page_bitmaps);
210        *back = page;
211 
212        data->page_pos_x = 0;
213        data->page_pos_y = 0;
214        data->page_line_height = 0;
215     }
216 
217     return page;
218 }
219 
220 
alloc_glyph_region(ALLEGRO_TTF_FONT_DATA * data,int ft_index,int w,int h,bool new,ALLEGRO_TTF_GLYPH_DATA * glyph,bool lock_whole_page)221 static unsigned char *alloc_glyph_region(ALLEGRO_TTF_FONT_DATA *data,
222    int ft_index, int w, int h, bool new, ALLEGRO_TTF_GLYPH_DATA *glyph,
223    bool lock_whole_page)
224 {
225    ALLEGRO_BITMAP *page;
226    int w4 = align4(w);
227    int h4 = align4(h);
228    int glyph_size = w4 > h4 ? w4 : h4;
229    bool lock = false;
230 
231    if (_al_vector_is_empty(&data->page_bitmaps) || new) {
232       page = push_new_page(data, glyph_size);
233       if (!page) {
234          ALLEGRO_ERROR("Failed to create a new page for glyph %d.\n", ft_index);
235          return NULL;
236       }
237    }
238    else {
239       ALLEGRO_BITMAP **back = _al_vector_ref_back(&data->page_bitmaps);
240       page = *back;
241    }
242 
243    ALLEGRO_DEBUG("Glyph %d: %dx%d (%dx%d)%s\n",
244       ft_index, w, h, w4, h4, new ? " new" : "");
245 
246    if (data->page_pos_x + w4 > al_get_bitmap_width(page)) {
247       data->page_pos_y += data->page_line_height;
248       data->page_pos_y = align4(data->page_pos_y);
249       data->page_pos_x = 0;
250       data->page_line_height = 0;
251    }
252 
253    if (data->page_pos_y + h4 > al_get_bitmap_height(page)) {
254       return alloc_glyph_region(data, ft_index, w, h, true, glyph, lock_whole_page);
255    }
256 
257    glyph->page_bitmap = page;
258    glyph->region.x = data->page_pos_x;
259    glyph->region.y = data->page_pos_y;
260    glyph->region.w = w;
261    glyph->region.h = h;
262 
263    data->page_pos_x = align4(data->page_pos_x + w4);
264    if (h > data->page_line_height) {
265       data->page_line_height = h4;
266    }
267 
268    REGION lock_rect;
269    if (lock_whole_page) {
270       lock_rect.x = 0;
271       lock_rect.y = 0;
272       lock_rect.w = al_get_bitmap_width(page);
273       lock_rect.h = al_get_bitmap_height(page);
274       if (!data->page_lr) {
275          lock = true;
276          ALLEGRO_DEBUG("Locking whole page: %p\n", page);
277       }
278    }
279    else {
280       /* Unlock just in case... */
281       unlock_current_page(data);
282       lock_rect.x = glyph->region.x;
283       lock_rect.y = glyph->region.y;
284       lock_rect.w = w4;
285       lock_rect.h = h4;
286       lock = true;
287       ALLEGRO_DEBUG("Locking glyph region: %p %d %d %d %d\n", page,
288          lock_rect.x, lock_rect.y, lock_rect.w, lock_rect.h);
289    }
290 
291    if (lock) {
292       char *ptr;
293       int i;
294 
295       data->page_lr = al_lock_bitmap_region(page,
296          lock_rect.x, lock_rect.y, lock_rect.w, lock_rect.h,
297          ALLEGRO_PIXEL_FORMAT_ABGR_8888_LE, ALLEGRO_LOCK_WRITEONLY);
298 
299       if (!data->page_lr) {
300          ALLEGRO_ERROR("Failed to lock page.\n");
301          return NULL;
302       }
303 
304       /* Clear the data so we don't get garbage when using filtering
305        * FIXME We could clear just the border but I'm not convinced that
306        * would be faster (yet)
307        */
308       for (i = 0; i < lock_rect.h; i++) {
309           ptr = (char *)(data->page_lr->data) + (i * data->page_lr->pitch);
310           memset(ptr, 0, lock_rect.w * 4);
311       }
312    }
313 
314    ASSERT(data->page_lr);
315 
316    /* Copy a displaced pointer for the glyph. */
317    return (unsigned char *)data->page_lr->data
318       + ((glyph->region.y + 1) - lock_rect.y) * data->page_lr->pitch
319       + ((glyph->region.x + 1) - lock_rect.x) * sizeof(int32_t);
320 }
321 
322 
copy_glyph_mono(ALLEGRO_TTF_FONT_DATA * font_data,FT_Face face,unsigned char * glyph_data)323 static void copy_glyph_mono(ALLEGRO_TTF_FONT_DATA *font_data, FT_Face face,
324    unsigned char *glyph_data)
325 {
326    int pitch = font_data->page_lr->pitch;
327    int x, y;
328 
329    for (y = 0; y < (int)face->glyph->bitmap.rows; y++) {
330       unsigned char const *ptr = face->glyph->bitmap.buffer + face->glyph->bitmap.pitch * y;
331       unsigned char *dptr = glyph_data + pitch * y;
332       int bit = 0;
333 
334       if (font_data->flags & ALLEGRO_NO_PREMULTIPLIED_ALPHA) {
335          for (x = 0; x < (int)face->glyph->bitmap.width; x++) {
336             unsigned char set = ((*ptr >> (7-bit)) & 1) ? 255 : 0;
337             *dptr++ = 255;
338             *dptr++ = 255;
339             *dptr++ = 255;
340             *dptr++ = set;
341             bit = (bit + 1) & 7;
342             if (bit == 0) {
343                ptr++;
344             }
345          }
346       }
347       else {
348          for (x = 0; x < (int)face->glyph->bitmap.width; x++) {
349             unsigned char set = ((*ptr >> (7-bit)) & 1) ? 255 : 0;
350             *dptr++ = set;
351             *dptr++ = set;
352             *dptr++ = set;
353             *dptr++ = set;
354             bit = (bit + 1) & 7;
355             if (bit == 0) {
356                ptr++;
357             }
358          }
359       }
360    }
361 }
362 
363 
copy_glyph_color(ALLEGRO_TTF_FONT_DATA * font_data,FT_Face face,unsigned char * glyph_data)364 static void copy_glyph_color(ALLEGRO_TTF_FONT_DATA *font_data, FT_Face face,
365    unsigned char *glyph_data)
366 {
367    int pitch = font_data->page_lr->pitch;
368    int x, y;
369 
370    for (y = 0; y < (int)face->glyph->bitmap.rows; y++) {
371       unsigned char const *ptr = face->glyph->bitmap.buffer + face->glyph->bitmap.pitch * y;
372       unsigned char *dptr = glyph_data + pitch * y;
373 
374       if (font_data->flags & ALLEGRO_NO_PREMULTIPLIED_ALPHA) {
375          for (x = 0; x < (int)face->glyph->bitmap.width; x++) {
376             unsigned char c = *ptr;
377             *dptr++ = 255;
378             *dptr++ = 255;
379             *dptr++ = 255;
380             *dptr++ = c;
381             ptr++;
382          }
383       }
384       else {
385          for (x = 0; x < (int)face->glyph->bitmap.width; x++) {
386             unsigned char c = *ptr;
387             *dptr++ = c;
388             *dptr++ = c;
389             *dptr++ = c;
390             *dptr++ = c;
391             ptr++;
392          }
393       }
394    }
395 }
396 
397 
398 /* NOTE: this function may disable the bitmap hold drawing state
399  * and leave the current page bitmap locked.
400  *
401  * NOTE: We have previously tried to be more clever about caching multiple
402  * glyphs during incidental cache misses, but found that approach to be slower.
403  */
cache_glyph(ALLEGRO_TTF_FONT_DATA * font_data,FT_Face face,int ft_index,ALLEGRO_TTF_GLYPH_DATA * glyph,bool lock_whole_page)404 static void cache_glyph(ALLEGRO_TTF_FONT_DATA *font_data, FT_Face face,
405    int ft_index, ALLEGRO_TTF_GLYPH_DATA *glyph, bool lock_whole_page)
406 {
407     FT_Int32 ft_load_flags;
408     FT_Error e;
409     int w, h;
410     unsigned char *glyph_data;
411 
412     if (glyph->page_bitmap || glyph->region.x < 0)
413         return;
414 
415     /* We shouldn't ever get here, as cache misses
416      * should have been set to ft_index = 0. */
417     ASSERT(!(font_data->skip_cache_misses && !lock_whole_page));
418 
419     // FIXME: make this a config setting? FT_LOAD_FORCE_AUTOHINT
420 
421     // FIXME: Investigate why some fonts don't work without the
422     // NO_BITMAP flags. Supposedly using that flag makes small sizes
423     // look bad so ideally we would not used it.
424     ft_load_flags = FT_LOAD_RENDER | FT_LOAD_NO_BITMAP;
425     if (font_data->flags & ALLEGRO_TTF_MONOCHROME)
426        ft_load_flags |= FT_LOAD_TARGET_MONO;
427     if (font_data->flags & ALLEGRO_TTF_NO_AUTOHINT)
428        ft_load_flags |= FT_LOAD_NO_AUTOHINT;
429 
430     e = FT_Load_Glyph(face, ft_index, ft_load_flags);
431     if (e) {
432        ALLEGRO_WARN("Failed loading glyph %d from.\n", ft_index);
433     }
434 
435     glyph->offset_x = face->glyph->bitmap_left;
436     glyph->offset_y = (face->size->metrics.ascender >> 6) - face->glyph->bitmap_top;
437     glyph->advance = face->glyph->advance.x >> 6;
438 
439     w = face->glyph->bitmap.width;
440     h = face->glyph->bitmap.rows;
441 
442     if (w == 0 || h == 0) {
443        /* Mark this glyph so we won't try to cache it next time. */
444        glyph->region.x = -1;
445        glyph->region.y = -1;
446        ALLEGRO_DEBUG("Glyph %d has zero size.\n", ft_index);
447        return;
448     }
449 
450     /* Each glyph has a 1-pixel border all around. Note: The border is kept
451      * even against the outer bitmap edge, to ensure consistent rendering.
452      */
453     glyph_data = alloc_glyph_region(font_data, ft_index,
454        w + 2, h + 2, false, glyph, lock_whole_page);
455 
456     if (glyph_data == NULL) {
457        return;
458     }
459 
460     if (font_data->flags & ALLEGRO_TTF_MONOCHROME)
461        copy_glyph_mono(font_data, face, glyph_data);
462     else
463        copy_glyph_color(font_data, face, glyph_data);
464 
465     if (!lock_whole_page) {
466        unlock_current_page(font_data);
467     }
468 }
469 
470 /* WARNING: It is only valid to call this function when the current page is empty
471  * (or already locked), otherwise it will gibberify the current glyphs on that page.
472  *
473  * This leaves the current page unlocked.
474  */
cache_glyphs(ALLEGRO_TTF_FONT_DATA * data,const char * text,size_t text_size)475 static void cache_glyphs(ALLEGRO_TTF_FONT_DATA *data, const char *text, size_t text_size)
476 {
477    ALLEGRO_USTR_INFO info;
478    const ALLEGRO_USTR* ustr = al_ref_buffer(&info, text, text_size);
479    FT_Face face = data->face;
480    int pos = 0;
481    int32_t ch;
482 
483    while ((ch = al_ustr_get_next(ustr, &pos)) >= 0) {
484       ALLEGRO_TTF_GLYPH_DATA *glyph;
485       int ft_index = FT_Get_Char_Index(face, ch);
486       get_glyph(data, ft_index, &glyph);
487       cache_glyph(data, face, ft_index, glyph, true);
488    }
489 }
490 
491 
get_kerning(ALLEGRO_TTF_FONT_DATA const * data,FT_Face face,int prev_ft_index,int ft_index)492 static int get_kerning(ALLEGRO_TTF_FONT_DATA const *data, FT_Face face,
493    int prev_ft_index, int ft_index)
494 {
495    /* Do kerning? */
496    if (!(data->flags & ALLEGRO_TTF_NO_KERNING) && prev_ft_index != -1) {
497       FT_Vector delta;
498       FT_Get_Kerning(face, prev_ft_index, ft_index,
499          FT_KERNING_DEFAULT, &delta);
500       return delta.x >> 6;
501    }
502 
503    return 0;
504 }
505 
506 
ttf_get_glyph_worker(ALLEGRO_FONT const * f,int prev_ft_index,int ft_index,int prev_codepoint,int codepoint,ALLEGRO_GLYPH * info)507 static bool ttf_get_glyph_worker(ALLEGRO_FONT const *f, int prev_ft_index, int ft_index, int prev_codepoint, int codepoint, ALLEGRO_GLYPH *info)
508 {
509    ALLEGRO_TTF_FONT_DATA *data = f->data;
510    FT_Face face = data->face;
511    ALLEGRO_TTF_GLYPH_DATA *glyph;
512    int advance = 0;
513 
514    if (!get_glyph(data, ft_index, &glyph)) {
515       if (f->fallback)
516          return f->fallback->vtable->get_glyph(f->fallback, prev_codepoint, codepoint, info);
517       else {
518          get_glyph(data, 0, &glyph);
519          ft_index = 0;
520       }
521    }
522 
523    cache_glyph(data, face, ft_index, glyph, false);
524 
525    advance += get_kerning(data, face, prev_ft_index, ft_index);
526 
527    if (glyph->page_bitmap) {
528       info->bitmap = glyph->page_bitmap;
529       info->x = glyph->region.x + 1;
530       info->y = glyph->region.y + 1;
531       info->w = glyph->region.w - 2;
532       info->h = glyph->region.h - 2;
533       info->kerning = advance;
534       info->offset_x = glyph->offset_x;
535       info->offset_y = glyph->offset_y;
536    }
537    else if (glyph->region.x > 0) {
538       ALLEGRO_ERROR("Glyph %d not on any page.\n", ft_index);
539       return false;
540    }
541    else {
542       info->bitmap = 0;
543    }
544 
545    advance += glyph->advance;
546 
547    info->advance = advance;
548 
549    return true;
550 }
551 
552 
ttf_get_glyph(ALLEGRO_FONT const * f,int prev_codepoint,int codepoint,ALLEGRO_GLYPH * glyph)553 static bool ttf_get_glyph(ALLEGRO_FONT const *f, int prev_codepoint, int codepoint, ALLEGRO_GLYPH *glyph)
554 {
555    ALLEGRO_TTF_FONT_DATA *data = f->data;
556    FT_Face face = data->face;
557    int prev_ft_index = (prev_codepoint == -1) ? -1 : (int)FT_Get_Char_Index(face, prev_codepoint);
558    int ft_index = FT_Get_Char_Index(face, codepoint);
559    return ttf_get_glyph_worker(f, prev_ft_index, ft_index, prev_codepoint, codepoint, glyph);
560 }
561 
562 
render_glyph(ALLEGRO_FONT const * f,ALLEGRO_COLOR color,int prev_ft_index,int ft_index,int32_t prev_ch,int32_t ch,float xpos,float ypos)563 static int render_glyph(ALLEGRO_FONT const *f, ALLEGRO_COLOR color,
564    int prev_ft_index, int ft_index, int32_t prev_ch, int32_t ch, float xpos, float ypos)
565 {
566    ALLEGRO_GLYPH glyph;
567 
568    if (ttf_get_glyph_worker(f, prev_ft_index, ft_index, prev_ch, ch, &glyph) == false)
569       return 0;
570 
571    if (glyph.bitmap != NULL) {
572       al_draw_tinted_bitmap_region(
573          glyph.bitmap, color,
574          glyph.x, glyph.y, glyph.w, glyph.h,
575          xpos + glyph.offset_x + glyph.kerning,
576          ypos + glyph.offset_y,
577          0
578       );
579    }
580 
581    return glyph.advance;
582 }
583 
584 
ttf_font_height(ALLEGRO_FONT const * f)585 static int ttf_font_height(ALLEGRO_FONT const *f)
586 {
587    ASSERT(f);
588    return f->height;
589 }
590 
591 
ttf_font_ascent(ALLEGRO_FONT const * f)592 static int ttf_font_ascent(ALLEGRO_FONT const *f)
593 {
594     ALLEGRO_TTF_FONT_DATA *data;
595     FT_Face face;
596 
597     ASSERT(f);
598 
599     data = f->data;
600     face = data->face;
601 
602     return face->size->metrics.ascender >> 6;
603 }
604 
605 
ttf_font_descent(ALLEGRO_FONT const * f)606 static int ttf_font_descent(ALLEGRO_FONT const *f)
607 {
608     ALLEGRO_TTF_FONT_DATA *data;
609     FT_Face face;
610 
611     ASSERT(f);
612 
613     data = f->data;
614     face = data->face;
615 
616     return (-face->size->metrics.descender) >> 6;
617 }
618 
619 
ttf_render_char(ALLEGRO_FONT const * f,ALLEGRO_COLOR color,int ch,float xpos,float ypos)620 static int ttf_render_char(ALLEGRO_FONT const *f, ALLEGRO_COLOR color,
621    int ch, float xpos, float ypos)
622 {
623    ALLEGRO_TTF_FONT_DATA *data = f->data;
624    FT_Face face = data->face;
625    int advance = 0;
626    int32_t ch32 = (int32_t) ch;
627 
628    int ft_index = FT_Get_Char_Index(face, ch32);
629    advance = render_glyph(f, color, -1, ft_index, -1, ch, xpos, ypos);
630 
631    return advance;
632 }
633 
634 
ttf_char_length(ALLEGRO_FONT const * f,int ch)635 static int ttf_char_length(ALLEGRO_FONT const *f, int ch)
636 {
637    int result;
638    ALLEGRO_TTF_FONT_DATA *data = f->data;
639    ALLEGRO_TTF_GLYPH_DATA *glyph;
640    FT_Face face = data->face;
641    int ft_index = FT_Get_Char_Index(face, ch);
642    if (!get_glyph(data, ft_index, &glyph)) {
643       if (f->fallback) {
644          return al_get_glyph_width(f, ch);
645       }
646       else {
647          get_glyph(data, 0, &glyph);
648          ft_index = 0;
649       }
650    }
651    cache_glyph(data, face, ft_index, glyph, false);
652    result = glyph->region.w - 2;
653 
654    return result;
655 }
656 
657 
ttf_render(ALLEGRO_FONT const * f,ALLEGRO_COLOR color,const ALLEGRO_USTR * text,float x,float y)658 static int ttf_render(ALLEGRO_FONT const *f, ALLEGRO_COLOR color,
659    const ALLEGRO_USTR *text, float x, float y)
660 {
661    ALLEGRO_TTF_FONT_DATA *data = f->data;
662    FT_Face face = data->face;
663    int pos = 0;
664    int advance = 0;
665    int prev_ft_index = -1;
666    int32_t prev_ch = -1;
667    int32_t ch;
668    bool hold;
669 
670    hold = al_is_bitmap_drawing_held();
671    al_hold_bitmap_drawing(true);
672 
673    while ((ch = al_ustr_get_next(text, &pos)) >= 0) {
674       int ft_index = FT_Get_Char_Index(face, ch);
675       advance += render_glyph(f, color, prev_ft_index, ft_index, prev_ch, ch,
676          x + advance, y);
677       prev_ft_index = ft_index;
678       prev_ch = ch;
679    }
680 
681    al_hold_bitmap_drawing(hold);
682 
683    return advance;
684 }
685 
686 
ttf_text_length(ALLEGRO_FONT const * f,const ALLEGRO_USTR * text)687 static int ttf_text_length(ALLEGRO_FONT const *f, const ALLEGRO_USTR *text)
688 {
689    int pos = 0;
690    int x = 0;
691    int32_t ch, nch;
692 
693    nch = al_ustr_get_next(text, &pos);
694    while (nch >= 0) {
695       ch = nch;
696       nch = al_ustr_get_next(text, &pos);
697 
698       x += al_get_glyph_advance(f, ch, nch < 0 ?
699          ALLEGRO_NO_KERNING : nch);
700    }
701 
702    return x;
703 }
704 
705 
ttf_get_text_dimensions(ALLEGRO_FONT const * f,ALLEGRO_USTR const * text,int * bbx,int * bby,int * bbw,int * bbh)706 static void ttf_get_text_dimensions(ALLEGRO_FONT const *f,
707    ALLEGRO_USTR const *text,
708    int *bbx, int *bby, int *bbw, int *bbh)
709 {
710    int pos = 0;
711    bool first = true;
712    int x = 0;
713    int32_t ch, nch;
714    int ymin = f->height;
715    int ymax = 0;
716    *bbx = 0;
717 
718    nch = al_ustr_get_next(text, &pos);
719    while (nch >= 0) {
720       int gx, gy, gw, gh;
721       ch = nch;
722       nch = al_ustr_get_next(text, &pos);
723       if (!al_get_glyph_dimensions(f, ch, &gx, &gy, &gw, &gh)) {
724          continue;
725       }
726 
727       if (nch < 0) {
728          x += gx + gw;
729       }
730       else {
731          x += al_get_glyph_advance(f, ch, nch);
732       }
733 
734       if (gy < ymin) {
735          ymin = gy;
736       }
737 
738       if (gh+gy > ymax) {
739          ymax = gh + gy;
740       }
741 
742       if (first) {
743          *bbx = gx;
744          first = false;
745       }
746    }
747 
748    *bby = ymin;
749    *bbw = x - *bbx;
750    *bbh = ymax - ymin;
751 }
752 
753 
754 #ifdef DEBUG_CACHE
755 #include "allegro5/allegro_image.h"
debug_cache(ALLEGRO_FONT * f)756 static void debug_cache(ALLEGRO_FONT *f)
757 {
758    ALLEGRO_TTF_FONT_DATA *data = f->data;
759    _AL_VECTOR *v = &data->page_bitmaps;
760    static int j = 0;
761    int i;
762 
763    al_init_image_addon();
764 
765    for (i = 0; i < (int)_al_vector_size(v); i++) {
766       ALLEGRO_BITMAP **bmp = _al_vector_ref(v, i);
767       ALLEGRO_USTR *u = al_ustr_newf("font%d_%d.png", j, i);
768       al_save_bitmap(al_cstr(u), *bmp);
769       al_ustr_free(u);
770    }
771    j++;
772 }
773 #endif
774 
775 
ttf_destroy(ALLEGRO_FONT * f)776 static void ttf_destroy(ALLEGRO_FONT *f)
777 {
778    ALLEGRO_TTF_FONT_DATA *data = f->data;
779    int i;
780 
781    unlock_current_page(data);
782 
783 #ifdef DEBUG_CACHE
784    debug_cache(f);
785 #endif
786 
787    FT_Done_Face(data->face);
788    for (i = _al_vector_size(&data->glyph_ranges) - 1; i >= 0; i--) {
789       ALLEGRO_TTF_GLYPH_RANGE *range = _al_vector_ref(&data->glyph_ranges, i);
790       al_free(range->glyphs);
791    }
792    _al_vector_free(&data->glyph_ranges);
793    for (i = _al_vector_size(&data->page_bitmaps) - 1; i >= 0; i--) {
794       ALLEGRO_BITMAP **bmp = _al_vector_ref(&data->page_bitmaps, i);
795       al_destroy_bitmap(*bmp);
796    }
797    _al_vector_free(&data->page_bitmaps);
798    al_free(data);
799    al_free(f);
800 }
801 
802 
ftread(FT_Stream stream,unsigned long offset,unsigned char * buffer,unsigned long count)803 static unsigned long ftread(FT_Stream stream, unsigned long offset,
804     unsigned char *buffer, unsigned long count)
805 {
806     ALLEGRO_TTF_FONT_DATA *data = stream->pathname.pointer;
807     unsigned long bytes;
808 
809     if (count == 0)
810        return 0;
811 
812     if (offset != data->offset)
813        al_fseek(data->file, data->base_offset + offset, ALLEGRO_SEEK_SET);
814     bytes = al_fread(data->file, buffer, count);
815     data->offset = offset + bytes;
816     return bytes;
817 }
818 
819 
ftclose(FT_Stream stream)820 static void ftclose(FT_Stream  stream)
821 {
822     ALLEGRO_TTF_FONT_DATA *data = stream->pathname.pointer;
823     al_fclose(data->file);
824     data->file = NULL;
825 }
826 
827 /* Function: al_load_ttf_font_f
828  */
al_load_ttf_font_f(ALLEGRO_FILE * file,char const * filename,int size,int flags)829 ALLEGRO_FONT *al_load_ttf_font_f(ALLEGRO_FILE *file,
830     char const *filename, int size, int flags)
831 {
832     return al_load_ttf_font_stretch_f(file, filename, 0, size, flags);
833 }
834 
835 
836 /* Function: al_load_ttf_font_stretch_f
837  */
al_load_ttf_font_stretch_f(ALLEGRO_FILE * file,char const * filename,int w,int h,int flags)838 ALLEGRO_FONT *al_load_ttf_font_stretch_f(ALLEGRO_FILE *file,
839     char const *filename, int w, int h, int flags)
840 {
841     FT_Face face;
842     ALLEGRO_TTF_FONT_DATA *data;
843     ALLEGRO_FONT *f;
844     ALLEGRO_PATH *path;
845     FT_Open_Args args;
846     int result;
847     ALLEGRO_CONFIG* system_cfg = al_get_system_config();
848     const char* min_page_size_str =
849       al_get_config_value(system_cfg, "ttf", "min_page_size");
850     const char* max_page_size_str =
851       al_get_config_value(system_cfg, "ttf", "max_page_size");
852     const char* cache_str =
853       al_get_config_value(system_cfg, "ttf", "cache_text");
854     const char* skip_cache_misses_str =
855       al_get_config_value(system_cfg, "ttf", "skip_cache_misses");
856 
857     if ((h > 0 && w < 0) || (h < 0 && w > 0)) {
858        ALLEGRO_ERROR("Height/width have opposite signs (w = %d, h = %d).\n", w, h);
859        return NULL;
860     }
861 
862     data = al_calloc(1, sizeof *data);
863     data->stream.read = ftread;
864     data->stream.close = ftclose;
865     data->stream.pathname.pointer = data;
866     data->base_offset = al_ftell(file);
867     data->stream.size = al_fsize(file);
868     data->file = file;
869     data->bitmap_format = al_get_new_bitmap_format();
870     data->bitmap_flags = al_get_new_bitmap_flags();
871     data->min_page_size = 256;
872     data->max_page_size = 8192;
873 
874     if (min_page_size_str) {
875       int min_page_size = atoi(min_page_size_str);
876       if (min_page_size > 0) {
877          data->min_page_size = min_page_size;
878       }
879     }
880 
881     if (max_page_size_str) {
882       int max_page_size = atoi(max_page_size_str);
883       if (max_page_size > 0 && max_page_size >= data->min_page_size) {
884          data->max_page_size = max_page_size;
885       }
886     }
887 
888     if (skip_cache_misses_str && !strcmp(skip_cache_misses_str, "true")) {
889        data->skip_cache_misses = true;
890     }
891 
892     memset(&args, 0, sizeof args);
893     args.flags = FT_OPEN_STREAM;
894     args.stream = &data->stream;
895 
896     if ((result = FT_Open_Face(ft, &args, 0, &face)) != 0) {
897         ALLEGRO_ERROR("Reading %s failed. Freetype error code %d\n", filename,
898           result);
899         // Note: Freetype already closed the file for us.
900         al_free(data);
901         return NULL;
902     }
903 
904     // FIXME: The below doesn't use Allegro's streaming.
905     /* Small hack for Type1 fonts which store kerning information in
906      * a separate file - and we try to guess the name of that file.
907      */
908     path = al_create_path(filename);
909     if (!strcmp(al_get_path_extension(path), ".pfa")) {
910         const char *helper;
911         ALLEGRO_DEBUG("Type1 font assumed for %s.\n", filename);
912 
913         al_set_path_extension(path, ".afm");
914         helper = al_path_cstr(path, '/');
915         FT_Attach_File(face, helper);
916         ALLEGRO_DEBUG("Guessed afm file %s.\n", helper);
917 
918         al_set_path_extension(path, ".tfm");
919         helper = al_path_cstr(path, '/');
920         FT_Attach_File(face, helper);
921         ALLEGRO_DEBUG("Guessed tfm file %s.\n", helper);
922     }
923     al_destroy_path(path);
924 
925     if (h > 0) {
926        FT_Set_Pixel_Sizes(face, w, h);
927     }
928     else {
929        /* Set the "real dimension" of the font to be the passed size,
930         * in pixels.
931         */
932        FT_Size_RequestRec req;
933        ASSERT(w <= 0);
934        ASSERT(h <= 0);
935        req.type = FT_SIZE_REQUEST_TYPE_REAL_DIM;
936        req.width = (-w) << 6;
937        req.height = (-h) << 6;
938        req.horiResolution = 0;
939        req.vertResolution = 0;
940        FT_Request_Size(face, &req);
941     }
942 
943     ALLEGRO_DEBUG("Font %s loaded with pixel size %d x %d.\n", filename,
944         w, h);
945     ALLEGRO_DEBUG("    ascent=%.1f, descent=%.1f, height=%.1f\n",
946         face->size->metrics.ascender / 64.0,
947         face->size->metrics.descender / 64.0,
948         face->size->metrics.height / 64.0);
949 
950     data->face = face;
951     data->flags = flags;
952 
953     _al_vector_init(&data->glyph_ranges, sizeof(ALLEGRO_TTF_GLYPH_RANGE));
954     _al_vector_init(&data->page_bitmaps, sizeof(ALLEGRO_BITMAP*));
955 
956     if (data->skip_cache_misses) {
957        cache_glyphs(data, "\0", 1);
958     }
959     if (cache_str) {
960        cache_glyphs(data, cache_str, strlen(cache_str));
961     }
962     unlock_current_page(data);
963 
964     f = al_calloc(sizeof *f, 1);
965     f->height = face->size->metrics.height >> 6;
966     f->vtable = &vt;
967     f->data = data;
968 
969     f->dtor_item = _al_register_destructor(_al_dtor_list, "ttf_font", f,
970        (void (*)(void *))al_destroy_font);
971 
972     return f;
973 }
974 
975 
976 /* Function: al_load_ttf_font
977  */
al_load_ttf_font(char const * filename,int size,int flags)978 ALLEGRO_FONT *al_load_ttf_font(char const *filename, int size, int flags)
979 {
980    return al_load_ttf_font_stretch(filename, 0, size, flags);
981 }
982 
983 
984 /* Function: al_load_ttf_font_stretch
985  */
al_load_ttf_font_stretch(char const * filename,int w,int h,int flags)986 ALLEGRO_FONT *al_load_ttf_font_stretch(char const *filename, int w, int h,
987    int flags)
988 {
989    ALLEGRO_FILE *f;
990    ALLEGRO_FONT *font;
991    ASSERT(filename);
992 
993    f = al_fopen(filename, "rb");
994    if (!f) {
995       ALLEGRO_ERROR("Unable to open file for reading: %s\n", filename);
996       return NULL;
997    }
998 
999    /* The file handle is owned by the function and the file is usually only
1000     * closed when the font is destroyed, in case Freetype has to load data
1001     * at a later time.
1002     */
1003    font = al_load_ttf_font_stretch_f(f, filename, w, h, flags);
1004 
1005    return font;
1006 }
1007 
1008 
ttf_get_font_ranges(ALLEGRO_FONT * font,int ranges_count,int * ranges)1009 static int ttf_get_font_ranges(ALLEGRO_FONT *font, int ranges_count,
1010    int *ranges)
1011 {
1012    ALLEGRO_TTF_FONT_DATA *data = font->data;
1013    FT_UInt g;
1014    FT_ULong unicode = FT_Get_First_Char(data->face, &g);
1015    int i = 0;
1016    if (i < ranges_count) {
1017       ranges[i * 2 + 0] = unicode;
1018       ranges[i * 2 + 1] = unicode;
1019    }
1020    while (g) {
1021       FT_ULong unicode2 = FT_Get_Next_Char(data->face, unicode, &g);
1022       if (unicode + 1 != unicode2) {
1023          if (i < ranges_count) {
1024             ranges[i * 2 + 1] = unicode;
1025             if (i + 1 < ranges_count) {
1026                ranges[(i + 1) * 2 + 0] = unicode2;
1027             }
1028          }
1029          i++;
1030       }
1031       if (i < ranges_count) {
1032          ranges[i * 2 + 1] = unicode2;
1033       }
1034       unicode = unicode2;
1035    }
1036    return i;
1037 }
1038 
ttf_get_glyph_dimensions(ALLEGRO_FONT const * f,int codepoint,int * bbx,int * bby,int * bbw,int * bbh)1039 static bool ttf_get_glyph_dimensions(ALLEGRO_FONT const *f,
1040    int codepoint,
1041    int *bbx, int *bby, int *bbw, int *bbh)
1042 {
1043    ALLEGRO_TTF_FONT_DATA *data = f->data;
1044    ALLEGRO_TTF_GLYPH_DATA *glyph;
1045    FT_Face face = data->face;
1046    int ft_index = FT_Get_Char_Index(face, codepoint);
1047    if (!get_glyph(data, ft_index, &glyph)) {
1048       if (f->fallback) {
1049          return al_get_glyph_dimensions(f->fallback, codepoint,
1050             bbx, bby, bbw, bbh);
1051       }
1052       else {
1053          get_glyph(data, 0, &glyph);
1054          ft_index = 0;
1055       }
1056    }
1057    cache_glyph(data, face, ft_index, glyph, false);
1058    *bbx = glyph->offset_x;
1059    *bbw = glyph->region.w - 2;
1060    *bbh = glyph->region.h - 2;
1061    *bby = glyph->offset_y;
1062 
1063    return true;
1064 }
1065 
ttf_get_glyph_advance(ALLEGRO_FONT const * f,int codepoint1,int codepoint2)1066 static int ttf_get_glyph_advance(ALLEGRO_FONT const *f, int codepoint1,
1067    int codepoint2)
1068 {
1069    ALLEGRO_TTF_FONT_DATA *data = f->data;
1070    FT_Face face = data->face;
1071    int ft_index = FT_Get_Char_Index(face, codepoint1);
1072    ALLEGRO_TTF_GLYPH_DATA *glyph;
1073    int kerning = 0;
1074    int advance = 0;
1075 
1076    if (codepoint1 == ALLEGRO_NO_KERNING) {
1077       return 0;
1078    }
1079 
1080    if (!get_glyph(data, ft_index, &glyph)) {
1081       if (f->fallback) {
1082          return al_get_glyph_advance(f->fallback, codepoint1, codepoint2);
1083       }
1084       else {
1085          get_glyph(data, 0, &glyph);
1086          ft_index = 0;
1087       }
1088    }
1089    cache_glyph(data, face, ft_index, glyph, false);
1090 
1091    if (codepoint2 != ALLEGRO_NO_KERNING) {
1092       int ft_index1 = FT_Get_Char_Index(face, codepoint1);
1093       int ft_index2 = FT_Get_Char_Index(face, codepoint2);
1094       kerning = get_kerning(data, face, ft_index1, ft_index2);
1095    }
1096 
1097    advance = glyph->advance;
1098    return advance + kerning;
1099 }
1100 
1101 
1102 
1103 /* Function: al_init_ttf_addon
1104  */
al_init_ttf_addon(void)1105 bool al_init_ttf_addon(void)
1106 {
1107    if (ttf_inited) {
1108       ALLEGRO_WARN("TTF addon already initialised.\n");
1109       return true;
1110    }
1111 
1112    FT_Init_FreeType(&ft);
1113    vt.font_height = ttf_font_height;
1114    vt.font_ascent = ttf_font_ascent;
1115    vt.font_descent = ttf_font_descent;
1116    vt.char_length = ttf_char_length;
1117    vt.text_length = ttf_text_length;
1118    vt.render_char = ttf_render_char;
1119    vt.render = ttf_render;
1120    vt.destroy = ttf_destroy;
1121    vt.get_text_dimensions = ttf_get_text_dimensions;
1122    vt.get_font_ranges = ttf_get_font_ranges;
1123    vt.get_glyph_dimensions = ttf_get_glyph_dimensions;
1124    vt.get_glyph_advance = ttf_get_glyph_advance;
1125    vt.get_glyph = ttf_get_glyph;
1126 
1127    al_register_font_loader(".ttf", al_load_ttf_font);
1128 
1129    _al_add_exit_func(al_shutdown_ttf_addon, "al_shutdown_ttf_addon");
1130 
1131    /* Can't fail right now - in the future we might dynamically load
1132     * the FreeType DLL here and/or initialize FreeType (which both
1133     * could fail and would cause a false return).
1134     */
1135    ttf_inited = true;
1136    return ttf_inited;
1137 }
1138 
1139 
1140 /* Function: al_is_ttf_addon_initialized
1141  */
al_is_ttf_addon_initialized(void)1142 bool al_is_ttf_addon_initialized(void)
1143 {
1144    return ttf_inited;
1145 }
1146 
1147 
1148 /* Function: al_shutdown_ttf_addon
1149  */
al_shutdown_ttf_addon(void)1150 void al_shutdown_ttf_addon(void)
1151 {
1152    if (!ttf_inited) {
1153       ALLEGRO_ERROR("TTF addon not initialised.\n");
1154       return;
1155    }
1156 
1157    al_register_font_loader(".ttf", NULL);
1158 
1159    FT_Done_FreeType(ft);
1160 
1161    ttf_inited = false;
1162 }
1163 
1164 
1165 /* Function: al_get_allegro_ttf_version
1166  */
al_get_allegro_ttf_version(void)1167 uint32_t al_get_allegro_ttf_version(void)
1168 {
1169    return ALLEGRO_VERSION_INT;
1170 }
1171 
1172 /* vim: set sts=3 sw=3 et: */
1173