diff options
author | WlodekM <[email protected]> | 2024-06-16 10:35:45 +0300 |
---|---|---|
committer | WlodekM <[email protected]> | 2024-06-16 10:35:45 +0300 |
commit | abef6da56913f1c55528103e60a50451a39628b1 (patch) | |
tree | b3c8092471ecbb73e568cd0d336efa0e7871ee8d /src/SystemFonts.c |
initial commit
Diffstat (limited to 'src/SystemFonts.c')
-rw-r--r-- | src/SystemFonts.c | 1111 |
1 files changed, 1111 insertions, 0 deletions
diff --git a/src/SystemFonts.c b/src/SystemFonts.c new file mode 100644 index 0000000..8f86246 --- /dev/null +++ b/src/SystemFonts.c @@ -0,0 +1,1111 @@ +#include "SystemFonts.h" +#include "Drawer2D.h" +#include "String.h" +#include "Funcs.h" +#include "Platform.h" +#include "ExtMath.h" +#include "Logger.h" +#include "Game.h" +#include "Event.h" +#include "Stream.h" +#include "Utils.h" +#include "Errors.h" +#include "Window.h" +#include "Options.h" + +static char defaultBuffer[STRING_SIZE]; +static cc_string font_default = String_FromArray(defaultBuffer); + +void SysFont_SetDefault(const cc_string* fontName) { + String_Copy(&font_default, fontName); + Event_RaiseVoid(&ChatEvents.FontChanged); +} + +static void OnInit(void) { + Options_Get(OPT_FONT_NAME, &font_default, ""); + if (Game_ClassicMode) font_default.length = 0; +} + +struct IGameComponent SystemFonts_Component = { + OnInit /* Init */ +}; + + +/*########################################################################################################################* +*------------------------------------------------------Fallback font------------------------------------------------------* +*#########################################################################################################################*/ +#define FallbackFont_ValidChar(c) ((c) > 32 && (c) < 127) +#define FallbackFont_ToIndex(c) ((c) - 33) /* First valid char is ! */ +#define SPACE_WIDTH 2 +#define CELL_SIZE 8 + +#define FallbackFont_GetRows(c) (FallbackFont_ValidChar(c) ? font_bitmap[FallbackFont_ToIndex(c)] : missing_cell) +#define FallbackFont_GetScale(size) ((size) >> 3) + +static const cc_uint8 missing_cell[CELL_SIZE] = { + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF +}; + +/* 8x8 font bitmap, represented with 1 bit for each pixel */ +/* Source: Goodly's texture pack for ClassiCube */ +static const cc_uint8 font_bitmap[][CELL_SIZE] = { + { 0x01,0x01,0x01,0x01,0x01,0x00,0x01,0x00 }, /* ! */ + { 0x05,0x05,0x05,0x00,0x00,0x00,0x00,0x00 }, /* " */ + { 0x0A,0x0A,0x1F,0x0A,0x1F,0x0A,0x0A,0x00 }, /* # */ + { 0x04,0x1F,0x01,0x1F,0x10,0x1F,0x04,0x00 }, /* $ */ + { 0x00,0x21,0x11,0x08,0x04,0x22,0x21,0x00 }, /* % */ + { 0x0C,0x12,0x0C,0x2E,0x19,0x11,0x2E,0x00 }, /* & */ + { 0x01,0x01,0x01,0x00,0x00,0x00,0x00,0x00 }, /* ' */ + { 0x04,0x02,0x01,0x01,0x01,0x02,0x04,0x00 }, /* ( */ + { 0x01,0x02,0x04,0x04,0x04,0x02,0x01,0x00 }, /* ) */ + { 0x00,0x02,0x07,0x02,0x05,0x00,0x00,0x00 }, /* * */ + { 0x00,0x04,0x04,0x1F,0x04,0x04,0x00,0x00 }, /* + */ + { 0x00,0x00,0x00,0x00,0x00,0x02,0x02,0x01 }, /* , */ + { 0x00,0x00,0x00,0x1F,0x00,0x00,0x00,0x00 }, /* - */ + { 0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x00 }, /* . */ + { 0x08,0x08,0x04,0x04,0x02,0x02,0x01,0x00 }, /* / */ + { 0x06,0x09,0x0D,0x0B,0x09,0x09,0x06,0x00 }, /* 0 */ + { 0x02,0x03,0x02,0x02,0x02,0x02,0x07,0x00 }, /* 1 */ + { 0x06,0x09,0x08,0x04,0x02,0x09,0x0F,0x00 }, /* 2 */ + { 0x06,0x09,0x08,0x06,0x08,0x09,0x06,0x00 }, /* 3 */ + { 0x05,0x05,0x05,0x0F,0x04,0x04,0x04,0x00 }, /* 4 */ + { 0x0F,0x01,0x07,0x08,0x08,0x09,0x06,0x00 }, /* 5 */ + { 0x06,0x09,0x01,0x07,0x09,0x09,0x06,0x00 }, /* 6 */ + { 0x0F,0x08,0x08,0x04,0x04,0x02,0x02,0x00 }, /* 7 */ + { 0x06,0x09,0x09,0x06,0x09,0x09,0x06,0x00 }, /* 8 */ + { 0x06,0x09,0x09,0x0E,0x08,0x09,0x06,0x00 }, /* 9 */ + { 0x00,0x01,0x01,0x00,0x00,0x01,0x01,0x00 }, /* : */ + { 0x00,0x02,0x02,0x00,0x00,0x02,0x02,0x01 }, /* ; */ + { 0x00,0x04,0x02,0x01,0x02,0x04,0x00,0x00 }, /* < */ + { 0x00,0x00,0x1F,0x00,0x00,0x1F,0x00,0x00 }, /* = */ + { 0x00,0x01,0x02,0x04,0x02,0x01,0x00,0x00 }, /* > */ + { 0x07,0x09,0x08,0x04,0x02,0x00,0x02,0x00 }, /* ? */ + { 0x0E,0x11,0x1D,0x1D,0x1D,0x01,0x0E,0x00 }, /* @ */ + { 0x06,0x09,0x09,0x0F,0x09,0x09,0x09,0x00 }, /* A */ + { 0x07,0x09,0x09,0x07,0x09,0x09,0x07,0x00 }, /* B */ + { 0x06,0x09,0x01,0x01,0x01,0x09,0x06,0x00 }, /* C */ + { 0x07,0x09,0x09,0x09,0x09,0x09,0x07,0x00 }, /* D */ + { 0x0F,0x01,0x01,0x07,0x01,0x01,0x0F,0x00 }, /* E */ + { 0x0F,0x01,0x01,0x07,0x01,0x01,0x01,0x00 }, /* F */ + { 0x06,0x09,0x01,0x0D,0x09,0x09,0x06,0x00 }, /* G */ + { 0x09,0x09,0x09,0x0F,0x09,0x09,0x09,0x00 }, /* H */ + { 0x07,0x02,0x02,0x02,0x02,0x02,0x07,0x00 }, /* I */ + { 0x08,0x08,0x08,0x08,0x08,0x09,0x07,0x00 }, /* J */ + { 0x09,0x09,0x05,0x03,0x05,0x09,0x09,0x00 }, /* K */ + { 0x01,0x01,0x01,0x01,0x01,0x01,0x0F,0x00 }, /* L */ + { 0x11,0x1B,0x15,0x11,0x11,0x11,0x11,0x00 }, /* M */ + { 0x09,0x0B,0x0D,0x09,0x09,0x09,0x09,0x00 }, /* N */ + { 0x06,0x09,0x09,0x09,0x09,0x09,0x06,0x00 }, /* O */ + { 0x07,0x09,0x09,0x07,0x01,0x01,0x01,0x00 }, /* P */ + { 0x06,0x09,0x09,0x09,0x09,0x05,0x0E,0x00 }, /* Q */ + { 0x07,0x09,0x09,0x07,0x09,0x09,0x09,0x00 }, /* R */ + { 0x06,0x09,0x01,0x06,0x08,0x09,0x06,0x00 }, /* S */ + { 0x07,0x02,0x02,0x02,0x02,0x02,0x02,0x00 }, /* T */ + { 0x09,0x09,0x09,0x09,0x09,0x09,0x06,0x00 }, /* U */ + { 0x11,0x11,0x11,0x11,0x11,0x0A,0x04,0x00 }, /* V */ + { 0x11,0x11,0x11,0x11,0x15,0x1B,0x11,0x00 }, /* W */ + { 0x11,0x11,0x0A,0x04,0x0A,0x11,0x11,0x00 }, /* X */ + { 0x11,0x11,0x0A,0x04,0x04,0x04,0x04,0x00 }, /* Y */ + { 0x0F,0x08,0x04,0x02,0x01,0x01,0x0F,0x00 }, /* Z */ + { 0x07,0x01,0x01,0x01,0x01,0x01,0x07,0x00 }, /* [ */ + { 0x01,0x01,0x02,0x02,0x04,0x04,0x08,0x00 }, /* \ */ + { 0x07,0x04,0x04,0x04,0x04,0x04,0x07,0x00 }, /* ] */ + { 0x04,0x0A,0x11,0x00,0x00,0x00,0x00,0x00 }, /* ^ */ + { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1F }, /* _ */ + { 0x01,0x01,0x02,0x00,0x00,0x00,0x00,0x00 }, /* ` */ + { 0x00,0x00,0x0E,0x09,0x09,0x0D,0x0B,0x00 }, /* a */ + { 0x01,0x01,0x07,0x09,0x09,0x09,0x07,0x00 }, /* b */ + { 0x00,0x00,0x06,0x09,0x01,0x09,0x06,0x00 }, /* c */ + { 0x08,0x08,0x0E,0x09,0x09,0x09,0x0E,0x00 }, /* d */ + { 0x00,0x00,0x06,0x09,0x0F,0x01,0x0E,0x00 }, /* e */ + { 0x06,0x01,0x07,0x01,0x01,0x01,0x01,0x00 }, /* f */ + { 0x00,0x00,0x0E,0x09,0x09,0x0E,0x08,0x07 }, /* g */ + { 0x01,0x01,0x07,0x09,0x09,0x09,0x09,0x00 }, /* h */ + { 0x01,0x00,0x01,0x01,0x01,0x01,0x01,0x00 }, /* i */ + { 0x08,0x00,0x08,0x08,0x08,0x08,0x09,0x06 }, /* j */ + { 0x01,0x01,0x09,0x05,0x03,0x05,0x09,0x00 }, /* k */ + { 0x01,0x01,0x01,0x01,0x01,0x01,0x02,0x00 }, /* l */ + { 0x00,0x00,0x0B,0x15,0x15,0x11,0x11,0x00 }, /* m */ + { 0x00,0x00,0x07,0x09,0x09,0x09,0x09,0x00 }, /* n */ + { 0x00,0x00,0x06,0x09,0x09,0x09,0x06,0x00 }, /* o */ + { 0x00,0x00,0x07,0x09,0x09,0x07,0x01,0x01 }, /* p */ + { 0x00,0x00,0x0E,0x09,0x09,0x0E,0x08,0x08 }, /* q */ + { 0x00,0x00,0x05,0x03,0x01,0x01,0x01,0x00 }, /* r */ + { 0x00,0x00,0x0E,0x01,0x06,0x08,0x07,0x00 }, /* s */ + { 0x02,0x02,0x07,0x02,0x02,0x02,0x02,0x00 }, /* t */ + { 0x00,0x00,0x09,0x09,0x09,0x09,0x0E,0x00 }, /* u */ + { 0x00,0x00,0x09,0x09,0x09,0x05,0x03,0x00 }, /* v */ + { 0x00,0x00,0x11,0x11,0x15,0x15,0x1A,0x00 }, /* w */ + { 0x00,0x00,0x05,0x05,0x02,0x05,0x05,0x00 }, /* x */ + { 0x00,0x00,0x09,0x09,0x09,0x0E,0x08,0x07 }, /* y */ + { 0x00,0x00,0x0F,0x08,0x04,0x02,0x0F,0x00 }, /* z */ + { 0x04,0x02,0x02,0x01,0x02,0x02,0x04,0x00 }, /* { */ + { 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01 }, /* | */ + { 0x01,0x02,0x02,0x04,0x02,0x02,0x01,0x00 }, /* } */ + { 0x00,0x00,0x26,0x19,0x00,0x00,0x00,0x00 }, /* ~ */ +}; + +static int Fallback_CellWidth(const cc_uint8* rows) { + int y, width, widest = 0; + + for (y = 0; y < CELL_SIZE; y++) + { + widest = max(widest, rows[y]); + } + width = Math_ilog2(widest) + 1; + + return width + 1; /* add 1 for padding */ +} + +int FallbackFont_TextWidth(const struct DrawTextArgs* args) { + cc_string left = args->text, part; + int size = args->font->size; + int scale = FallbackFont_GetScale(size); + char colorCode = 'f'; + int i, width = 0; + + while (Drawer2D_UNSAFE_NextPart(&left, &part, &colorCode)) + { + for (i = 0; i < part.length; i++) + { + cc_uint8 c = part.buffer[i]; + if (c == ' ') { + width += SPACE_WIDTH * scale; + } else { + width += Fallback_CellWidth(FallbackFont_GetRows(c)) * scale; + } + } + } + + width = max(1, width); + if (args->useShadow) width += 1 * scale; + return width; +} + +static void Fallback_DrawCell(struct Bitmap* bmp, int x, int y, + int scale, const cc_uint8* rows, BitmapCol color) { + int dst_width = CELL_SIZE * scale; + int dst_height = CELL_SIZE * scale; + int xx, srcX, dstX; + int yy, srcY, dstY; + BitmapCol* dst_row; + cc_uint8 src_row; + + for (yy = 0; yy < dst_height; yy++) + { + srcY = yy / scale; + dstY = y + yy; + if (dstY < 0 || dstY >= bmp->height) continue; + + dst_row = Bitmap_GetRow(bmp, dstY); + src_row = rows[srcY]; + + for (xx = 0; xx < dst_width; xx++) + { + srcX = xx / scale; + dstX = x + xx; + if (dstX < 0 || dstX >= bmp->width) continue; + + if (src_row & (1 << srcX)) { + dst_row[dstX] = color; + } + } + } +} + +void FallbackFont_DrawText(struct DrawTextArgs* args, struct Bitmap* bmp, int x, int y, cc_bool shadow) { + cc_string left = args->text, part; + int size = args->font->size; + int scale = FallbackFont_GetScale(size); + char colorCode = 'f'; + const cc_uint8* rows; + BitmapCol color; + int i; + + if (shadow) { + x += 1 * scale; + y += 1 * scale; + } + + /* adjust coords to make drawn text match GDI fonts */ + y += (args->font->height - size) / 2; + + while (Drawer2D_UNSAFE_NextPart(&left, &part, &colorCode)) + { + color = Drawer2D_GetColor(colorCode); + if (shadow) color = GetShadowColor(color); + + for (i = 0; i < part.length; i++) + { + cc_uint8 c = part.buffer[i]; + if (c == ' ') { x += SPACE_WIDTH * scale; continue; } + + rows = FallbackFont_GetRows(c); + + Fallback_DrawCell(bmp, x, y, scale, rows, color); + x += Fallback_CellWidth(rows) * scale; + } + } +} + + +/*########################################################################################################################* +*--------------------------------------------------------Freetype---------------------------------------------------------* +*#########################################################################################################################*/ +#if defined CC_BUILD_FREETYPE +#include "freetype/ft2build.h" +#include "freetype/freetype.h" +#include "freetype/ftmodapi.h" +#include "freetype/ftglyph.h" + +int cc_strncmp(const char* strA, const char* strB, size_t maxCount) { + const unsigned char* a = (const unsigned char*)strA; + const unsigned char* b = (const unsigned char*)strB; + int i; + + for (i = 0; a[i] && i < maxCount; i++) + { + if (a[i] != b[i]) return a[i] - b[i]; + } + return 0; +} + +int cc_strcmp(const char* strA, const char* strB) { + const unsigned char* a = (const unsigned char*)strA; + const unsigned char* b = (const unsigned char*)strB; + int i; + + for (i = 0; a[i]; i++) + { + if (a[i] != b[i]) return a[i] - b[i]; + } + return 0; +} + +size_t cc_strlen(const char* a) { + int i = 0; + while (*a++) i++; + return i; +} + +char* cc_strstr(const char* str, const char* substr) { + if (!substr[0]) return (char*)str; + + for (; *str; str++) + { + /* Definitely not a match */ + if (*str != substr[0]) continue; + + /* It's a possible match */ + if (cc_strcmp(str, substr) == 0) return (char*)str; + } + return NULL; +} + +int cc_memcmp(const void* ptrA, const void* ptrB, size_t num) { + const unsigned char* a = (const unsigned char*)ptrA; + const unsigned char* b = (const unsigned char*)ptrB; + + for (; num > 0; num--, a++, b++) + { + if (*a != *b) return *a - *b; + } + return 0; +} + +void* cc_memchr(const void* ptr, int ch, size_t num) { + const char* a = (const char*)ptr; + + for (; num > 0; num--, a++) + { + if (*a == ch) return (void*)a; + } + return NULL; +} + +static void swap(void* v1, void* v2, int size) { + char buffer[1024]; + + Mem_Copy(buffer, v1, size); + Mem_Copy(v1, v2, size); + Mem_Copy(v2, buffer, size); +} + +// https://www.geeksforgeeks.org/generic-implementation-of-quicksort-algorithm-in-c/ +static void _qsort(void* v, int size, int left, int right, + int (*comp)(const void*, const void*)) { + void* ptrL; + void* ptrM; + void* ptrI; + void* ptrE; + int i, last, mid; + + if (left >= right) return; + mid = (left + right) / 2; + + ptrL = (char*)v + (left * size); + ptrM = (char*)v + (mid * size); + swap(ptrL, ptrM, size); + last = left; + + for (i = left + 1; i <= right; i++) + { + ptrI = (char*)v + (i * size); + if ((*comp)(ptrL, ptrI) > 0) { + last++; + ptrE = (char*)v + (last * size); + swap(ptrI, ptrE, size); + } + } + + ptrE = (char*)v + (last * size); + swap(ptrL, ptrE, size); + _qsort(v, size, left, last - 1, comp); + _qsort(v, size, last + 1, right, comp); +} + +void cc_qsort(void* v, size_t count, size_t size, + int (*comp)(const void*, const void*)) { + if (!count) return; + _qsort(v, 0, count - 1, size, comp); +} + + + +static FT_Library ft_lib; +static struct FT_MemoryRec_ ft_mem; +static struct StringsBuffer font_list; +static cc_bool fonts_changed; +/* Finds the path and face number of the given system font, with closest matching style */ +static cc_string Font_Lookup(const cc_string* fontName, int flags); + +struct SysFont { + FT_Face face; + struct Stream src, file; + FT_StreamRec stream; + cc_uint8 buffer[8192]; /* small buffer to minimise disk I/O */ + cc_uint16 widths[256]; /* cached width of each character glyph */ + FT_BitmapGlyph glyphs[256]; /* cached glyphs */ + FT_BitmapGlyph shadow_glyphs[256]; /* cached glyphs (for back layer shadow) */ +#ifdef CC_BUILD_DARWIN + char filename[FILENAME_SIZE + 1]; +#endif +}; + +static unsigned long SysFont_Read(FT_Stream s, unsigned long offset, unsigned char* buffer, unsigned long count) { + struct SysFont* font; + cc_result res; + if (!count && offset > s->size) return 1; + + font = (struct SysFont*)s->descriptor.pointer; + if (s->pos != offset) font->src.Seek(&font->src, offset); + + res = Stream_Read(&font->src, buffer, count); + return res ? 0 : count; +} + +static void SysFont_Done(struct SysFont* font) { + int i; + + /* Close the actual underlying file */ + struct Stream* source = &font->file; + if (!source->meta.file) return; + source->Close(source); + + for (i = 0; i < 256; i++) + { + if (!font->glyphs[i]) continue; + FT_Done_Glyph((FT_Glyph)font->glyphs[i]); + } + for (i = 0; i < 256; i++) + { + if (!font->shadow_glyphs[i]) continue; + FT_Done_Glyph((FT_Glyph)font->shadow_glyphs[i]); + } +} + +static void SysFont_Close(FT_Stream stream) { + struct SysFont* font = (struct SysFont*)stream->descriptor.pointer; + SysFont_Done(font); +} + +static cc_result SysFont_Init(const cc_string* path, struct SysFont* font, FT_Open_Args* args) { + cc_file file; + cc_uint32 size; + cc_result res; +#ifdef CC_BUILD_DARWIN + cc_string filename; +#endif + + if ((res = File_Open(&file, path))) return res; + if ((res = File_Length(file, &size))) { File_Close(file); return res; } + + font->stream.base = NULL; + font->stream.size = size; + font->stream.pos = 0; + + font->stream.descriptor.pointer = font; + font->stream.read = SysFont_Read; + font->stream.close = SysFont_Close; + + font->stream.memory = &ft_mem; + font->stream.cursor = NULL; + font->stream.limit = NULL; + + args->flags = FT_OPEN_STREAM; + args->pathname = NULL; + args->stream = &font->stream; + + Stream_FromFile(&font->file, file); + Stream_ReadonlyBuffered(&font->src, &font->file, font->buffer, sizeof(font->buffer)); + + /* For OSX font suitcase files */ +#ifdef CC_BUILD_DARWIN + String_InitArray_NT(filename, font->filename); + String_Copy(&filename, path); + font->filename[filename.length] = '\0'; + args->pathname = font->filename; +#endif + Mem_Set(font->widths, 0xFF, sizeof(font->widths)); + Mem_Set(font->glyphs, 0x00, sizeof(font->glyphs)); + Mem_Set(font->shadow_glyphs, 0x00, sizeof(font->shadow_glyphs)); + return 0; +} + +static void* FT_AllocWrapper(FT_Memory memory, long size) { return Mem_TryAlloc(size, 1); } +static void FT_FreeWrapper(FT_Memory memory, void* block) { Mem_Free(block); } +static void* FT_ReallocWrapper(FT_Memory memory, long cur_size, long new_size, void* block) { + return Mem_TryRealloc(block, new_size, 1); +} + + +#define FONT_CACHE_FILE "fontscache.txt" +static cc_string font_candidates[] = { + String_FromConst(""), /* replaced with font_default */ + String_FromConst("Arial"), /* preferred font on all platforms */ + String_FromConst("Liberation Sans"), /* Nice looking fallbacks for linux */ + String_FromConst("Nimbus Sans"), + String_FromConst("Bitstream Charter"), + String_FromConst("Cantarell"), + String_FromConst("DejaVu Sans Book"), + String_FromConst("Century Schoolbook L Roman"), /* commonly available on linux */ + String_FromConst("Liberation Serif"), /* for SerenityOS */ + String_FromConst("Slate For OnePlus"), /* Android 10, some devices */ + String_FromConst("Roboto"), /* Android (broken on some Android 10 devices) */ + String_FromConst("Geneva"), /* for ancient macOS versions */ + String_FromConst("Droid Sans"), /* for old Android versions */ + String_FromConst("Google Sans") /* Droid Sans is now known as Google Sans on some Android devices (e.g. a Pixel 6) */ +}; + +static void InitFreeTypeLibrary(void) { + FT_Error err; + if (ft_lib) return; + + ft_mem.alloc = FT_AllocWrapper; + ft_mem.free = FT_FreeWrapper; + ft_mem.realloc = FT_ReallocWrapper; + + err = FT_New_Library(&ft_mem, &ft_lib); + if (err) Logger_Abort2(err, "Failed to init freetype"); + FT_Add_Default_Modules(ft_lib); +} + +static cc_bool loadedPlatformFonts; +/* Updates fonts list cache with platform's list of fonts */ +/* This should be avoided due to overhead potential */ +static void SysFonts_LoadPlatform(void) { + if (loadedPlatformFonts) return; + loadedPlatformFonts = true; + + /* TODO this basically gets called all the time on non-Window platforms */ + /* Maybe we can cache the default system font to avoid this extra work? */ + if (font_list.count == 0) + Window_ShowDialog("One time load", "Initialising font cache, this can take several seconds."); + + InitFreeTypeLibrary(); + Platform_LoadSysFonts(); + + if (fonts_changed) SysFonts_SaveCache(); +} + +static cc_bool loadedCachedFonts; +static void SysFonts_LoadCached(void) { + if (loadedCachedFonts) return; + loadedCachedFonts = true; + + EntryList_UNSAFE_Load(&font_list, FONT_CACHE_FILE); +} + +void SysFonts_SaveCache(void) { + EntryList_Save(&font_list, FONT_CACHE_FILE); +} + + +/* Some language-specific fonts don't support English letters */ +/* and show entirely as '[]' - better off ignoring such fonts */ +static cc_bool SysFonts_SkipFont(FT_Face face) { + if (!face->charmap) return false; + + return FT_Get_Char_Index(face, 'a') == 0 && FT_Get_Char_Index(face, 'z') == 0 + && FT_Get_Char_Index(face, 'A') == 0 && FT_Get_Char_Index(face, 'Z') == 0; +} + +static void SysFonts_Add(const cc_string* path, FT_Face face, int index, char type, const char* defStyle) { + cc_string key; char keyBuffer[STRING_SIZE]; + cc_string value; char valueBuffer[FILENAME_SIZE]; + cc_string style = String_Empty; + + if (!face->family_name || !(face->face_flags & FT_FACE_FLAG_SCALABLE)) return; + /* don't want 'Arial Regular' or 'Arial Bold' */ + if (face->style_name) { + style = String_FromReadonly(face->style_name); + if (String_CaselessEqualsConst(&style, defStyle)) style.length = 0; + } + if (SysFonts_SkipFont(face)) type = 'X'; + + String_InitArray(key, keyBuffer); + if (style.length) { + String_Format3(&key, "%c %c %r", face->family_name, face->style_name, &type); + } else { + String_Format2(&key, "%c %r", face->family_name, &type); + } + + String_InitArray(value, valueBuffer); + String_Format2(&value, "%s,%i", path, &index); + + Platform_Log2("Face: %s = %s", &key, &value); + EntryList_Set(&font_list, &key, &value, '='); + fonts_changed = true; +} + +static int SysFonts_DoRegister(const cc_string* path, int faceIndex, SysFont_RegisterCallback callback) { + struct SysFont font; + FT_Open_Args args; + FT_Error err; + int flags, count; + + if (SysFont_Init(path, &font, &args)) return 0; + err = FT_New_Face(ft_lib, &args, faceIndex, &font.face); + if (err) { SysFont_Done(&font); return 0; } + + flags = font.face->style_flags; + count = font.face->num_faces; + + if (flags == (FT_STYLE_FLAG_BOLD | FT_STYLE_FLAG_ITALIC)) { + SysFonts_Add(path, font.face, faceIndex, 'Z', "Bold Italic"); + } else if (flags == FT_STYLE_FLAG_BOLD) { + SysFonts_Add(path, font.face, faceIndex, 'B', "Bold"); + } else if (flags == FT_STYLE_FLAG_ITALIC) { + SysFonts_Add(path, font.face, faceIndex, 'I', "Italic"); + } else if (flags == 0) { + SysFonts_Add(path, font.face, faceIndex, 'R', "Regular"); + } + + if (callback) callback(path); + FT_Done_Face(font.face); + return count; +} + +cc_result SysFonts_Register(const cc_string* path, SysFont_RegisterCallback callback) { + cc_string entry, name, value; + cc_string fontPath, index; + int i, count; + + /* if font is already known, skip it */ + for (i = 0; i < font_list.count; i++) { + StringsBuffer_UNSAFE_GetRaw(&font_list, i, &entry); + String_UNSAFE_Separate(&entry, '=', &name, &value); + + String_UNSAFE_Separate(&value, ',', &fontPath, &index); + if (String_CaselessEquals(path, &fontPath)) return 0; + } + + count = SysFonts_DoRegister(path, 0, callback); + /* there may be more than one font in a font file */ + for (i = 1; i < count; i++) { + SysFonts_DoRegister(path, i, callback); + } + return 0; +} + + +static cc_string Font_LookupOf(const cc_string* fontName, const char type) { + cc_string name; char nameBuffer[STRING_SIZE + 2]; + String_InitArray(name, nameBuffer); + + String_Format2(&name, "%s %r", fontName, &type); + return EntryList_UNSAFE_Get(&font_list, &name, '='); +} + +static cc_string Font_DoLookup(const cc_string* fontName, int flags) { + cc_string path = String_Empty; + + if (flags & FONT_FLAGS_BOLD) path = Font_LookupOf(fontName, 'B'); + return path.length ? path : Font_LookupOf(fontName, 'R'); +} + +static cc_string Font_Lookup(const cc_string* fontName, int flags) { + cc_string path; + + SysFonts_LoadCached(); + path = Font_DoLookup(fontName, flags); + if (path.length) return path; + + SysFonts_LoadPlatform(); + return Font_DoLookup(fontName, flags); +} + + +const cc_string* SysFonts_UNSAFE_GetDefault(void) { + cc_string* font, path; + int i; + font_candidates[0] = font_default; + + for (i = 0; i < Array_Elems(font_candidates); i++) { + font = &font_candidates[i]; + if (!font->length) continue; + + path = Font_Lookup(font, FONT_FLAGS_NONE); + if (path.length) return font; + } + return &String_Empty; +} + +void SysFonts_GetNames(struct StringsBuffer* buffer) { + cc_string entry, name, path; + int i; + SysFonts_LoadCached(); + SysFonts_LoadPlatform(); + + for (i = 0; i < font_list.count; i++) { + StringsBuffer_UNSAFE_GetRaw(&font_list, i, &entry); + String_UNSAFE_Separate(&entry, '=', &name, &path); + + /* only want Regular fonts here */ + if (name.length < 2 || name.buffer[name.length - 1] != 'R') continue; + name.length -= 2; + StringsBuffer_Add(buffer, &name); + } + StringsBuffer_Sort(buffer); +} + +#define TEXT_CEIL(x) (((x) + 63) >> 6) +cc_result SysFont_Make(struct FontDesc* desc, const cc_string* fontName, int size, int flags) { + struct SysFont* font; + cc_string value, path, index; + int faceIndex, dpiX, dpiY; + FT_Open_Args args; + FT_Error err; + + desc->size = size; + desc->flags = flags; + desc->handle = NULL; + + value = Font_Lookup(fontName, flags); + if (!value.length) return ERR_INVALID_ARGUMENT; + String_UNSAFE_Separate(&value, ',', &path, &index); + Convert_ParseInt(&index, &faceIndex); + + font = (struct SysFont*)Mem_TryAlloc(1, sizeof(struct SysFont)); + if (!font) return ERR_OUT_OF_MEMORY; + + InitFreeTypeLibrary(); + if ((err = SysFont_Init(&path, font, &args))) { Mem_Free(font); return err; } + desc->handle = font; + + /* TODO: Use 72 instead of 96 dpi for mobile devices */ + dpiX = (int)(DisplayInfo.ScaleX * 96); + dpiY = (int)(DisplayInfo.ScaleY * 96); + + if ((err = FT_New_Face(ft_lib, &args, faceIndex, &font->face))) return err; + if ((err = FT_Set_Char_Size(font->face, size * 64, 0, dpiX, dpiY))) return err; + + /* height of any text when drawn with the given system font */ + desc->height = TEXT_CEIL(font->face->size->metrics.height); + return 0; +} + +void SysFont_MakeDefault(struct FontDesc* desc, int size, int flags) { + cc_string* font; + cc_result res; + int i; + font_candidates[0] = font_default; + + for (i = 0; i < Array_Elems(font_candidates); i++) { + font = &font_candidates[i]; + if (!font->length) continue; + res = SysFont_Make(desc, &font_candidates[i], size, flags); + + /* Cached system fonts list may be outdated - force update it */ + if (res == ReturnCode_FileNotFound && !loadedPlatformFonts) { + StringsBuffer_Clear(&font_list); + SysFonts_LoadPlatform(); + res = SysFont_Make(desc, &font_candidates[i], size, flags); + } + + if (res == ERR_INVALID_ARGUMENT) { + /* Fon't doesn't exist in list, skip over it */ + } else if (res) { + Font_Free(desc); + Logger_SysWarn2(res, "creating font", font); + } else { + if (i) String_Copy(&font_candidates[0], font); + return; + } + } + + Window_ShowDialog("Failed to init default font", "Falling back to built-in font"); + Font_MakeBitmapped(desc, size, flags); +} + +void SysFont_Free(struct FontDesc* desc) { + struct SysFont* font = (struct SysFont*)desc->handle; + FT_Done_Face(font->face); + Mem_Free(font); +} + +int SysFont_TextWidth(struct DrawTextArgs* args) { + struct SysFont* font = (struct SysFont*)args->font->handle; + FT_Face face = font->face; + cc_string text = args->text; + int i, width = 0, charWidth; + FT_Error res; + cc_unichar uc; + + for (i = 0; i < text.length; i++) { + char c = text.buffer[i]; + if (c == '&' && Drawer2D_ValidColorCodeAt(&text, i + 1)) { + i++; continue; /* skip over the color code */ + } + + charWidth = font->widths[(cc_uint8)c]; + /* need to calculate glyph width */ + if (charWidth == UInt16_MaxValue) { + uc = Convert_CP437ToUnicode(c); + res = FT_Load_Char(face, uc, 0); + + if (res) { + Platform_Log2("Error %e measuring width of %r", &res, &c); + charWidth = 0; + } else { + charWidth = face->glyph->advance.x; + } + + font->widths[(cc_uint8)c] = charWidth; + } + width += charWidth; + } + if (!width) return 0; + + width = TEXT_CEIL(width); + if (args->useShadow) width += 2; + return width; +} + +static void DrawGrayscaleGlyph(FT_Bitmap* img, struct Bitmap* bmp, int x, int y, BitmapCol col) { + cc_uint8* src; + BitmapCol* dst; + cc_uint8 I, invI; /* intensity */ + int xx, yy; + + for (yy = 0; yy < img->rows; yy++) { + if ((unsigned)(y + yy) >= (unsigned)bmp->height) continue; + src = img->buffer + (yy * img->pitch); + dst = Bitmap_GetRow(bmp, y + yy) + x; + + for (xx = 0; xx < img->width; xx++, src++, dst++) { + if ((unsigned)(x + xx) >= (unsigned)bmp->width) continue; + I = *src; invI = UInt8_MaxValue - I; + + /* TODO: Support transparent text */ + /* dst->A = ((col.A * intensity) >> 8) + ((dst->A * invIntensity) >> 8);*/ + /* TODO: Not shift when multiplying */ + *dst = BitmapCol_Make( + ((BitmapCol_R(col) * I) >> 8) + ((BitmapCol_R(*dst) * invI) >> 8), + ((BitmapCol_G(col) * I) >> 8) + ((BitmapCol_G(*dst) * invI) >> 8), + ((BitmapCol_B(col) * I) >> 8) + ((BitmapCol_B(*dst) * invI) >> 8), + I + ((BitmapCol_A(*dst) * invI) >> 8) + ); + } + } +} + +static void DrawBlackWhiteGlyph(FT_Bitmap* img, struct Bitmap* bmp, int x, int y, BitmapCol col) { + cc_uint8* src; + BitmapCol* dst; + cc_uint8 intensity; + int xx, yy; + + for (yy = 0; yy < img->rows; yy++) { + if ((unsigned)(y + yy) >= (unsigned)bmp->height) continue; + src = img->buffer + (yy * img->pitch); + dst = Bitmap_GetRow(bmp, y + yy) + x; + + for (xx = 0; xx < img->width; xx++, dst++) { + if ((unsigned)(x + xx) >= (unsigned)bmp->width) continue; + intensity = src[xx >> 3]; + + /* TODO: transparent text (don't set A to 255) */ + if (intensity & (1 << (7 - (xx & 7)))) { + *dst = col | BitmapColor_A_Bits(255); + } + } + } +} + +static FT_Vector shadow_delta = { 83, -83 }; +void SysFont_DrawText(struct DrawTextArgs* args, struct Bitmap* bmp, int x, int y, cc_bool shadow) { + struct SysFont* font = (struct SysFont*)args->font->handle; + FT_BitmapGlyph* glyphs = font->glyphs; + + FT_Face face = font->face; + cc_string text = args->text; + int descender, height, begX = x; + BitmapCol color; + + /* glyph state */ + FT_BitmapGlyph glyph; + FT_Bitmap* img; + int i, offset; + FT_Error res; + cc_unichar uc; + + if (shadow) { + glyphs = font->shadow_glyphs; + FT_Set_Transform(face, NULL, &shadow_delta); + } + + height = args->font->height; + descender = TEXT_CEIL(face->size->metrics.descender); + + color = Drawer2D.Colors['f']; + if (shadow) color = GetShadowColor(color); + + for (i = 0; i < text.length; i++) { + char c = text.buffer[i]; + if (c == '&' && Drawer2D_ValidColorCodeAt(&text, i + 1)) { + color = Drawer2D_GetColor(text.buffer[i + 1]); + + if (shadow) color = GetShadowColor(color); + i++; continue; /* skip over the color code */ + } + + glyph = glyphs[(cc_uint8)c]; + if (!glyph) { + uc = Convert_CP437ToUnicode(c); + res = FT_Load_Char(face, uc, FT_LOAD_RENDER); + + if (res) { + Platform_Log2("Error %e drawing %r", &res, &text.buffer[i]); + continue; + } + + /* due to FT_LOAD_RENDER, glyph is always a bitmap one */ + FT_Get_Glyph(face->glyph, (FT_Glyph*)&glyph); /* TODO: Check error */ + glyphs[(cc_uint8)c] = glyph; + } + + offset = (height + descender) - glyph->top; + x += glyph->left; y += offset; + + img = &glyph->bitmap; + if (img->num_grays == 2) { + DrawBlackWhiteGlyph(img, bmp, x, y, color); + } else { + DrawGrayscaleGlyph(img, bmp, x, y, color); + } + + x += TEXT_CEIL(glyph->root.advance.x >> 10); + x -= glyph->left; y -= offset; + } + + if (args->font->flags & FONT_FLAGS_UNDERLINE) { + int ul_pos = FT_MulFix(face->underline_position, face->size->metrics.y_scale); + int ul_thick = FT_MulFix(face->underline_thickness, face->size->metrics.y_scale); + + int ulHeight = TEXT_CEIL(ul_thick); + int ulY = height + TEXT_CEIL(ul_pos); + Drawer2D_Fill(bmp, begX, ulY + y, x - begX, ulHeight, color); + } + + if (shadow) FT_Set_Transform(face, NULL, NULL); +} +#elif defined CC_BUILD_WEB +static cc_string font_arial = String_FromConst("Arial"); + +const cc_string* SysFonts_UNSAFE_GetDefault(void) { + /* Fallback to Arial as default font */ + /* TODO use serif instead?? */ + return font_default.length ? &font_default : &font_arial; +} + +void SysFonts_GetNames(struct StringsBuffer* buffer) { + static const char* font_names[] = { + "Arial", "Arial Black", "Courier New", "Comic Sans MS", "Georgia", "Garamond", + "Helvetica", "Impact", "Tahoma", "Times New Roman", "Trebuchet MS", "Verdana", + "cursive", "fantasy", "monospace", "sans-serif", "serif", "system-ui" + }; + int i; + + for (i = 0; i < Array_Elems(font_names); i++) { + cc_string str = String_FromReadonly(font_names[i]); + StringsBuffer_Add(buffer, &str); + } +} + +cc_result SysFont_Make(struct FontDesc* desc, const cc_string* fontName, int size, int flags) { + desc->size = size; + desc->flags = flags; + desc->height = Drawer2D_AdjHeight(size); + + desc->handle = Mem_TryAlloc(fontName->length + 1, 1); + if (!desc->handle) return ERR_OUT_OF_MEMORY; + + String_CopyToRaw(desc->handle, fontName->length + 1, fontName); + return 0; +} + +void SysFont_MakeDefault(struct FontDesc* desc, int size, int flags) { + SysFont_Make(desc, SysFonts_UNSAFE_GetDefault(), size, flags); +} + +void SysFont_Free(struct FontDesc* desc) { + Mem_Free(desc->handle); +} + +void SysFonts_SaveCache(void) { } +cc_result SysFonts_Register(const cc_string* path, SysFont_RegisterCallback callback) { + return ERR_NOT_SUPPORTED; +} + +extern void interop_SetFont(const char* font, int size, int flags); +extern double interop_TextWidth(const char* text, const int len); +extern double interop_TextDraw(const char* text, const int len, struct Bitmap* bmp, int x, int y, cc_bool shadow, const char* hex); + +int SysFont_TextWidth(struct DrawTextArgs* args) { + struct FontDesc* font = args->font; + cc_string left = args->text, part; + double width = 0; + char colorCode; + + interop_SetFont(font->handle, font->size, font->flags); + while (Drawer2D_UNSAFE_NextPart(&left, &part, &colorCode)) + { + char buffer[NATIVE_STR_LEN]; + int len = String_EncodeUtf8(buffer, &part); + width += interop_TextWidth(buffer, len); + } + return Math_Ceil(width); +} + +void SysFont_DrawText(struct DrawTextArgs* args, struct Bitmap* bmp, int x, int y, cc_bool shadow) { + struct FontDesc* font = args->font; + cc_string left = args->text, part; + BitmapCol color; + char colorCode = 'f'; + double xOffset = 0; + char hexBuffer[7]; + cc_string hex; + + /* adjust y position to more closely match FreeType drawn text */ + y += (args->font->height - args->font->size) / 2; + interop_SetFont(font->handle, font->size, font->flags); + + while (Drawer2D_UNSAFE_NextPart(&left, &part, &colorCode)) + { + char buffer[NATIVE_STR_LEN]; + int len = String_EncodeUtf8(buffer, &part); + + color = Drawer2D_GetColor(colorCode); + if (shadow) color = GetShadowColor(color); + + String_InitArray(hex, hexBuffer); + String_Append(&hex, '#'); + String_AppendHex(&hex, BitmapCol_R(color)); + String_AppendHex(&hex, BitmapCol_G(color)); + String_AppendHex(&hex, BitmapCol_B(color)); + + /* TODO pass as double directly instead of (int) ?*/ + xOffset += interop_TextDraw(buffer, len, bmp, x + (int)xOffset, y, shadow, hexBuffer); + } +} +#elif defined CC_BUILD_IOS +/* implemented in interop_ios.m */ +extern void interop_GetFontNames(struct StringsBuffer* buffer); +extern cc_result interop_SysFontMake(struct FontDesc* desc, const cc_string* fontName, int size, int flags); +extern void interop_SysMakeDefault(struct FontDesc* desc, int size, int flags); +extern void interop_SysFontFree(void* handle); +extern int interop_SysTextWidth(struct DrawTextArgs* args); +extern void interop_SysTextDraw(struct DrawTextArgs* args, struct Bitmap* bmp, int x, int y, cc_bool shadow); + +void SysFonts_SaveCache(void) { } +cc_result SysFonts_Register(const cc_string* path, SysFont_RegisterCallback callback) { + return ERR_NOT_SUPPORTED; +} + +const cc_string* SysFonts_UNSAFE_GetDefault(void) { + return &String_Empty; +} + +void SysFonts_GetNames(struct StringsBuffer* buffer) { + interop_GetFontNames(buffer); +} + +cc_result SysFont_Make(struct FontDesc* desc, const cc_string* fontName, int size, int flags) { + return interop_SysFontMake(desc, fontName, size, flags); +} + +void SysFont_MakeDefault(struct FontDesc* desc, int size, int flags) { + interop_SysMakeDefault(desc, size, flags); +} + +void SysFont_Free(struct FontDesc* desc) { + interop_SysFontFree(desc->handle); +} + +int SysFont_TextWidth(struct DrawTextArgs* args) { + return interop_SysTextWidth(args); +} + +void SysFont_DrawText(struct DrawTextArgs* args, struct Bitmap* bmp, int x, int y, cc_bool shadow) { + interop_SysTextDraw(args, bmp, x, y, shadow); +} +#else +void SysFonts_SaveCache(void) { } +cc_result SysFonts_Register(const cc_string* path, SysFont_RegisterCallback callback) { + return ERR_NOT_SUPPORTED; +} + +const cc_string* SysFonts_UNSAFE_GetDefault(void) { return &String_Empty; } + +void SysFonts_GetNames(struct StringsBuffer* buffer) { } + +cc_result SysFont_Make(struct FontDesc* desc, const cc_string* fontName, int size, int flags) { + size *= DisplayInfo.ScaleY; + /* Round upwards to nearest 8 */ + size = (size + 7) & ~0x07; + + desc->size = size; + desc->flags = flags; + desc->height = Drawer2D_AdjHeight(size); + + desc->handle = (void*)1; + return 0; +} + +void SysFont_MakeDefault(struct FontDesc* desc, int size, int flags) { + SysFont_Make(desc, NULL, size, flags); +} + +void SysFont_Free(struct FontDesc* desc) { + desc->handle = NULL; +} + +int SysFont_TextWidth(struct DrawTextArgs* args) { + return FallbackFont_TextWidth(args); +} + +void SysFont_DrawText(struct DrawTextArgs* args, struct Bitmap* bmp, int x, int y, cc_bool shadow) { + FallbackFont_DrawText(args, bmp, x, y, shadow); +} +#endif |