388bc70157
[GB.SDL2] * BUG: Don't crash when drawing one-character static strings.
528 lines
9.9 KiB
C
528 lines
9.9 KiB
C
/***************************************************************************
|
|
|
|
c_font.c
|
|
|
|
(c) 2000-2017 Benoît Minisini <g4mba5@gmail.com>
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2, or (at your option)
|
|
any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
MA 02110-1301, USA.
|
|
|
|
***************************************************************************/
|
|
|
|
#define __C_FONT_C
|
|
|
|
#include "default_font.h"
|
|
#include "c_font.h"
|
|
|
|
typedef
|
|
struct {
|
|
LIST list;
|
|
char *path;
|
|
char *name;
|
|
}
|
|
FONT_LOAD;
|
|
|
|
static FONT_LOAD *_font_list = NULL;
|
|
|
|
#define THIS ((CFONT *)_object)
|
|
|
|
static void create_cache(CFONT *_object)
|
|
{
|
|
if (!THIS->cache)
|
|
GB.HashTable.New(&THIS->cache, GB_COMP_BINARY);
|
|
}
|
|
|
|
CFONT *FONT_create()
|
|
{
|
|
CFONT *font = (CFONT *)GB.New(CLASS_Font, NULL, NULL);
|
|
create_cache(font);
|
|
return font;
|
|
}
|
|
|
|
static FONT_LOAD *find_font(const char *name)
|
|
{
|
|
FONT_LOAD *p;
|
|
|
|
LIST_for_each(p, _font_list)
|
|
{
|
|
if (strcasecmp(name, p->name) == 0)
|
|
return p;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static bool load_font(char *path, char *name)
|
|
{
|
|
FONT_LOAD *load;
|
|
char *addr;
|
|
int len;
|
|
|
|
if (GB.LoadFile(path, strlen(path), &addr, &len))
|
|
return TRUE;
|
|
|
|
GB.ReleaseFile(addr, len);
|
|
|
|
if (!name || !*name)
|
|
{
|
|
char *p = strrchr(path, '/');
|
|
if (p)
|
|
name = p + 1;
|
|
else
|
|
name = path;
|
|
|
|
p = strrchr(name, '.');
|
|
if (p)
|
|
name = GB.TempString(name, p - name);
|
|
}
|
|
|
|
if (find_font(name))
|
|
{
|
|
GB.Error("Font already exists");
|
|
return TRUE;
|
|
}
|
|
|
|
GB.AllocZero(POINTER(&load), sizeof(FONT_LOAD));
|
|
load->path = GB.NewZeroString(path);
|
|
load->name = GB.NewZeroString(name);
|
|
LIST_insert(&_font_list, load, &load->list);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void release_cache(CFONT *_object)
|
|
{
|
|
GB.HashTable.Enum(THIS->cache, (GB_HASHTABLE_ENUM_FUNC)SDL_FreeImage);
|
|
GB.HashTable.Free(&THIS->cache);
|
|
}
|
|
|
|
static bool init_font()
|
|
{
|
|
if (!TTF_WasInit() && TTF_Init())
|
|
{
|
|
GB.Error("Unable to initialize TTF library: &1", TTF_GetError());
|
|
return TRUE;
|
|
}
|
|
else
|
|
return FALSE;
|
|
}
|
|
|
|
static bool check_font(CFONT *_object)
|
|
{
|
|
FONT_LOAD *font;
|
|
int style;
|
|
char *addr;
|
|
int len;
|
|
|
|
if (!THIS->dirty)
|
|
return FALSE;
|
|
|
|
if (THIS->font)
|
|
{
|
|
if (init_font())
|
|
return TRUE;
|
|
TTF_CloseFont(THIS->font);
|
|
THIS->font = NULL;
|
|
}
|
|
|
|
release_cache(THIS);
|
|
create_cache(THIS);
|
|
|
|
if (!THIS->name)
|
|
{
|
|
THIS->dirty = FALSE;
|
|
return FALSE;
|
|
}
|
|
|
|
font = find_font(THIS->name);
|
|
if (!font)
|
|
{
|
|
GB.Error("Unknown font: &1", THIS->name);
|
|
return TRUE;
|
|
}
|
|
|
|
if (GB.LoadFile(font->path, strlen(font->path), &addr, &len))
|
|
return TRUE;
|
|
|
|
if (init_font())
|
|
return TRUE;
|
|
|
|
THIS->font = TTF_OpenFontRW(SDL_RWFromConstMem(addr, len), TRUE, THIS->size);
|
|
if (!THIS->font)
|
|
{
|
|
GB.Error("Unable to load font: &1: &2", THIS->name, TTF_GetError());
|
|
return TRUE;
|
|
}
|
|
|
|
style = TTF_STYLE_NORMAL;
|
|
if (THIS->bold) style |= TTF_STYLE_BOLD;
|
|
if (THIS->italic) style |= TTF_STYLE_ITALIC;
|
|
TTF_SetFontStyle(THIS->font, style);
|
|
|
|
THIS->dirty = FALSE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static int get_font_ascent(void *_object)
|
|
{
|
|
if (THIS->font)
|
|
return TTF_FontAscent(THIS->font);
|
|
else
|
|
return DEFAULT_FONT_ASCENT * THIS->size / DEFAULT_FONT_HEIGHT;
|
|
}
|
|
|
|
static int get_font_descent(void *_object)
|
|
{
|
|
if (THIS->font)
|
|
return TTF_FontDescent(THIS->font);
|
|
else
|
|
return DEFAULT_FONT_DESCENT * THIS->size / DEFAULT_FONT_HEIGHT;
|
|
}
|
|
|
|
static void get_text_size(void *_object, const char *text, int *width, int *height)
|
|
{
|
|
if (!text || !*text)
|
|
{
|
|
*width = 0;
|
|
*height = get_font_ascent(THIS) + get_font_descent(THIS);
|
|
return;
|
|
}
|
|
|
|
if (THIS->font)
|
|
{
|
|
TTF_SizeUTF8(THIS->font, text, width, height);
|
|
return;
|
|
}
|
|
|
|
*width = UTF8_get_length(text, strlen(text)) * DEFAULT_FONT_WIDTH * THIS->size / DEFAULT_FONT_HEIGHT;
|
|
*height = THIS->size;
|
|
}
|
|
|
|
|
|
SDL_Image *FONT_render_text(CFONT *_object, CWINDOW *window, char *text, int len, int *w, int *h)
|
|
{
|
|
SDL_Surface *surface;
|
|
SDL_Image *image;
|
|
bool exist;
|
|
|
|
if (check_font(THIS))
|
|
return NULL;
|
|
|
|
exist = !GB.HashTable.Get(THIS->cache, text, len, POINTER(&image));
|
|
|
|
if (exist)
|
|
{
|
|
if (image->window == window)
|
|
{
|
|
SDL_QueryTexture(image->texture, NULL, NULL, w, h);
|
|
if (!THIS->name)
|
|
{
|
|
*w = *w * THIS->size / DEFAULT_FONT_HEIGHT;
|
|
*h = *h * THIS->size / DEFAULT_FONT_HEIGHT;
|
|
}
|
|
|
|
return image;
|
|
}
|
|
|
|
SDL_FreeImage(image);
|
|
GB.HashTable.Remove(THIS->cache, text, len);
|
|
}
|
|
|
|
if (THIS->name)
|
|
{
|
|
SDL_Color color = { 0xFF, 0xFF, 0xFF, 0xFF };
|
|
char c = text[len];
|
|
if (c) text[len] = 0;
|
|
surface = TTF_RenderUTF8_Blended(THIS->font, text, color);
|
|
if (c) text[len] = c;
|
|
*w = surface->w;
|
|
*h = surface->h;
|
|
}
|
|
else
|
|
{
|
|
int size = UTF8_get_length(text, len);
|
|
|
|
surface = SDL_CreateRGBSurface(0, size * DEFAULT_FONT_WIDTH, DEFAULT_FONT_HEIGHT, 32, RMASK, GMASK, BMASK, AMASK);
|
|
|
|
if (SDL_MUSTLOCK(surface))
|
|
SDL_LockSurface(surface);
|
|
|
|
FONT_render_default((uint *)surface->pixels, size, text, len);
|
|
|
|
if (SDL_MUSTLOCK(surface))
|
|
SDL_UnlockSurface(surface);
|
|
|
|
*w = surface->w * THIS->size / DEFAULT_FONT_HEIGHT;
|
|
*h = surface->h * THIS->size / DEFAULT_FONT_HEIGHT;
|
|
}
|
|
|
|
image = SDL_CreateImage(surface);
|
|
|
|
if (GB.HashTable.Count(THIS->cache) >= 128)
|
|
{
|
|
release_cache(THIS);
|
|
create_cache(THIS);
|
|
}
|
|
|
|
GB.HashTable.Add(THIS->cache, text, len, image);
|
|
return image;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
|
|
BEGIN_METHOD_VOID(Font_exit)
|
|
|
|
FONT_LOAD *next;
|
|
|
|
while (_font_list)
|
|
{
|
|
next = _font_list->list.next;
|
|
GB.FreeString(&_font_list->path);
|
|
GB.FreeString(&_font_list->name);
|
|
GB.Free(POINTER(&_font_list));
|
|
_font_list = next;
|
|
}
|
|
|
|
END_METHOD
|
|
|
|
BEGIN_METHOD(Font_Load, GB_STRING path; GB_STRING name)
|
|
|
|
char *path = GB.ToZeroString(ARG(path));
|
|
char *name;
|
|
|
|
if (MISSING(name))
|
|
name = NULL;
|
|
else
|
|
name = GB.ToZeroString(ARG(name));
|
|
|
|
load_font(path, name);
|
|
|
|
END_METHOD
|
|
|
|
BEGIN_METHOD(Font_get, GB_STRING font)
|
|
|
|
CFONT *font;
|
|
char *desc = GB.ToZeroString(ARG(font));
|
|
char *elt;
|
|
int val;
|
|
bool bold = FALSE;
|
|
bool italic = FALSE;
|
|
int size = 10;
|
|
char *name = NULL;
|
|
|
|
for (elt = strtok(desc, ","); elt; elt = strtok(NULL, ","))
|
|
{
|
|
if (strcasecmp(elt, "bold") == 0)
|
|
{
|
|
bold = TRUE;
|
|
continue;
|
|
}
|
|
|
|
if (strcasecmp(elt, "italic") == 0)
|
|
{
|
|
italic = TRUE;
|
|
continue;
|
|
}
|
|
|
|
val = atoi(elt);
|
|
if (val)
|
|
{
|
|
size = val;
|
|
continue;
|
|
}
|
|
|
|
if (name)
|
|
{
|
|
GB.Error("Font name defined twice");
|
|
goto ERROR;
|
|
}
|
|
|
|
name = GB.NewZeroString(elt);
|
|
}
|
|
|
|
if (size < 1 || size > 1024)
|
|
{
|
|
GB.Error("Incorrect font size");
|
|
goto ERROR;
|
|
}
|
|
|
|
font = FONT_create();
|
|
font->name = name;
|
|
font->bold = bold;
|
|
font->italic = italic;
|
|
font->size = size;
|
|
font->dirty = TRUE;
|
|
|
|
GB.ReturnObject(font);
|
|
return;
|
|
|
|
ERROR:
|
|
|
|
GB.FreeString(&name);
|
|
return;
|
|
|
|
END_METHOD
|
|
|
|
BEGIN_METHOD_VOID(Font_free)
|
|
|
|
release_cache(THIS);
|
|
|
|
if (THIS->font)
|
|
TTF_CloseFont(THIS->font);
|
|
|
|
GB.FreeString(&THIS->name);
|
|
|
|
END_METHOD
|
|
|
|
BEGIN_PROPERTY(Font_Size)
|
|
|
|
if (READ_PROPERTY)
|
|
GB.ReturnInteger(THIS->size);
|
|
else
|
|
{
|
|
int size = VPROP(GB_INTEGER);
|
|
if (size < 1 || size > 1024)
|
|
GB.Error("Incorrect font size");
|
|
else if (THIS->size != size)
|
|
{
|
|
THIS->size = size;
|
|
if (THIS->name)
|
|
THIS->dirty = TRUE;
|
|
}
|
|
}
|
|
|
|
END_PROPERTY
|
|
|
|
BEGIN_PROPERTY(Font_Bold)
|
|
|
|
if (READ_PROPERTY)
|
|
GB.ReturnBoolean(THIS->bold);
|
|
else if (THIS->bold != VPROP(GB_BOOLEAN))
|
|
{
|
|
THIS->bold = VPROP(GB_BOOLEAN);
|
|
if (THIS->font && !THIS->dirty)
|
|
TTF_SetFontStyle(THIS->font, (TTF_GetFontStyle(THIS->font) ^ TTF_STYLE_BOLD));
|
|
}
|
|
|
|
END_PROPERTY
|
|
|
|
BEGIN_PROPERTY(Font_Italic)
|
|
|
|
if (READ_PROPERTY)
|
|
GB.ReturnBoolean(THIS->italic);
|
|
else if (THIS->italic != VPROP(GB_BOOLEAN))
|
|
{
|
|
THIS->italic = VPROP(GB_BOOLEAN);
|
|
if (THIS->font && !THIS->dirty)
|
|
TTF_SetFontStyle(THIS->font, (TTF_GetFontStyle(THIS->font) ^ TTF_STYLE_ITALIC));
|
|
}
|
|
|
|
END_PROPERTY
|
|
|
|
BEGIN_PROPERTY(Font_Name)
|
|
|
|
if (READ_PROPERTY)
|
|
GB.ReturnString(THIS->name);
|
|
else
|
|
{
|
|
GB.StoreString(PROP(GB_STRING), &THIS->name);
|
|
THIS->dirty = TRUE;
|
|
}
|
|
|
|
END_PROPERTY
|
|
|
|
BEGIN_PROPERTY(Font_Ascent)
|
|
|
|
if (check_font(THIS))
|
|
return;
|
|
|
|
GB.ReturnInteger(TTF_FontAscent(THIS->font));
|
|
|
|
END_PROPERTY
|
|
|
|
BEGIN_PROPERTY(Font_Descent)
|
|
|
|
if (check_font(THIS))
|
|
return;
|
|
|
|
GB.ReturnInteger(TTF_FontDescent(THIS->font));
|
|
|
|
END_PROPERTY
|
|
|
|
BEGIN_PROPERTY(Font_Fixed)
|
|
|
|
if (check_font(THIS))
|
|
return;
|
|
|
|
GB.ReturnBoolean(TTF_FontFaceIsFixedWidth(THIS->font));
|
|
|
|
END_PROPERTY
|
|
|
|
BEGIN_METHOD(Font_TextWidth, GB_STRING text)
|
|
|
|
int w, h;
|
|
|
|
if (check_font(THIS))
|
|
return;
|
|
|
|
get_text_size(THIS, GB.ToZeroString(ARG(text)), &w, &h);
|
|
GB.ReturnInteger(w);
|
|
|
|
END_METHOD
|
|
|
|
BEGIN_METHOD(Font_TextHeight, GB_STRING text)
|
|
|
|
int w, h;
|
|
|
|
if (check_font(THIS))
|
|
return;
|
|
|
|
get_text_size(THIS, GB.ToZeroString(ARG(text)), &w, &h);
|
|
GB.ReturnInteger(h);
|
|
|
|
END_METHOD
|
|
|
|
//-------------------------------------------------------------------------
|
|
|
|
GB_DESC FontDesc[] =
|
|
{
|
|
GB_DECLARE("Font", sizeof(CFONT)), GB_NOT_CREATABLE(),
|
|
|
|
GB_CONSTANT("DefaultHeight", "i", DEFAULT_FONT_HEIGHT),
|
|
|
|
GB_STATIC_METHOD("Load", NULL, Font_Load, "(Path)s[(Name)s]"),
|
|
GB_STATIC_METHOD("_get", "Font", Font_get, "(Font)s"),
|
|
GB_STATIC_METHOD("_exit", NULL, Font_exit, NULL),
|
|
|
|
GB_METHOD("_free", NULL, Font_free, NULL),
|
|
|
|
GB_PROPERTY("Size", "i", Font_Size),
|
|
GB_PROPERTY("Bold", "b", Font_Bold),
|
|
GB_PROPERTY("Italic", "b", Font_Italic),
|
|
//GB_PROPERTY("Underline", "b", Font_Underline),
|
|
|
|
GB_PROPERTY_READ("Name", "s", Font_Name),
|
|
GB_PROPERTY_READ("Ascent", "i", Font_Ascent),
|
|
GB_PROPERTY_READ("Descent", "i", Font_Descent),
|
|
GB_PROPERTY_READ("Fixed", "b", Font_Fixed),
|
|
|
|
GB_METHOD("TextWidth", "i", Font_TextWidth, "(Text)s"),
|
|
GB_METHOD("TextHeight", "i", Font_TextHeight, "(Text)s"),
|
|
/*GB_METHOD("GetImage", "Image", Font_GetImage, "(Text)s"),*/
|
|
|
|
GB_END_DECLARE
|
|
};
|