Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
1162 lines (955 sloc) 29.8 KB
#define ALLEGRO_INTERNAL_UNSTABLE
#include "allegro5/allegro.h"
#ifdef ALLEGRO_CFG_OPENGL
#include "allegro5/allegro_opengl.h"
#endif
#include "allegro5/internal/aintern.h"
#include "allegro5/internal/aintern_vector.h"
#include "allegro5/allegro_ttf.h"
#include "allegro5/internal/aintern_font.h"
#include "allegro5/internal/aintern_ttf_cfg.h"
#include "allegro5/internal/aintern_dtor.h"
#include "allegro5/internal/aintern_system.h"
#include <ft2build.h>
#include FT_FREETYPE_H
#include <stdlib.h>
ALLEGRO_DEBUG_CHANNEL("font")
/* Some low-end drivers enable automatic S3TC compression, which
* requires glTexSubImage2D to only work on multiples of aligned
* 4x4 pixel blocks with some buggy OpenGL drivers.
* There's not much we can do about that in general - if the user
* locks a portion of a bitmap not conformin to this it will fail
* with such a driver.
*
* However in many programs this is no problem at all safe for rendering
* glyphs and simply aligning to 4 pixels here fixes it.
*/
#define ALIGN_TO_4_PIXEL
#define RANGE_SIZE 128
typedef struct REGION
{
short x;
short y;
short w;
short h;
} REGION;
typedef struct ALLEGRO_TTF_GLYPH_DATA
{
ALLEGRO_BITMAP *page_bitmap;
REGION region;
short offset_x;
short offset_y;
short advance;
} ALLEGRO_TTF_GLYPH_DATA;
typedef struct ALLEGRO_TTF_GLYPH_RANGE
{
int32_t range_start;
ALLEGRO_TTF_GLYPH_DATA *glyphs; /* [RANGE_SIZE] */
} ALLEGRO_TTF_GLYPH_RANGE;
typedef struct ALLEGRO_TTF_FONT_DATA
{
FT_Face face;
int flags;
_AL_VECTOR glyph_ranges; /* sorted array of of ALLEGRO_TTF_GLYPH_RANGE */
_AL_VECTOR page_bitmaps; /* of ALLEGRO_BITMAP pointers */
int page_pos_x;
int page_pos_y;
int page_line_height;
ALLEGRO_LOCKED_REGION *page_lr;
FT_StreamRec stream;
ALLEGRO_FILE *file;
unsigned long base_offset;
unsigned long offset;
int bitmap_format;
int bitmap_flags;
int min_page_size;
int max_page_size;
bool skip_cache_misses;
} ALLEGRO_TTF_FONT_DATA;
/* globals */
static bool ttf_inited;
static FT_Library ft;
static ALLEGRO_FONT_VTABLE vt;
static INLINE int align4(int x)
{
#ifdef ALIGN_TO_4_PIXEL
return (x + 3) & ~3;
#else
return x;
#endif
}
/* Returns false if the glyph is invalid.
*/
static bool get_glyph(ALLEGRO_TTF_FONT_DATA *data,
int ft_index, ALLEGRO_TTF_GLYPH_DATA **glyph)
{
ALLEGRO_TTF_GLYPH_RANGE *range;
int32_t range_start;
int lo, hi, mid;
ASSERT(glyph);
range_start = ft_index - (ft_index % RANGE_SIZE);
/* Binary search for the range. */
lo = 0;
hi = _al_vector_size(&data->glyph_ranges);
mid = (hi + lo)/2;
range = NULL;
while (lo < hi) {
ALLEGRO_TTF_GLYPH_RANGE *r = _al_vector_ref(&data->glyph_ranges, mid);
if (r->range_start == range_start) {
range = r;
break;
}
else if (r->range_start < range_start) {
lo = mid + 1;
}
else {
hi = mid;
}
mid = (hi + lo)/2;
}
if (!range) {
range = _al_vector_alloc_mid(&data->glyph_ranges, mid);
range->range_start = range_start;
range->glyphs = al_calloc(RANGE_SIZE, sizeof(ALLEGRO_TTF_GLYPH_DATA));
}
*glyph = &range->glyphs[ft_index - range_start];
/* If we're skipping cache misses and it isn't already cached, return it as invalid. */
if (data->skip_cache_misses && !(*glyph)->page_bitmap && (*glyph)->region.x >= 0) {
return false;
}
return ft_index != 0;
}
static void unlock_current_page(ALLEGRO_TTF_FONT_DATA *data)
{
if (data->page_lr) {
ALLEGRO_BITMAP **back = _al_vector_ref_back(&data->page_bitmaps);
ASSERT(al_is_bitmap_locked(*back));
al_unlock_bitmap(*back);
data->page_lr = NULL;
ALLEGRO_DEBUG("Unlocking page: %p\n", *back);
}
}
static ALLEGRO_BITMAP *push_new_page(ALLEGRO_TTF_FONT_DATA *data, int glyph_size)
{
ALLEGRO_BITMAP **back;
ALLEGRO_BITMAP *page;
ALLEGRO_STATE state;
int page_size = 1;
/* 16 seems to work well. A particular problem are fixed width fonts which
* take an inordinate amount of space. */
while (page_size < 16 * glyph_size) {
page_size *= 2;
}
if (page_size < data->min_page_size) {
page_size = data->min_page_size;
}
if (page_size > data->max_page_size) {
page_size = data->max_page_size;
}
if (glyph_size > page_size) {
ALLEGRO_ERROR("Unable create new page, glyph too large: %d > %d\n",
glyph_size, page_size);
return NULL;
}
unlock_current_page(data);
/* The bitmap will be destroyed when the parent font is destroyed so
* it is not safe to register a destructor for it.
*/
_al_push_destructor_owner();
al_store_state(&state, ALLEGRO_STATE_NEW_BITMAP_PARAMETERS);
al_set_new_bitmap_format(data->bitmap_format);
al_set_new_bitmap_flags(data->bitmap_flags);
page = al_create_bitmap(page_size, page_size);
al_restore_state(&state);
_al_pop_destructor_owner();
if (page) {
back = _al_vector_alloc_back(&data->page_bitmaps);
*back = page;
data->page_pos_x = 0;
data->page_pos_y = 0;
data->page_line_height = 0;
}
return page;
}
static unsigned char *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)
{
ALLEGRO_BITMAP *page;
int w4 = align4(w);
int h4 = align4(h);
int glyph_size = w4 > h4 ? w4 : h4;
bool lock = false;
if (_al_vector_is_empty(&data->page_bitmaps) || new) {
page = push_new_page(data, glyph_size);
if (!page) {
ALLEGRO_ERROR("Failed to create a new page for glyph %d.\n", ft_index);
return NULL;
}
}
else {
ALLEGRO_BITMAP **back = _al_vector_ref_back(&data->page_bitmaps);
page = *back;
}
ALLEGRO_DEBUG("Glyph %d: %dx%d (%dx%d)%s\n",
ft_index, w, h, w4, h4, new ? " new" : "");
if (data->page_pos_x + w4 > al_get_bitmap_width(page)) {
data->page_pos_y += data->page_line_height;
data->page_pos_y = align4(data->page_pos_y);
data->page_pos_x = 0;
data->page_line_height = 0;
}
if (data->page_pos_y + h4 > al_get_bitmap_height(page)) {
return alloc_glyph_region(data, ft_index, w, h, true, glyph, lock_whole_page);
}
glyph->page_bitmap = page;
glyph->region.x = data->page_pos_x;
glyph->region.y = data->page_pos_y;
glyph->region.w = w;
glyph->region.h = h;
data->page_pos_x = align4(data->page_pos_x + w4);
if (h > data->page_line_height) {
data->page_line_height = h4;
}
REGION lock_rect;
if (lock_whole_page) {
lock_rect.x = 0;
lock_rect.y = 0;
lock_rect.w = al_get_bitmap_width(page);
lock_rect.h = al_get_bitmap_height(page);
if (!data->page_lr) {
lock = true;
ALLEGRO_DEBUG("Locking whole page: %p\n", page);
}
}
else {
/* Unlock just in case... */
unlock_current_page(data);
lock_rect.x = glyph->region.x;
lock_rect.y = glyph->region.y;
lock_rect.w = w4;
lock_rect.h = h4;
lock = true;
ALLEGRO_DEBUG("Locking glyph region: %p %d %d %d %d\n", page,
lock_rect.x, lock_rect.y, lock_rect.w, lock_rect.h);
}
if (lock) {
char *ptr;
int i;
data->page_lr = al_lock_bitmap_region(page,
lock_rect.x, lock_rect.y, lock_rect.w, lock_rect.h,
ALLEGRO_PIXEL_FORMAT_ABGR_8888_LE, ALLEGRO_LOCK_WRITEONLY);
if (!data->page_lr) {
ALLEGRO_ERROR("Failed to lock page.\n");
return NULL;
}
/* Clear the data so we don't get garbage when using filtering
* FIXME We could clear just the border but I'm not convinced that
* would be faster (yet)
*/
for (i = 0; i < lock_rect.h; i++) {
ptr = (char *)(data->page_lr->data) + (i * data->page_lr->pitch);
memset(ptr, 0, lock_rect.w * 4);
}
}
ASSERT(data->page_lr);
/* Copy a displaced pointer for the glyph. */
return (unsigned char *)data->page_lr->data
+ ((glyph->region.y + 1) - lock_rect.y) * data->page_lr->pitch
+ ((glyph->region.x + 1) - lock_rect.x) * sizeof(int32_t);
}
static void copy_glyph_mono(ALLEGRO_TTF_FONT_DATA *font_data, FT_Face face,
unsigned char *glyph_data)
{
int pitch = font_data->page_lr->pitch;
int x, y;
for (y = 0; y < (int)face->glyph->bitmap.rows; y++) {
unsigned char const *ptr = face->glyph->bitmap.buffer + face->glyph->bitmap.pitch * y;
unsigned char *dptr = glyph_data + pitch * y;
int bit = 0;
if (font_data->flags & ALLEGRO_NO_PREMULTIPLIED_ALPHA) {
for (x = 0; x < (int)face->glyph->bitmap.width; x++) {
unsigned char set = ((*ptr >> (7-bit)) & 1) ? 255 : 0;
*dptr++ = 255;
*dptr++ = 255;
*dptr++ = 255;
*dptr++ = set;
bit = (bit + 1) & 7;
if (bit == 0) {
ptr++;
}
}
}
else {
for (x = 0; x < (int)face->glyph->bitmap.width; x++) {
unsigned char set = ((*ptr >> (7-bit)) & 1) ? 255 : 0;
*dptr++ = set;
*dptr++ = set;
*dptr++ = set;
*dptr++ = set;
bit = (bit + 1) & 7;
if (bit == 0) {
ptr++;
}
}
}
}
}
static void copy_glyph_color(ALLEGRO_TTF_FONT_DATA *font_data, FT_Face face,
unsigned char *glyph_data)
{
int pitch = font_data->page_lr->pitch;
int x, y;
for (y = 0; y < (int)face->glyph->bitmap.rows; y++) {
unsigned char const *ptr = face->glyph->bitmap.buffer + face->glyph->bitmap.pitch * y;
unsigned char *dptr = glyph_data + pitch * y;
if (font_data->flags & ALLEGRO_NO_PREMULTIPLIED_ALPHA) {
for (x = 0; x < (int)face->glyph->bitmap.width; x++) {
unsigned char c = *ptr;
*dptr++ = 255;
*dptr++ = 255;
*dptr++ = 255;
*dptr++ = c;
ptr++;
}
}
else {
for (x = 0; x < (int)face->glyph->bitmap.width; x++) {
unsigned char c = *ptr;
*dptr++ = c;
*dptr++ = c;
*dptr++ = c;
*dptr++ = c;
ptr++;
}
}
}
}
/* NOTE: this function may disable the bitmap hold drawing state
* and leave the current page bitmap locked.
*
* NOTE: We have previously tried to be more clever about caching multiple
* glyphs during incidental cache misses, but found that approach to be slower.
*/
static void cache_glyph(ALLEGRO_TTF_FONT_DATA *font_data, FT_Face face,
int ft_index, ALLEGRO_TTF_GLYPH_DATA *glyph, bool lock_whole_page)
{
FT_Int32 ft_load_flags;
FT_Error e;
int w, h;
unsigned char *glyph_data;
if (glyph->page_bitmap || glyph->region.x < 0)
return;
/* We shouldn't ever get here, as cache misses
* should have been set to ft_index = 0. */
ASSERT(!(font_data->skip_cache_misses && !lock_whole_page));
// FIXME: make this a config setting? FT_LOAD_FORCE_AUTOHINT
// FIXME: Investigate why some fonts don't work without the
// NO_BITMAP flags. Supposedly using that flag makes small sizes
// look bad so ideally we would not used it.
ft_load_flags = FT_LOAD_RENDER | FT_LOAD_NO_BITMAP;
if (font_data->flags & ALLEGRO_TTF_MONOCHROME)
ft_load_flags |= FT_LOAD_TARGET_MONO;
if (font_data->flags & ALLEGRO_TTF_NO_AUTOHINT)
ft_load_flags |= FT_LOAD_NO_AUTOHINT;
e = FT_Load_Glyph(face, ft_index, ft_load_flags);
if (e) {
ALLEGRO_WARN("Failed loading glyph %d from.\n", ft_index);
}
glyph->offset_x = face->glyph->bitmap_left;
glyph->offset_y = (face->size->metrics.ascender >> 6) - face->glyph->bitmap_top;
glyph->advance = face->glyph->advance.x >> 6;
w = face->glyph->bitmap.width;
h = face->glyph->bitmap.rows;
if (w == 0 || h == 0) {
/* Mark this glyph so we won't try to cache it next time. */
glyph->region.x = -1;
glyph->region.y = -1;
ALLEGRO_DEBUG("Glyph %d has zero size.\n", ft_index);
return;
}
/* Each glyph has a 1-pixel border all around. Note: The border is kept
* even against the outer bitmap edge, to ensure consistent rendering.
*/
glyph_data = alloc_glyph_region(font_data, ft_index,
w + 2, h + 2, false, glyph, lock_whole_page);
if (glyph_data == NULL) {
return;
}
if (font_data->flags & ALLEGRO_TTF_MONOCHROME)
copy_glyph_mono(font_data, face, glyph_data);
else
copy_glyph_color(font_data, face, glyph_data);
if (!lock_whole_page) {
unlock_current_page(font_data);
}
}
/* WARNING: It is only valid to call this function when the current page is empty
* (or already locked), otherwise it will gibberify the current glyphs on that page.
*
* This leaves the current page unlocked.
*/
static void cache_glyphs(ALLEGRO_TTF_FONT_DATA *data, const char *text, size_t text_size)
{
ALLEGRO_USTR_INFO info;
const ALLEGRO_USTR* ustr = al_ref_buffer(&info, text, text_size);
FT_Face face = data->face;
int pos = 0;
int32_t ch;
while ((ch = al_ustr_get_next(ustr, &pos)) >= 0) {
ALLEGRO_TTF_GLYPH_DATA *glyph;
int ft_index = FT_Get_Char_Index(face, ch);
get_glyph(data, ft_index, &glyph);
cache_glyph(data, face, ft_index, glyph, true);
}
}
static int get_kerning(ALLEGRO_TTF_FONT_DATA const *data, FT_Face face,
int prev_ft_index, int ft_index)
{
/* Do kerning? */
if (!(data->flags & ALLEGRO_TTF_NO_KERNING) && prev_ft_index != -1) {
FT_Vector delta;
FT_Get_Kerning(face, prev_ft_index, ft_index,
FT_KERNING_DEFAULT, &delta);
return delta.x >> 6;
}
return 0;
}
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)
{
ALLEGRO_TTF_FONT_DATA *data = f->data;
FT_Face face = data->face;
ALLEGRO_TTF_GLYPH_DATA *glyph;
int advance = 0;
if (!get_glyph(data, ft_index, &glyph)) {
if (f->fallback)
return f->fallback->vtable->get_glyph(f->fallback, prev_codepoint, codepoint, info);
else {
get_glyph(data, 0, &glyph);
ft_index = 0;
}
}
cache_glyph(data, face, ft_index, glyph, false);
advance += get_kerning(data, face, prev_ft_index, ft_index);
if (glyph->page_bitmap) {
info->bitmap = glyph->page_bitmap;
info->x = glyph->region.x + 1;
info->y = glyph->region.y + 1;
info->w = glyph->region.w - 2;
info->h = glyph->region.h - 2;
info->kerning = advance;
info->offset_x = glyph->offset_x;
info->offset_y = glyph->offset_y;
}
else if (glyph->region.x > 0) {
ALLEGRO_ERROR("Glyph %d not on any page.\n", ft_index);
return false;
}
else {
info->bitmap = 0;
}
advance += glyph->advance;
info->advance = advance;
return true;
}
static bool ttf_get_glyph(ALLEGRO_FONT const *f, int prev_codepoint, int codepoint, ALLEGRO_GLYPH *glyph)
{
ALLEGRO_TTF_FONT_DATA *data = f->data;
FT_Face face = data->face;
int prev_ft_index = (prev_codepoint == -1) ? -1 : (int)FT_Get_Char_Index(face, prev_codepoint);
int ft_index = FT_Get_Char_Index(face, codepoint);
return ttf_get_glyph_worker(f, prev_ft_index, ft_index, prev_codepoint, codepoint, glyph);
}
static int 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)
{
ALLEGRO_GLYPH glyph;
if (ttf_get_glyph_worker(f, prev_ft_index, ft_index, prev_ch, ch, &glyph) == false)
return 0;
if (glyph.bitmap != NULL) {
al_draw_tinted_bitmap_region(
glyph.bitmap, color,
glyph.x, glyph.y, glyph.w, glyph.h,
xpos + glyph.offset_x + glyph.kerning,
ypos + glyph.offset_y,
0
);
}
return glyph.advance;
}
static int ttf_font_height(ALLEGRO_FONT const *f)
{
ASSERT(f);
return f->height;
}
static int ttf_font_ascent(ALLEGRO_FONT const *f)
{
ALLEGRO_TTF_FONT_DATA *data;
FT_Face face;
ASSERT(f);
data = f->data;
face = data->face;
return face->size->metrics.ascender >> 6;
}
static int ttf_font_descent(ALLEGRO_FONT const *f)
{
ALLEGRO_TTF_FONT_DATA *data;
FT_Face face;
ASSERT(f);
data = f->data;
face = data->face;
return (-face->size->metrics.descender) >> 6;
}
static int ttf_render_char(ALLEGRO_FONT const *f, ALLEGRO_COLOR color,
int ch, float xpos, float ypos)
{
ALLEGRO_TTF_FONT_DATA *data = f->data;
FT_Face face = data->face;
int advance = 0;
int32_t ch32 = (int32_t) ch;
int ft_index = FT_Get_Char_Index(face, ch32);
advance = render_glyph(f, color, -1, ft_index, -1, ch, xpos, ypos);
return advance;
}
static int ttf_char_length(ALLEGRO_FONT const *f, int ch)
{
int result;
ALLEGRO_TTF_FONT_DATA *data = f->data;
ALLEGRO_TTF_GLYPH_DATA *glyph;
FT_Face face = data->face;
int ft_index = FT_Get_Char_Index(face, ch);
if (!get_glyph(data, ft_index, &glyph)) {
if (f->fallback) {
return al_get_glyph_width(f, ch);
}
else {
get_glyph(data, 0, &glyph);
ft_index = 0;
}
}
cache_glyph(data, face, ft_index, glyph, false);
result = glyph->region.w - 2;
return result;
}
static int ttf_render(ALLEGRO_FONT const *f, ALLEGRO_COLOR color,
const ALLEGRO_USTR *text, float x, float y)
{
ALLEGRO_TTF_FONT_DATA *data = f->data;
FT_Face face = data->face;
int pos = 0;
int advance = 0;
int prev_ft_index = -1;
int32_t prev_ch = -1;
int32_t ch;
bool hold;
hold = al_is_bitmap_drawing_held();
al_hold_bitmap_drawing(true);
while ((ch = al_ustr_get_next(text, &pos)) >= 0) {
int ft_index = FT_Get_Char_Index(face, ch);
advance += render_glyph(f, color, prev_ft_index, ft_index, prev_ch, ch,
x + advance, y);
prev_ft_index = ft_index;
prev_ch = ch;
}
al_hold_bitmap_drawing(hold);
return advance;
}
static int ttf_text_length(ALLEGRO_FONT const *f, const ALLEGRO_USTR *text)
{
int pos = 0;
int x = 0;
int32_t ch, nch;
nch = al_ustr_get_next(text, &pos);
while (nch >= 0) {
ch = nch;
nch = al_ustr_get_next(text, &pos);
x += al_get_glyph_advance(f, ch, nch < 0 ?
ALLEGRO_NO_KERNING : nch);
}
return x;
}
static void ttf_get_text_dimensions(ALLEGRO_FONT const *f,
ALLEGRO_USTR const *text,
int *bbx, int *bby, int *bbw, int *bbh)
{
int pos = 0;
bool first = true;
int x = 0;
int32_t ch, nch;
int ymin = f->height;
int ymax = 0;
*bbx = 0;
nch = al_ustr_get_next(text, &pos);
while (nch >= 0) {
int gx, gy, gw, gh;
ch = nch;
nch = al_ustr_get_next(text, &pos);
if (!al_get_glyph_dimensions(f, ch, &gx, &gy, &gw, &gh)) {
continue;
}
if (nch < 0) {
x += gx + gw;
}
else {
x += al_get_glyph_advance(f, ch, nch);
}
if (gy < ymin) {
ymin = gy;
}
if (gh+gy > ymax) {
ymax = gh + gy;
}
if (first) {
*bbx = gx;
first = false;
}
}
*bby = ymin;
*bbw = x - *bbx;
*bbh = ymax - ymin;
}
#ifdef DEBUG_CACHE
#include "allegro5/allegro_image.h"
static void debug_cache(ALLEGRO_FONT *f)
{
ALLEGRO_TTF_FONT_DATA *data = f->data;
_AL_VECTOR *v = &data->page_bitmaps;
static int j = 0;
int i;
al_init_image_addon();
for (i = 0; i < (int)_al_vector_size(v); i++) {
ALLEGRO_BITMAP **bmp = _al_vector_ref(v, i);
ALLEGRO_USTR *u = al_ustr_newf("font%d_%d.png", j, i);
al_save_bitmap(al_cstr(u), *bmp);
al_ustr_free(u);
}
j++;
}
#endif
static void ttf_destroy(ALLEGRO_FONT *f)
{
ALLEGRO_TTF_FONT_DATA *data = f->data;
int i;
unlock_current_page(data);
#ifdef DEBUG_CACHE
debug_cache(f);
#endif
FT_Done_Face(data->face);
for (i = _al_vector_size(&data->glyph_ranges) - 1; i >= 0; i--) {
ALLEGRO_TTF_GLYPH_RANGE *range = _al_vector_ref(&data->glyph_ranges, i);
al_free(range->glyphs);
}
_al_vector_free(&data->glyph_ranges);
for (i = _al_vector_size(&data->page_bitmaps) - 1; i >= 0; i--) {
ALLEGRO_BITMAP **bmp = _al_vector_ref(&data->page_bitmaps, i);
al_destroy_bitmap(*bmp);
}
_al_vector_free(&data->page_bitmaps);
al_free(data);
al_free(f);
}
static unsigned long ftread(FT_Stream stream, unsigned long offset,
unsigned char *buffer, unsigned long count)
{
ALLEGRO_TTF_FONT_DATA *data = stream->pathname.pointer;
unsigned long bytes;
if (count == 0)
return 0;
if (offset != data->offset)
al_fseek(data->file, data->base_offset + offset, ALLEGRO_SEEK_SET);
bytes = al_fread(data->file, buffer, count);
data->offset = offset + bytes;
return bytes;
}
static void ftclose(FT_Stream stream)
{
ALLEGRO_TTF_FONT_DATA *data = stream->pathname.pointer;
al_fclose(data->file);
data->file = NULL;
}
/* Function: al_load_ttf_font_f
*/
ALLEGRO_FONT *al_load_ttf_font_f(ALLEGRO_FILE *file,
char const *filename, int size, int flags)
{
return al_load_ttf_font_stretch_f(file, filename, 0, size, flags);
}
/* Function: al_load_ttf_font_stretch_f
*/
ALLEGRO_FONT *al_load_ttf_font_stretch_f(ALLEGRO_FILE *file,
char const *filename, int w, int h, int flags)
{
FT_Face face;
ALLEGRO_TTF_FONT_DATA *data;
ALLEGRO_FONT *f;
ALLEGRO_PATH *path;
FT_Open_Args args;
int result;
ALLEGRO_CONFIG* system_cfg = al_get_system_config();
const char* min_page_size_str =
al_get_config_value(system_cfg, "ttf", "min_page_size");
const char* max_page_size_str =
al_get_config_value(system_cfg, "ttf", "max_page_size");
const char* cache_str =
al_get_config_value(system_cfg, "ttf", "cache_text");
const char* skip_cache_misses_str =
al_get_config_value(system_cfg, "ttf", "skip_cache_misses");
if ((h > 0 && w < 0) || (h < 0 && w > 0)) {
ALLEGRO_ERROR("Height/width have opposite signs (w = %d, h = %d).\n", w, h);
return NULL;
}
data = al_calloc(1, sizeof *data);
data->stream.read = ftread;
data->stream.close = ftclose;
data->stream.pathname.pointer = data;
data->base_offset = al_ftell(file);
data->stream.size = al_fsize(file);
data->file = file;
data->bitmap_format = al_get_new_bitmap_format();
data->bitmap_flags = al_get_new_bitmap_flags();
data->min_page_size = 256;
data->max_page_size = 8192;
if (min_page_size_str) {
int min_page_size = atoi(min_page_size_str);
if (min_page_size > 0) {
data->min_page_size = min_page_size;
}
}
if (max_page_size_str) {
int max_page_size = atoi(max_page_size_str);
if (max_page_size > 0 && max_page_size >= data->min_page_size) {
data->max_page_size = max_page_size;
}
}
if (skip_cache_misses_str && !strcmp(skip_cache_misses_str, "true")) {
data->skip_cache_misses = true;
}
memset(&args, 0, sizeof args);
args.flags = FT_OPEN_STREAM;
args.stream = &data->stream;
if ((result = FT_Open_Face(ft, &args, 0, &face)) != 0) {
ALLEGRO_ERROR("Reading %s failed. Freetype error code %d\n", filename,
result);
// Note: Freetype already closed the file for us.
al_free(data);
return NULL;
}
// FIXME: The below doesn't use Allegro's streaming.
/* Small hack for Type1 fonts which store kerning information in
* a separate file - and we try to guess the name of that file.
*/
path = al_create_path(filename);
if (!strcmp(al_get_path_extension(path), ".pfa")) {
const char *helper;
ALLEGRO_DEBUG("Type1 font assumed for %s.\n", filename);
al_set_path_extension(path, ".afm");
helper = al_path_cstr(path, '/');
FT_Attach_File(face, helper);
ALLEGRO_DEBUG("Guessed afm file %s.\n", helper);
al_set_path_extension(path, ".tfm");
helper = al_path_cstr(path, '/');
FT_Attach_File(face, helper);
ALLEGRO_DEBUG("Guessed tfm file %s.\n", helper);
}
al_destroy_path(path);
if (h > 0) {
FT_Set_Pixel_Sizes(face, w, h);
}
else {
/* Set the "real dimension" of the font to be the passed size,
* in pixels.
*/
FT_Size_RequestRec req;
ASSERT(w <= 0);
ASSERT(h <= 0);
req.type = FT_SIZE_REQUEST_TYPE_REAL_DIM;
req.width = (-w) << 6;
req.height = (-h) << 6;
req.horiResolution = 0;
req.vertResolution = 0;
FT_Request_Size(face, &req);
}
ALLEGRO_DEBUG("Font %s loaded with pixel size %d x %d.\n", filename,
w, h);
ALLEGRO_DEBUG(" ascent=%.1f, descent=%.1f, height=%.1f\n",
face->size->metrics.ascender / 64.0,
face->size->metrics.descender / 64.0,
face->size->metrics.height / 64.0);
data->face = face;
data->flags = flags;
_al_vector_init(&data->glyph_ranges, sizeof(ALLEGRO_TTF_GLYPH_RANGE));
_al_vector_init(&data->page_bitmaps, sizeof(ALLEGRO_BITMAP*));
if (data->skip_cache_misses) {
cache_glyphs(data, "\0", 1);
}
if (cache_str) {
cache_glyphs(data, cache_str, strlen(cache_str));
}
unlock_current_page(data);
f = al_calloc(sizeof *f, 1);
f->height = face->size->metrics.height >> 6;
f->vtable = &vt;
f->data = data;
f->dtor_item = _al_register_destructor(_al_dtor_list, "ttf_font", f,
(void (*)(void *))al_destroy_font);
return f;
}
/* Function: al_load_ttf_font
*/
ALLEGRO_FONT *al_load_ttf_font(char const *filename, int size, int flags)
{
return al_load_ttf_font_stretch(filename, 0, size, flags);
}
/* Function: al_load_ttf_font_stretch
*/
ALLEGRO_FONT *al_load_ttf_font_stretch(char const *filename, int w, int h,
int flags)
{
ALLEGRO_FILE *f;
ALLEGRO_FONT *font;
ASSERT(filename);
f = al_fopen(filename, "rb");
if (!f) {
ALLEGRO_ERROR("Unable to open file for reading: %s\n", filename);
return NULL;
}
/* The file handle is owned by the function and the file is usually only
* closed when the font is destroyed, in case Freetype has to load data
* at a later time.
*/
font = al_load_ttf_font_stretch_f(f, filename, w, h, flags);
return font;
}
static int ttf_get_font_ranges(ALLEGRO_FONT *font, int ranges_count,
int *ranges)
{
ALLEGRO_TTF_FONT_DATA *data = font->data;
FT_UInt g;
FT_ULong unicode = FT_Get_First_Char(data->face, &g);
int i = 0;
if (i < ranges_count) {
ranges[i * 2 + 0] = unicode;
ranges[i * 2 + 1] = unicode;
}
while (g) {
FT_ULong unicode2 = FT_Get_Next_Char(data->face, unicode, &g);
if (unicode + 1 != unicode2) {
if (i < ranges_count) {
ranges[i * 2 + 1] = unicode;
if (i + 1 < ranges_count) {
ranges[(i + 1) * 2 + 0] = unicode2;
}
}
i++;
}
if (i < ranges_count) {
ranges[i * 2 + 1] = unicode2;
}
unicode = unicode2;
}
return i;
}
static bool ttf_get_glyph_dimensions(ALLEGRO_FONT const *f,
int codepoint,
int *bbx, int *bby, int *bbw, int *bbh)
{
ALLEGRO_TTF_FONT_DATA *data = f->data;
ALLEGRO_TTF_GLYPH_DATA *glyph;
FT_Face face = data->face;
int ft_index = FT_Get_Char_Index(face, codepoint);
if (!get_glyph(data, ft_index, &glyph)) {
if (f->fallback) {
return al_get_glyph_dimensions(f->fallback, codepoint,
bbx, bby, bbw, bbh);
}
else {
get_glyph(data, 0, &glyph);
ft_index = 0;
}
}
cache_glyph(data, face, ft_index, glyph, false);
*bbx = glyph->offset_x;
*bbw = glyph->region.w - 2;
*bbh = glyph->region.h - 2;
*bby = glyph->offset_y;
return true;
}
static int ttf_get_glyph_advance(ALLEGRO_FONT const *f, int codepoint1,
int codepoint2)
{
ALLEGRO_TTF_FONT_DATA *data = f->data;
FT_Face face = data->face;
int ft_index = FT_Get_Char_Index(face, codepoint1);
ALLEGRO_TTF_GLYPH_DATA *glyph;
int kerning = 0;
int advance = 0;
if (codepoint1 == ALLEGRO_NO_KERNING) {
return 0;
}
if (!get_glyph(data, ft_index, &glyph)) {
if (f->fallback) {
return al_get_glyph_advance(f->fallback, codepoint1, codepoint2);
}
else {
get_glyph(data, 0, &glyph);
ft_index = 0;
}
}
cache_glyph(data, face, ft_index, glyph, false);
if (codepoint2 != ALLEGRO_NO_KERNING) {
int ft_index1 = FT_Get_Char_Index(face, codepoint1);
int ft_index2 = FT_Get_Char_Index(face, codepoint2);
kerning = get_kerning(data, face, ft_index1, ft_index2);
}
advance = glyph->advance;
return advance + kerning;
}
/* Function: al_init_ttf_addon
*/
bool al_init_ttf_addon(void)
{
if (ttf_inited) {
ALLEGRO_WARN("TTF addon already initialised.\n");
return true;
}
FT_Init_FreeType(&ft);
vt.font_height = ttf_font_height;
vt.font_ascent = ttf_font_ascent;
vt.font_descent = ttf_font_descent;
vt.char_length = ttf_char_length;
vt.text_length = ttf_text_length;
vt.render_char = ttf_render_char;
vt.render = ttf_render;
vt.destroy = ttf_destroy;
vt.get_text_dimensions = ttf_get_text_dimensions;
vt.get_font_ranges = ttf_get_font_ranges;
vt.get_glyph_dimensions = ttf_get_glyph_dimensions;
vt.get_glyph_advance = ttf_get_glyph_advance;
vt.get_glyph = ttf_get_glyph;
al_register_font_loader(".ttf", al_load_ttf_font);
/* Can't fail right now - in the future we might dynamically load
* the FreeType DLL here and/or initialize FreeType (which both
* could fail and would cause a false return).
*/
ttf_inited = true;
return ttf_inited;
}
/* Function: al_shutdown_ttf_addon
*/
void al_shutdown_ttf_addon(void)
{
if (!ttf_inited) {
ALLEGRO_ERROR("TTF addon not initialised.\n");
return;
}
al_register_font_loader(".ttf", NULL);
FT_Done_FreeType(ft);
ttf_inited = false;
}
/* Function: al_get_allegro_ttf_version
*/
uint32_t al_get_allegro_ttf_version(void)
{
return ALLEGRO_VERSION_INT;
}
/* vim: set sts=3 sw=3 et: */
You can’t perform that action at this time.