#define ALLEGRO_INTERNAL_UNSTABLE #include "allegro5/allegro.h" #include "allegro5/internal/aintern.h" #include "allegro5/internal/aintern_vector.h" #include "allegro5/allegro_schrift.h" #include "allegro5/internal/aintern_font.h" #include "allegro5/internal/aintern_dtor.h" #include "allegro5/internal/aintern_system.h" #include "allegro5/internal/aintern_exitfunc.h" #include "schrift.h" #include #include "../primitives/allegro5/allegro_primitives.h" ALLEGRO_DEBUG_CHANNEL("font") #define CANI_ALLEGRO_SCHRIFT_VER 1 #define delimit_red (unsigned)0 // shut up i know what i'm doing #define delimit_green (unsigned)0 #define delimit_blue (unsigned)255 #define delimit_alpha (unsigned)255 #define delimit_color al_map_rgba(delimit_red, delimit_green, delimit_blue, delimit_alpha) /* globals */ static bool ttf_inited; // Destroys the source bitmap and replaces it with a version // expanded downward by add no. of pixels. ALLEGRO_BITMAP *grow_bitmap(ALLEGRO_BITMAP *bmp, uint16_t add) { const uint32_t h = al_get_bitmap_height(bmp) + add; ALLEGRO_BITMAP *tgt = al_get_target_bitmap(); ALLEGRO_BITMAP *grown = al_create_bitmap(al_get_bitmap_width(bmp), h); al_set_target_bitmap(grown); al_draw_bitmap(bmp, 0, 0, 0); al_draw_filled_rectangle(0, h - add, al_get_bitmap_width(bmp), h, delimit_color); al_set_target_bitmap(tgt); al_destroy_bitmap(bmp); return grown; } ALLEGRO_FONT *al_load_ttf_font_f(ALLEGRO_FILE *file, char const *filename, int size, int flags) { #define CLEANUP free(filebuf); if (gtex) al_destroy_bitmap(gtex); al_set_target_bitmap(tgt) #define FAIL CLEANUP; return NULL const int64_t filesize = al_fsize(file); // this should not be signed! if (filesize < 1) { ALLEGRO_ERROR("Can't load font %s: file is empty or unknown size.\n", filename); return NULL; } void *filebuf = malloc(filesize); SFT sft = { .flags = SFT_DOWNWARD_Y }; ALLEGRO_FONT *f = NULL; ALLEGRO_BITMAP *gtex = NULL; int result = 0; ALLEGRO_BITMAP *tgt = al_get_target_bitmap(); ALLEGRO_CONFIG* system_cfg = al_get_system_config(); const char* cache_text_str = al_get_config_value(system_cfg, "ttf", "cache_text"); if (!cache_text_str) { ALLEGRO_ERROR("No characters defined in cache_text! Using defaults\n"); cache_text_str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.,!?/()@#$%^&*-_=+<>~`\u007F "; } // read font file #define chunksize 1024 size_t offset = 0; while (!al_feof(file)) { if (al_ferror(file)) { ALLEGRO_ERROR("Can't load font %s: error reading file.\n", filename); FAIL; } offset += al_fread(file, (uint8_t*)filebuf + offset, chunksize); } sft.xScale = abs(size); sft.yScale = abs(size); sft.font = sft_loadmem(filebuf, filesize); if (!sft.font) { ALLEGRO_ERROR("Can't load font %s: schrift error, is it a valid format?\n", filename); FAIL; } // convert glyphs into textures #define wmax (int32_t)1024 int32_t woff = 1, hoff = 1, hmax = 128; // signed to calculate difference at texture edges gtex = al_create_bitmap(wmax, hmax); al_set_target_bitmap(gtex); al_clear_to_color(delimit_color); // todo invert alpha at final pass unsigned int i = 0; const unsigned int len = strlen(cache_text_str); unsigned int lh = 0, rh = 0; while (i < len) { SFT_Glyph glyph; SFT_GMetrics geom; if (0 > sft_lookup(&sft, cache_text_str[i], &glyph)) goto glyph_measure_fail; if (0 > sft_gmetrics(&sft, glyph, &geom)) goto glyph_measure_fail; if (lh < geom.minHeight) lh = geom.minHeight; const int glbottom = geom.yOffset + lh + geom.minHeight; if (glbottom > 0 && rh < (unsigned)glbottom) rh = glbottom; i++; continue; glyph_measure_fail: ALLEGRO_ERROR("Failure generating glyph %c in %s!\n", cache_text_str[i], filename); FAIL; } ALLEGRO_INFO("scanned all glyphs for font height %u", lh); i = 0; while (i < len) { ALLEGRO_INFO("generating glyph for %c\n", cache_text_str[i]); SFT_Glyph glyph; SFT_GMetrics geom; SFT_Image img; ALLEGRO_LOCKED_REGION *block = NULL; if (0 > sft_lookup(&sft, cache_text_str[i], &glyph)) goto glyph_lookup_fail; if (0 > sft_gmetrics(&sft, glyph, &geom)) goto glyph_lookup_fail; if ((wmax - woff) < geom.minWidth + 1) { hoff += rh + 1; woff = 1; } if ((hmax - hoff) < rh + 1) { ALLEGRO_WARN("ran out of room! growing glyph texture\n"); gtex = grow_bitmap(gtex, hmax); al_set_target_bitmap(gtex); hmax *= 2; } img.width = geom.minWidth; img.height = geom.minHeight; img.pixels = malloc(img.width * img.height); if (!img.pixels) goto glyph_lookup_oom; if (0 > sft_render(&sft, glyph, img)) goto glyph_lookup_fail; ALLEGRO_INFO("got raster\n"); al_draw_filled_rectangle( woff, hoff, woff + geom.minWidth, hoff + rh, al_map_rgba(0, 0, 0, 255) ); block = al_lock_bitmap_region(gtex, woff, hoff + (geom.yOffset + lh), geom.minWidth, geom.minHeight, ALLEGRO_PIXEL_FORMAT_SINGLE_CHANNEL_8, ALLEGRO_LOCK_WRITEONLY); assert(block->pixel_size == 1); ALLEGRO_INFO("got vram lock\n"); int row = 0; // should not be signed!!!!! C programmers are fucking assholes! while (row < img.height) { memcpy( (uint8_t*)block->data + block->pitch * row, (uint8_t*)img.pixels + img.width * row, img.width ); row++; } free(img.pixels); al_unlock_bitmap(gtex); ALLEGRO_INFO("blit raster to vram\n"); woff += geom.minWidth + 1; i++; continue; glyph_lookup_oom: ALLEGRO_ERROR("No buffer to generate glyph %c in %s\n", cache_text_str[i], filename); FAIL; glyph_lookup_fail: ALLEGRO_ERROR("Failure generating glyph %c in %s!\n", cache_text_str[i], filename); FAIL; } // a shader would be better, but we don't know the target platform. ALLEGRO_LOCKED_REGION *glock = al_lock_bitmap(gtex, ALLEGRO_PIXEL_FORMAT_ABGR_8888, ALLEGRO_LOCK_READWRITE); assert(glock->pixel_size == 4); const unsigned int gtexw = al_get_bitmap_width(gtex), gtexh = al_get_bitmap_height(gtex); unsigned int pixiw = 0, pixih = 0, pixi = 0; while (pixi < gtexh * gtexw) { uint32_t* pixel = (uint32_t*)((uint8_t*)glock->data + (glock->pitch * (signed)pixih)) + pixiw; constexpr uint32_t delimiter = delimit_alpha << 24 | delimit_blue << 16 | delimit_green << 8 | delimit_red; if (*pixel == delimiter) *pixel = 0xFF000000; else *pixel = (*pixel & 0xffu) * 0x01010101u; pixi++; if (++pixiw >= gtexw) { pixiw = 0; pixih++; } } al_unlock_bitmap(gtex); ALLEGRO_INFO("converted colors\n"); // debug al_save_bitmap("schrift_test.png", gtex); // convert texture to bitmap font const unsigned int ranges_size = i; int ranges[ranges_size*2]; i = 0; while (i < ranges_size) { ranges[i*2] = (unsigned char)cache_text_str[i]; ranges[i*2+1] = (unsigned char)cache_text_str[i]; i++; } f = al_grab_font_from_bitmap(gtex, ranges_size, ranges); ALLEGRO_DEBUG("Font %s loaded.\n", filename); CLEANUP; return f; } ALLEGRO_FONT *al_load_ttf_font(char const *filename, int size, 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; } font = al_load_ttf_font_f(f, filename, size, flags); return font; } bool al_init_ttf_addon(void) { if (ttf_inited) { ALLEGRO_WARN("TTF addon already initialised.\n"); return true; } al_register_font_loader(".ttf", al_load_ttf_font); _al_add_exit_func(al_shutdown_ttf_addon, "al_shutdown_ttf_addon"); ttf_inited = true; return ttf_inited; } bool al_is_ttf_addon_initialized(void) { return ttf_inited; } void al_shutdown_ttf_addon(void) { if (!ttf_inited) { ALLEGRO_ERROR("TTF addon not initialised.\n"); return; } al_register_font_loader(".ttf", NULL); ttf_inited = false; } uint32_t al_get_allegro_ttf_version(void) { return CANI_ALLEGRO_SCHRIFT_VER; } ALLEGRO_FONT *al_load_ttf_font_stretch(char const *filename, int w, int h, int flags) { ALLEGRO_WARN("Unimplemented function\n"); al_load_ttf_font(filename, h, flags); } ALLEGRO_FONT *al_load_ttf_font_stretch_f(ALLEGRO_FILE *file, char const *filename, int w, int h, int flags) { ALLEGRO_WARN("Unimplemented function\n"); al_load_ttf_font_f(file, filename, h, flags); }