Font: Overhaul font asset discovery
Previously, any font names requested by RGSS would be translated directly to filenames by lowercasing and replacing spaces with underscores (and finally doing some extension substitution). To make this whole thing work smoother as well as get closer to how font discovery is done in VX, we now scan the "Fonts/" folder at startup and index all present font assets by their family name; now, if an "Open Sans" font is present in "Fonts/", it will be used regardless of filename. Font assets with "Regular" style are preferred, but in their absence, mkxp will make use of any other style it can find for the respective family. This is not the exact same behavior as VX, but it should cover 95% of use cases. Previously, one could substitute fonts via filenames, ie. to substitute "Arial" with "Open Sans", one would just rename "OpenSans.ttf" to "arial.ttf" and put it in "Fonts/". With the above change, this is no longer possible. As an alternative, one can now explicitly specify font family substitutions via mkxp.conf; eg. for the above case, one would add fontSub=Arial>Open Sans to the configuration file. Multiple such rules can be specified. In the process, I also added the ability to provide 'Font.(default_)name' with an array of font families to search for the first existing one instead of a plain string. This makes the behavior closer to RMXP; however, it doesn't work 100% the same: when a reference to the 'Font.name' array is held and additional strings are added to it without re-assignig the array to 'Font.name', those will be ignored.
This commit is contained in:
parent
31626c9acc
commit
1ef6e04520
|
@ -52,12 +52,14 @@ RB_METHOD(bitmapInitialize)
|
|||
setPrivateData(self, b);
|
||||
|
||||
/* Wrap properties */
|
||||
Font *font = new Font();
|
||||
b->setFont(font);
|
||||
font->setColor(new Color(*font->getColor()));
|
||||
VALUE fontKlass = rb_const_get(rb_cObject, rb_intern("Font"));
|
||||
VALUE fontObj = rb_obj_alloc(fontKlass);
|
||||
rb_obj_call_init(fontObj, 0, 0);
|
||||
|
||||
VALUE fontProp = wrapProperty(self, font, "font", FontType);
|
||||
wrapProperty(fontProp, font->getColor(), "color", ColorType);
|
||||
Font *font = getPrivateData<Font>(fontObj);
|
||||
b->setFont(font);
|
||||
|
||||
rb_iv_set(self, "font", fontObj);
|
||||
|
||||
return self;
|
||||
}
|
||||
|
|
|
@ -23,6 +23,9 @@
|
|||
#include "binding-util.h"
|
||||
#include "binding-types.h"
|
||||
#include "exception.h"
|
||||
#include "sharedstate.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
DEF_TYPE(Font);
|
||||
|
||||
|
@ -36,14 +39,16 @@ RB_METHOD(fontDoesExist)
|
|||
return rb_bool_new(Font::doesExist(name));
|
||||
}
|
||||
|
||||
RB_METHOD(FontSetName);
|
||||
|
||||
RB_METHOD(fontInitialize)
|
||||
{
|
||||
const char *name = 0;
|
||||
VALUE name = Qnil;
|
||||
int size = 0;
|
||||
|
||||
rb_get_args(argc, argv, "|zi", &name, &size RB_ARG_END);
|
||||
rb_get_args(argc, argv, "|oi", &name, &size RB_ARG_END);
|
||||
|
||||
Font *f = new Font(name, size);
|
||||
Font *f = new Font(0, size);
|
||||
|
||||
setPrivateData(self, f);
|
||||
|
||||
|
@ -51,6 +56,13 @@ RB_METHOD(fontInitialize)
|
|||
f->setColor(new Color(*f->getColor()));
|
||||
wrapProperty(self, f->getColor(), "color", ColorType);
|
||||
|
||||
if (NIL_P(name))
|
||||
name = rb_iv_get(rb_obj_class(self), "default_name");
|
||||
|
||||
/* Going over the 'name=' function automatically causes
|
||||
* a possbile name array to be re-verified for existing fonts */
|
||||
FontSetName(1, &name, self);
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
|
@ -77,21 +89,63 @@ RB_METHOD(FontGetName)
|
|||
{
|
||||
RB_UNUSED_PARAM;
|
||||
|
||||
Font *f = getPrivateData<Font>(self);
|
||||
return rb_iv_get(self, "name");
|
||||
}
|
||||
|
||||
return rb_str_new_cstr(f->getName());
|
||||
static void
|
||||
fontSetNameHelper(VALUE self, int argc, VALUE *argv,
|
||||
const char *nameIv, char *outBuf, size_t outLen)
|
||||
{
|
||||
rb_check_argc(argc, 1);
|
||||
|
||||
VALUE arg = argv[0];
|
||||
int type = rb_type(arg);
|
||||
|
||||
// Fixme: in RGSS3, specifying "" (and only that) as font name results in
|
||||
// no text being drawn (everything else is substituted with Arial I think)
|
||||
strncpy(outBuf, "", outLen);
|
||||
|
||||
if (type == RUBY_T_STRING)
|
||||
{
|
||||
strncpy(outBuf, RSTRING_PTR(arg), outLen);
|
||||
}
|
||||
else if (type == RUBY_T_ARRAY)
|
||||
{
|
||||
for (long i = 0; i < RARRAY_LEN(arg); ++i)
|
||||
{
|
||||
VALUE str = rb_ary_entry(arg, i);
|
||||
|
||||
/* Non-string objects are tolerated (ignored) */
|
||||
if (rb_type(str) != RUBY_T_STRING)
|
||||
continue;
|
||||
|
||||
const char *family = RSTRING_PTR(str);
|
||||
|
||||
/* We only set the core Font object's name attribute
|
||||
* to the actually existing font name */
|
||||
if (!shState->fontState().fontPresent(family))
|
||||
continue;
|
||||
|
||||
strncpy(outBuf, family, outLen);
|
||||
}
|
||||
}
|
||||
|
||||
/* RMXP doesn't even care if the argument type is
|
||||
* something other than string/array. Whatever... */
|
||||
rb_iv_set(self, nameIv, arg);
|
||||
}
|
||||
|
||||
RB_METHOD(FontSetName)
|
||||
{
|
||||
Font *f = getPrivateData<Font>(self);
|
||||
|
||||
VALUE name;
|
||||
rb_get_args(argc, argv, "S", &name RB_ARG_END);
|
||||
char result[256];
|
||||
fontSetNameHelper(self, argc, argv, "default_name",
|
||||
result, sizeof(result));
|
||||
|
||||
f->setName(RSTRING_PTR(name));
|
||||
f->setName(result);
|
||||
|
||||
return name;
|
||||
return argv[0];
|
||||
}
|
||||
|
||||
#undef DEF_PROP_CHK_DISP
|
||||
|
@ -124,18 +178,19 @@ DEF_KLASS_PROP(Font, bool, DefaultItalic, "b", rb_bool_new)
|
|||
RB_METHOD(FontGetDefaultName)
|
||||
{
|
||||
RB_UNUSED_PARAM;
|
||||
return rb_str_new_cstr(Font::getDefaultName());
|
||||
|
||||
return rb_iv_get(self, "default_name");
|
||||
}
|
||||
|
||||
RB_METHOD(FontSetDefaultName)
|
||||
{
|
||||
RB_UNUSED_PARAM;
|
||||
VALUE nameObj;
|
||||
rb_get_args(argc, argv, "S", &nameObj RB_ARG_END);
|
||||
char result[256];
|
||||
fontSetNameHelper(self, argc, argv, "default_name",
|
||||
result, sizeof(result));
|
||||
|
||||
Font::setDefaultName(RSTRING_PTR(nameObj));
|
||||
Font::setDefaultName(result);
|
||||
|
||||
return nameObj;
|
||||
return argv[0];
|
||||
}
|
||||
|
||||
RB_METHOD(FontGetDefaultColor)
|
||||
|
@ -174,6 +229,7 @@ fontBindingInit()
|
|||
|
||||
Font::setDefaultColor(new Color(*Font::getDefaultColor()));
|
||||
wrapProperty(klass, Font::getDefaultColor(), "default_color", ColorType);
|
||||
rb_iv_set(klass, "default_name", rb_str_new_cstr(Font::getDefaultName()));
|
||||
|
||||
INIT_KLASS_PROP_BIND(Font, DefaultName, "default_name");
|
||||
INIT_KLASS_PROP_BIND(Font, DefaultSize, "default_size");
|
||||
|
|
|
@ -113,3 +113,18 @@
|
|||
# RTP=/path/to/rtp1
|
||||
# RTP=/path/to/rtp2.zip
|
||||
# RTP=/path/to/game.rgssad
|
||||
|
||||
|
||||
# Font substitutions allow drop-in replacements of fonts
|
||||
# to be used without changing the RGSS scripts,
|
||||
# eg. providing 'Open Sans' when the game thinkgs it's
|
||||
# using 'Arial'. Font family to be substituted and
|
||||
# replacement family are separated by one sole '>'.
|
||||
# Be careful not to include any spaces.
|
||||
# This is not connected to the built-in font, which is
|
||||
# always used when a non-existing font family is
|
||||
# requested by RGSS.
|
||||
# (default: none)
|
||||
#
|
||||
# fontSub=Arial>Open Sans
|
||||
# fontSub=Times New Roman>Liberation Serif
|
||||
|
|
|
@ -83,6 +83,7 @@ void Config::read(int argc, char *argv[])
|
|||
podesc.add_options()
|
||||
PO_DESC_ALL
|
||||
("RTP", po::value<StringVec>()->composing())
|
||||
("fontSub", po::value<StringVec>()->composing())
|
||||
;
|
||||
|
||||
po::variables_map vm;
|
||||
|
@ -115,6 +116,8 @@ void Config::read(int argc, char *argv[])
|
|||
|
||||
GUARD_ALL( rtps = vm["RTP"].as<StringVec>(); );
|
||||
|
||||
GUARD_ALL( fontSubs = vm["fontSub"].as<StringVec>(); );
|
||||
|
||||
#undef PO_DESC
|
||||
#undef PO_DESC_ALL
|
||||
}
|
||||
|
|
|
@ -54,6 +54,8 @@ struct Config
|
|||
std::string customScript;
|
||||
std::vector<std::string> rtps;
|
||||
|
||||
std::vector<std::string> fontSubs;
|
||||
|
||||
/* Game INI contents */
|
||||
struct {
|
||||
std::string scripts;
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
#include "filesystem.h"
|
||||
|
||||
#include "font.h"
|
||||
#include "util.h"
|
||||
#include "exception.h"
|
||||
#include "boost-hash.h"
|
||||
|
@ -498,6 +499,99 @@ static const PHYSFS_Archiver RGSS_Archiver =
|
|||
RGSS_closeArchive
|
||||
};
|
||||
|
||||
|
||||
static inline PHYSFS_File *sdlPHYS(SDL_RWops *ops)
|
||||
{
|
||||
return static_cast<PHYSFS_File*>(ops->hidden.unknown.data1);
|
||||
}
|
||||
|
||||
static Sint64 SDL_RWopsSize(SDL_RWops *ops)
|
||||
{
|
||||
PHYSFS_File *f = sdlPHYS(ops);
|
||||
|
||||
if (!f)
|
||||
return -1;
|
||||
|
||||
return PHYSFS_fileLength(f);
|
||||
}
|
||||
|
||||
static Sint64 SDL_RWopsSeek(SDL_RWops *ops, int64_t offset, int whence)
|
||||
{
|
||||
PHYSFS_File *f = sdlPHYS(ops);
|
||||
|
||||
if (!f)
|
||||
return -1;
|
||||
|
||||
int64_t base;
|
||||
|
||||
switch (whence)
|
||||
{
|
||||
default:
|
||||
case RW_SEEK_SET :
|
||||
base = 0;
|
||||
break;
|
||||
case RW_SEEK_CUR :
|
||||
base = PHYSFS_tell(f);
|
||||
break;
|
||||
case RW_SEEK_END :
|
||||
base = PHYSFS_fileLength(f);
|
||||
break;
|
||||
}
|
||||
|
||||
int result = PHYSFS_seek(f, base + offset);
|
||||
|
||||
return (result != 0) ? PHYSFS_tell(f) : -1;
|
||||
}
|
||||
|
||||
static size_t SDL_RWopsRead(SDL_RWops *ops, void *buffer, size_t size, size_t maxnum)
|
||||
{
|
||||
PHYSFS_File *f = sdlPHYS(ops);
|
||||
|
||||
if (!f)
|
||||
return 0;
|
||||
|
||||
PHYSFS_sint64 result = PHYSFS_readBytes(f, buffer, size*maxnum);
|
||||
|
||||
return (result != -1) ? (result / size) : 0;
|
||||
}
|
||||
|
||||
static size_t SDL_RWopsWrite(SDL_RWops *ops, const void *buffer, size_t size, size_t num)
|
||||
{
|
||||
PHYSFS_File *f = sdlPHYS(ops);
|
||||
|
||||
if (!f)
|
||||
return 0;
|
||||
|
||||
PHYSFS_sint64 result = PHYSFS_writeBytes(f, buffer, size*num);
|
||||
|
||||
return (result != -1) ? (result / size) : 0;
|
||||
}
|
||||
|
||||
static int SDL_RWopsClose(SDL_RWops *ops)
|
||||
{
|
||||
PHYSFS_File *f = sdlPHYS(ops);
|
||||
|
||||
if (!f)
|
||||
return -1;
|
||||
|
||||
int result = PHYSFS_close(f);
|
||||
|
||||
f = 0;
|
||||
|
||||
return (result != 0) ? 0 : -1;
|
||||
}
|
||||
|
||||
static int SDL_RWopsCloseFree(SDL_RWops *ops)
|
||||
{
|
||||
int result = SDL_RWopsClose(ops);
|
||||
|
||||
SDL_FreeRW(ops);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const Uint32 SDL_RWOPS_PHYSFS = SDL_RWOPS_UNKNOWN+10;
|
||||
|
||||
struct FileSystemPrivate
|
||||
{
|
||||
/* Maps: lower case filename, To: actual (mixed case) filename.
|
||||
|
@ -650,6 +744,24 @@ struct FileSystemPrivate
|
|||
|
||||
return handle;
|
||||
}
|
||||
|
||||
void initReadOps(PHYSFS_File *handle,
|
||||
SDL_RWops &ops,
|
||||
bool freeOnClose)
|
||||
{
|
||||
ops.size = SDL_RWopsSize;
|
||||
ops.seek = SDL_RWopsSeek;
|
||||
ops.read = SDL_RWopsRead;
|
||||
ops.write = SDL_RWopsWrite;
|
||||
|
||||
if (freeOnClose)
|
||||
ops.close = SDL_RWopsCloseFree;
|
||||
else
|
||||
ops.close = SDL_RWopsClose;
|
||||
|
||||
ops.type = SDL_RWOPS_PHYSFS;
|
||||
ops.hidden.unknown.data1 = handle;
|
||||
}
|
||||
};
|
||||
|
||||
FileSystem::FileSystem(const char *argv0,
|
||||
|
@ -746,98 +858,59 @@ void FileSystem::createPathCache()
|
|||
p->havePathCache = true;
|
||||
}
|
||||
|
||||
static inline PHYSFS_File *sdlPHYS(SDL_RWops *ops)
|
||||
static void strToLower(std::string &str)
|
||||
{
|
||||
return static_cast<PHYSFS_File*>(ops->hidden.unknown.data1);
|
||||
for (size_t i = 0; i < str.size(); ++i)
|
||||
str[i] = tolower(str[i]);
|
||||
}
|
||||
|
||||
static Sint64 SDL_RWopsSize(SDL_RWops *ops)
|
||||
struct FontSetsCBData
|
||||
{
|
||||
PHYSFS_File *f = sdlPHYS(ops);
|
||||
FileSystemPrivate *p;
|
||||
SharedFontState *sfs;
|
||||
};
|
||||
|
||||
if (!f)
|
||||
return -1;
|
||||
static void fontSetEnumCB(void *data, const char *,
|
||||
const char *fname)
|
||||
{
|
||||
FontSetsCBData *d = static_cast<FontSetsCBData*>(data);
|
||||
FileSystemPrivate *p = d->p;
|
||||
|
||||
return PHYSFS_fileLength(f);
|
||||
/* Only consider filenames with font extensions */
|
||||
const char *ext = p->findExt(fname);
|
||||
|
||||
if (!ext)
|
||||
return;
|
||||
|
||||
std::string lower(ext);
|
||||
strToLower(lower);
|
||||
|
||||
if (!contains(p->extensions[FileSystem::Font], lower))
|
||||
return;
|
||||
|
||||
std::string filename("Fonts/");
|
||||
filename += fname;
|
||||
|
||||
PHYSFS_File *handle = PHYSFS_openRead(filename.c_str());
|
||||
|
||||
if (!handle)
|
||||
return;
|
||||
|
||||
SDL_RWops ops;
|
||||
p->initReadOps(handle, ops, false);
|
||||
|
||||
d->sfs->initFontSetCB(ops, filename);
|
||||
|
||||
SDL_RWclose(&ops);
|
||||
}
|
||||
|
||||
static Sint64 SDL_RWopsSeek(SDL_RWops *ops, int64_t offset, int whence)
|
||||
void FileSystem::initFontSets(SharedFontState &sfs)
|
||||
{
|
||||
PHYSFS_File *f = sdlPHYS(ops);
|
||||
FontSetsCBData d = { p, &sfs };
|
||||
|
||||
if (!f)
|
||||
return -1;
|
||||
|
||||
int64_t base;
|
||||
|
||||
switch (whence)
|
||||
{
|
||||
default:
|
||||
case RW_SEEK_SET :
|
||||
base = 0;
|
||||
break;
|
||||
case RW_SEEK_CUR :
|
||||
base = PHYSFS_tell(f);
|
||||
break;
|
||||
case RW_SEEK_END :
|
||||
base = PHYSFS_fileLength(f);
|
||||
break;
|
||||
PHYSFS_enumerateFilesCallback("Fonts", fontSetEnumCB, &d);
|
||||
}
|
||||
|
||||
int result = PHYSFS_seek(f, base + offset);
|
||||
|
||||
return (result != 0) ? PHYSFS_tell(f) : -1;
|
||||
}
|
||||
|
||||
static size_t SDL_RWopsRead(SDL_RWops *ops, void *buffer, size_t size, size_t maxnum)
|
||||
{
|
||||
PHYSFS_File *f = sdlPHYS(ops);
|
||||
|
||||
if (!f)
|
||||
return 0;
|
||||
|
||||
PHYSFS_sint64 result = PHYSFS_readBytes(f, buffer, size*maxnum);
|
||||
|
||||
return (result != -1) ? (result / size) : 0;
|
||||
}
|
||||
|
||||
static size_t SDL_RWopsWrite(SDL_RWops *ops, const void *buffer, size_t size, size_t num)
|
||||
{
|
||||
PHYSFS_File *f = sdlPHYS(ops);
|
||||
|
||||
if (!f)
|
||||
return 0;
|
||||
|
||||
PHYSFS_sint64 result = PHYSFS_writeBytes(f, buffer, size*num);
|
||||
|
||||
return (result != -1) ? (result / size) : 0;
|
||||
}
|
||||
|
||||
static int SDL_RWopsClose(SDL_RWops *ops)
|
||||
{
|
||||
PHYSFS_File *f = sdlPHYS(ops);
|
||||
|
||||
if (!f)
|
||||
return -1;
|
||||
|
||||
int result = PHYSFS_close(f);
|
||||
|
||||
f = 0;
|
||||
|
||||
return (result != 0) ? 0 : -1;
|
||||
}
|
||||
|
||||
static int SDL_RWopsCloseFree(SDL_RWops *ops)
|
||||
{
|
||||
int result = SDL_RWopsClose(ops);
|
||||
|
||||
SDL_FreeRW(ops);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const Uint32 SDL_RWOPS_PHYSFS = SDL_RWOPS_UNKNOWN+10;
|
||||
|
||||
void FileSystem::openRead(SDL_RWops &ops,
|
||||
const char *filename,
|
||||
FileType type,
|
||||
|
@ -846,18 +919,17 @@ void FileSystem::openRead(SDL_RWops &ops,
|
|||
{
|
||||
PHYSFS_File *handle = p->openReadHandle(filename, type, foundExt);
|
||||
|
||||
ops.size = SDL_RWopsSize;
|
||||
ops.seek = SDL_RWopsSeek;
|
||||
ops.read = SDL_RWopsRead;
|
||||
ops.write = SDL_RWopsWrite;
|
||||
p->initReadOps(handle, ops, freeOnClose);
|
||||
}
|
||||
|
||||
if (freeOnClose)
|
||||
ops.close = SDL_RWopsCloseFree;
|
||||
else
|
||||
ops.close = SDL_RWopsClose;
|
||||
void FileSystem::openReadRaw(SDL_RWops &ops,
|
||||
const char *filename,
|
||||
bool freeOnClose)
|
||||
{
|
||||
PHYSFS_File *handle = PHYSFS_openRead(filename);
|
||||
assert(handle);
|
||||
|
||||
ops.type = SDL_RWOPS_PHYSFS;
|
||||
ops.hidden.unknown.data1 = handle;
|
||||
p->initReadOps(handle, ops, freeOnClose);
|
||||
}
|
||||
|
||||
bool FileSystem::exists(const char *filename, FileType type)
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
#include <SDL_rwops.h>
|
||||
|
||||
struct FileSystemPrivate;
|
||||
struct SharedFontState;
|
||||
|
||||
class FileSystem
|
||||
{
|
||||
|
@ -35,9 +36,13 @@ public:
|
|||
|
||||
void addPath(const char *path);
|
||||
|
||||
/* Call this after the last 'addPath()' */
|
||||
/* Call these after the last 'addPath()' */
|
||||
void createPathCache();
|
||||
|
||||
/* Scans "Fonts/" and creates inventory of
|
||||
* available font assets */
|
||||
void initFontSets(SharedFontState &sfs);
|
||||
|
||||
/* For extension supplementing */
|
||||
enum FileType
|
||||
{
|
||||
|
@ -53,6 +58,11 @@ public:
|
|||
bool freeOnClose = false,
|
||||
const char **foundExt = 0);
|
||||
|
||||
/* Circumvents extension supplementing */
|
||||
void openReadRaw(SDL_RWops &ops,
|
||||
const char *filename,
|
||||
bool freeOnClose = false);
|
||||
|
||||
bool exists(const char *filename,
|
||||
FileType type = Undefined);
|
||||
|
||||
|
|
172
src/font.cpp
172
src/font.cpp
|
@ -26,6 +26,7 @@
|
|||
#include "exception.h"
|
||||
#include "boost-hash.h"
|
||||
#include "util.h"
|
||||
#include "config.h"
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
@ -45,88 +46,151 @@
|
|||
|
||||
typedef std::pair<std::string, int> FontKey;
|
||||
|
||||
static void strToLower(std::string &str)
|
||||
{
|
||||
for (size_t i = 0; i < str.size(); ++i)
|
||||
str[i] = tolower(str[i]);
|
||||
}
|
||||
|
||||
struct FontPoolPrivate
|
||||
{
|
||||
BoostHash<FontKey, TTF_Font*> hash;
|
||||
};
|
||||
|
||||
FontPool::FontPool()
|
||||
{
|
||||
p = new FontPoolPrivate;
|
||||
}
|
||||
|
||||
FontPool::~FontPool()
|
||||
{
|
||||
BoostHash<FontKey, TTF_Font*>::const_iterator iter;
|
||||
for (iter = p->hash.cbegin(); iter != p->hash.cend(); ++iter)
|
||||
TTF_CloseFont(iter->second);
|
||||
|
||||
delete p;
|
||||
}
|
||||
|
||||
static SDL_RWops *openBundledFont()
|
||||
{
|
||||
return SDL_RWFromConstMem(BNDL_F_D(BUNDLED_FONT), BNDL_F_L(BUNDLED_FONT));
|
||||
}
|
||||
|
||||
_TTF_Font *FontPool::request(const char *filename,
|
||||
int size)
|
||||
struct FontSet
|
||||
{
|
||||
// FIXME Find out how font path resolution is done in VX/Ace
|
||||
std::string nameKey(filename);
|
||||
strToLower(nameKey);
|
||||
strReplace(nameKey, ' ', '_');
|
||||
/* 'Regular' style */
|
||||
std::string regular;
|
||||
|
||||
bool useBundled = false;
|
||||
std::string path = std::string("Fonts/") + nameKey;
|
||||
if (!shState->fileSystem().exists(path.c_str(), FileSystem::Font))
|
||||
/* Any other styles (used in case no 'Regular' exists) */
|
||||
std::string other;
|
||||
};
|
||||
|
||||
struct SharedFontStatePrivate
|
||||
{
|
||||
/* Use the same name key for the bundled font
|
||||
* even when it resulted from multiple different
|
||||
* font name requests. The space at the front is
|
||||
* to prevent collisions (spaces are normally
|
||||
* replaced with '_' */
|
||||
useBundled = true;
|
||||
nameKey = " bundled";
|
||||
/* Maps: font family name, To: substituted family name,
|
||||
* as specified via configuration file / arguments */
|
||||
BoostHash<std::string, std::string> subs;
|
||||
|
||||
/* Maps: font family name, To: set of physical
|
||||
* font filenames located in "Fonts/" */
|
||||
BoostHash<std::string, FontSet> sets;
|
||||
|
||||
/* Pool of already opened fonts; once opened, they are reused
|
||||
* and never closed until the termination of the program */
|
||||
BoostHash<FontKey, TTF_Font*> pool;
|
||||
};
|
||||
|
||||
SharedFontState::SharedFontState(const Config &conf)
|
||||
{
|
||||
p = new SharedFontStatePrivate;
|
||||
|
||||
/* Parse font substitutions */
|
||||
for (size_t i = 0; i < conf.fontSubs.size(); ++i)
|
||||
{
|
||||
const std::string &raw = conf.fontSubs[i];
|
||||
size_t sepPos = raw.find_first_of('>');
|
||||
|
||||
if (sepPos == std::string::npos)
|
||||
continue;
|
||||
|
||||
std::string from = raw.substr(0, sepPos);
|
||||
std::string to = raw.substr(sepPos+1);
|
||||
|
||||
p->subs.insert(from, to);
|
||||
}
|
||||
}
|
||||
|
||||
FontKey key(nameKey, size);
|
||||
SharedFontState::~SharedFontState()
|
||||
{
|
||||
BoostHash<FontKey, TTF_Font*>::const_iterator iter;
|
||||
for (iter = p->pool.cbegin(); iter != p->pool.cend(); ++iter)
|
||||
TTF_CloseFont(iter->second);
|
||||
|
||||
TTF_Font *font = p->hash.value(key, 0);
|
||||
delete p;
|
||||
}
|
||||
|
||||
void SharedFontState::initFontSetCB(SDL_RWops &ops,
|
||||
const std::string &filename)
|
||||
{
|
||||
TTF_Font *font = TTF_OpenFontRW(&ops, 0, 0);
|
||||
|
||||
if (!font)
|
||||
return;
|
||||
|
||||
std::string family = TTF_FontFaceFamilyName(font);
|
||||
std::string style = TTF_FontFaceStyleName(font);
|
||||
|
||||
TTF_CloseFont(font);
|
||||
|
||||
FontSet &set = p->sets[family];
|
||||
|
||||
if (style == "Regular")
|
||||
set.regular = filename;
|
||||
else
|
||||
set.other = filename;
|
||||
}
|
||||
|
||||
_TTF_Font *SharedFontState::getFont(std::string family,
|
||||
int size)
|
||||
{
|
||||
/* Check for substitutions */
|
||||
if (p->subs.contains(family))
|
||||
family = p->subs[family];
|
||||
|
||||
/* Find out if the font asset exists */
|
||||
const FontSet &req = p->sets[family];
|
||||
|
||||
if (req.regular.empty() && req.other.empty())
|
||||
{
|
||||
/* Doesn't exist; use built-in font */
|
||||
family = "";
|
||||
}
|
||||
|
||||
FontKey key(family, size);
|
||||
|
||||
TTF_Font *font = p->pool.value(key);
|
||||
|
||||
if (font)
|
||||
return font;
|
||||
|
||||
/* Not in hash, open */
|
||||
/* Not in pool; open new handle */
|
||||
SDL_RWops *ops;
|
||||
|
||||
if (useBundled)
|
||||
if (family.empty())
|
||||
{
|
||||
/* Built-in font */
|
||||
ops = openBundledFont();
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Use 'other' path as alternative in case
|
||||
* we have no 'regular' styled font asset */
|
||||
const char *path = !req.regular.empty()
|
||||
? req.regular.c_str() : req.other.c_str();
|
||||
|
||||
ops = SDL_AllocRW();
|
||||
shState->fileSystem().openRead(*ops, path.c_str(), FileSystem::Font, true);
|
||||
shState->fileSystem().openReadRaw(*ops, path, true);
|
||||
}
|
||||
|
||||
// FIXME 0.9 is guesswork at this point
|
||||
font = TTF_OpenFontRW(ops, 1, (float) size * .90);
|
||||
// float gamma = (96.0/45.0)*(5.0/14.0)*(size-5);
|
||||
// font = TTF_OpenFontRW(ops, 1, gamma /** .90*/);
|
||||
font = TTF_OpenFontRW(ops, 1, size* .90);
|
||||
|
||||
if (!font)
|
||||
throw Exception(Exception::SDLError, "SDL: %s", SDL_GetError());
|
||||
throw Exception(Exception::SDLError, "%s", SDL_GetError());
|
||||
|
||||
p->hash.insert(key, font);
|
||||
p->pool.insert(key, font);
|
||||
|
||||
return font;
|
||||
}
|
||||
|
||||
bool SharedFontState::fontPresent(std::string family)
|
||||
{
|
||||
/* Check for substitutions */
|
||||
if (p->subs.contains(family))
|
||||
family = p->subs[family];
|
||||
|
||||
const FontSet &set = p->sets[family];
|
||||
|
||||
return !(set.regular.empty() && set.other.empty());
|
||||
}
|
||||
|
||||
|
||||
struct FontPrivate
|
||||
{
|
||||
|
@ -183,9 +247,7 @@ Color FontPrivate::defaultColorTmp(255, 255, 255, 255);
|
|||
|
||||
bool Font::doesExist(const char *name)
|
||||
{
|
||||
std::string path = std::string("Fonts/") + std::string(name);
|
||||
|
||||
return shState->fileSystem().exists(path.c_str(), FileSystem::Font);
|
||||
return shState->fontState().fontPresent(name);
|
||||
}
|
||||
|
||||
Font::Font(const char *name,
|
||||
|
@ -223,6 +285,10 @@ void Font::setSize(int value)
|
|||
if (p->size == value)
|
||||
return;
|
||||
|
||||
/* Catch illegal values (according to RMXP) */
|
||||
if (value < 6 || value > 96)
|
||||
throw Exception(Exception::ArgumentError, "%s", "bad value for size");
|
||||
|
||||
p->size = value;
|
||||
p->sdlFont = 0;
|
||||
}
|
||||
|
@ -253,7 +319,7 @@ void Font::setDefaultName(const char *value)
|
|||
_TTF_Font *Font::getSdlFont()
|
||||
{
|
||||
if (!p->sdlFont)
|
||||
p->sdlFont = shState->fontPool().request(p->name.c_str(),
|
||||
p->sdlFont = shState->fontState().getFont(p->name.c_str(),
|
||||
p->size);
|
||||
|
||||
int style = TTF_STYLE_NORMAL;
|
||||
|
|
40
src/font.h
40
src/font.h
|
@ -25,22 +25,50 @@
|
|||
#include "etc.h"
|
||||
#include "util.h"
|
||||
|
||||
struct SDL_RWops;
|
||||
struct _TTF_Font;
|
||||
struct FontPoolPrivate;
|
||||
struct Config;
|
||||
|
||||
class FontPool
|
||||
struct SharedFontStatePrivate;
|
||||
|
||||
class SharedFontState
|
||||
{
|
||||
public:
|
||||
FontPool();
|
||||
~FontPool();
|
||||
SharedFontState(const Config &conf);
|
||||
~SharedFontState();
|
||||
|
||||
_TTF_Font *request(const char *filename,
|
||||
/* Called from FileSystem during font cache initialization
|
||||
* (when "Fonts/" is scanned for available assets).
|
||||
* 'ops' is an opened handle to a possible font file,
|
||||
* 'filename' is the corresponding path */
|
||||
void initFontSetCB(SDL_RWops &ops,
|
||||
const std::string &filename);
|
||||
|
||||
_TTF_Font *getFont(std::string family,
|
||||
int size);
|
||||
|
||||
bool fontPresent(std::string family);
|
||||
|
||||
private:
|
||||
FontPoolPrivate *p;
|
||||
SharedFontStatePrivate *p;
|
||||
};
|
||||
|
||||
/* Concerning Font::name/defaultName :
|
||||
* In RGSS, this is not actually a string; any type of
|
||||
* object is accepted, however anything but strings and
|
||||
* arrays is ignored (and text drawing turns blank).
|
||||
* Single strings are interpreted as font family names,
|
||||
* and directly passed to the underlying C++ object;
|
||||
* arrays however are searched for the first string
|
||||
* object corresponding to a valid font family name,
|
||||
* and rendering is done with that. In mkxp, we pass
|
||||
* this first valid font family as the 'name' attribute
|
||||
* back to the C++ object on assignment and object
|
||||
* creation (in case Font.default_name is also an array).
|
||||
* Invalid parameters (things other than strings or
|
||||
* arrays not containing any valid family name) are
|
||||
* passed back as "". */
|
||||
|
||||
struct FontPrivate;
|
||||
|
||||
class Font
|
||||
|
|
|
@ -67,8 +67,8 @@ struct SharedStatePrivate
|
|||
ShaderSet shaders;
|
||||
|
||||
TexPool texPool;
|
||||
FontPool fontPool;
|
||||
|
||||
SharedFontState fontState;
|
||||
Font *defaultFont;
|
||||
|
||||
TEX::ID globalTex;
|
||||
|
@ -90,6 +90,7 @@ struct SharedStatePrivate
|
|||
rtData(*threadData),
|
||||
config(threadData->config),
|
||||
graphics(threadData),
|
||||
fontState(threadData->config),
|
||||
stampCounter(0)
|
||||
{
|
||||
if (!config.gameFolder.empty())
|
||||
|
@ -121,6 +122,8 @@ struct SharedStatePrivate
|
|||
if (config.pathCache)
|
||||
fileSystem.createPathCache();
|
||||
|
||||
fileSystem.initFontSets(fontState);
|
||||
|
||||
globalTexW = 128;
|
||||
globalTexH = 64;
|
||||
|
||||
|
@ -206,8 +209,8 @@ GSATT(Audio&, audio)
|
|||
GSATT(GLState&, _glState)
|
||||
GSATT(ShaderSet&, shaders)
|
||||
GSATT(TexPool&, texPool)
|
||||
GSATT(FontPool&, fontPool)
|
||||
GSATT(Quad&, gpQuad)
|
||||
GSATT(SharedFontState&, fontState)
|
||||
|
||||
void SharedState::setBindingData(void *data)
|
||||
{
|
||||
|
|
|
@ -43,8 +43,8 @@ class Input;
|
|||
class Audio;
|
||||
class GLState;
|
||||
class TexPool;
|
||||
class FontPool;
|
||||
class Font;
|
||||
class SharedFontState;
|
||||
struct GlobalIBO;
|
||||
struct Config;
|
||||
struct Vec2i;
|
||||
|
@ -74,8 +74,8 @@ struct SharedState
|
|||
ShaderSet &shaders();
|
||||
|
||||
TexPool &texPool();
|
||||
FontPool &fontPool();
|
||||
|
||||
SharedFontState &fontState();
|
||||
Font &defaultFont();
|
||||
|
||||
sigc::signal<void> prepareDraw;
|
||||
|
|
|
@ -48,13 +48,6 @@ extern const StaticRect autotileRects[];
|
|||
typedef std::vector<SVertex> SVVector;
|
||||
typedef struct { SVVector v[4]; } TileVBuffer;
|
||||
|
||||
/* Check if [C]ontainer contains [V]alue */
|
||||
template<typename C, typename V>
|
||||
inline bool contains(const C &c, const V &v)
|
||||
{
|
||||
return std::find(c.begin(), c.end(), v) != c.end();
|
||||
}
|
||||
|
||||
static const int tilesetW = 8 * 32;
|
||||
static const int autotileW = 3 * 32;
|
||||
static const int autotileH = 4 * 32;
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
|
||||
#include <stdio.h>
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
|
||||
static inline int
|
||||
wrapRange(int value, int min, int max)
|
||||
|
@ -93,6 +94,13 @@ inline void strReplace(std::string &str,
|
|||
str[i] = after;
|
||||
}
|
||||
|
||||
/* Check if [C]ontainer contains [V]alue */
|
||||
template<typename C, typename V>
|
||||
inline bool contains(const C &c, const V &v)
|
||||
{
|
||||
return std::find(c.begin(), c.end(), v) != c.end();
|
||||
}
|
||||
|
||||
#define ARRAY_SIZE(obj) (sizeof(obj) / sizeof((obj)[0]))
|
||||
|
||||
#define elementsN(obj) const int obj##N = ARRAY_SIZE(obj)
|
||||
|
|
Loading…
Reference in New Issue