/* SDL_FontCache: A font cache for SDL and SDL_ttf by Jonathan Dearborn See SDL_FontCache.h for license info. */ #include #include #include #include #include // Visual C does not support static inline #ifndef static_inline #ifdef _MSC_VER #define static_inline static #else #define static_inline static inline #endif #endif #if SDL_VERSION_ATLEAST(2,0,0) #define FC_GET_ALPHA(sdl_color) ((sdl_color).a) #else #define FC_GET_ALPHA(sdl_color) ((sdl_color).unused) #endif // Need SDL_RenderIsClipEnabled() for proper clipping support #if SDL_VERSION_ATLEAST(2,0,4) #define ENABLE_SDL_CLIPPING #endif #define FC_MIN(a,b) ((a) < (b)? (a) : (b)) #define FC_MAX(a,b) ((a) > (b)? (a) : (b)) // vsnprintf replacement from Valentin Milea: // http://stackoverflow.com/questions/2915672/snprintf-and-visual-studio-2010 #if defined(_MSC_VER) && _MSC_VER < 1900 #define snprintf c99_snprintf #define vsnprintf c99_vsnprintf __inline int c99_vsnprintf(char *outBuf, size_t size, const char *format, va_list ap) { int count = -1; if (size != 0) count = _vsnprintf_s(outBuf, size, _TRUNCATE, format, ap); if (count == -1) count = _vscprintf(format, ap); return count; } __inline int c99_snprintf(char *outBuf, size_t size, const char *format, ...) { int count; va_list ap; va_start(ap, format); count = c99_vsnprintf(outBuf, size, format, ap); va_end(ap); return count; } #endif #define FC_EXTRACT_VARARGS(buffer, start_args) \ { \ va_list lst; \ va_start(lst, start_args); \ vsnprintf(buffer, fc_buffer_size, start_args, lst); \ va_end(lst); \ } // Extra pixels of padding around each glyph to avoid linear filtering artifacts #define FC_CACHE_PADDING 1 static Uint8 has_clip(FC_Target* dest) { #ifdef FC_USE_SDL_GPU return dest->use_clip_rect; #elif defined(ENABLE_SDL_CLIPPING) return SDL_RenderIsClipEnabled(dest); #else return 0; #endif } static FC_Rect get_clip(FC_Target* dest) { #ifdef FC_USE_SDL_GPU return dest->clip_rect; #elif defined(ENABLE_SDL_CLIPPING) SDL_Rect r; SDL_RenderGetClipRect(dest, &r); return r; #else SDL_Rect r = {0, 0, 0, 0}; return r; #endif } static void set_clip(FC_Target* dest, FC_Rect* rect) { #ifdef FC_USE_SDL_GPU if(rect != NULL) GPU_SetClipRect(dest, *rect); else GPU_UnsetClip(dest); #elif defined(ENABLE_SDL_CLIPPING) SDL_RenderSetClipRect(dest, rect); #endif } static void set_color(FC_Image* src, Uint8 r, Uint8 g, Uint8 b, Uint8 a) { #ifdef FC_USE_SDL_GPU GPU_SetRGBA(src, r, g, b, a); #else SDL_SetTextureColorMod(src, r, g, b); SDL_SetTextureAlphaMod(src, a); #endif } static char* new_concat(const char* a, const char* b) { // Create new buffer unsigned int size = strlen(a) + strlen(b); char* new_string = (char*)malloc(size+1); // Concatenate strings in the new buffer strcpy(new_string, a); strcat(new_string, b); return new_string; } static char* replace_concat(char** a, const char* b) { char* new_string = new_concat(*a, b); free(*a); *a = new_string; return *a; } // Width of a tab in units of the space width (sorry, no tab alignment!) static unsigned int fc_tab_width = 4; // Shared buffer for variadic text static char* fc_buffer = NULL; static unsigned int fc_buffer_size = 1024; static Uint8 fc_has_render_target_support = 0; // The number of fonts that has been created but not freed static int NUM_EXISTING_FONTS = 0; // Globals for GetString functions static char* ASCII_STRING = NULL; static char* LATIN_1_STRING = NULL; static char* ASCII_LATIN_1_STRING = NULL; char* FC_GetStringASCII(void) { if(ASCII_STRING == NULL) { int i; char c; ASCII_STRING = (char*)malloc(512); memset(ASCII_STRING, 0, 512); i = 0; c = 32; while(1) { ASCII_STRING[i] = c; if(c == 126) break; ++i; ++c; } } return U8_strdup(ASCII_STRING); } char* FC_GetStringLatin1(void) { if(LATIN_1_STRING == NULL) { int i; unsigned char c; LATIN_1_STRING = (char*)malloc(512); memset(LATIN_1_STRING, 0, 512); i = 0; c = 0xA0; while(1) { LATIN_1_STRING[i] = 0xC2; LATIN_1_STRING[i+1] = c; if(c == 0xBF) break; i += 2; ++c; } i += 2; c = 0x80; while(1) { LATIN_1_STRING[i] = 0xC3; LATIN_1_STRING[i+1] = c; if(c == 0xBF) break; i += 2; ++c; } } return U8_strdup(LATIN_1_STRING); } char* FC_GetStringASCII_Latin1(void) { if(ASCII_LATIN_1_STRING == NULL) ASCII_LATIN_1_STRING = new_concat(FC_GetStringASCII(), FC_GetStringLatin1()); return U8_strdup(ASCII_LATIN_1_STRING); } FC_Rect FC_MakeRect(float x, float y, float w, float h) { FC_Rect r = {(int) x, (int) y, (int) w, (int) h}; return r; } FC_Scale FC_MakeScale(float x, float y) { FC_Scale s = {x, y}; return s; } SDL_Color FC_MakeColor(Uint8 r, Uint8 g, Uint8 b, Uint8 a) { SDL_Color c = {r, g, b, a}; return c; } FC_Effect FC_MakeEffect(FC_AlignEnum alignment, FC_Scale scale, SDL_Color color) { FC_Effect e; e.alignment = alignment; e.scale = scale; e.color = color; return e; } FC_GlyphData FC_MakeGlyphData(int cache_level, Sint16 x, Sint16 y, Uint16 w, Uint16 h) { FC_GlyphData gd; gd.rect.x = x; gd.rect.y = y; gd.rect.w = w; gd.rect.h = h; gd.cache_level = cache_level; return gd; } // Enough to hold all of the ascii characters and some. #define FC_DEFAULT_NUM_BUCKETS 300 typedef struct FC_MapNode { Uint32 key; FC_GlyphData value; struct FC_MapNode* next; } FC_MapNode; typedef struct FC_Map { int num_buckets; FC_MapNode** buckets; } FC_Map; static FC_Map* FC_MapCreate(int num_buckets) { int i; FC_Map* map = (FC_Map*)malloc(sizeof(FC_Map)); map->num_buckets = num_buckets; map->buckets = (FC_MapNode**)malloc(num_buckets * sizeof(FC_MapNode*)); for(i = 0; i < num_buckets; ++i) { map->buckets[i] = NULL; } return map; } /*static void FC_MapClear(FC_Map* map) { int i; if(map == NULL) return; // Go through each bucket for(i = 0; i < map->num_buckets; ++i) { // Delete the nodes in order FC_MapNode* node = map->buckets[i]; while(node != NULL) { FC_MapNode* last = node; node = node->next; free(last); } // Set the bucket to empty map->buckets[i] = NULL; } }*/ static void FC_MapFree(FC_Map* map) { int i; if(map == NULL) return; // Go through each bucket for(i = 0; i < map->num_buckets; ++i) { // Delete the nodes in order FC_MapNode* node = map->buckets[i]; while(node != NULL) { FC_MapNode* last = node; node = node->next; free(last); } } free(map->buckets); free(map); } // Note: Does not handle duplicates in any special way. static FC_GlyphData* FC_MapInsert(FC_Map* map, Uint32 codepoint, FC_GlyphData glyph) { Uint32 index; FC_MapNode* node; if(map == NULL) return NULL; // Get index for bucket index = codepoint % map->num_buckets; // If this bucket is empty, create a node and return its value if(map->buckets[index] == NULL) { node = map->buckets[index] = (FC_MapNode*)malloc(sizeof(FC_MapNode)); node->key = codepoint; node->value = glyph; node->next = NULL; return &node->value; } for(node = map->buckets[index]; node != NULL; node = node->next) { // Find empty node and add a new one on. if(node->next == NULL) { node->next = (FC_MapNode*)malloc(sizeof(FC_MapNode)); node = node->next; node->key = codepoint; node->value = glyph; node->next = NULL; return &node->value; } } return NULL; } static FC_GlyphData* FC_MapFind(FC_Map* map, Uint32 codepoint) { Uint32 index; FC_MapNode* node; if(map == NULL) return NULL; // Get index for bucket index = codepoint % map->num_buckets; // Go through list until we find a match for(node = map->buckets[index]; node != NULL; node = node->next) { if(node->key == codepoint) return &node->value; } return NULL; } struct FC_Font { #ifndef FC_USE_SDL_GPU SDL_Renderer* renderer; #endif TTF_Font* ttf_source; // TTF_Font source of characters Uint8 owns_ttf_source; // Can we delete the TTF_Font ourselves? FC_FilterEnum filter; SDL_Color default_color; Uint16 height; Uint16 maxWidth; Uint16 baseline; int ascent; int descent; int lineSpacing; int letterSpacing; // Uses 32-bit (4-byte) Unicode codepoints to refer to each glyph // Codepoints are little endian (reversed from UTF-8) so that something like 0x00000005 is ASCII 5 and the map can be indexed by ASCII values FC_Map* glyphs; FC_GlyphData last_glyph; // Texture packing cursor int glyph_cache_size; int glyph_cache_count; FC_Image** glyph_cache; char* loading_string; std::recursive_mutex* mutex = nullptr; }; // Private static FC_GlyphData* FC_PackGlyphData(FC_Font* font, Uint32 codepoint, Uint16 width, Uint16 maxWidth, Uint16 maxHeight); static FC_Rect FC_RenderLeft(FC_Font* font, FC_Target* dest, float x, float y, FC_Scale scale, const char* text); static FC_Rect FC_RenderCenter(FC_Font* font, FC_Target* dest, float x, float y, FC_Scale scale, const char* text); static FC_Rect FC_RenderRight(FC_Font* font, FC_Target* dest, float x, float y, FC_Scale scale, const char* text); static_inline SDL_Surface* FC_CreateSurface32(Uint32 width, Uint32 height) { #if SDL_BYTEORDER == SDL_BIG_ENDIAN return SDL_CreateRGBSurface(SDL_SWSURFACE, width, height, 32, 0xFF000000, 0x00FF0000, 0x0000FF00, 0x000000FF); #else return SDL_CreateRGBSurface(SDL_SWSURFACE, width, height, 32, 0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000); #endif } char* U8_alloc(unsigned int size) { char* result; if(size == 0) return NULL; result = (char*)malloc(size); result[0] = '\0'; return result; } void U8_free(char* string) { free(string); } char* U8_strdup(const char* string) { char* result; if(string == NULL) return NULL; result = (char*)malloc(strlen(string)+1); strcpy(result, string); return result; } int U8_strlen(const char* string) { int length = 0; if(string == NULL) return 0; while(*string != '\0') { string = U8_next(string); ++length; } return length; } int U8_charsize(const char* character) { if(character == NULL) return 0; if((unsigned char)*character <= 0x7F) return 1; else if((unsigned char)*character < 0xE0) return 2; else if((unsigned char)*character < 0xF0) return 3; else return 4; return 1; } int U8_charcpy(char* buffer, const char* source, int buffer_size) { int charsize; if(buffer == NULL || source == NULL || buffer_size < 1) return 0; charsize = U8_charsize(source); if(charsize > buffer_size) return 0; memcpy(buffer, source, charsize); return charsize; } const char* U8_next(const char* string) { return string + U8_charsize(string); } int U8_strinsert(char* string, int position, const char* source, int max_bytes) { int pos_u8char; int len; int add_len; int ulen; const char* string_start = string; if(string == NULL || source == NULL) return 0; len = strlen(string); add_len = strlen(source); ulen = U8_strlen(string); if(position == -1) position = ulen; if(position < 0 || position > ulen || len + add_len + 1 > max_bytes) return 0; // Move string pointer to the proper position pos_u8char = 0; while(*string != '\0' && pos_u8char < position) { string = (char*)U8_next(string); ++pos_u8char; } // Move the rest of the string out of the way memmove(string + add_len, string, len - (string - string_start) + 1); // Copy in the new characters memcpy(string, source, add_len); return 1; } void U8_strdel(char* string, int position) { if(string == NULL || position < 0) return; while(*string != '\0') { if(position == 0) { int chars_to_erase = U8_charsize(string); int remaining_bytes = strlen(string) + 1; memmove(string, string + chars_to_erase, remaining_bytes); break; } string = (char*)U8_next(string); --position; } } static_inline FC_Rect FC_RectUnion(FC_Rect A, FC_Rect B) { float x,x2,y,y2; x = FC_MIN(A.x, B.x); y = FC_MIN(A.y, B.y); x2 = FC_MAX(A.x+A.w, B.x+B.w); y2 = FC_MAX(A.y+A.h, B.y+B.h); { FC_Rect result = {(int) x, (int) y, (int) FC_MAX(0, x2 - x), (int) FC_MAX(0, y2 - y)}; return result; } } // Adapted from SDL_IntersectRect static_inline FC_Rect FC_RectIntersect(FC_Rect A, FC_Rect B) { FC_Rect result; float Amin, Amax, Bmin, Bmax; // Horizontal intersection Amin = A.x; Amax = Amin + A.w; Bmin = B.x; Bmax = Bmin + B.w; if(Bmin > Amin) Amin = Bmin; result.x = Amin; if(Bmax < Amax) Amax = Bmax; result.w = Amax - Amin > 0 ? Amax - Amin : 0; // Vertical intersection Amin = A.y; Amax = Amin + A.h; Bmin = B.y; Bmax = Bmin + B.h; if(Bmin > Amin) Amin = Bmin; result.y = Amin; if(Bmax < Amax) Amax = Bmax; result.h = Amax - Amin > 0 ? Amax - Amin : 0; return result; } FC_Rect FC_DefaultRenderCallback(FC_Image* src, FC_Rect* srcrect, FC_Target* dest, float x, float y, float xscale, float yscale) { float w = srcrect->w * xscale; float h = srcrect->h * yscale; FC_Rect result; // FIXME: Why does the scaled offset look so wrong? #ifdef FC_USE_SDL_GPU { GPU_Rect r = *srcrect; GPU_BlitScale(src, &r, dest, x + xscale*r.w/2.0f, y + r.h/2.0f, xscale, yscale); } #else { SDL_RendererFlip flip = SDL_FLIP_NONE; if(xscale < 0) { xscale = -xscale; flip = (SDL_RendererFlip) ((int)flip | (int)SDL_FLIP_HORIZONTAL); } if(yscale < 0) { yscale = -yscale; flip = (SDL_RendererFlip) ((int)flip | (int)SDL_FLIP_VERTICAL); } SDL_Rect r = *srcrect; SDL_Rect dr = {(int)x, (int)y, (int)(xscale*r.w), (int)(yscale*r.h)}; SDL_RenderCopyEx(dest, src, &r, &dr, 0, NULL, flip); } #endif result.x = x; result.y = y; result.w = w; result.h = h; return result; } static FC_Rect (*fc_render_callback)(FC_Image* src, FC_Rect* srcrect, FC_Target* dest, float x, float y, float xscale, float yscale) = &FC_DefaultRenderCallback; void FC_SetRenderCallback(FC_Rect (*callback)(FC_Image* src, FC_Rect* srcrect, FC_Target* dest, float x, float y, float xscale, float yscale)) { if(callback == NULL) fc_render_callback = &FC_DefaultRenderCallback; else fc_render_callback = callback; } void FC_GetUTF8FromCodepoint(char* result, Uint32 codepoint) { char a, b, c, d; if(result == NULL) return; a = (codepoint >> 24) & 0xFF; b = (codepoint >> 16) & 0xFF; c = (codepoint >> 8) & 0xFF; d = codepoint & 0xFF; if(a == 0) { if(b == 0) { if(c == 0) { result[0] = d; result[1] = '\0'; } else { result[0] = c; result[1] = d; result[2] = '\0'; } } else { result[0] = b; result[1] = c; result[2] = d; result[3] = '\0'; } } else { result[0] = a; result[1] = b; result[2] = c; result[3] = d; result[4] = '\0'; } } Uint32 FC_GetCodepointFromUTF8(const char** c, Uint8 advance_pointer) { Uint32 result = 0; const char* str; if(c == NULL || *c == NULL) return 0; str = *c; if((unsigned char)*str <= 0x7F) result = *str; else if((unsigned char)*str < 0xE0) { result |= (unsigned char)(*str) << 8; result |= (unsigned char)(*(str+1)); if(advance_pointer) *c += 1; } else if((unsigned char)*str < 0xF0) { result |= (unsigned char)(*str) << 16; result |= (unsigned char)(*(str+1)) << 8; result |= (unsigned char)(*(str+2)); if(advance_pointer) *c += 2; } else { result |= (unsigned char)(*str) << 24; result |= (unsigned char)(*(str+1)) << 16; result |= (unsigned char)(*(str+2)) << 8; result |= (unsigned char)(*(str+3)); if(advance_pointer) *c += 3; } return result; } void FC_SetLoadingString(FC_Font* font, const char* string) { if(font == NULL) return; free(font->loading_string); font->loading_string = U8_strdup(string); } unsigned int FC_GetBufferSize(void) { return fc_buffer_size; } void FC_SetBufferSize(unsigned int size) { free(fc_buffer); if(size > 0) { fc_buffer_size = size; fc_buffer = (char*)malloc(fc_buffer_size); } else fc_buffer = (char*)malloc(fc_buffer_size); } unsigned int FC_GetTabWidth(void) { return fc_tab_width; } void FC_SetTabWidth(unsigned int width_in_spaces) { fc_tab_width = width_in_spaces; } // Constructors static void FC_Init(FC_Font* font) { if(font == NULL) return; #ifndef FC_USE_SDL_GPU font->renderer = NULL; #endif font->ttf_source = NULL; font->owns_ttf_source = 0; font->filter = FC_FILTER_NEAREST; font->default_color.r = 0; font->default_color.g = 0; font->default_color.b = 0; FC_GET_ALPHA(font->default_color) = 255; font->height = 0; // ascent+descent font->maxWidth = 0; font->baseline = 0; font->ascent = 0; font->descent = 0; font->lineSpacing = 0; font->letterSpacing = 0; // Give a little offset for when filtering/mipmaps are used. Depending on mipmap level, this will still not be enough. font->last_glyph.rect.x = FC_CACHE_PADDING; font->last_glyph.rect.y = FC_CACHE_PADDING; font->last_glyph.rect.w = 0; font->last_glyph.rect.h = 0; font->last_glyph.cache_level = 0; if(font->glyphs != NULL) FC_MapFree(font->glyphs); font->glyphs = FC_MapCreate(FC_DEFAULT_NUM_BUCKETS); font->glyph_cache_size = 3; font->glyph_cache_count = 0; font->mutex = new std::recursive_mutex(); font->glyph_cache = (FC_Image**)malloc(font->glyph_cache_size * sizeof(FC_Image*)); if (font->loading_string == NULL) font->loading_string = FC_GetStringASCII(); if(fc_buffer == NULL) fc_buffer = (char*)malloc(fc_buffer_size); } static Uint8 FC_GrowGlyphCache(FC_Font* font) { if(font == NULL) return 0; #ifdef FC_USE_SDL_GPU GPU_Image* new_level = GPU_CreateImage(font->height * 12, font->height * 12, GPU_FORMAT_RGBA); GPU_SetAnchor(new_level, 0.5f, 0.5f); // Just in case the default is different #else SDL_Texture* new_level = SDL_CreateTexture(font->renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, font->height * 12, font->height * 12); #endif if(new_level == NULL || !FC_SetGlyphCacheLevel(font, font->glyph_cache_count, new_level)) { FC_Log("Error: SDL_FontCache ran out of packing space and could not add another cache level.\n"); #ifdef FC_USE_SDL_GPU GPU_FreeImage(new_level); #else SDL_DestroyTexture(new_level); #endif return 0; } // bug: we do not have the correct color here, this might be the wrong color! // , most functions use set_color_for_all_caches() // - for evading this bug, you must use FC_SetDefaultColor(), before using any draw functions set_color(new_level, font->default_color.r, font->default_color.g, font->default_color.b, FC_GET_ALPHA(font->default_color)); #ifndef FC_USE_SDL_GPU { Uint8 r, g, b, a; SDL_Texture* prev_target = SDL_GetRenderTarget(font->renderer); SDL_Rect prev_clip, prev_viewport; int prev_logicalw, prev_logicalh; Uint8 prev_clip_enabled; float prev_scalex, prev_scaley; // only backup if previous target existed (SDL will preserve them for the default target) if (prev_target) { prev_clip_enabled = has_clip(font->renderer); if (prev_clip_enabled) prev_clip = get_clip(font->renderer); SDL_RenderGetViewport(font->renderer, &prev_viewport); SDL_RenderGetScale(font->renderer, &prev_scalex, &prev_scaley); SDL_RenderGetLogicalSize(font->renderer, &prev_logicalw, &prev_logicalh); } SDL_SetTextureBlendMode(new_level, SDL_BLENDMODE_BLEND); SDL_SetRenderTarget(font->renderer, new_level); SDL_GetRenderDrawColor(font->renderer, &r, &g, &b, &a); SDL_SetRenderDrawColor(font->renderer, 0, 0, 0, 0); SDL_RenderClear(font->renderer); SDL_SetRenderDrawColor(font->renderer, r, g, b, a); SDL_SetRenderTarget(font->renderer, prev_target); if (prev_target) { if (prev_clip_enabled) set_clip(font->renderer, &prev_clip); if (prev_logicalw && prev_logicalh) SDL_RenderSetLogicalSize(font->renderer, prev_logicalw, prev_logicalh); else { SDL_RenderSetViewport(font->renderer, &prev_viewport); SDL_RenderSetScale(font->renderer, prev_scalex, prev_scaley); } } } #endif return 1; } Uint8 FC_UploadGlyphCache(FC_Font* font, int cache_level, SDL_Surface* data_surface) { if(font == NULL || data_surface == NULL) return 0; #ifdef FC_USE_SDL_GPU GPU_Image* new_level = GPU_CopyImageFromSurface(data_surface); GPU_SetAnchor(new_level, 0.5f, 0.5f); // Just in case the default is different if(FC_GetFilterMode(font) == FC_FILTER_LINEAR) GPU_SetImageFilter(new_level, GPU_FILTER_LINEAR); else GPU_SetImageFilter(new_level, GPU_FILTER_NEAREST); #else SDL_Texture* new_level; if(!fc_has_render_target_support) new_level = SDL_CreateTextureFromSurface(font->renderer, data_surface); else { // Must upload with render target enabled so we can put more glyphs on later SDL_Renderer* renderer = font->renderer; // Set filter mode for new texture char old_filter_mode[16]; // Save it so we can change the hint value in the meantime const char* old_filter_hint = SDL_GetHint(SDL_HINT_RENDER_SCALE_QUALITY); if(!old_filter_hint) old_filter_hint = "nearest"; snprintf(old_filter_mode, 16, "%s", old_filter_hint); if(FC_GetFilterMode(font) == FC_FILTER_LINEAR) SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1"); else SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0"); new_level = SDL_CreateTexture(renderer, data_surface->format->format, SDL_TEXTUREACCESS_TARGET, data_surface->w, data_surface->h); SDL_SetTextureBlendMode(new_level, SDL_BLENDMODE_BLEND); // Reset filter mode for the temp texture SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0"); { Uint8 r, g, b, a; SDL_Texture* temp = SDL_CreateTextureFromSurface(renderer, data_surface); SDL_Texture* prev_target = SDL_GetRenderTarget(renderer); SDL_Rect prev_clip, prev_viewport; int prev_logicalw, prev_logicalh; Uint8 prev_clip_enabled; float prev_scalex, prev_scaley; // only backup if previous target existed (SDL will preserve them for the default target) if (prev_target) { prev_clip_enabled = has_clip(renderer); if (prev_clip_enabled) prev_clip = get_clip(renderer); SDL_RenderGetViewport(renderer, &prev_viewport); SDL_RenderGetScale(renderer, &prev_scalex, &prev_scaley); SDL_RenderGetLogicalSize(renderer, &prev_logicalw, &prev_logicalh); } SDL_SetTextureBlendMode(temp, SDL_BLENDMODE_NONE); SDL_SetRenderTarget(renderer, new_level); SDL_GetRenderDrawColor(renderer, &r, &g, &b, &a); SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0); SDL_RenderClear(renderer); SDL_SetRenderDrawColor(renderer, r, g, b, a); SDL_RenderCopy(renderer, temp, NULL, NULL); SDL_SetRenderTarget(renderer, prev_target); if (prev_target) { if (prev_clip_enabled) set_clip(renderer, &prev_clip); if (prev_logicalw && prev_logicalh) SDL_RenderSetLogicalSize(renderer, prev_logicalw, prev_logicalh); else { SDL_RenderSetViewport(renderer, &prev_viewport); SDL_RenderSetScale(renderer, prev_scalex, prev_scaley); } } SDL_DestroyTexture(temp); } // Reset to the old filter value SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, old_filter_mode); } #endif if(new_level == NULL || !FC_SetGlyphCacheLevel(font, cache_level, new_level)) { FC_Log("Error: SDL_FontCache ran out of packing space and could not add another cache level.\n"); #ifdef FC_USE_SDL_GPU GPU_FreeImage(new_level); #else SDL_DestroyTexture(new_level); #endif return 0; } return 1; } static FC_GlyphData* FC_PackGlyphData(FC_Font* font, Uint32 codepoint, Uint16 width, Uint16 maxWidth, Uint16 maxHeight) { FC_Map* glyphs = font->glyphs; FC_GlyphData* last_glyph = &font->last_glyph; Uint16 height = font->height + FC_CACHE_PADDING; // TAB is special! if(codepoint == '\t') { FC_GlyphData spaceGlyph; FC_GetGlyphData(font, &spaceGlyph, ' '); width = fc_tab_width * spaceGlyph.rect.w; } if(last_glyph->rect.x + last_glyph->rect.w + width >= maxWidth - FC_CACHE_PADDING) { if(last_glyph->rect.y + height + height >= maxHeight - FC_CACHE_PADDING) { // Get ready to pack on the next cache level when it is ready last_glyph->cache_level = font->glyph_cache_count; last_glyph->rect.x = FC_CACHE_PADDING; last_glyph->rect.y = FC_CACHE_PADDING; last_glyph->rect.w = 0; return NULL; } else { // Go to next row last_glyph->rect.x = FC_CACHE_PADDING; last_glyph->rect.y += height; last_glyph->rect.w = 0; } } // Move to next space last_glyph->rect.x += last_glyph->rect.w + 1 + FC_CACHE_PADDING; last_glyph->rect.w = width; return FC_MapInsert(glyphs, codepoint, FC_MakeGlyphData(last_glyph->cache_level, last_glyph->rect.x, last_glyph->rect.y, last_glyph->rect.w, last_glyph->rect.h)); } FC_Image* FC_GetGlyphCacheLevel(FC_Font* font, int cache_level) { if(font == NULL || cache_level < 0 || cache_level > font->glyph_cache_count) return NULL; return font->glyph_cache[cache_level]; } Uint8 FC_SetGlyphCacheLevel(FC_Font* font, int cache_level, FC_Image* cache_texture) { if(font == NULL || cache_level < 0) return 0; // Must be sequentially added if(cache_level > font->glyph_cache_count + 1) return 0; if(cache_level == font->glyph_cache_count) { font->glyph_cache_count++; // Grow cache? if(font->glyph_cache_count > font->glyph_cache_size) { // Copy old cache to new one int i; FC_Image** new_cache; new_cache = (FC_Image**)malloc(font->glyph_cache_count * sizeof(FC_Image*)); for(i = 0; i < font->glyph_cache_size; ++i) new_cache[i] = font->glyph_cache[i]; // Save new cache free(font->glyph_cache); font->glyph_cache_size = font->glyph_cache_count; font->glyph_cache = new_cache; } } font->glyph_cache[cache_level] = cache_texture; return 1; } FC_Font* FC_CreateFont(void) { FC_Font* font; font = (FC_Font*)malloc(sizeof(FC_Font)); memset((void*)font, 0, sizeof(FC_Font)); FC_Init(font); ++NUM_EXISTING_FONTS; return font; } // Assume this many will be enough... #define FC_LOAD_MAX_SURFACES 10 #ifdef FC_USE_SDL_GPU Uint8 FC_LoadFontFromTTF(FC_Font* font, TTF_Font* ttf, SDL_Color color) #else Uint8 FC_LoadFontFromTTF(FC_Font* font, SDL_Renderer* renderer, TTF_Font* ttf, SDL_Color color) #endif { if(font == NULL || ttf == NULL) return 0; #ifndef FC_USE_SDL_GPU if(renderer == NULL) return 0; #endif FC_ClearFont(font); std::scoped_lock lock(*font->mutex); // Might as well check render target support here #ifdef FC_USE_SDL_GPU fc_has_render_target_support = GPU_IsFeatureEnabled(GPU_FEATURE_RENDER_TARGETS); #else SDL_RendererInfo info; SDL_GetRendererInfo(renderer, &info); fc_has_render_target_support = (info.flags & SDL_RENDERER_TARGETTEXTURE); font->renderer = renderer; #endif font->ttf_source = ttf; //font->line_height = TTF_FontLineSkip(ttf); font->height = TTF_FontHeight(ttf); font->ascent = TTF_FontAscent(ttf); font->descent = -TTF_FontDescent(ttf); // Some bug for certain fonts can result in an incorrect height. if(font->height < font->ascent - font->descent) font->height = font->ascent - font->descent; font->baseline = font->height - font->descent; font->default_color = color; { SDL_Color white = {255, 255, 255, 255}; SDL_Surface* glyph_surf; char buff[5]; const char* buff_ptr = buff; const char* source_string; Uint8 packed = 0; // Copy glyphs from the surface to the font texture and store the position data // Pack row by row into a square texture // Try figuring out dimensions that make sense for the font size. unsigned int w = font->height*12; unsigned int h = font->height*12; SDL_Surface* surfaces[FC_LOAD_MAX_SURFACES]; int num_surfaces = 1; surfaces[0] = FC_CreateSurface32(w, h); font->last_glyph.rect.x = FC_CACHE_PADDING; font->last_glyph.rect.y = FC_CACHE_PADDING; font->last_glyph.rect.w = 0; font->last_glyph.rect.h = font->height; source_string = font->loading_string; for(; *source_string != '\0'; source_string = U8_next(source_string)) { memset(buff, 0, 5); if(!U8_charcpy(buff, source_string, 5)) continue; glyph_surf = TTF_RenderUTF8_Blended(ttf, buff, white); if(glyph_surf == NULL) continue; // Try packing. If it fails, create a new surface for the next cache level. packed = (FC_PackGlyphData(font, FC_GetCodepointFromUTF8(&buff_ptr, 0), glyph_surf->w, surfaces[num_surfaces-1]->w, surfaces[num_surfaces-1]->h) != NULL); if(!packed) { int i = num_surfaces-1; if(num_surfaces >= FC_LOAD_MAX_SURFACES) { // Can't do any more! FC_Log("SDL_FontCache error: Could not create enough cache surfaces to fit all of the loading string!\n"); SDL_FreeSurface(glyph_surf); break; } // Upload the current surface to the glyph cache now so we can keep the cache level packing cursor up to date as we go. FC_UploadGlyphCache(font, i, surfaces[i]); SDL_FreeSurface(surfaces[i]); #ifndef FC_USE_SDL_GPU SDL_SetTextureBlendMode(font->glyph_cache[i], SDL_BLENDMODE_BLEND); #endif // Update the glyph cursor to the new cache level. We need to do this here because the actual cache lags behind our use of the packing above. font->last_glyph.cache_level = num_surfaces; surfaces[num_surfaces] = FC_CreateSurface32(w, h); num_surfaces++; } // Try packing for the new surface, then blit onto it. if(packed || FC_PackGlyphData(font, FC_GetCodepointFromUTF8(&buff_ptr, 0), glyph_surf->w, surfaces[num_surfaces-1]->w, surfaces[num_surfaces-1]->h) != NULL) { SDL_SetSurfaceBlendMode(glyph_surf, SDL_BLENDMODE_NONE); SDL_Rect srcRect = {0, 0, glyph_surf->w, glyph_surf->h}; SDL_Rect destrect = font->last_glyph.rect; SDL_BlitSurface(glyph_surf, &srcRect, surfaces[num_surfaces-1], &destrect); } SDL_FreeSurface(glyph_surf); } { int i = num_surfaces-1; FC_UploadGlyphCache(font, i, surfaces[i]); SDL_FreeSurface(surfaces[i]); #ifndef FC_USE_SDL_GPU SDL_SetTextureBlendMode(font->glyph_cache[i], SDL_BLENDMODE_BLEND); #endif } } return 1; } #ifdef FC_USE_SDL_GPU Uint8 FC_LoadFont(FC_Font* font, const char* filename_ttf, Uint32 pointSize, SDL_Color color, int style) #else Uint8 FC_LoadFont(FC_Font* font, FC_Target* renderer, const char* filename_ttf, Uint32 pointSize, SDL_Color color, int style) #endif { SDL_RWops* rwops; if(font == NULL) return 0; rwops = SDL_RWFromFile(filename_ttf, "rb"); if(rwops == NULL) { FC_Log("Unable to open file for reading: %s \n", SDL_GetError()); return 0; } #ifdef FC_USE_SDL_GPU return FC_LoadFont_RW(font, rwops, 1, pointSize, color, style); #else return FC_LoadFont_RW(font, renderer, rwops, 1, pointSize, color, style); #endif } #ifdef FC_USE_SDL_GPU Uint8 FC_LoadFont_RW(FC_Font* font, SDL_RWops* file_rwops_ttf, Uint8 own_rwops, Uint32 pointSize, SDL_Color color, int style) #else Uint8 FC_LoadFont_RW(FC_Font* font, FC_Target* renderer, SDL_RWops* file_rwops_ttf, Uint8 own_rwops, Uint32 pointSize, SDL_Color color, int style) #endif { Uint8 result; TTF_Font* ttf; Uint8 outline; if(font == NULL) return 0; if(!TTF_WasInit() && TTF_Init() < 0) { FC_Log("Unable to initialize SDL_ttf: %s \n", TTF_GetError()); if(own_rwops) SDL_RWclose(file_rwops_ttf); return 0; } ttf = TTF_OpenFontRW(file_rwops_ttf, own_rwops, pointSize); if(ttf == NULL) { FC_Log("Unable to load TrueType font: %s \n", TTF_GetError()); if(own_rwops) SDL_RWclose(file_rwops_ttf); return 0; } outline = (style & TTF_STYLE_OUTLINE); if(outline) { style &= ~TTF_STYLE_OUTLINE; TTF_SetFontOutline(ttf, 1); } TTF_SetFontStyle(ttf, style); #ifdef FC_USE_SDL_GPU result = FC_LoadFontFromTTF(font, ttf, color); #else result = FC_LoadFontFromTTF(font, renderer, ttf, color); #endif // Can only load new (uncached) glyphs if we can keep the SDL_RWops open. font->owns_ttf_source = own_rwops; if(!own_rwops) { TTF_CloseFont(font->ttf_source); font->ttf_source = NULL; } return result; } #ifndef FC_USE_SDL_GPU void FC_ResetFontFromRendererReset(FC_Font* font, SDL_Renderer* renderer, Uint32 evType) { TTF_Font* ttf; SDL_Color col; Uint8 owns_ttf; if (font == NULL) return; // Destroy glyph cache if (evType == SDL_RENDER_TARGETS_RESET) { int i; for (i = 0; i < font->glyph_cache_count; ++i) SDL_DestroyTexture(font->glyph_cache[i]); } free(font->glyph_cache); ttf = font->ttf_source; col = font->default_color; owns_ttf = font->owns_ttf_source; FC_Init(font); // Can only reload glyphs if we own the SDL_RWops. if (owns_ttf) FC_LoadFontFromTTF(font, renderer, ttf, col); font->owns_ttf_source = owns_ttf; } #endif void FC_ClearFont(FC_Font* font) { int i; if(font == NULL) return; // Release resources if(font->owns_ttf_source) TTF_CloseFont(font->ttf_source); font->owns_ttf_source = 0; font->ttf_source = NULL; // Delete glyph map FC_MapFree(font->glyphs); font->glyphs = NULL; // Delete glyph cache for(i = 0; i < font->glyph_cache_count; ++i) { #ifdef FC_USE_SDL_GPU GPU_FreeImage(font->glyph_cache[i]); #else SDL_DestroyTexture(font->glyph_cache[i]); #endif } free(font->glyph_cache); font->glyph_cache = NULL; delete font->mutex; font->mutex = nullptr; // Reset font FC_Init(font); } void FC_FreeFont(FC_Font* font) { int i; if(font == NULL) return; // Release resources if(font->owns_ttf_source) TTF_CloseFont(font->ttf_source); // Delete glyph map FC_MapFree(font->glyphs); // Delete glyph cache for(i = 0; i < font->glyph_cache_count; ++i) { #ifdef FC_USE_SDL_GPU GPU_FreeImage(font->glyph_cache[i]); #else SDL_DestroyTexture(font->glyph_cache[i]); #endif } free(font->glyph_cache); free(font->loading_string); free(font); // If the last font has been freed; assume shutdown and free the global variables if (--NUM_EXISTING_FONTS <= 0) { free(ASCII_STRING); ASCII_STRING = NULL; free(LATIN_1_STRING); LATIN_1_STRING = NULL; free(ASCII_LATIN_1_STRING); ASCII_LATIN_1_STRING = NULL; free(fc_buffer); fc_buffer = NULL; } } int FC_GetNumCacheLevels(FC_Font* font) { return font->glyph_cache_count; } Uint8 FC_AddGlyphToCache(FC_Font* font, SDL_Surface* glyph_surface) { if(font == NULL || glyph_surface == NULL) return 0; SDL_SetSurfaceBlendMode(glyph_surface, SDL_BLENDMODE_NONE); FC_Image* dest = FC_GetGlyphCacheLevel(font, font->last_glyph.cache_level); if(dest == NULL) return 0; #ifdef FC_USE_SDL_GPU { GPU_Target* target = GPU_LoadTarget(dest); if(target == NULL) return 0; GPU_Image* img = GPU_CopyImageFromSurface(glyph_surface); GPU_SetAnchor(img, 0.5f, 0.5f); // Just in case the default is different GPU_SetImageFilter(img, GPU_FILTER_NEAREST); GPU_SetBlendMode(img, GPU_BLEND_SET); SDL_Rect destrect = font->last_glyph.rect; GPU_Blit(img, NULL, target, destrect.x + destrect.w/2, destrect.y + destrect.h/2); GPU_FreeImage(img); GPU_FreeTarget(target); } #else { SDL_Renderer* renderer = font->renderer; SDL_Texture* img; SDL_Rect destrect; SDL_Texture* prev_target = SDL_GetRenderTarget(renderer); SDL_Rect prev_clip, prev_viewport; int prev_logicalw, prev_logicalh; Uint8 prev_clip_enabled; float prev_scalex, prev_scaley; // only backup if previous target existed (SDL will preserve them for the default target) if (prev_target) { prev_clip_enabled = has_clip(renderer); if (prev_clip_enabled) prev_clip = get_clip(renderer); SDL_RenderGetViewport(renderer, &prev_viewport); SDL_RenderGetScale(renderer, &prev_scalex, &prev_scaley); SDL_RenderGetLogicalSize(renderer, &prev_logicalw, &prev_logicalh); } img = SDL_CreateTextureFromSurface(renderer, glyph_surface); destrect = font->last_glyph.rect; SDL_SetRenderTarget(renderer, dest); SDL_RenderCopy(renderer, img, NULL, &destrect); SDL_SetRenderTarget(renderer, prev_target); if (prev_target) { if (prev_clip_enabled) set_clip(renderer, &prev_clip); if (prev_logicalw && prev_logicalh) SDL_RenderSetLogicalSize(renderer, prev_logicalw, prev_logicalh); else { SDL_RenderSetViewport(renderer, &prev_viewport); SDL_RenderSetScale(renderer, prev_scalex, prev_scaley); } } SDL_DestroyTexture(img); } #endif return 1; } unsigned int FC_GetNumCodepoints(FC_Font* font) { FC_Map* glyphs; int i; unsigned int result = 0; if(font == NULL || font->glyphs == NULL) return 0; glyphs = font->glyphs; for(i = 0; i < glyphs->num_buckets; ++i) { FC_MapNode* node; for(node = glyphs->buckets[i]; node != NULL; node = node->next) { result++; } } return result; } void FC_GetCodepoints(FC_Font* font, Uint32* result) { FC_Map* glyphs; int i; unsigned int count = 0; if(font == NULL || font->glyphs == NULL) return; glyphs = font->glyphs; for(i = 0; i < glyphs->num_buckets; ++i) { FC_MapNode* node; for(node = glyphs->buckets[i]; node != NULL; node = node->next) { result[count] = node->key; count++; } } } Uint8 FC_GetGlyphData(FC_Font* font, FC_GlyphData* result, Uint32 codepoint) { FC_GlyphData* e = FC_MapFind(font->glyphs, codepoint); if(e == NULL) { char buff[5]; int w, h; SDL_Color white = {255, 255, 255, 255}; SDL_Surface* surf; FC_Image* cache_image; if(font->ttf_source == NULL) return 0; FC_GetUTF8FromCodepoint(buff, codepoint); cache_image = FC_GetGlyphCacheLevel(font, font->last_glyph.cache_level); if(cache_image == NULL) { FC_Log("SDL_FontCache: Failed to load cache image, so cannot add new glyphs!\n"); return 0; } #ifdef FC_USE_SDL_GPU w = cache_image->w; h = cache_image->h; #else SDL_QueryTexture(cache_image, NULL, NULL, &w, &h); #endif surf = TTF_RenderUTF8_Blended(font->ttf_source, buff, white); if(surf == NULL) { return 0; } e = FC_PackGlyphData(font, codepoint, surf->w, w, h); if(e == NULL) { // Grow the cache FC_GrowGlyphCache(font); // Try packing again e = FC_PackGlyphData(font, codepoint, surf->w, w, h); if(e == NULL) { SDL_FreeSurface(surf); return 0; } } // Render onto the cache texture FC_AddGlyphToCache(font, surf); SDL_FreeSurface(surf); } if(result != NULL && e != NULL) *result = *e; return 1; } FC_GlyphData* FC_SetGlyphData(FC_Font* font, Uint32 codepoint, FC_GlyphData glyph_data) { return FC_MapInsert(font->glyphs, codepoint, glyph_data); } // Drawing static FC_Rect FC_RenderLeft(FC_Font* font, FC_Target* dest, float x, float y, FC_Scale scale, const char* text) { const char* c = text; FC_Rect srcRect; FC_Rect dstRect; FC_Rect dirtyRect = FC_MakeRect(x, y, 0, 0); FC_GlyphData glyph; Uint32 codepoint; float destX = x; float destY = y; float destH; float destLineSpacing; float destLetterSpacing; if(font == NULL) return dirtyRect; destH = font->height * scale.y; destLineSpacing = font->lineSpacing*scale.y; destLetterSpacing = font->letterSpacing*scale.x; if(c == NULL || font->glyph_cache_count == 0 || dest == NULL) return dirtyRect; int newlineX = x; for(; *c != '\0'; c++) { if(*c == '\n') { destX = newlineX; destY += destH + destLineSpacing; continue; } codepoint = FC_GetCodepointFromUTF8(&c, 1); // Increments 'c' to skip the extra UTF-8 bytes if(!FC_GetGlyphData(font, &glyph, codepoint)) { codepoint = ' '; if(!FC_GetGlyphData(font, &glyph, codepoint)) continue; // Skip bad characters } if (codepoint == ' ') { destX += glyph.rect.w*scale.x + destLetterSpacing; continue; } /*if(destX >= dest->w) continue; if(destY >= dest->h) continue;*/ #ifdef FC_USE_SDL_GPU srcRect.x = glyph.rect.x; srcRect.y = glyph.rect.y; srcRect.w = glyph.rect.w; srcRect.h = glyph.rect.h; #else srcRect = glyph.rect; #endif dstRect = fc_render_callback(FC_GetGlyphCacheLevel(font, glyph.cache_level), &srcRect, dest, destX, destY, scale.x, scale.y); if(dirtyRect.w == 0 || dirtyRect.h == 0) dirtyRect = dstRect; else dirtyRect = FC_RectUnion(dirtyRect, dstRect); destX += glyph.rect.w*scale.x + destLetterSpacing; } return dirtyRect; } static void set_color_for_all_caches(FC_Font* font, SDL_Color color) { // TODO: How can I predict which glyph caches are to be used? FC_Image* img; int i; int num_levels = FC_GetNumCacheLevels(font); for(i = 0; i < num_levels; ++i) { img = FC_GetGlyphCacheLevel(font, i); set_color(img, color.r, color.g, color.b, FC_GET_ALPHA(color)); } } FC_Rect FC_Draw(FC_Font* font, FC_Target* dest, float x, float y, const char* formatted_text, ...) { if(formatted_text == NULL || font == NULL) return FC_MakeRect(x, y, 0, 0); std::scoped_lock lock(*font->mutex); FC_EXTRACT_VARARGS(fc_buffer, formatted_text); set_color_for_all_caches(font, font->default_color); auto res = FC_RenderLeft(font, dest, x, y, FC_MakeScale(1,1), fc_buffer); return res; } typedef struct FC_StringList { char* value; struct FC_StringList* next; } FC_StringList; void FC_StringListFree(FC_StringList* node) { // Delete the nodes in order while(node != NULL) { FC_StringList* last = node; node = node->next; free(last->value); free(last); } } FC_StringList** FC_StringListPushBack(FC_StringList** node, char* value, Uint8 copy) { if(node == NULL) { return NULL; } // Get to the last node while(*node != NULL) { node = &(*node)->next; } *node = (FC_StringList*)malloc(sizeof(FC_StringList)); (*node)->value = (copy? U8_strdup(value) : value); (*node)->next = NULL; return node; } FC_StringList** FC_StringListPushBackBytes(FC_StringList** node, const char* data, int num_bytes) { if(node == NULL) { return node; } // Get to the last node while(*node != NULL) { node = &(*node)->next; } *node = (FC_StringList*)malloc(sizeof(FC_StringList)); (*node)->value = (char*)malloc(num_bytes + 1); memcpy((*node)->value, data, num_bytes); (*node)->value[num_bytes] = '\0'; (*node)->next = NULL; return node; } static FC_StringList* FC_Explode(const char* text, char delimiter) { FC_StringList* head; FC_StringList* new_node; FC_StringList** node; const char* start; const char* end; unsigned int size; if(text == NULL) return NULL; head = NULL; node = &head; // Doesn't technically support UTF-8, but it's probably fine, right? size = 0; start = end = text; while(1) { if(*end == delimiter || *end == '\0') { *node = (FC_StringList*)malloc(sizeof(FC_StringList)); new_node = *node; new_node->value = (char*)malloc(size + 1); memcpy(new_node->value, start, size); new_node->value[size] = '\0'; new_node->next = NULL; if(*end == '\0') break; node = &((*node)->next); start = end+1; size = 0; } else ++size; ++end; } return head; } static FC_StringList* FC_ExplodeBreakingSpace(const char* text, FC_StringList** spaces) { FC_StringList* head; FC_StringList** node; const char* start; const char* end; unsigned int size; if(text == NULL) return NULL; head = NULL; node = &head; // Warning: spaces must not be initialized before this function *spaces = NULL; // Doesn't technically support UTF-8, but it's probably fine, right? size = 0; start = end = text; while(1) { // Add any characters here that should make separate words (except for \n?) if(*end == ' ' || *end == '\t' || *end == '\0') { FC_StringListPushBackBytes(node, start, size); FC_StringListPushBackBytes(spaces, end, 1); if(*end == '\0') break; node = &((*node)->next); start = end+1; size = 0; } else ++size; ++end; } return head; } static FC_StringList* FC_ExplodeAndKeep(const char* text, char delimiter) { FC_StringList* head; FC_StringList** node; const char* start; const char* end; unsigned int size; if(text == NULL) return NULL; head = NULL; node = &head; // Doesn't technically support UTF-8, but it's probably fine, right? size = 0; start = end = text; while(1) { if(*end == delimiter || *end == '\0') { FC_StringListPushBackBytes(node, start, size); if(*end == '\0') break; node = &((*node)->next); start = end; size = 1; } else ++size; ++end; } return head; } static void FC_RenderAlign(FC_Font* font, FC_Target* dest, float x, float y, int width, FC_Scale scale, FC_AlignEnum align, const char* text) { switch(align) { case FC_ALIGN_LEFT: FC_RenderLeft(font, dest, x, y, scale, text); break; case FC_ALIGN_CENTER: FC_RenderCenter(font, dest, x + width/2, y, scale, text); break; case FC_ALIGN_RIGHT: FC_RenderRight(font, dest, x + width, y, scale, text); break; } } static FC_StringList* FC_GetBufferFitToColumn(FC_Font* font, int width, FC_Scale scale, Uint8 keep_newlines) { FC_StringList* result = NULL; FC_StringList** current = &result; FC_StringList *ls, *iter; ls = (keep_newlines? FC_ExplodeAndKeep(fc_buffer, '\n') : FC_Explode(fc_buffer, '\n')); for(iter = ls; iter != NULL; iter = iter->next) { char* line = iter->value; // If line is too long, then add words one at a time until we go over. if(width > 0 && FC_GetWidth(font, "%s", line) > width) { FC_StringList *words, *word_iter, *spaces, *spaces_iter; words = FC_ExplodeBreakingSpace(line, &spaces); // Skip the first word for the iterator, so there will always be at least one word per line line = new_concat(words->value, spaces->value); for(word_iter = words->next, spaces_iter = spaces->next; word_iter != NULL && spaces_iter != NULL; word_iter = word_iter->next, spaces_iter = spaces_iter->next) { char* line_plus_word = new_concat(line, word_iter->value); char* word_plus_space = new_concat(word_iter->value, spaces_iter->value); if(FC_GetWidth(font, "%s", line_plus_word) > width) { current = FC_StringListPushBack(current, line, 0); line = word_plus_space; } else { replace_concat(&line, word_plus_space); free(word_plus_space); } free(line_plus_word); } current = FC_StringListPushBack(current, line, 0); FC_StringListFree(words); FC_StringListFree(spaces); } else { current = FC_StringListPushBack(current, line, 0); iter->value = NULL; } } FC_StringListFree(ls); return result; } static void FC_DrawColumnFromBuffer(FC_Font* font, FC_Target* dest, FC_Rect box, int* total_height, FC_Scale scale, FC_AlignEnum align) { int y = box.y; FC_StringList *ls, *iter; ls = FC_GetBufferFitToColumn(font, box.w, scale, 0); int8_t onFirst = 1; for(iter = ls; iter != NULL; iter = iter->next) { FC_RenderAlign(font, dest, box.x, y, box.w, scale, onFirst ? FC_ALIGN_LEFT : align, iter->value); y += FC_GetLineHeight(font); onFirst = 0; } FC_StringListFree(ls); if(total_height != NULL) *total_height = y - box.y; } FC_Rect FC_DrawBox(FC_Font* font, FC_Target* dest, FC_Rect box, const char* formatted_text, ...) { Uint8 useClip; if(formatted_text == NULL || font == NULL) return FC_MakeRect(box.x, box.y, 0, 0); std::scoped_lock lock(*font->mutex); FC_EXTRACT_VARARGS(fc_buffer, formatted_text); useClip = has_clip(dest); FC_Rect oldclip, newclip; if(useClip) { oldclip = get_clip(dest); newclip = FC_RectIntersect(oldclip, box); } else newclip = box; set_clip(dest, &newclip); set_color_for_all_caches(font, font->default_color); FC_DrawColumnFromBuffer(font, dest, box, NULL, FC_MakeScale(1,1), FC_ALIGN_LEFT); if(useClip) set_clip(dest, &oldclip); else set_clip(dest, NULL); return box; } FC_Rect FC_DrawBoxAlign(FC_Font* font, FC_Target* dest, FC_Rect box, FC_AlignEnum align, const char* formatted_text, ...) { Uint8 useClip; if(formatted_text == NULL || font == NULL) return FC_MakeRect(box.x, box.y, 0, 0); std::scoped_lock lock(*font->mutex); FC_EXTRACT_VARARGS(fc_buffer, formatted_text); useClip = has_clip(dest); FC_Rect oldclip, newclip; if(useClip) { oldclip = get_clip(dest); newclip = FC_RectIntersect(oldclip, box); } else newclip = box; set_clip(dest, &newclip); set_color_for_all_caches(font, font->default_color); FC_DrawColumnFromBuffer(font, dest, box, NULL, FC_MakeScale(1,1), align); if(useClip) set_clip(dest, &oldclip); else set_clip(dest, NULL); return box; } FC_Rect FC_DrawBoxScale(FC_Font* font, FC_Target* dest, FC_Rect box, FC_Scale scale, const char* formatted_text, ...) { Uint8 useClip; if(formatted_text == NULL || font == NULL) return FC_MakeRect(box.x, box.y, 0, 0); std::scoped_lock lock(*font->mutex); FC_EXTRACT_VARARGS(fc_buffer, formatted_text); useClip = has_clip(dest); FC_Rect oldclip, newclip; if(useClip) { oldclip = get_clip(dest); newclip = FC_RectIntersect(oldclip, box); } else newclip = box; set_clip(dest, &newclip); set_color_for_all_caches(font, font->default_color); FC_DrawColumnFromBuffer(font, dest, box, NULL, scale, FC_ALIGN_LEFT); if(useClip) set_clip(dest, &oldclip); else set_clip(dest, NULL); return box; } FC_Rect FC_DrawBoxColor(FC_Font* font, FC_Target* dest, FC_Rect box, SDL_Color color, const char* formatted_text, ...) { Uint8 useClip; if(formatted_text == NULL || font == NULL) return FC_MakeRect(box.x, box.y, 0, 0); std::scoped_lock lock(*font->mutex); FC_EXTRACT_VARARGS(fc_buffer, formatted_text); useClip = has_clip(dest); FC_Rect oldclip, newclip; if(useClip) { oldclip = get_clip(dest); newclip = FC_RectIntersect(oldclip, box); } else newclip = box; set_clip(dest, &newclip); set_color_for_all_caches(font, color); FC_DrawColumnFromBuffer(font, dest, box, NULL, FC_MakeScale(1,1), FC_ALIGN_LEFT); if(useClip) set_clip(dest, &oldclip); else set_clip(dest, NULL); return box; } FC_Rect FC_DrawBoxEffect(FC_Font* font, FC_Target* dest, FC_Rect box, FC_Effect effect, const char* formatted_text, ...) { Uint8 useClip; if(formatted_text == NULL || font == NULL) return FC_MakeRect(box.x, box.y, 0, 0); std::scoped_lock lock(*font->mutex); FC_EXTRACT_VARARGS(fc_buffer, formatted_text); useClip = has_clip(dest); FC_Rect oldclip, newclip; if(useClip) { oldclip = get_clip(dest); newclip = FC_RectIntersect(oldclip, box); } else newclip = box; set_clip(dest, &newclip); set_color_for_all_caches(font, effect.color); FC_DrawColumnFromBuffer(font, dest, box, NULL, effect.scale, effect.alignment); if(useClip) set_clip(dest, &oldclip); else set_clip(dest, NULL); return box; } FC_Rect FC_DrawColumn(FC_Font* font, FC_Target* dest, float x, float y, Uint16 width, const char* formatted_text, ...) { FC_Rect box = {(int) x, (int) y, (int) width, (int) 0}; int total_height; if(formatted_text == NULL || font == NULL) return FC_MakeRect(x, y, 0, 0); std::scoped_lock lock(*font->mutex); FC_EXTRACT_VARARGS(fc_buffer, formatted_text); set_color_for_all_caches(font, font->default_color); FC_DrawColumnFromBuffer(font, dest, box, &total_height, FC_MakeScale(1,1), FC_ALIGN_LEFT); auto res = FC_MakeRect(box.x, box.y, width, total_height); return res; } FC_Rect FC_DrawColumnToTexture(FC_Font* font, SDL_Texture* target, float x, float y, Uint16 width, const char* text) { std::scoped_lock lock(*font->mutex); // Draw the text onto it SDL_SetRenderTarget(font->renderer, target); // make sure the texture is clean. SDL_RenderClear(font->renderer); FC_Rect res = FC_DrawColumn(font, font->renderer, x, y, width, text); SDL_SetRenderTarget(font->renderer, NULL); return res; } FC_Rect FC_DrawColumnAlign(FC_Font* font, FC_Target* dest, float x, float y, Uint16 width, FC_AlignEnum align, const char* formatted_text, ...) { FC_Rect box = {(int) x, (int) y, (int) width, (int) 0}; int total_height; if(formatted_text == NULL || font == NULL) return FC_MakeRect(x, y, 0, 0); std::scoped_lock lock(*font->mutex); FC_EXTRACT_VARARGS(fc_buffer, formatted_text); set_color_for_all_caches(font, font->default_color); switch(align) { case FC_ALIGN_CENTER: box.x -= width/2; break; case FC_ALIGN_RIGHT: box.x -= width; break; default: break; } FC_DrawColumnFromBuffer(font, dest, box, &total_height, FC_MakeScale(1,1), align); auto res = FC_MakeRect(box.x, box.y, width, total_height); return res; } FC_Rect FC_DrawColumnScale(FC_Font* font, FC_Target* dest, float x, float y, Uint16 width, FC_Scale scale, const char* formatted_text, ...) { FC_Rect box = {(int) x, (int) y, (int) width, (int) 0}; int total_height; if(formatted_text == NULL || font == NULL) return FC_MakeRect(x, y, 0, 0); std::scoped_lock lock(*font->mutex); FC_EXTRACT_VARARGS(fc_buffer, formatted_text); set_color_for_all_caches(font, font->default_color); FC_DrawColumnFromBuffer(font, dest, box, &total_height, scale, FC_ALIGN_CENTER); auto res = FC_MakeRect(box.x, box.y, width, total_height); return res; } FC_Rect FC_DrawColumnColor(FC_Font* font, FC_Target* dest, float x, float y, Uint16 width, SDL_Color color, const char* formatted_text, ...) { FC_Rect box = {(int) x, (int) y, (int) width, (int) 0}; int total_height; if(formatted_text == NULL || font == NULL) return FC_MakeRect(x, y, 0, 0); std::scoped_lock lock(*font->mutex); FC_EXTRACT_VARARGS(fc_buffer, formatted_text); set_color_for_all_caches(font, color); FC_DrawColumnFromBuffer(font, dest, box, &total_height, FC_MakeScale(1,1), FC_ALIGN_LEFT); auto res = FC_MakeRect(box.x, box.y, width, total_height); return res; } FC_Rect FC_DrawColumnEffect(FC_Font* font, FC_Target* dest, float x, float y, Uint16 width, FC_Effect effect, const char* formatted_text, ...) { FC_Rect box = {(int) x, (int) y, (int) width, (int) 0}; int total_height; if(formatted_text == NULL || font == NULL) return FC_MakeRect(x, y, 0, 0); std::scoped_lock lock(*font->mutex); FC_EXTRACT_VARARGS(fc_buffer, formatted_text); set_color_for_all_caches(font, effect.color); switch(effect.alignment) { case FC_ALIGN_CENTER: box.x -= width/2; break; case FC_ALIGN_RIGHT: box.x -= width; break; default: break; } FC_DrawColumnFromBuffer(font, dest, box, &total_height, effect.scale, effect.alignment); auto res = FC_MakeRect(box.x, box.y, width, total_height); return res; } static FC_Rect FC_RenderCenter(FC_Font* font, FC_Target* dest, float x, float y, FC_Scale scale, const char* text) { FC_Rect result = {(int) x, (int) y, (int) 0, (int) 0}; if(text == NULL || font == NULL) return result; char* str = U8_strdup(text); char* del = str; char* c; // Go through str, when you find a \n, replace it with \0 and print it // then move down, back, and continue. for(c = str; *c != '\0';) { if(*c == '\n') { *c = '\0'; result = FC_RectUnion(FC_RenderLeft(font, dest, x - scale.x*FC_GetWidth(font, "%s", str)/2.0f, y, scale, str), result); *c = '\n'; c++; str = c; y += scale.y*font->height; } else c++; } result = FC_RectUnion(FC_RenderLeft(font, dest, x - scale.x*FC_GetWidth(font, "%s", str)/2.0f, y, scale, str), result); free(del); return result; } static FC_Rect FC_RenderRight(FC_Font* font, FC_Target* dest, float x, float y, FC_Scale scale, const char* text) { FC_Rect result = {(int) x, (int) y, (int) 0, (int) 0}; if(text == NULL || font == NULL) return result; char* str = U8_strdup(text); char* del = str; char* c; for(c = str; *c != '\0';) { if(*c == '\n') { *c = '\0'; result = FC_RectUnion(FC_RenderLeft(font, dest, x - scale.x*FC_GetWidth(font, "%s", str), y, scale, str), result); *c = '\n'; c++; str = c; y += scale.y*font->height; } else c++; } result = FC_RectUnion(FC_RenderLeft(font, dest, x - scale.x*FC_GetWidth(font, "%s", str), y, scale, str), result); free(del); return result; } FC_Rect FC_DrawScale(FC_Font* font, FC_Target* dest, float x, float y, FC_Scale scale, const char* formatted_text, ...) { if(formatted_text == NULL || font == NULL) return FC_MakeRect(x, y, 0, 0); std::scoped_lock lock(*font->mutex); FC_EXTRACT_VARARGS(fc_buffer, formatted_text); set_color_for_all_caches(font, font->default_color); auto res = FC_RenderLeft(font, dest, x, y, scale, fc_buffer); return res; } FC_Rect FC_DrawAlign(FC_Font* font, FC_Target* dest, float x, float y, FC_AlignEnum align, const char* formatted_text, ...) { if(formatted_text == NULL || font == NULL) return FC_MakeRect(x, y, 0, 0); std::scoped_lock lock(*font->mutex); FC_EXTRACT_VARARGS(fc_buffer, formatted_text); set_color_for_all_caches(font, font->default_color); FC_Rect result; switch(align) { case FC_ALIGN_LEFT: result = FC_RenderLeft(font, dest, x, y, FC_MakeScale(1,1), fc_buffer); break; case FC_ALIGN_CENTER: result = FC_RenderCenter(font, dest, x, y, FC_MakeScale(1,1), fc_buffer); break; case FC_ALIGN_RIGHT: result = FC_RenderRight(font, dest, x, y, FC_MakeScale(1,1), fc_buffer); break; default: result = FC_MakeRect(x, y, 0, 0); break; } return result; } FC_Rect FC_DrawColor(FC_Font* font, FC_Target* dest, float x, float y, SDL_Color color, const char* formatted_text, ...) { if(formatted_text == NULL || font == NULL) return FC_MakeRect(x, y, 0, 0); std::scoped_lock lock(*font->mutex); FC_EXTRACT_VARARGS(fc_buffer, formatted_text); set_color_for_all_caches(font, color); auto res = FC_RenderLeft(font, dest, x, y, FC_MakeScale(1,1), fc_buffer); return res; } FC_Rect FC_DrawEffect(FC_Font* font, FC_Target* dest, float x, float y, FC_Effect effect, const char* formatted_text, ...) { if(formatted_text == NULL || font == NULL) return FC_MakeRect(x, y, 0, 0); std::scoped_lock lock(*font->mutex); FC_EXTRACT_VARARGS(fc_buffer, formatted_text); set_color_for_all_caches(font, effect.color); FC_Rect result; switch(effect.alignment) { case FC_ALIGN_LEFT: result = FC_RenderLeft(font, dest, x, y, effect.scale, fc_buffer); break; case FC_ALIGN_CENTER: result = FC_RenderCenter(font, dest, x, y, effect.scale, fc_buffer); break; case FC_ALIGN_RIGHT: result = FC_RenderRight(font, dest, x, y, effect.scale, fc_buffer); break; default: result = FC_MakeRect(x, y, 0, 0); break; } return result; } // Getters FC_FilterEnum FC_GetFilterMode(FC_Font* font) { if(font == NULL) return FC_FILTER_NEAREST; return font->filter; } Uint16 FC_GetLineHeight(FC_Font* font) { if(font == NULL) return 0; return font->height; } Uint16 FC_GetHeight(FC_Font* font, const char* formatted_text, ...) { if(formatted_text == NULL || font == NULL) return 0; std::scoped_lock lock(*font->mutex); FC_EXTRACT_VARARGS(fc_buffer, formatted_text); Uint16 numLines = 1; const char* c; for (c = fc_buffer; *c != '\0'; c++) { if(*c == '\n') numLines++; } // Actual height of letter region + line spacing auto res = font->height*numLines + font->lineSpacing*(numLines - 1); //height*numLines; return res; } Uint16 FC_GetWidth(FC_Font* font, const char* formatted_text, ...) { if(formatted_text == NULL || font == NULL) return 0; std::scoped_lock lock(*font->mutex); FC_EXTRACT_VARARGS(fc_buffer, formatted_text); const char* c; Uint16 width = 0; Uint16 bigWidth = 0; // Allows for multi-line strings for (c = fc_buffer; *c != '\0'; c++) { if(*c == '\n') { bigWidth = bigWidth >= width? bigWidth : width; width = 0; continue; } FC_GlyphData glyph; Uint32 codepoint = FC_GetCodepointFromUTF8(&c, 1); if(FC_GetGlyphData(font, &glyph, codepoint) || FC_GetGlyphData(font, &glyph, ' ')) width += glyph.rect.w; } bigWidth = bigWidth >= width? bigWidth : width; return bigWidth; } // If width == -1, use no width limit FC_Rect FC_GetCharacterOffset(FC_Font* font, Uint16 position_index, int column_width, const char* formatted_text, ...) { FC_Rect result = {0, 0, 1, FC_GetLineHeight(font)}; FC_StringList *ls, *iter; int num_lines = 0; Uint8 done = 0; if(formatted_text == NULL || column_width == 0 || position_index == 0 || font == NULL) return result; std::scoped_lock lock(*font->mutex); FC_EXTRACT_VARARGS(fc_buffer, formatted_text); ls = FC_GetBufferFitToColumn(font, column_width, FC_MakeScale(1,1), 1); for(iter = ls; iter != NULL;) { char* line; int i = 0; FC_StringList* next_iter = iter->next; ++num_lines; for(line = iter->value; line != NULL && *line != '\0'; line = (char*)U8_next(line)) { ++i; --position_index; if(position_index == 0) { // FIXME: Doesn't handle box-wrapped newlines correctly line = (char*)U8_next(line); line[0] = '\0'; result.x = FC_GetWidth(font, "%s", iter->value); done = 1; break; } } if(done) break; // Prevent line wrapping if there are no more lines if(next_iter == NULL && !done) result.x = FC_GetWidth(font, "%s", iter->value); iter = next_iter; } FC_StringListFree(ls); if(num_lines > 1) { result.y = (num_lines - 1) * FC_GetLineHeight(font); } return result; } Uint16 FC_GetColumnHeight(FC_Font* font, Uint16 width, const char* formatted_text, ...) { int y = 0; FC_StringList *ls, *iter; if(font == NULL) return 0; if(formatted_text == NULL || width == 0) return font->height; std::scoped_lock lock(*font->mutex); FC_EXTRACT_VARARGS(fc_buffer, formatted_text); ls = FC_GetBufferFitToColumn(font, width, FC_MakeScale(1,1), 0); for(iter = ls; iter != NULL; iter = iter->next) { y += FC_GetLineHeight(font); } FC_StringListFree(ls); return y; } static int FC_GetAscentFromCodepoint(FC_Font* font, Uint32 codepoint) { FC_GlyphData glyph; if(font == NULL) return 0; // FIXME: Store ascent so we can return it here FC_GetGlyphData(font, &glyph, codepoint); return glyph.rect.h; } static int FC_GetDescentFromCodepoint(FC_Font* font, Uint32 codepoint) { FC_GlyphData glyph; if(font == NULL) return 0; // FIXME: Store descent so we can return it here FC_GetGlyphData(font, &glyph, codepoint); return glyph.rect.h; } int FC_GetAscent(FC_Font* font, const char* formatted_text, ...) { Uint32 codepoint; int max, ascent; const char* c; if(font == NULL) return 0; if(formatted_text == NULL) return font->ascent; std::scoped_lock lock(*font->mutex); FC_EXTRACT_VARARGS(fc_buffer, formatted_text); max = 0; c = fc_buffer; while(*c != '\0') { codepoint = FC_GetCodepointFromUTF8(&c, 1); if(codepoint != 0) { ascent = FC_GetAscentFromCodepoint(font, codepoint); if(ascent > max) max = ascent; } ++c; } return max; } int FC_GetDescent(FC_Font* font, const char* formatted_text, ...) { Uint32 codepoint; int max, descent; const char* c; if(font == NULL) return 0; if(formatted_text == NULL) return font->descent; std::scoped_lock lock(*font->mutex); FC_EXTRACT_VARARGS(fc_buffer, formatted_text); max = 0; c = fc_buffer; while(*c != '\0') { codepoint = FC_GetCodepointFromUTF8(&c, 1); if(codepoint != 0) { descent = FC_GetDescentFromCodepoint(font, codepoint); if(descent > max) max = descent; } ++c; } return max; } int FC_GetBaseline(FC_Font* font) { if(font == NULL) return 0; return font->baseline; } int FC_GetSpacing(FC_Font* font) { if(font == NULL) return 0; return font->letterSpacing; } int FC_GetLineSpacing(FC_Font* font) { if(font == NULL) return 0; return font->lineSpacing; } Uint16 FC_GetMaxWidth(FC_Font* font) { if(font == NULL) return 0; return font->maxWidth; } SDL_Color FC_GetDefaultColor(FC_Font* font) { if(font == NULL) { SDL_Color c = {0,0,0,255}; return c; } return font->default_color; } FC_Rect FC_GetBounds(FC_Font* font, float x, float y, FC_AlignEnum align, FC_Scale scale, const char* formatted_text, ...) { FC_Rect result = {(int) x, (int) y, (int) 0, (int) 0}; if(formatted_text == NULL) return result; // Create a temp buffer while GetWidth and GetHeight use fc_buffer. char* temp = (char*)malloc(fc_buffer_size); FC_EXTRACT_VARARGS(temp, formatted_text); result.w = FC_GetWidth(font, "%s", temp) * scale.x; result.h = FC_GetHeight(font, "%s", temp) * scale.y; switch(align) { case FC_ALIGN_LEFT: break; case FC_ALIGN_CENTER: result.x -= result.w/2; break; case FC_ALIGN_RIGHT: result.x -= result.w; break; default: break; } free(temp); return result; } Uint8 FC_InRect(float x, float y, FC_Rect input_rect) { return (input_rect.x <= x && x <= input_rect.x + input_rect.w && input_rect.y <= y && y <= input_rect.y + input_rect.h); } // TODO: Make it work with alignment Uint16 FC_GetPositionFromOffset(FC_Font* font, float x, float y, int column_width, FC_AlignEnum align, const char* formatted_text, ...) { FC_StringList *ls, *iter; Uint8 done = 0; int height = FC_GetLineHeight(font); Uint16 position = 0; int current_x = 0; int current_y = 0; FC_GlyphData glyph_data; if(formatted_text == NULL || column_width == 0 || font == NULL) return 0; std::scoped_lock lock(*font->mutex); FC_EXTRACT_VARARGS(fc_buffer, formatted_text); ls = FC_GetBufferFitToColumn(font, column_width, FC_MakeScale(1,1), 1); for(iter = ls; iter != NULL; iter = iter->next) { char* line; for(line = iter->value; line != NULL && *line != '\0'; line = (char*)U8_next(line)) { if(FC_GetGlyphData(font, &glyph_data, FC_GetCodepointFromUTF8((const char**)&line, 0))) { if(FC_InRect(x, y, FC_MakeRect(current_x, current_y, glyph_data.rect.w, glyph_data.rect.h))) { done = 1; break; } current_x += glyph_data.rect.w; } position++; } if(done) break; current_x = 0; current_y += height; if(y < current_y) break; } FC_StringListFree(ls); return position; } int FC_GetWrappedText(FC_Font* font, char* result, int max_result_size, Uint16 width, const char* formatted_text, ...) { FC_StringList *ls, *iter; if(font == NULL) return 0; if(formatted_text == NULL || width == 0) return 0; std::scoped_lock lock(*font->mutex); FC_EXTRACT_VARARGS(fc_buffer, formatted_text); ls = FC_GetBufferFitToColumn(font, width, FC_MakeScale(1,1), 0); int size_so_far = 0; int size_remaining = max_result_size-1; // reserve for \0 for(iter = ls; iter != NULL && size_remaining > 0; iter = iter->next) { // Copy as much of this line as we can int len = strlen(iter->value); int num_bytes = FC_MIN(len, size_remaining); memcpy(&result[size_so_far], iter->value, num_bytes); size_so_far += num_bytes; // If there's another line, add newline character if(size_remaining > 0 && iter->next != NULL) { --size_remaining; result[size_so_far] = '\n'; ++size_so_far; } } FC_StringListFree(ls); result[size_so_far] = '\0'; return size_so_far; } // Setters void FC_SetFilterMode(FC_Font* font, FC_FilterEnum filter) { if(font == NULL) return; if(font->filter != filter) { font->filter = filter; #ifdef FC_USE_SDL_GPU // Update each texture to use this filter mode { int i; GPU_FilterEnum gpu_filter = GPU_FILTER_NEAREST; if(FC_GetFilterMode(font) == FC_FILTER_LINEAR) gpu_filter = GPU_FILTER_LINEAR; for(i = 0; i < font->glyph_cache_count; ++i) { GPU_SetImageFilter(font->glyph_cache[i], gpu_filter); } } #endif } } void FC_SetSpacing(FC_Font* font, int LetterSpacing) { if(font == NULL) return; font->letterSpacing = LetterSpacing; } void FC_SetLineSpacing(FC_Font* font, int LineSpacing) { if(font == NULL) return; font->lineSpacing = LineSpacing; } void FC_SetDefaultColor(FC_Font* font, SDL_Color color) { if(font == NULL) return; font->default_color = color; }