From ac7602386ea69f4cb35cd07e2478e96efdca1dc3 Mon Sep 17 00:00:00 2001 From: UnknownShadow200 Date: Tue, 25 Apr 2023 16:19:47 +1000 Subject: [PATCH] Split up Drawer2D module into Drawer2D and SystemFonts modules --- android/app/CMakeLists.txt | 1 + src/ClassiCube.vcxproj | 2 + src/ClassiCube.vcxproj.filters | 6 + src/Drawer2D.c | 894 +-------------------------------- src/Drawer2D.h | 34 +- src/Game.c | 2 + src/Launcher.c | 2 + src/Menus.c | 3 +- src/Platform_3DS.c | 8 - src/Platform_PSP.c | 6 - src/Platform_Posix.c | 2 +- src/Platform_Web.c | 2 +- src/Platform_WinApi.c | 2 +- src/SystemFonts.c | 854 +++++++++++++++++++++++++++++++ src/SystemFonts.h | 36 ++ src/interop_ios.m | 23 - 16 files changed, 944 insertions(+), 933 deletions(-) create mode 100644 src/SystemFonts.c create mode 100644 src/SystemFonts.h diff --git a/android/app/CMakeLists.txt b/android/app/CMakeLists.txt index 6f8c4ac15..9392363e3 100644 --- a/android/app/CMakeLists.txt +++ b/android/app/CMakeLists.txt @@ -97,6 +97,7 @@ add_library(classicube SHARED ../../src/EnvRenderer.c ../../src/Animations.c ../../src/LBackend.c + ../../src/SystemFonts.c ) # add lib dependencies diff --git a/src/ClassiCube.vcxproj b/src/ClassiCube.vcxproj index 83f42cab2..e79663e13 100644 --- a/src/ClassiCube.vcxproj +++ b/src/ClassiCube.vcxproj @@ -420,6 +420,7 @@ + @@ -501,6 +502,7 @@ + diff --git a/src/ClassiCube.vcxproj.filters b/src/ClassiCube.vcxproj.filters index fddbe0628..ecf640e20 100644 --- a/src/ClassiCube.vcxproj.filters +++ b/src/ClassiCube.vcxproj.filters @@ -330,6 +330,9 @@ Header Files\Graphics + + Header Files\2D + @@ -590,6 +593,9 @@ Source Files\Game + + Source Files\2D + diff --git a/src/Drawer2D.c b/src/Drawer2D.c index b970319a3..726e3ef2f 100644 --- a/src/Drawer2D.c +++ b/src/Drawer2D.c @@ -14,6 +14,7 @@ #include "Window.h" #include "Options.h" #include "TexturePack.h" +#include "SystemFonts.h" struct _Drawer2DData Drawer2D; #define Font_IsBitmap(font) (!(font)->handle) @@ -34,16 +35,8 @@ void DrawTextArgs_MakeEmpty(struct DrawTextArgs* args, struct FontDesc* font, cc /*########################################################################################################################* *-----------------------------------------------------Font functions------------------------------------------------------* *#########################################################################################################################*/ -static char defaultBuffer[STRING_SIZE]; -static cc_string font_default = String_FromArray(defaultBuffer); +int Drawer2D_AdjHeight(int point) { return Math_CeilDiv(point * 3, 2); } -void Font_SetDefault(const cc_string* fontName) { - String_Copy(&font_default, fontName); - Event_RaiseVoid(&ChatEvents.FontChanged); -} - -/* adjusts height to be closer to system fonts */ -static int Drawer2D_AdjHeight(int point) { return Math_CeilDiv(point * 3, 2); } void Font_MakeBitmapped(struct FontDesc* desc, int size, int flags) { /* TODO: Scale X and Y independently */ size = Display_ScaleY(size); @@ -61,6 +54,15 @@ void Font_Make(struct FontDesc* desc, int size, int flags) { } } +void Font_Free(struct FontDesc* desc) { + struct SysFont* font; + desc->size = 0; + if (Font_IsBitmap(desc)) return; + + SysFont_Free(desc); + desc->handle = NULL; +} + static struct Bitmap fontBitmap; static int tileSize = 8; /* avoid divide by 0 if default.png missing */ /* So really 16 characters per row */ @@ -129,11 +131,6 @@ void Font_SetPadding(struct FontDesc* desc, int amount) { desc->height = desc->size + Display_ScaleY(amount) * 2; } -/* Measures width of the given text when drawn with the given system font. */ -static int Font_SysTextWidth(struct DrawTextArgs* args); -/* Draws the given text with the given system font onto the given bitmap. */ -static void Font_SysTextDraw(struct DrawTextArgs* args, struct Bitmap* bmp, int x, int y, cc_bool shadow); - /*########################################################################################################################* *---------------------------------------------------Drawing functions-----------------------------------------------------* @@ -383,18 +380,6 @@ char Drawer2D_LastColor(const cc_string* text, int start) { } cc_bool Drawer2D_IsWhiteColor(char c) { return c == '\0' || c == 'f' || c == 'F'; } -/* Divides R/G/B by 4 */ -#define SHADOW_MASK ((0x3F << BITMAPCOLOR_R_SHIFT) | (0x3F << BITMAPCOLOR_G_SHIFT) | (0x3F << BITMAPCOLOR_B_SHIFT)) -CC_NOINLINE static BitmapCol GetShadowColor(BitmapCol c) { - if (Drawer2D.BlackTextShadows) return BITMAPCOLOR_BLACK; - - /* Initial layout: aaaa_aaaa|rrrr_rrrr|gggg_gggg|bbbb_bbbb */ - /* Shift right 2: 00aa_aaaa|aarr_rrrr|rrgg_gggg|ggbb_bbbb */ - /* And by 3f3f3f: 0000_0000|00rr_rrrr|00gg_gggg|00bb_bbbb */ - /* Or by alpha : aaaa_aaaa|00rr_rrrr|00gg_gggg|00bb_bbbb */ - return (c & BITMAPCOLOR_A_MASK) | ((c >> 2) & SHADOW_MASK); -} - /* TODO: Needs to account for DPI */ #define Drawer2D_ShadowOffset(point) (point / 8) #define Drawer2D_XPadding(point) (Math_CeilDiv(point, 8)) @@ -421,8 +406,7 @@ void Drawer2D_ReducePadding_Height(int* height, int point, int scale) { *height -= padding * 2; } -/* Quickly fills the given box region */ -static void Drawer2D_Underline(struct Bitmap* bmp, int x, int y, int width, int height, BitmapCol color) { +void Drawer2D_Fill(struct Bitmap* bmp, int x, int y, int width, int height, BitmapCol color) { BitmapCol* row; int xx, yy; @@ -530,7 +514,7 @@ static void DrawBitmappedTextCore(struct Bitmap* bmp, struct DrawTextArgs* args, for (; i < count && color == colors[i]; i++) { dstWidth += dstWidths[i] + xPadding; } - Drawer2D_Underline(bmp, x, underlineY, dstWidth, underlineHeight, color); + Drawer2D_Fill(bmp, x, underlineY, dstWidth, underlineHeight, color); x += dstWidth; } } @@ -575,13 +559,13 @@ void Context2D_DrawText(struct Context2D* ctx, struct DrawTextArgs* args, int x, if (Drawer2D_IsEmptyText(&args->text)) return; if (Font_IsBitmap(args->font)) { DrawBitmappedText(bmp, args, x, y); return; } - if (args->useShadow) { Font_SysTextDraw(args, bmp, x, y, true); } - Font_SysTextDraw(args, bmp, x, y, false); + if (args->useShadow) { SysFont_DrawText(args, bmp, x, y, true); } + SysFont_DrawText(args, bmp, x, y, false); } int Drawer2D_TextWidth(struct DrawTextArgs* args) { if (Font_IsBitmap(args->font)) return MeasureBitmappedWidth(args); - return Font_SysTextWidth(args); + return SysFont_TextWidth(args); } int Drawer2D_TextHeight(struct DrawTextArgs* args) { @@ -674,12 +658,10 @@ static void OnReset(void) { static void OnInit(void) { OnReset(); + TextureEntry_Register(&default_entry); + Drawer2D.BitmappedText = Game_ClassicMode || !Options_GetBool(OPT_USE_CHAT_FONT, false); Drawer2D.BlackTextShadows = Options_GetBool(OPT_BLACK_TEXT, false); - - Options_Get(OPT_FONT_NAME, &font_default, ""); - if (Game_ClassicMode) font_default.length = 0; - TextureEntry_Register(&default_entry); } static void OnFree(void) { @@ -691,842 +673,4 @@ struct IGameComponent Drawer2D_Component = { OnInit, /* Init */ OnFree, /* Free */ OnReset, /* Reset */ -}; - - -/*########################################################################################################################* -*------------------------------------------------------System fonts-------------------------------------------------------* -*#########################################################################################################################*/ -#if defined CC_BUILD_FREETYPE -#include "freetype/ft2build.h" -#include "freetype/freetype.h" -#include "freetype/ftmodapi.h" -#include "freetype/ftglyph.h" - -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_Free(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_Free(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"), /* ice 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 */ -}; - -static void SysFonts_InitLibrary(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); -} - -/* Updates fonts list cache with system's list of fonts */ -/* This should be avoided due to overhead potential */ -static void SysFonts_Update(void) { - static cc_bool updatedFonts; - if (updatedFonts) return; - updatedFonts = true; - - SysFonts_InitLibrary(); - Platform_LoadSysFonts(); - if (fonts_changed) EntryList_Save(&font_list, FONT_CACHE_FILE); -} - -static void SysFonts_Load(void) { - /* Need to keep track of whether font cache has been checked at least once */ - /* (Otherwise if unable to find any cached fonts and then unable to load any fonts, - /* font_list.count will always be 0 and the 'Initialising font cache' dialog will - confusingly get shown over and over until all font_candidates entries are checked) */ - static cc_bool checkedCache; - if (checkedCache) return; - checkedCache = true; - - EntryList_UNSAFE_Load(&font_list, FONT_CACHE_FILE); - if (font_list.count) return; - - Window_ShowDialog("One time load", "Initialising font cache, this can take several seconds."); - SysFonts_Update(); -} - - -/* 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) { - 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_Free(&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"); - } - - FT_Done_Face(font.face); - return count; -} - -void SysFonts_Register(const cc_string* path) { - 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; - } - - count = SysFonts_DoRegister(path, 0); - /* there may be more than one font in a font file */ - for (i = 1; i < count; i++) { - SysFonts_DoRegister(path, i); - } -} - - -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; - if (!font_list.count) SysFonts_Load(); - 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 = Font_DoLookup(fontName, flags); - if (path.length) return path; - - SysFonts_Update(); - 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; - if (!font_list.count) SysFonts_Load(); - SysFonts_Update(); - - 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; - - SysFonts_InitLibrary(); - 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); - - 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; - } - } - Logger_Abort2(res, "Failed to init default font"); -} - -void Font_Free(struct FontDesc* desc) { - struct SysFont* font; - desc->size = 0; - if (Font_IsBitmap(desc)) return; - - font = (struct SysFont*)desc->handle; - FT_Done_Face(font->face); - Mem_Free(font); - desc->handle = NULL; -} - -static int Font_SysTextWidth(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 %i 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 }; -static void Font_SysTextDraw(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 %i 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_Underline(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 Font_Free(struct FontDesc* desc) { - Mem_Free(desc->handle); - desc->handle = NULL; - desc->size = 0; -} - -void SysFonts_Register(const cc_string* path) { } -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); - -static int Font_SysTextWidth(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); -} - -static void Font_SysTextDraw(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_Register(const cc_string* path) { } - -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 Font_Free(struct FontDesc* desc) { - desc->size = 0; - if (Font_IsBitmap(desc)) return; - - interop_SysFontFree(desc->handle); - desc->handle = NULL; -} - -static int Font_SysTextWidth(struct DrawTextArgs* args) { - return interop_SysTextWidth(args); -} - -static void Font_SysTextDraw(struct DrawTextArgs* args, struct Bitmap* bmp, int x, int y, cc_bool shadow) { - interop_SysTextDraw(args, bmp, x, y, shadow); -} -#elif defined CC_BUILD_PSP -void SysFonts_Register(const cc_string* path) { } - -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) { - 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 Font_Free(struct FontDesc* desc) { - desc->size = 0; - desc->handle = NULL; -} - -static int Font_SysTextWidth(struct DrawTextArgs* args) { - return 10; -} - -static void Font_SysTextDraw(struct DrawTextArgs* args, struct Bitmap* bmp, int x, int y, cc_bool shadow) { -} -#elif defined CC_BUILD_3DS -#include <3ds.h> - -void SysFonts_Register(const cc_string* path) { } - -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) { - desc->size = size; - desc->flags = flags; - desc->height = Drawer2D_AdjHeight(size); - - desc->handle = fontGetSystemFont(); - return 0; -} - -void SysFont_MakeDefault(struct FontDesc* desc, int size, int flags) { - SysFont_Make(desc, NULL, size, flags); -} - -void Font_Free(struct FontDesc* desc) { - desc->size = 0; - desc->handle = NULL; -} - -static int Font_SysTextWidth(struct DrawTextArgs* args) { - int width = 0; - cc_string left = args->text, part; - char colorCode = 'f'; - CFNT_s* font = (CFNT_s*)args->font->handle; - - while (Drawer2D_UNSAFE_NextPart(&left, &part, &colorCode)) - { - for (int i = 0; i < part.length; i++) - { - cc_unichar cp = Convert_CP437ToUnicode(part.buffer[i]); - int glyphIndex = fontGlyphIndexFromCodePoint(font, cp); - if (glyphIndex < 0) continue; - - charWidthInfo_s* wInfo = fontGetCharWidthInfo(font, glyphIndex); - width += wInfo->charWidth; - } - } - return max(1, width); -} - -static void DrawGlyph(CFNT_s* font, struct Bitmap* bmp, int x, int y, int glyphIndex) { - TGLP_s* tglp = font->finf.tglp; - - //int glyphsPerSheet = tglp->nRows * tglp->nLines; - //int sheetIdx = glyphIndex / glyphsPerSheet; - //int sheetPos = glyphIndex % glyphsPerSheet; - //u8* sheet = tglp->sheetData + (sheetIdx * tglp->sheetSize); - - //int rowY = sheetPos / tglp->nRows; - //int rowX = sheetPos % tglp->nRows; - - //int FMT = tglp->sheetFmt; - //Platform_Log1("FMT: %i", &FMT); - - charWidthInfo_s* wInfo = fontGetCharWidthInfo(font, glyphIndex); - - /*for (int Y = 0; Y < tglp->cellHeight; Y++) - { - for (int X = wInfo->left; X < wInfo->left + wInfo->glyphWidth; X++) - { - int XX = x + X, YY = y + Y; - if (XX < 0 || YY < 0 || XX >= bmp->width || YY >= bmp->height) continue; - - int srcX = X + rowX * tglp->cellWidth; - int srcY = Y + rowY * tglp->cellHeight; // TODO wrong. morton offset too? - - u8 VALUE = ((sheet + srcY * (tglp->sheetWidth >> 1))[srcX >> 1] & 0x0F) << 4; - Bitmap_GetPixel(bmp, XX, YY) = BitmapColor_RGB(VALUE, VALUE, VALUE); - } - }*/ - - for (int Y = 0; Y < tglp->cellHeight; Y++) - { - for (int X = wInfo->left; X < wInfo->left + wInfo->glyphWidth; X++) - { - int XX = x + X, YY = y + Y; - if (XX < 0 || YY < 0 || XX >= bmp->width || YY >= bmp->height) continue; - - Bitmap_GetPixel(bmp, XX, YY) = BITMAPCOLOR_WHITE; - } - } -} - -static void Font_SysTextDraw(struct DrawTextArgs* args, struct Bitmap* bmp, int x, int y, cc_bool shadow) { - cc_string left = args->text, part; - char colorCode = 'f'; - CFNT_s* font = (CFNT_s*)args->font->handle; - - while (Drawer2D_UNSAFE_NextPart(&left, &part, &colorCode)) - { - for (int i = 0; i < part.length; i++) - { - cc_unichar cp = Convert_CP437ToUnicode(part.buffer[i]); - int glyphIndex = fontGlyphIndexFromCodePoint(font, cp); - if (glyphIndex < 0) continue; - - DrawGlyph(font, bmp, x, y, glyphIndex); - charWidthInfo_s* wInfo = fontGetCharWidthInfo(font, glyphIndex); - x += wInfo->charWidth; - } - } -} -#endif \ No newline at end of file +}; \ No newline at end of file diff --git a/src/Drawer2D.h b/src/Drawer2D.h index 6e3171e3a..0595237a7 100644 --- a/src/Drawer2D.h +++ b/src/Drawer2D.h @@ -12,7 +12,6 @@ struct DrawTextArgs { cc_string text; struct FontDesc* font; cc_bool useShadow; struct Context2D { struct Bitmap bmp; int width, height; void* meta; }; struct Texture; struct IGameComponent; -struct StringsBuffer; extern struct IGameComponent Drawer2D_Component; #define DRAWER2D_MAX_TEXT_LENGTH 256 @@ -93,18 +92,33 @@ char Drawer2D_LastColor(const cc_string* text, int start); cc_bool Drawer2D_IsWhiteColor(char c); cc_bool Drawer2D_UNSAFE_NextPart(cc_string* left, cc_string* part, char* colorCode); +/* Divides R/G/B by 4 */ +#define SHADOW_MASK ((0x3F << BITMAPCOLOR_R_SHIFT) | (0x3F << BITMAPCOLOR_G_SHIFT) | (0x3F << BITMAPCOLOR_B_SHIFT)) +static CC_INLINE BitmapCol GetShadowColor(BitmapCol c) { + if (Drawer2D.BlackTextShadows) return BITMAPCOLOR_BLACK; + + /* Initial layout: aaaa_aaaa|rrrr_rrrr|gggg_gggg|bbbb_bbbb */ + /* Shift right 2: 00aa_aaaa|aarr_rrrr|rrgg_gggg|ggbb_bbbb */ + /* And by 3f3f3f: 0000_0000|00rr_rrrr|00gg_gggg|00bb_bbbb */ + /* Or by alpha : aaaa_aaaa|00rr_rrrr|00gg_gggg|00bb_bbbb */ + return (c & BITMAPCOLOR_A_MASK) | ((c >> 2) & SHADOW_MASK); +} + /* Allocates a new instance of the default font using the given size and flags */ /* Uses Font_MakeBitmapped or SysFont_MakeDefault depending on Drawer2D_BitmappedText */ CC_API void Font_Make(struct FontDesc* desc, int size, int flags); /* Frees an allocated font */ CC_API void Font_Free(struct FontDesc* desc); -/* Sets default system font name and raises ChatEvents.FontChanged */ -void Font_SetDefault(const cc_string* fontName); /* Returns the line height for drawing text using the given font */ int Font_CalcHeight(const struct FontDesc* font, cc_bool useShadow); +/* Adjusts height to be closer to system fonts */ +int Drawer2D_AdjHeight(int point); void Drawer2D_ReducePadding_Tex(struct Texture* tex, int point, int scale); void Drawer2D_ReducePadding_Height(int* height, int point, int scale); +/* Quickly fills the given box region */ +void Drawer2D_Fill(struct Bitmap* bmp, int x, int y, int width, int height, BitmapCol color); + /* Sets the bitmap used for drawing bitmapped fonts. (i.e. default.png) */ /* The bitmap must be square and consist of a 16x16 tile layout */ cc_bool Font_SetBitmapAtlas(struct Bitmap* bmp); @@ -112,18 +126,4 @@ cc_bool Font_SetBitmapAtlas(struct Bitmap* bmp); void Font_SetPadding(struct FontDesc* desc, int amount); /* Initialises the given font for drawing bitmapped text using default.png */ void Font_MakeBitmapped(struct FontDesc* desc, int size, int flags); - -/* Allocates a new system font from the given arguments */ -cc_result SysFont_Make(struct FontDesc* desc, const cc_string* fontName, int size, int flags); -/* Allocates a new system font from the given arguments using default system font */ -/* NOTE: Unlike SysFont_Make, this may fallback onto other system fonts (e.g. Arial, Roboto, etc) */ -void SysFont_MakeDefault(struct FontDesc* desc, int size, int flags); - -/* Gets the name of the default system font used */ -const cc_string* SysFonts_UNSAFE_GetDefault(void); -/* Gets the list of all supported system font names on this platform */ -CC_API void SysFonts_GetNames(struct StringsBuffer* buffer); -/* Attempts to decode one or more fonts from the given file */ -/* NOTE: If this file has been decoded before (fontscache.txt), does nothing */ -void SysFonts_Register(const cc_string* path); #endif diff --git a/src/Game.c b/src/Game.c index 95fb72ae3..b39c52f19 100644 --- a/src/Game.c +++ b/src/Game.c @@ -36,6 +36,7 @@ #include "Protocol.h" #include "Picking.h" #include "Animations.h" +#include "SystemFonts.h" struct _GameData Game; cc_uint64 Game_FrameStart; @@ -388,6 +389,7 @@ static void Game_Load(void) { Game_AddComponent(&Gfx_Component); Game_AddComponent(&Blocks_Component); Game_AddComponent(&Drawer2D_Component); + Game_AddComponent(&SystemFonts_Component); Game_AddComponent(&Chat_Component); Game_AddComponent(&Particles_Component); diff --git a/src/Launcher.c b/src/Launcher.c index e0900ec4d..387caa553 100644 --- a/src/Launcher.c +++ b/src/Launcher.c @@ -20,6 +20,7 @@ #include "Options.h" #include "LBackend.h" #include "PackedCol.h" +#include "SystemFonts.h" struct LScreen* Launcher_Active; cc_bool Launcher_ShouldExit, Launcher_ShouldUpdate; @@ -234,6 +235,7 @@ void Launcher_Run(void) { #endif Drawer2D_Component.Init(); + SystemFonts_Component.Init(); Drawer2D.BitmappedText = false; Drawer2D.BlackTextShadows = true; diff --git a/src/Menus.c b/src/Menus.c index d4b9d758f..9d7645096 100644 --- a/src/Menus.c +++ b/src/Menus.c @@ -32,6 +32,7 @@ #include "Input.h" #include "Utils.h" #include "Errors.h" +#include "SystemFonts.h" /* Describes a menu option button */ struct MenuOptionDesc { @@ -1573,7 +1574,7 @@ static void FontListScreen_EntryClick(void* screen, void* widget) { cc_string fontName = ListScreen_UNSAFE_GetCur(s, widget); Options_Set(OPT_FONT_NAME, &fontName); - Font_SetDefault(&fontName); + SysFont_SetDefault(&fontName); } static void FontListScreen_UpdateEntry(struct ListScreen* s, struct ButtonWidget* button, const cc_string* text) { diff --git a/src/Platform_3DS.c b/src/Platform_3DS.c index 5647218dd..57f2dca4a 100644 --- a/src/Platform_3DS.c +++ b/src/Platform_3DS.c @@ -4,7 +4,6 @@ #include "_PlatformBase.h" #include "Stream.h" #include "ExtMath.h" -#include "Drawer2D.h" #include "Funcs.h" #include "Window.h" #include "Utils.h" @@ -309,13 +308,6 @@ void Waitable_WaitFor(void* handle, cc_uint32 milliseconds) { } -/*########################################################################################################################* -*--------------------------------------------------------Font/Text--------------------------------------------------------* -*#########################################################################################################################*/ -void Platform_LoadSysFonts(void) { -} - - /*########################################################################################################################* *---------------------------------------------------------Socket----------------------------------------------------------* *#########################################################################################################################*/ diff --git a/src/Platform_PSP.c b/src/Platform_PSP.c index 5040001c8..fa99a5174 100644 --- a/src/Platform_PSP.c +++ b/src/Platform_PSP.c @@ -315,12 +315,6 @@ void Waitable_WaitFor(void* handle, cc_uint32 milliseconds) { } -/*########################################################################################################################* -*--------------------------------------------------------Font/Text--------------------------------------------------------* -*#########################################################################################################################*/ -void Platform_LoadSysFonts(void) { } - - /*########################################################################################################################* *---------------------------------------------------------Socket----------------------------------------------------------* *#########################################################################################################################*/ diff --git a/src/Platform_Posix.c b/src/Platform_Posix.c index e184370d9..932574aae 100644 --- a/src/Platform_Posix.c +++ b/src/Platform_Posix.c @@ -4,7 +4,7 @@ #include "_PlatformBase.h" #include "Stream.h" #include "ExtMath.h" -#include "Drawer2D.h" +#include "SystemFonts.h" #include "Funcs.h" #include "Window.h" #include "Utils.h" diff --git a/src/Platform_Web.c b/src/Platform_Web.c index 53a4f8721..d2dd0fe08 100644 --- a/src/Platform_Web.c +++ b/src/Platform_Web.c @@ -4,7 +4,7 @@ #include "_PlatformBase.h" #include "Stream.h" #include "ExtMath.h" -#include "Drawer2D.h" +#include "SystemFonts.h" #include "Funcs.h" #include "Window.h" #include "Utils.h" diff --git a/src/Platform_WinApi.c b/src/Platform_WinApi.c index 87cbd1279..a54691acd 100644 --- a/src/Platform_WinApi.c +++ b/src/Platform_WinApi.c @@ -3,7 +3,7 @@ #include "_PlatformBase.h" #include "Stream.h" -#include "Drawer2D.h" +#include "SystemFonts.h" #include "Funcs.h" #include "Utils.h" #include "Errors.h" diff --git a/src/SystemFonts.c b/src/SystemFonts.c new file mode 100644 index 000000000..bc88d72ba --- /dev/null +++ b/src/SystemFonts.c @@ -0,0 +1,854 @@ +#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 */ +}; + + +/*########################################################################################################################* +*--------------------------------------------------------Freetype---------------------------------------------------------* +*#########################################################################################################################*/ +#if defined CC_BUILD_FREETYPE +#include "freetype/ft2build.h" +#include "freetype/freetype.h" +#include "freetype/ftmodapi.h" +#include "freetype/ftglyph.h" + +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"), /* ice 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 */ +}; + +static void SysFonts_InitLibrary(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); +} + +/* Updates fonts list cache with system's list of fonts */ +/* This should be avoided due to overhead potential */ +static void SysFonts_Update(void) { + static cc_bool updatedFonts; + if (updatedFonts) return; + updatedFonts = true; + + SysFonts_InitLibrary(); + Platform_LoadSysFonts(); + if (fonts_changed) EntryList_Save(&font_list, FONT_CACHE_FILE); +} + +static void SysFonts_Load(void) { + /* Need to keep track of whether font cache has been checked at least once */ + /* (Otherwise if unable to find any cached fonts and then unable to load any fonts, + /* font_list.count will always be 0 and the 'Initialising font cache' dialog will + confusingly get shown over and over until all font_candidates entries are checked) */ + static cc_bool checkedCache; + if (checkedCache) return; + checkedCache = true; + + EntryList_UNSAFE_Load(&font_list, FONT_CACHE_FILE); + if (font_list.count) return; + + Window_ShowDialog("One time load", "Initialising font cache, this can take several seconds."); + SysFonts_Update(); +} + + +/* 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) { + 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"); + } + + FT_Done_Face(font.face); + return count; +} + +void SysFonts_Register(const cc_string* path) { + 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; + } + + count = SysFonts_DoRegister(path, 0); + /* there may be more than one font in a font file */ + for (i = 1; i < count; i++) { + SysFonts_DoRegister(path, i); + } +} + + +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; + if (!font_list.count) SysFonts_Load(); + 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 = Font_DoLookup(fontName, flags); + if (path.length) return path; + + SysFonts_Update(); + 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; + if (!font_list.count) SysFonts_Load(); + SysFonts_Update(); + + 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; + + SysFonts_InitLibrary(); + 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); + + 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; + } + } + Logger_Abort2(res, "Failed to init default font"); +} + +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 %i 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 %i 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_Register(const cc_string* path) { } +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_Register(const cc_string* path) { } + +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 Font_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); +} +#elif defined CC_BUILD_PSP +void SysFonts_Register(const cc_string* path) { } + +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) { + 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) { +} + +int SysFont_TextWidth(struct DrawTextArgs* args) { + return 10; +} + +void SysFont_DrawText(struct DrawTextArgs* args, struct Bitmap* bmp, int x, int y, cc_bool shadow) { +} +#elif defined CC_BUILD_3DS +#include <3ds.h> + +void SysFonts_Register(const cc_string* path) { } + +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) { + desc->size = size; + desc->flags = flags; + desc->height = Drawer2D_AdjHeight(size); + + desc->handle = fontGetSystemFont(); + return 0; +} + +void SysFont_MakeDefault(struct FontDesc* desc, int size, int flags) { + SysFont_Make(desc, NULL, size, flags); +} + +void SysFont_Free(struct FontDesc* desc) { +} + +int SysFont_TextWidth(struct DrawTextArgs* args) { + int width = 0; + cc_string left = args->text, part; + char colorCode = 'f'; + CFNT_s* font = (CFNT_s*)args->font->handle; + + while (Drawer2D_UNSAFE_NextPart(&left, &part, &colorCode)) + { + for (int i = 0; i < part.length; i++) + { + cc_unichar cp = Convert_CP437ToUnicode(part.buffer[i]); + int glyphIndex = fontGlyphIndexFromCodePoint(font, cp); + if (glyphIndex < 0) continue; + + charWidthInfo_s* wInfo = fontGetCharWidthInfo(font, glyphIndex); + width += wInfo->charWidth; + } + } + return max(1, width); +} + +static void DrawGlyph(CFNT_s* font, struct Bitmap* bmp, int x, int y, int glyphIndex) { + TGLP_s* tglp = font->finf.tglp; + + //int glyphsPerSheet = tglp->nRows * tglp->nLines; + //int sheetIdx = glyphIndex / glyphsPerSheet; + //int sheetPos = glyphIndex % glyphsPerSheet; + //u8* sheet = tglp->sheetData + (sheetIdx * tglp->sheetSize); + + //int rowY = sheetPos / tglp->nRows; + //int rowX = sheetPos % tglp->nRows; + + //int FMT = tglp->sheetFmt; + //Platform_Log1("FMT: %i", &FMT); + + charWidthInfo_s* wInfo = fontGetCharWidthInfo(font, glyphIndex); + + /*for (int Y = 0; Y < tglp->cellHeight; Y++) + { + for (int X = wInfo->left; X < wInfo->left + wInfo->glyphWidth; X++) + { + int XX = x + X, YY = y + Y; + if (XX < 0 || YY < 0 || XX >= bmp->width || YY >= bmp->height) continue; + + int srcX = X + rowX * tglp->cellWidth; + int srcY = Y + rowY * tglp->cellHeight; // TODO wrong. morton offset too? + + u8 VALUE = ((sheet + srcY * (tglp->sheetWidth >> 1))[srcX >> 1] & 0x0F) << 4; + Bitmap_GetPixel(bmp, XX, YY) = BitmapColor_RGB(VALUE, VALUE, VALUE); + } + }*/ + + for (int Y = 0; Y < tglp->cellHeight; Y++) + { + for (int X = wInfo->left; X < wInfo->left + wInfo->glyphWidth; X++) + { + int XX = x + X, YY = y + Y; + if (XX < 0 || YY < 0 || XX >= bmp->width || YY >= bmp->height) continue; + + Bitmap_GetPixel(bmp, XX, YY) = BITMAPCOLOR_WHITE; + } + } +} + +void SysFont_DrawText(struct DrawTextArgs* args, struct Bitmap* bmp, int x, int y, cc_bool shadow) { + cc_string left = args->text, part; + char colorCode = 'f'; + CFNT_s* font = (CFNT_s*)args->font->handle; + + while (Drawer2D_UNSAFE_NextPart(&left, &part, &colorCode)) + { + for (int i = 0; i < part.length; i++) + { + cc_unichar cp = Convert_CP437ToUnicode(part.buffer[i]); + int glyphIndex = fontGlyphIndexFromCodePoint(font, cp); + if (glyphIndex < 0) continue; + + DrawGlyph(font, bmp, x, y, glyphIndex); + charWidthInfo_s* wInfo = fontGetCharWidthInfo(font, glyphIndex); + x += wInfo->charWidth; + } + } +} +#endif diff --git a/src/SystemFonts.h b/src/SystemFonts.h new file mode 100644 index 000000000..7f9942e45 --- /dev/null +++ b/src/SystemFonts.h @@ -0,0 +1,36 @@ +#ifndef CC_SYSTEMFONTS_H +#define CC_SYSTEMFONTS_H +#include "Core.h" +/* Manages loading and drawing platform specific system fonts + Copyright 2014-2022 ClassiCube | Licensed under BSD-3 +*/ +struct Bitmap; +struct FontDesc; +struct DrawTextArgs; +struct IGameComponent; +struct StringsBuffer; +extern struct IGameComponent SystemFonts_Component; + +/* Allocates a new system font from the given arguments */ +cc_result SysFont_Make(struct FontDesc* desc, const cc_string* fontName, int size, int flags); +/* Frees an allocated system font */ +void SysFont_Free(struct FontDesc* desc); +/* Allocates a new system font from the given arguments using default system font */ +/* NOTE: Unlike SysFont_Make, this may fallback onto other system fonts (e.g. Arial, Roboto, etc) */ +void SysFont_MakeDefault(struct FontDesc* desc, int size, int flags); +/* Sets default system font name and raises ChatEvents.FontChanged */ +void SysFont_SetDefault(const cc_string* fontName); + +/* Measures width of the given text when drawn with the given system font */ +int SysFont_TextWidth(struct DrawTextArgs* args); +/* Draws the given text with the given system font onto the given bitmap */ +void SysFont_DrawText(struct DrawTextArgs* args, struct Bitmap* bmp, int x, int y, cc_bool shadow); + +/* Gets the name of the default system font used */ +const cc_string* SysFonts_UNSAFE_GetDefault(void); +/* Gets the list of all supported system font names on this platform */ +CC_API void SysFonts_GetNames(struct StringsBuffer* buffer); +/* Attempts to decode one or more fonts from the given file */ +/* NOTE: If this file has been decoded before (fontscache.txt), does nothing */ +void SysFonts_Register(const cc_string* path); +#endif diff --git a/src/interop_ios.m b/src/interop_ios.m index 4426df959..4676f6469 100644 --- a/src/interop_ios.m +++ b/src/interop_ios.m @@ -896,17 +896,6 @@ int interop_SysTextWidth(struct DrawTextArgs* args) { return Math_Ceil(width); } -#define SHADOW_MASK ((0x3F << BITMAPCOL_R_SHIFT) | (0x3F << BITMAPCOL_G_SHIFT) | (0x3F << BITMAPCOL_B_SHIFT)) -CC_NOINLINE static BitmapCol GetShadowColor(BitmapCol c) { - if (Drawer2D.BlackTextShadows) return BITMAPCOL_BLACK; - - // Initial layout: aaaa_aaaa|rrrr_rrrr|gggg_gggg|bbbb_bbbb - // Shift right 2: 00aa_aaaa|aarr_rrrr|rrgg_gggg|ggbb_bbbb - // And by 3f3f3f: 0000_0000|00rr_rrrr|00gg_gggg|00bb_bbbb - // Or by alpha : aaaa_aaaa|00rr_rrrr|00gg_gggg|00bb_bbbb - return (c & BITMAPCOL_A_MASK) | ((c >> 2) & SHADOW_MASK); -} - void interop_SysTextDraw(struct DrawTextArgs* args, struct Context2D* ctx, int x, int y, cc_bool shadow) { CTFontRef font = (CTFontRef)args->font->handle; cc_string left = args->text, part; @@ -990,18 +979,6 @@ void interop_SysFontFree(void* handle) { CFBridgingRelease(handle); } -#define SHADOW_MASK ((0x3F << BITMAPCOL_R_SHIFT) | (0x3F << BITMAPCOL_G_SHIFT) | (0x3F << BITMAPCOL_B_SHIFT)) -CC_NOINLINE static BitmapCol GetShadowColor(BitmapCol c) { - // TODO move to Drawer2D.h - if (Drawer2D.BlackTextShadows) return BITMAPCOL_BLACK; - - // Initial layout: aaaa_aaaa|rrrr_rrrr|gggg_gggg|bbbb_bbbb - // Shift right 2: 00aa_aaaa|aarr_rrrr|rrgg_gggg|ggbb_bbbb - // And by 3f3f3f: 0000_0000|00rr_rrrr|00gg_gggg|00bb_bbbb - // Or by alpha : aaaa_aaaa|00rr_rrrr|00gg_gggg|00bb_bbbb - return (c & BITMAPCOL_A_MASK) | ((c >> 2) & SHADOW_MASK); -} - static NSMutableAttributedString* GetAttributedString(struct DrawTextArgs* args, cc_bool shadow) { UIFont* font = (__bridge UIFont*)args->font->handle; cc_string left = args->text, part;