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