289 lines
9.1 KiB
C
289 lines
9.1 KiB
C
#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 <stdlib.h>
|
|
#include <math.h>
|
|
|
|
#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)
|
|
#define wmax (int32_t)2048 // fixed atlas width - 1024 or 2048 are ideal depending on font size
|
|
// height is automatically adjusted in powers of two
|
|
// todo we should expand this less when the card supports npot textures
|
|
|
|
/* 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");
|
|
// todo probably should just store a list of ranges instead, maybe even ditch the config var entirely
|
|
cache_text_str = " abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.,!?/()@#$%^&*-_=+<>~`\u007FĀāĂ㥹ĆćĈĉĊċČčĎďĐđĒēĔĕĖėĘęĚěĜĝĞğĠġĢģĤĥĦħĨĩĪīĬĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀŁłŃńŅņŇňʼnŊŋŌōŎŏŐőŒœŔŕŖŗŘřŚśŜŝŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſ";
|
|
}
|
|
|
|
// 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
|
|
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);
|
|
|
|
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;
|
|
const unsigned int rectw = (floor(geom.advanceWidth) > geom.minWidth) ? floor(geom.advanceWidth) : geom.minWidth;
|
|
if ((wmax - woff) < rectw + 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"); // i want to use ceiling, it seems more correct? but it still looks too crooked
|
|
al_draw_filled_rectangle(
|
|
woff,
|
|
hoff,
|
|
woff + rectw,
|
|
hoff + rh,
|
|
al_map_rgba(0, 0, 0, 255)
|
|
);
|
|
int padding = (int)(geom.advanceWidth - (double)geom.minWidth)/2;
|
|
if (padding < 0) padding = 0;
|
|
block =
|
|
al_lock_bitmap_region(gtex, woff + padding, 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 += rectw + 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);
|
|
}
|