blob: 462c4235c79786705aa4d96b2d162df43ecafd69 [file] [log] [blame]
/*
* fontconfig/src/fccfg.c
*
* Copyright © 2000 Keith Packard
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
* the above copyright notice appear in all copies and that both that
* copyright notice and this permission notice appear in supporting
* documentation, and that the name of the author(s) not be used in
* advertising or publicity pertaining to distribution of the software without
* specific, written prior permission. The authors make no
* representations about the suitability of this software for any purpose. It
* is provided "as is" without express or implied warranty.
*
* THE AUTHOR(S) DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
* INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
* EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY SPECIAL, INDIRECT OR
* CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
* DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
* TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
/* Objects MT-safe for readonly access. */
#include "fcint.h"
#ifdef HAVE_DIRENT_H
#include <dirent.h>
#endif
#include <sys/types.h>
#if defined (_WIN32) && !defined (R_OK)
#define R_OK 4
#endif
#if defined(_WIN32) && !defined(S_ISFIFO)
#define S_ISFIFO(m) 0
#endif
static FcConfig *_fcConfig; /* MT-safe */
static FcMutex *_lock;
static void
lock_config (void)
{
FcMutex *lock;
retry:
lock = fc_atomic_ptr_get (&_lock);
if (!lock)
{
lock = (FcMutex *) malloc (sizeof (FcMutex));
FcMutexInit (lock);
if (!fc_atomic_ptr_cmpexch (&_lock, NULL, lock))
{
FcMutexFinish (lock);
free (lock);
goto retry;
}
FcMutexLock (lock);
/* Initialize random state */
FcRandom ();
return;
}
FcMutexLock (lock);
}
static void
unlock_config (void)
{
FcMutex *lock;
lock = fc_atomic_ptr_get (&_lock);
FcMutexUnlock (lock);
}
static void
free_lock (void)
{
FcMutex *lock;
lock = fc_atomic_ptr_get (&_lock);
if (lock && fc_atomic_ptr_cmpexch (&_lock, lock, NULL))
{
FcMutexFinish (lock);
free (lock);
}
}
static FcConfig *
FcConfigEnsure (void)
{
FcConfig *config;
retry:
config = fc_atomic_ptr_get (&_fcConfig);
if (!config)
{
config = FcInitLoadConfigAndFonts ();
if (!config || !fc_atomic_ptr_cmpexch (&_fcConfig, NULL, config)) {
if (config)
FcConfigDestroy (config);
goto retry;
}
}
return config;
}
static void
FcDestroyAsRule (void *data)
{
FcRuleDestroy (data);
}
static void
FcDestroyAsRuleSet (void *data)
{
FcRuleSetDestroy (data);
}
FcBool
FcConfigInit (void)
{
return FcConfigEnsure () ? FcTrue : FcFalse;
}
void
FcConfigFini (void)
{
FcConfig *cfg = fc_atomic_ptr_get (&_fcConfig);
if (cfg && fc_atomic_ptr_cmpexch (&_fcConfig, cfg, NULL))
FcConfigDestroy (cfg);
free_lock ();
}
static FcChar8 *
FcConfigRealPath(const FcChar8 *path)
{
char resolved_name[FC_PATH_MAX+1];
char *resolved_ret;
if (!path)
return NULL;
#ifndef _WIN32
resolved_ret = realpath((const char *) path, resolved_name);
#else
if (GetFullPathNameA ((LPCSTR) path, FC_PATH_MAX, resolved_name, NULL) == 0)
{
fprintf (stderr, "Fontconfig warning: GetFullPathNameA failed.\n");
return NULL;
}
resolved_ret = resolved_name;
#endif
if (resolved_ret)
path = (FcChar8 *) resolved_ret;
return FcStrCopyFilename(path);
}
FcConfig *
FcConfigCreate (void)
{
FcSetName set;
FcConfig *config;
FcMatchKind k;
FcBool err = FcFalse;
config = malloc (sizeof (FcConfig));
if (!config)
goto bail0;
config->configDirs = FcStrSetCreate ();
if (!config->configDirs)
goto bail1;
config->configMapDirs = FcStrSetCreate();
if (!config->configMapDirs)
goto bail1_5;
config->configFiles = FcStrSetCreate ();
if (!config->configFiles)
goto bail2;
config->fontDirs = FcStrSetCreate ();
if (!config->fontDirs)
goto bail3;
config->acceptGlobs = FcStrSetCreate ();
if (!config->acceptGlobs)
goto bail4;
config->rejectGlobs = FcStrSetCreate ();
if (!config->rejectGlobs)
goto bail5;
config->acceptPatterns = FcFontSetCreate ();
if (!config->acceptPatterns)
goto bail6;
config->rejectPatterns = FcFontSetCreate ();
if (!config->rejectPatterns)
goto bail7;
config->cacheDirs = FcStrSetCreate ();
if (!config->cacheDirs)
goto bail8;
for (k = FcMatchKindBegin; k < FcMatchKindEnd; k++)
{
config->subst[k] = FcPtrListCreate (FcDestroyAsRuleSet);
if (!config->subst[k])
err = FcTrue;
}
if (err)
goto bail9;
config->maxObjects = 0;
for (set = FcSetSystem; set <= FcSetApplication; set++)
config->fonts[set] = 0;
config->rescanTime = time(0);
config->rescanInterval = 30;
config->expr_pool = NULL;
config->sysRoot = FcConfigRealPath((const FcChar8 *) getenv("FONTCONFIG_SYSROOT"));
config->rulesetList = FcPtrListCreate (FcDestroyAsRuleSet);
if (!config->rulesetList)
goto bail9;
config->availConfigFiles = FcStrSetCreate ();
if (!config->availConfigFiles)
goto bail10;
FcRefInit (&config->ref, 1);
return config;
bail10:
FcPtrListDestroy (config->rulesetList);
bail9:
for (k = FcMatchKindBegin; k < FcMatchKindEnd; k++)
if (config->subst[k])
FcPtrListDestroy (config->subst[k]);
FcStrSetDestroy (config->cacheDirs);
bail8:
FcFontSetDestroy (config->rejectPatterns);
bail7:
FcFontSetDestroy (config->acceptPatterns);
bail6:
FcStrSetDestroy (config->rejectGlobs);
bail5:
FcStrSetDestroy (config->acceptGlobs);
bail4:
FcStrSetDestroy (config->fontDirs);
bail3:
FcStrSetDestroy (config->configFiles);
bail2:
FcStrSetDestroy (config->configMapDirs);
bail1_5:
FcStrSetDestroy (config->configDirs);
bail1:
free (config);
bail0:
return 0;
}
static FcFileTime
FcConfigNewestFile (FcStrSet *files)
{
FcStrList *list = FcStrListCreate (files);
FcFileTime newest = { 0, FcFalse };
FcChar8 *file;
struct stat statb;
if (list)
{
while ((file = FcStrListNext (list)))
if (FcStat (file, &statb) == 0)
if (!newest.set || statb.st_mtime - newest.time > 0)
{
newest.set = FcTrue;
newest.time = statb.st_mtime;
}
FcStrListDone (list);
}
return newest;
}
FcBool
FcConfigUptoDate (FcConfig *config)
{
FcFileTime config_time, config_dir_time, font_time;
time_t now = time(0);
FcBool ret = FcTrue;
config = FcConfigReference (config);
if (!config)
return FcFalse;
config_time = FcConfigNewestFile (config->configFiles);
config_dir_time = FcConfigNewestFile (config->configDirs);
font_time = FcConfigNewestFile (config->fontDirs);
if ((config_time.set && config_time.time - config->rescanTime > 0) ||
(config_dir_time.set && (config_dir_time.time - config->rescanTime) > 0) ||
(font_time.set && (font_time.time - config->rescanTime) > 0))
{
/* We need to check for potential clock problems here (OLPC ticket #6046) */
if ((config_time.set && (config_time.time - now) > 0) ||
(config_dir_time.set && (config_dir_time.time - now) > 0) ||
(font_time.set && (font_time.time - now) > 0))
{
fprintf (stderr,
"Fontconfig warning: Directory/file mtime in the future. New fonts may not be detected.\n");
config->rescanTime = now;
goto bail;
}
else
{
ret = FcFalse;
goto bail;
}
}
config->rescanTime = now;
bail:
FcConfigDestroy (config);
return ret;
}
FcExpr *
FcConfigAllocExpr (FcConfig *config)
{
if (!config->expr_pool || config->expr_pool->next == config->expr_pool->end)
{
FcExprPage *new_page;
new_page = malloc (sizeof (FcExprPage));
if (!new_page)
return 0;
new_page->next_page = config->expr_pool;
new_page->next = new_page->exprs;
config->expr_pool = new_page;
}
return config->expr_pool->next++;
}
FcConfig *
FcConfigReference (FcConfig *config)
{
if (!config)
{
/* lock during obtaining the value from _fcConfig and count up refcount there,
* there are the race between them.
*/
lock_config ();
retry:
config = fc_atomic_ptr_get (&_fcConfig);
if (!config)
{
unlock_config ();
config = FcInitLoadConfigAndFonts ();
if (!config)
goto retry;
lock_config ();
if (!fc_atomic_ptr_cmpexch (&_fcConfig, NULL, config))
{
FcConfigDestroy (config);
goto retry;
}
}
FcRefInc (&config->ref);
unlock_config ();
}
else
FcRefInc (&config->ref);
return config;
}
void
FcConfigDestroy (FcConfig *config)
{
FcSetName set;
FcExprPage *page;
FcMatchKind k;
if (FcRefDec (&config->ref) != 1)
return;
(void) fc_atomic_ptr_cmpexch (&_fcConfig, config, NULL);
FcStrSetDestroy (config->configDirs);
FcStrSetDestroy (config->configMapDirs);
FcStrSetDestroy (config->fontDirs);
FcStrSetDestroy (config->cacheDirs);
FcStrSetDestroy (config->configFiles);
FcStrSetDestroy (config->acceptGlobs);
FcStrSetDestroy (config->rejectGlobs);
FcFontSetDestroy (config->acceptPatterns);
FcFontSetDestroy (config->rejectPatterns);
for (k = FcMatchKindBegin; k < FcMatchKindEnd; k++)
FcPtrListDestroy (config->subst[k]);
FcPtrListDestroy (config->rulesetList);
FcStrSetDestroy (config->availConfigFiles);
for (set = FcSetSystem; set <= FcSetApplication; set++)
if (config->fonts[set])
FcFontSetDestroy (config->fonts[set]);
page = config->expr_pool;
while (page)
{
FcExprPage *next = page->next_page;
free (page);
page = next;
}
if (config->sysRoot)
FcStrFree (config->sysRoot);
free (config);
}
/*
* Add cache to configuration, adding fonts and directories
*/
FcBool
FcConfigAddCache (FcConfig *config, FcCache *cache,
FcSetName set, FcStrSet *dirSet, FcChar8 *forDir)
{
FcFontSet *fs;
intptr_t *dirs;
int i;
FcBool relocated = FcFalse;
if (strcmp ((char *)FcCacheDir(cache), (char *)forDir) != 0)
relocated = FcTrue;
/*
* Add fonts
*/
fs = FcCacheSet (cache);
if (fs)
{
int nref = 0;
for (i = 0; i < fs->nfont; i++)
{
FcPattern *font = FcFontSetFont (fs, i);
FcChar8 *font_file;
FcChar8 *relocated_font_file = NULL;
if (FcPatternObjectGetString (font, FC_FILE_OBJECT,
0, &font_file) == FcResultMatch)
{
if (relocated)
{
FcChar8 *slash = FcStrLastSlash (font_file);
relocated_font_file = FcStrBuildFilename (forDir, slash + 1, NULL);
font_file = relocated_font_file;
}
/*
* Check to see if font is banned by filename
*/
if (!FcConfigAcceptFilename (config, font_file))
{
free (relocated_font_file);
continue;
}
}
/*
* Check to see if font is banned by pattern
*/
if (!FcConfigAcceptFont (config, font))
{
free (relocated_font_file);
continue;
}
if (relocated_font_file)
{
font = FcPatternCacheRewriteFile (font, cache, relocated_font_file);
free (relocated_font_file);
}
if (FcFontSetAdd (config->fonts[set], font))
nref++;
}
FcDirCacheReference (cache, nref);
}
/*
* Add directories
*/
dirs = FcCacheDirs (cache);
if (dirs)
{
for (i = 0; i < cache->dirs_count; i++)
{
const FcChar8 *dir = FcCacheSubdir (cache, i);
FcChar8 *s = NULL;
if (relocated)
{
FcChar8 *base = FcStrBasename (dir);
dir = s = FcStrBuildFilename (forDir, base, NULL);
FcStrFree (base);
}
if (FcConfigAcceptFilename (config, dir))
FcStrSetAddFilename (dirSet, dir);
if (s)
FcStrFree (s);
}
}
return FcTrue;
}
static FcBool
FcConfigAddDirList (FcConfig *config, FcSetName set, FcStrSet *dirSet)
{
FcStrList *dirlist;
FcChar8 *dir;
FcCache *cache;
dirlist = FcStrListCreate (dirSet);
if (!dirlist)
return FcFalse;
while ((dir = FcStrListNext (dirlist)))
{
if (FcDebug () & FC_DBG_FONTSET)
printf ("adding fonts from %s\n", dir);
cache = FcDirCacheRead (dir, FcFalse, config);
if (!cache)
continue;
FcConfigAddCache (config, cache, set, dirSet, dir);
FcDirCacheUnload (cache);
}
FcStrListDone (dirlist);
return FcTrue;
}
/*
* Scan the current list of directories in the configuration
* and build the set of available fonts.
*/
FcBool
FcConfigBuildFonts (FcConfig *config)
{
FcFontSet *fonts;
FcBool ret = FcTrue;
config = FcConfigReference (config);
if (!config)
return FcFalse;
fonts = FcFontSetCreate ();
if (!fonts)
{
ret = FcFalse;
goto bail;
}
FcConfigSetFonts (config, fonts, FcSetSystem);
if (!FcConfigAddDirList (config, FcSetSystem, config->fontDirs))
{
ret = FcFalse;
goto bail;
}
if (FcDebug () & FC_DBG_FONTSET)
FcFontSetPrint (fonts);
bail:
FcConfigDestroy (config);
return ret;
}
FcBool
FcConfigSetCurrent (FcConfig *config)
{
FcConfig *cfg;
if (config)
{
if (!config->fonts[FcSetSystem])
if (!FcConfigBuildFonts (config))
return FcFalse;
FcRefInc (&config->ref);
}
lock_config ();
retry:
cfg = fc_atomic_ptr_get (&_fcConfig);
if (config == cfg)
{
unlock_config ();
if (config)
FcConfigDestroy (config);
return FcTrue;
}
if (!fc_atomic_ptr_cmpexch (&_fcConfig, cfg, config))
goto retry;
unlock_config ();
if (cfg)
FcConfigDestroy (cfg);
return FcTrue;
}
FcConfig *
FcConfigGetCurrent (void)
{
return FcConfigEnsure ();
}
FcBool
FcConfigAddConfigDir (FcConfig *config,
const FcChar8 *d)
{
return FcStrSetAddFilename (config->configDirs, d);
}
FcStrList *
FcConfigGetConfigDirs (FcConfig *config)
{
FcStrList *ret;
config = FcConfigReference (config);
if (!config)
return NULL;
ret = FcStrListCreate (config->configDirs);
FcConfigDestroy (config);
return ret;
}
FcBool
FcConfigAddFontDir (FcConfig *config,
const FcChar8 *d,
const FcChar8 *m,
const FcChar8 *salt)
{
if (FcDebug() & FC_DBG_CACHE)
{
if (m)
{
printf ("%s -> %s%s%s%s\n", d, m, salt ? " (salt: " : "", salt ? (const char *)salt : "", salt ? ")" : "");
}
else if (salt)
{
printf ("%s%s%s%s\n", d, salt ? " (salt: " : "", salt ? (const char *)salt : "", salt ? ")" : "");
}
}
return FcStrSetAddFilenamePairWithSalt (config->fontDirs, d, m, salt);
}
FcBool
FcConfigResetFontDirs (FcConfig *config)
{
if (FcDebug() & FC_DBG_CACHE)
{
printf ("Reset font directories!\n");
}
return FcStrSetDeleteAll (config->fontDirs);
}
FcStrList *
FcConfigGetFontDirs (FcConfig *config)
{
FcStrList *ret;
config = FcConfigReference (config);
if (!config)
return NULL;
ret = FcStrListCreate (config->fontDirs);
FcConfigDestroy (config);
return ret;
}
static FcBool
FcConfigPathStartsWith(const FcChar8 *path,
const FcChar8 *start)
{
int len = strlen((char *) start);
if (strncmp((char *) path, (char *) start, len) != 0)
return FcFalse;
switch (path[len]) {
case '\0':
case FC_DIR_SEPARATOR:
return FcTrue;
default:
return FcFalse;
}
}
FcChar8 *
FcConfigMapFontPath(FcConfig *config,
const FcChar8 *path)
{
FcStrList *list;
FcChar8 *dir;
const FcChar8 *map, *rpath;
FcChar8 *retval;
list = FcConfigGetFontDirs(config);
if (!list)
return 0;
while ((dir = FcStrListNext(list)))
if (FcConfigPathStartsWith(path, dir))
break;
FcStrListDone(list);
if (!dir)
return 0;
map = FcStrTripleSecond(dir);
if (!map)
return 0;
rpath = path + strlen ((char *) dir);
while (*rpath == '/')
rpath++;
retval = FcStrBuildFilename(map, rpath, NULL);
if (retval)
{
size_t len = strlen ((const char *) retval);
while (len > 0 && retval[len-1] == '/')
len--;
/* trim the last slash */
retval[len] = 0;
}
return retval;
}
const FcChar8 *
FcConfigMapSalt (FcConfig *config,
const FcChar8 *path)
{
FcStrList *list;
FcChar8 *dir;
list = FcConfigGetFontDirs (config);
if (!list)
return NULL;
while ((dir = FcStrListNext (list)))
if (FcConfigPathStartsWith (path, dir))
break;
FcStrListDone (list);
if (!dir)
return NULL;
return FcStrTripleThird (dir);
}
FcBool
FcConfigAddCacheDir (FcConfig *config,
const FcChar8 *d)
{
return FcStrSetAddFilename (config->cacheDirs, d);
}
FcStrList *
FcConfigGetCacheDirs (FcConfig *config)
{
FcStrList *ret;
config = FcConfigReference (config);
if (!config)
return NULL;
ret = FcStrListCreate (config->cacheDirs);
FcConfigDestroy (config);
return ret;
}
FcBool
FcConfigAddConfigFile (FcConfig *config,
const FcChar8 *f)
{
FcBool ret;
FcChar8 *file = FcConfigGetFilename (config, f);
if (!file)
return FcFalse;
ret = FcStrSetAdd (config->configFiles, file);
FcStrFree (file);
return ret;
}
FcStrList *
FcConfigGetConfigFiles (FcConfig *config)
{
FcStrList *ret;
config = FcConfigReference (config);
if (!config)
return NULL;
ret = FcStrListCreate (config->configFiles);
FcConfigDestroy (config);
return ret;
}
FcChar8 *
FcConfigGetCache (FcConfig *config FC_UNUSED)
{
return NULL;
}
FcFontSet *
FcConfigGetFonts (FcConfig *config,
FcSetName set)
{
if (!config)
{
config = FcConfigGetCurrent ();
if (!config)
return 0;
}
return config->fonts[set];
}
void
FcConfigSetFonts (FcConfig *config,
FcFontSet *fonts,
FcSetName set)
{
if (config->fonts[set])
FcFontSetDestroy (config->fonts[set]);
config->fonts[set] = fonts;
}
FcBlanks *
FcBlanksCreate (void)
{
/* Deprecated. */
return NULL;
}
void
FcBlanksDestroy (FcBlanks *b FC_UNUSED)
{
/* Deprecated. */
}
FcBool
FcBlanksAdd (FcBlanks *b FC_UNUSED, FcChar32 ucs4 FC_UNUSED)
{
/* Deprecated. */
return FcFalse;
}
FcBool
FcBlanksIsMember (FcBlanks *b FC_UNUSED, FcChar32 ucs4 FC_UNUSED)
{
/* Deprecated. */
return FcFalse;
}
FcBlanks *
FcConfigGetBlanks (FcConfig *config FC_UNUSED)
{
/* Deprecated. */
return NULL;
}
FcBool
FcConfigAddBlank (FcConfig *config FC_UNUSED,
FcChar32 blank FC_UNUSED)
{
/* Deprecated. */
return FcFalse;
}
int
FcConfigGetRescanInterval (FcConfig *config)
{
int ret;
config = FcConfigReference (config);
if (!config)
return 0;
ret = config->rescanInterval;
FcConfigDestroy (config);
return ret;
}
FcBool
FcConfigSetRescanInterval (FcConfig *config, int rescanInterval)
{
config = FcConfigReference (config);
if (!config)
return FcFalse;
config->rescanInterval = rescanInterval;
FcConfigDestroy (config);
return FcTrue;
}
/*
* A couple of typos escaped into the library
*/
int
FcConfigGetRescanInverval (FcConfig *config)
{
return FcConfigGetRescanInterval (config);
}
FcBool
FcConfigSetRescanInverval (FcConfig *config, int rescanInterval)
{
return FcConfigSetRescanInterval (config, rescanInterval);
}
FcBool
FcConfigAddRule (FcConfig *config,
FcRule *rule,
FcMatchKind kind)
{
/* deprecated */
return FcFalse;
}
static FcValue
FcConfigPromote (FcValue v, FcValue u, FcValuePromotionBuffer *buf)
{
switch (v.type)
{
case FcTypeInteger:
v.type = FcTypeDouble;
v.u.d = (double) v.u.i;
/* Fallthrough */
case FcTypeDouble:
if (u.type == FcTypeRange && buf)
{
v.u.r = FcRangePromote (v.u.d, buf);
v.type = FcTypeRange;
}
break;
case FcTypeVoid:
if (u.type == FcTypeMatrix)
{
v.u.m = &FcIdentityMatrix;
v.type = FcTypeMatrix;
}
else if (u.type == FcTypeLangSet && buf)
{
v.u.l = FcLangSetPromote (NULL, buf);
v.type = FcTypeLangSet;
}
else if (u.type == FcTypeCharSet && buf)
{
v.u.c = FcCharSetPromote (buf);
v.type = FcTypeCharSet;
}
break;
case FcTypeString:
if (u.type == FcTypeLangSet && buf)
{
v.u.l = FcLangSetPromote (v.u.s, buf);
v.type = FcTypeLangSet;
}
break;
default:
break;
}
return v;
}
FcBool
FcConfigCompareValue (const FcValue *left_o,
unsigned int op_,
const FcValue *right_o)
{
FcValue left;
FcValue right;
FcBool ret = FcFalse;
FcOp op = FC_OP_GET_OP (op_);
int flags = FC_OP_GET_FLAGS (op_);
FcValuePromotionBuffer buf1, buf2;
if (left_o->type != right_o->type)
{
left = FcValueCanonicalize(left_o);
right = FcValueCanonicalize(right_o);
left = FcConfigPromote (left, right, &buf1);
right = FcConfigPromote (right, left, &buf2);
left_o = &left;
right_o = &right;
if (left_o->type != right_o->type)
{
if (op == FcOpNotEqual || op == FcOpNotContains)
ret = FcTrue;
return ret;
}
}
switch (left_o->type) {
case FcTypeUnknown:
break; /* No way to guess how to compare for this object */
case FcTypeInteger: {
int l = left_o->u.i;
int r = right_o->u.i;
switch ((int) op) {
case FcOpEqual:
case FcOpContains:
case FcOpListing:
ret = l == r;
break;
case FcOpNotEqual:
case FcOpNotContains:
ret = l != r;
break;
case FcOpLess:
ret = l < r;
break;
case FcOpLessEqual:
ret = l <= r;
break;
case FcOpMore:
ret = l > r;
break;
case FcOpMoreEqual:
ret = l >= r;
break;
default:
break;
}
break;
}
case FcTypeDouble: {
double l = left_o->u.d;
double r = right_o->u.d;
switch ((int) op) {
case FcOpEqual:
case FcOpContains:
case FcOpListing:
ret = l == r;
break;
case FcOpNotEqual:
case FcOpNotContains:
ret = l != r;
break;
case FcOpLess:
ret = l < r;
break;
case FcOpLessEqual:
ret = l <= r;
break;
case FcOpMore:
ret = l > r;
break;
case FcOpMoreEqual:
ret = l >= r;
break;
default:
break;
}
break;
}
case FcTypeBool: {
FcBool l = left_o->u.b;
FcBool r = right_o->u.b;
switch ((int) op) {
case FcOpEqual:
ret = l == r;
break;
case FcOpContains:
case FcOpListing:
ret = l == r || l >= FcDontCare;
break;
case FcOpNotEqual:
ret = l != r;
break;
case FcOpNotContains:
ret = !(l == r || l >= FcDontCare);
break;
case FcOpLess:
ret = l != r && r >= FcDontCare;
break;
case FcOpLessEqual:
ret = l == r || r >= FcDontCare;
break;
case FcOpMore:
ret = l != r && l >= FcDontCare;
break;
case FcOpMoreEqual:
ret = l == r || l >= FcDontCare;
break;
default:
break;
}
break;
}
case FcTypeString: {
const FcChar8 *l = FcValueString (left_o);
const FcChar8 *r = FcValueString (right_o);
switch ((int) op) {
case FcOpEqual:
case FcOpListing:
if (flags & FcOpFlagIgnoreBlanks)
ret = FcStrCmpIgnoreBlanksAndCase (l, r) == 0;
else
ret = FcStrCmpIgnoreCase (l, r) == 0;
break;
case FcOpContains:
ret = FcStrStrIgnoreCase (l, r) != 0;
break;
case FcOpNotEqual:
if (flags & FcOpFlagIgnoreBlanks)
ret = FcStrCmpIgnoreBlanksAndCase (l, r) != 0;
else
ret = FcStrCmpIgnoreCase (l, r) != 0;
break;
case FcOpNotContains:
ret = FcStrStrIgnoreCase (l, r) == 0;
break;
default:
break;
}
break;
}
case FcTypeMatrix: {
switch ((int) op) {
case FcOpEqual:
case FcOpContains:
case FcOpListing:
ret = FcMatrixEqual (left_o->u.m, right_o->u.m);
break;
case FcOpNotEqual:
case FcOpNotContains:
ret = !FcMatrixEqual (left_o->u.m, right_o->u.m);
break;
default:
break;
}
break;
}
case FcTypeCharSet: {
const FcCharSet *l = FcValueCharSet (left_o);
const FcCharSet *r = FcValueCharSet (right_o);
switch ((int) op) {
case FcOpContains:
case FcOpListing:
/* left contains right if right is a subset of left */
ret = FcCharSetIsSubset (r, l);
break;
case FcOpNotContains:
/* left contains right if right is a subset of left */
ret = !FcCharSetIsSubset (r, l);
break;
case FcOpEqual:
ret = FcCharSetEqual (l, r);
break;
case FcOpNotEqual:
ret = !FcCharSetEqual (l, r);
break;
default:
break;
}
break;
}
case FcTypeLangSet: {
const FcLangSet *l = FcValueLangSet (left_o);
const FcLangSet *r = FcValueLangSet (right_o);
switch ((int) op) {
case FcOpContains:
case FcOpListing:
ret = FcLangSetContains (l, r);
break;
case FcOpNotContains:
ret = !FcLangSetContains (l, r);
break;
case FcOpEqual:
ret = FcLangSetEqual (l, r);
break;
case FcOpNotEqual:
ret = !FcLangSetEqual (l, r);
break;
default:
break;
}
break;
}
case FcTypeVoid:
switch ((int) op) {
case FcOpEqual:
case FcOpContains:
case FcOpListing:
ret = FcTrue;
break;
default:
break;
}
break;
case FcTypeFTFace:
switch ((int) op) {
case FcOpEqual:
case FcOpContains:
case FcOpListing:
ret = left_o->u.f == right_o->u.f;
break;
case FcOpNotEqual:
case FcOpNotContains:
ret = left_o->u.f != right_o->u.f;
break;
default:
break;
}
break;
case FcTypeRange: {
const FcRange *l = FcValueRange (left_o);
const FcRange *r = FcValueRange (right_o);
ret = FcRangeCompare (op, l, r);
break;
}
}
return ret;
}
#define _FcDoubleFloor(d) ((int) (d))
#define _FcDoubleCeil(d) ((double) (int) (d) == (d) ? (int) (d) : (int) ((d) + 1))
#define FcDoubleFloor(d) ((d) >= 0 ? _FcDoubleFloor(d) : -_FcDoubleCeil(-(d)))
#define FcDoubleCeil(d) ((d) >= 0 ? _FcDoubleCeil(d) : -_FcDoubleFloor(-(d)))
#define FcDoubleRound(d) FcDoubleFloor ((d) + 0.5)
#define FcDoubleTrunc(d) ((d) >= 0 ? _FcDoubleFloor (d) : -_FcDoubleFloor (-(d)))
static FcValue
FcConfigEvaluate (FcPattern *p, FcPattern *p_pat, FcMatchKind kind, FcExpr *e)
{
FcValue v, vl, vr, vle, vre;
FcMatrix *m;
FcChar8 *str;
FcOp op = FC_OP_GET_OP (e->op);
FcValuePromotionBuffer buf1, buf2;
switch ((int) op) {
case FcOpInteger:
v.type = FcTypeInteger;
v.u.i = e->u.ival;
break;
case FcOpDouble:
v.type = FcTypeDouble;
v.u.d = e->u.dval;
break;
case FcOpString:
v.type = FcTypeString;
v.u.s = e->u.sval;
v = FcValueSave (v);
break;
case FcOpMatrix:
{
FcMatrix m;
FcValue xx, xy, yx, yy;
v.type = FcTypeMatrix;
xx = FcConfigPromote (FcConfigEvaluate (p, p_pat, kind, e->u.mexpr->xx), v, NULL);
xy = FcConfigPromote (FcConfigEvaluate (p, p_pat, kind, e->u.mexpr->xy), v, NULL);
yx = FcConfigPromote (FcConfigEvaluate (p, p_pat, kind, e->u.mexpr->yx), v, NULL);
yy = FcConfigPromote (FcConfigEvaluate (p, p_pat, kind, e->u.mexpr->yy), v, NULL);
if (xx.type == FcTypeDouble && xy.type == FcTypeDouble &&
yx.type == FcTypeDouble && yy.type == FcTypeDouble)
{
m.xx = xx.u.d;
m.xy = xy.u.d;
m.yx = yx.u.d;
m.yy = yy.u.d;
v.u.m = &m;
}
else
v.type = FcTypeVoid;
v = FcValueSave (v);
}
break;
case FcOpCharSet:
v.type = FcTypeCharSet;
v.u.c = e->u.cval;
v = FcValueSave (v);
break;
case FcOpLangSet:
v.type = FcTypeLangSet;
v.u.l = e->u.lval;
v = FcValueSave (v);
break;
case FcOpRange:
v.type = FcTypeRange;
v.u.r = e->u.rval;
v = FcValueSave (v);
break;
case FcOpBool:
v.type = FcTypeBool;
v.u.b = e->u.bval;
break;
case FcOpField:
if (kind == FcMatchFont && e->u.name.kind == FcMatchPattern)
{
if (FcResultMatch != FcPatternObjectGet (p_pat, e->u.name.object, 0, &v))
v.type = FcTypeVoid;
}
else if (kind == FcMatchPattern && e->u.name.kind == FcMatchFont)
{
fprintf (stderr,
"Fontconfig warning: <name> tag has target=\"font\" in a <match target=\"pattern\">.\n");
v.type = FcTypeVoid;
}
else
{
if (FcResultMatch != FcPatternObjectGet (p, e->u.name.object, 0, &v))
v.type = FcTypeVoid;
}
v = FcValueSave (v);
break;
case FcOpConst:
if (FcNameConstant (e->u.constant, &v.u.i))
v.type = FcTypeInteger;
else
v.type = FcTypeVoid;
break;
case FcOpQuest:
vl = FcConfigEvaluate (p, p_pat, kind, e->u.tree.left);
if (vl.type == FcTypeBool)
{
if (vl.u.b)
v = FcConfigEvaluate (p, p_pat, kind, e->u.tree.right->u.tree.left);
else
v = FcConfigEvaluate (p, p_pat, kind, e->u.tree.right->u.tree.right);
}
else
v.type = FcTypeVoid;
FcValueDestroy (vl);
break;
case FcOpEqual:
case FcOpNotEqual:
case FcOpLess:
case FcOpLessEqual:
case FcOpMore:
case FcOpMoreEqual:
case FcOpContains:
case FcOpNotContains:
case FcOpListing:
vl = FcConfigEvaluate (p, p_pat, kind, e->u.tree.left);
vr = FcConfigEvaluate (p, p_pat, kind, e->u.tree.right);
v.type = FcTypeBool;
v.u.b = FcConfigCompareValue (&vl, e->op, &vr);
FcValueDestroy (vl);
FcValueDestroy (vr);
break;
case FcOpOr:
case FcOpAnd:
case FcOpPlus:
case FcOpMinus:
case FcOpTimes:
case FcOpDivide:
vl = FcConfigEvaluate (p, p_pat, kind, e->u.tree.left);
vr = FcConfigEvaluate (p, p_pat, kind, e->u.tree.right);
vle = FcConfigPromote (vl, vr, &buf1);
vre = FcConfigPromote (vr, vle, &buf2);
if (vle.type == vre.type)
{
switch ((int) vle.type) {
case FcTypeDouble:
switch ((int) op) {
case FcOpPlus:
v.type = FcTypeDouble;
v.u.d = vle.u.d + vre.u.d;
break;
case FcOpMinus:
v.type = FcTypeDouble;
v.u.d = vle.u.d - vre.u.d;
break;
case FcOpTimes:
v.type = FcTypeDouble;
v.u.d = vle.u.d * vre.u.d;
break;
case FcOpDivide:
v.type = FcTypeDouble;
v.u.d = vle.u.d / vre.u.d;
break;
default:
v.type = FcTypeVoid;
break;
}
if (v.type == FcTypeDouble &&
v.u.d == (double) (int) v.u.d)
{
v.type = FcTypeInteger;
v.u.i = (int) v.u.d;
}
break;
case FcTypeBool:
switch ((int) op) {
case FcOpOr:
v.type = FcTypeBool;
v.u.b = vle.u.b || vre.u.b;
break;
case FcOpAnd:
v.type = FcTypeBool;
v.u.b = vle.u.b && vre.u.b;
break;
default:
v.type = FcTypeVoid;
break;
}
break;
case FcTypeString:
switch ((int) op) {
case FcOpPlus:
v.type = FcTypeString;
str = FcStrPlus (vle.u.s, vre.u.s);
v.u.s = FcStrdup (str);
FcStrFree (str);
if (!v.u.s)
v.type = FcTypeVoid;
break;
default:
v.type = FcTypeVoid;
break;
}
break;
case FcTypeMatrix:
switch ((int) op) {
case FcOpTimes:
v.type = FcTypeMatrix;
m = malloc (sizeof (FcMatrix));
if (m)
{
FcMatrixMultiply (m, vle.u.m, vre.u.m);
v.u.m = m;
}
else
{
v.type = FcTypeVoid;
}
break;
default:
v.type = FcTypeVoid;
break;
}
break;
case FcTypeCharSet:
switch ((int) op) {
case FcOpPlus:
v.type = FcTypeCharSet;
v.u.c = FcCharSetUnion (vle.u.c, vre.u.c);
if (!v.u.c)
v.type = FcTypeVoid;
break;
case FcOpMinus:
v.type = FcTypeCharSet;
v.u.c = FcCharSetSubtract (vle.u.c, vre.u.c);
if (!v.u.c)
v.type = FcTypeVoid;
break;
default:
v.type = FcTypeVoid;
break;
}
break;
case FcTypeLangSet:
switch ((int) op) {
case FcOpPlus:
v.type = FcTypeLangSet;
v.u.l = FcLangSetUnion (vle.u.l, vre.u.l);
if (!v.u.l)
v.type = FcTypeVoid;
break;
case FcOpMinus:
v.type = FcTypeLangSet;
v.u.l = FcLangSetSubtract (vle.u.l, vre.u.l);
if (!v.u.l)
v.type = FcTypeVoid;
break;
default:
v.type = FcTypeVoid;
break;
}
break;
default:
v.type = FcTypeVoid;
break;
}
}
else
v.type = FcTypeVoid;
FcValueDestroy (vl);
FcValueDestroy (vr);
break;
case FcOpNot:
vl = FcConfigEvaluate (p, p_pat, kind, e->u.tree.left);
switch ((int) vl.type) {
case FcTypeBool:
v.type = FcTypeBool;
v.u.b = !vl.u.b;
break;
default:
v.type = FcTypeVoid;
break;
}
FcValueDestroy (vl);
break;
case FcOpFloor:
vl = FcConfigEvaluate (p, p_pat, kind, e->u.tree.left);
switch ((int) vl.type) {
case FcTypeInteger:
v = vl;
break;
case FcTypeDouble:
v.type = FcTypeInteger;
v.u.i = FcDoubleFloor (vl.u.d);
break;
default:
v.type = FcTypeVoid;
break;
}
FcValueDestroy (vl);
break;
case FcOpCeil:
vl = FcConfigEvaluate (p, p_pat, kind, e->u.tree.left);
switch ((int) vl.type) {
case FcTypeInteger:
v = vl;
break;
case FcTypeDouble:
v.type = FcTypeInteger;
v.u.i = FcDoubleCeil (vl.u.d);
break;
default:
v.type = FcTypeVoid;
break;
}
FcValueDestroy (vl);
break;
case FcOpRound:
vl = FcConfigEvaluate (p, p_pat, kind, e->u.tree.left);
switch ((int) vl.type) {
case FcTypeInteger:
v = vl;
break;
case FcTypeDouble:
v.type = FcTypeInteger;
v.u.i = FcDoubleRound (vl.u.d);
break;
default:
v.type = FcTypeVoid;
break;
}
FcValueDestroy (vl);
break;
case FcOpTrunc:
vl = FcConfigEvaluate (p, p_pat, kind, e->u.tree.left);
switch ((int) vl.type) {
case FcTypeInteger:
v = vl;
break;
case FcTypeDouble:
v.type = FcTypeInteger;
v.u.i = FcDoubleTrunc (vl.u.d);
break;
default:
v.type = FcTypeVoid;
break;
}
FcValueDestroy (vl);
break;
default:
v.type = FcTypeVoid;
break;
}
return v;
}
/* The bulk of the time in FcConfigSubstitute is spent walking
* lists of family names. We speed this up with a hash table.
* Since we need to take the ignore-blanks option into account,
* we use two separate hash tables.
*/
typedef struct
{
int count;
} FamilyTableEntry;
typedef struct
{
FcHashTable *family_blank_hash;
FcHashTable *family_hash;
} FamilyTable;
static FcBool
FamilyTableLookup (FamilyTable *table,
FcOp _op,
const FcChar8 *s)
{
FamilyTableEntry *fe;
int flags = FC_OP_GET_FLAGS (_op);
FcHashTable *hash;
if (flags & FcOpFlagIgnoreBlanks)
hash = table->family_blank_hash;
else
hash = table->family_hash;
return FcHashTableFind (hash, (const void *)s, (void **)&fe);
}
static void
FamilyTableAdd (FamilyTable *table,
FcValueListPtr values)
{
FcValueListPtr ll;
for (ll = values; ll; ll = FcValueListNext (ll))
{
const FcChar8 *s = FcValueString (&ll->value);
FamilyTableEntry *fe;
if (!FcHashTableFind (table->family_hash, (const void *)s, (void **)&fe))
{
fe = malloc (sizeof (FamilyTableEntry));
fe->count = 0;
FcHashTableAdd (table->family_hash, (void *)s, fe);
}
fe->count++;
if (!FcHashTableFind (table->family_blank_hash, (const void *)s, (void **)&fe))
{
fe = malloc (sizeof (FamilyTableEntry));
fe->count = 0;
FcHashTableAdd (table->family_blank_hash, (void *)s, fe);
}
fe->count++;
}
}
static void
FamilyTableDel (FamilyTable *table,
const FcChar8 *s)
{
FamilyTableEntry *fe;
if (FcHashTableFind (table->family_hash, (void *)s, (void **)&fe))
{
fe->count--;
if (fe->count == 0)
FcHashTableRemove (table->family_hash, (void *)s);
}
if (FcHashTableFind (table->family_blank_hash, (void *)s, (void **)&fe))
{
fe->count--;
if (fe->count == 0)
FcHashTableRemove (table->family_blank_hash, (void *)s);
}
}
static FcBool
copy_string (const void *src, void **dest)
{
*dest = strdup ((char *)src);
return FcTrue;
}
static void
FamilyTableInit (FamilyTable *table,
FcPattern *p)
{
FcPatternElt *e;
table->family_blank_hash = FcHashTableCreate ((FcHashFunc)FcStrHashIgnoreBlanksAndCase,
(FcCompareFunc)FcStrCmpIgnoreBlanksAndCase,
(FcCopyFunc)copy_string,
NULL,
free,
free);
table->family_hash = FcHashTableCreate ((FcHashFunc)FcStrHashIgnoreCase,
(FcCompareFunc)FcStrCmpIgnoreCase,
(FcCopyFunc)copy_string,
NULL,
free,
free);
e = FcPatternObjectFindElt (p, FC_FAMILY_OBJECT);
if (e)
FamilyTableAdd (table, FcPatternEltValues (e));
}
static void
FamilyTableClear (FamilyTable *table)
{
if (table->family_blank_hash)
FcHashTableDestroy (table->family_blank_hash);
if (table->family_hash)
FcHashTableDestroy (table->family_hash);
}
static FcValueList *
FcConfigMatchValueList (FcPattern *p,
FcPattern *p_pat,
FcMatchKind kind,
FcTest *t,
FcValueList *values,
FamilyTable *table)
{
FcValueList *ret = 0;
FcExpr *e = t->expr;
FcValue value;
FcValueList *v;
FcOp op;
while (e)
{
/* Compute the value of the match expression */
if (FC_OP_GET_OP (e->op) == FcOpComma)
{
value = FcConfigEvaluate (p, p_pat, kind, e->u.tree.left);
e = e->u.tree.right;
}
else
{
value = FcConfigEvaluate (p, p_pat, kind, e);
e = 0;
}
if (t->object == FC_FAMILY_OBJECT && table)
{
op = FC_OP_GET_OP (t->op);
if (op == FcOpEqual || op == FcOpListing)
{
if (!FamilyTableLookup (table, t->op, FcValueString (&value)))
{
ret = 0;
goto done;
}
}
if (op == FcOpNotEqual && t->qual == FcQualAll)
{
ret = 0;
if (!FamilyTableLookup (table, t->op, FcValueString (&value)))
{
ret = values;
}
goto done;
}
}
for (v = values; v; v = FcValueListNext(v))
{
/* Compare the pattern value to the match expression value */
if (FcConfigCompareValue (&v->value, t->op, &value))
{
if (!ret)
ret = v;
if (t->qual != FcQualAll)
break;
}
else
{
if (t->qual == FcQualAll)
{
ret = 0;
break;
}
}
}
done:
FcValueDestroy (value);
}
return ret;
}
static FcValueList *
FcConfigValues (FcPattern *p, FcPattern *p_pat, FcMatchKind kind, FcExpr *e, FcValueBinding binding)
{
FcValueList *l;
if (!e)
return 0;
l = (FcValueList *) malloc (sizeof (FcValueList));
if (!l)
return 0;
if (FC_OP_GET_OP (e->op) == FcOpComma)
{
l->value = FcConfigEvaluate (p, p_pat, kind, e->u.tree.left);
l->next = FcConfigValues (p, p_pat, kind, e->u.tree.right, binding);
}
else
{
l->value = FcConfigEvaluate (p, p_pat, kind, e);
l->next = NULL;
}
l->binding = binding;
if (l->value.type == FcTypeVoid)
{
FcValueList *next = FcValueListNext(l);
free (l);
l = next;
}
return l;
}
static FcBool
FcConfigAdd (FcValueListPtr *head,
FcValueList *position,
FcBool append,
FcValueList *new,
FcObject object,
FamilyTable *table)
{
FcValueListPtr *prev, l, last, v;
FcValueBinding sameBinding;
/*
* Make sure the stored type is valid for built-in objects
*/
for (l = new; l != NULL; l = FcValueListNext (l))
{
if (!FcObjectValidType (object, l->value.type))
{
fprintf (stderr,
"Fontconfig warning: FcPattern object %s does not accept value", FcObjectName (object));
FcValuePrintFile (stderr, l->value);
fprintf (stderr, "\n");
if (FcDebug () & FC_DBG_EDIT)
{
printf ("Not adding\n");
}
return FcFalse;
}
}
if (object == FC_FAMILY_OBJECT && table)
{
FamilyTableAdd (table, new);
}
if (position)
sameBinding = position->binding;
else
sameBinding = FcValueBindingWeak;
for (v = new; v != NULL; v = FcValueListNext(v))
if (v->binding == FcValueBindingSame)
v->binding = sameBinding;
if (append)
{
if (position)
prev = &position->next;
else
for (prev = head; *prev != NULL;
prev = &(*prev)->next)
;
}
else
{
if (position)
{
for (prev = head; *prev != NULL;
prev = &(*prev)->next)
{
if (*prev == position)
break;
}
}
else
prev = head;
if (FcDebug () & FC_DBG_EDIT)
{
if (*prev == NULL)
printf ("position not on list\n");
}
}
if (FcDebug () & FC_DBG_EDIT)
{
printf ("%s list before ", append ? "Append" : "Prepend");
FcValueListPrintWithPosition (*head, *prev);
printf ("\n");
}
if (new)
{
last = new;
while (last->next != NULL)
last = last->next;
last->next = *prev;
*prev = new;
}
if (FcDebug () & FC_DBG_EDIT)
{
printf ("%s list after ", append ? "Append" : "Prepend");
FcValueListPrint (*head);
printf ("\n");
}
return FcTrue;
}
static void
FcConfigDel (FcValueListPtr *head,
FcValueList *position,
FcObject object,
FamilyTable *table)
{
FcValueListPtr *prev;
if (object == FC_FAMILY_OBJECT && table)
{
FamilyTableDel (table, FcValueString (&position->value));
}
for (prev = head; *prev != NULL; prev = &(*prev)->next)
{
if (*prev == position)
{
*prev = position->next;
position->next = NULL;
FcValueListDestroy (position);
break;
}
}
}
static void
FcConfigPatternAdd (FcPattern *p,
FcObject object,
FcValueList *list,
FcBool append,
FamilyTable *table)
{
if (list)
{
FcPatternElt *e = FcPatternObjectInsertElt (p, object);
if (!e)
return;
FcConfigAdd (&e->values, 0, append, list, object, table);
}
}
/*
* Delete all values associated with a field
*/
static void
FcConfigPatternDel (FcPattern *p,
FcObject object,
FamilyTable *table)
{
FcPatternElt *e = FcPatternObjectFindElt (p, object);
if (!e)
return;
while (e->values != NULL)
FcConfigDel (&e->values, e->values, object, table);
}
static void
FcConfigPatternCanon (FcPattern *p,
FcObject object)
{
FcPatternElt *e = FcPatternObjectFindElt (p, object);
if (!e)
return;
if (e->values == NULL)
FcPatternObjectDel (p, object);
}
FcBool
FcConfigSubstituteWithPat (FcConfig *config,
FcPattern *p,
FcPattern *p_pat,
FcMatchKind kind)
{
FcValue v;
FcPtrList *s;
FcPtrListIter iter, iter2;
FcRule *r;
FcRuleSet *rs;
FcValueList *l, **value = NULL, *vl;
FcPattern *m;
FcStrSet *strs;
FcObject object = FC_INVALID_OBJECT;
FcPatternElt **elt = NULL, *e;
int i, nobjs;
FcBool retval = FcTrue;
FcTest **tst = NULL;
FamilyTable data;
FamilyTable *table = &data;
if (kind < FcMatchKindBegin || kind >= FcMatchKindEnd)
return FcFalse;
config = FcConfigReference (config);
if (!config)
return FcFalse;
s = config->subst[kind];
if (kind == FcMatchPattern)
{
strs = FcGetDefaultLangs ();
if (strs)
{
FcStrList *l = FcStrListCreate (strs);
FcChar8 *lang;
FcValue v;
FcLangSet *lsund = FcLangSetCreate ();
FcLangSetAdd (lsund, (const FcChar8 *)"und");
FcStrSetDestroy (strs);
while (l && (lang = FcStrListNext (l)))
{
FcPatternElt *e = FcPatternObjectFindElt (p, FC_LANG_OBJECT);
if (e)
{
FcValueListPtr ll;
for (ll = FcPatternEltValues (e); ll; ll = FcValueListNext (ll))
{
FcValue vv = FcValueCanonicalize (&ll->value);
if (vv.type == FcTypeLangSet)
{
FcLangSet *ls = FcLangSetCreate ();
FcBool b;
FcLangSetAdd (ls, lang);
b = FcLangSetContains (vv.u.l, ls);
FcLangSetDestroy (ls);
if (b)
goto bail_lang;
if (FcLangSetContains (vv.u.l, lsund))
goto bail_lang;
}
else
{
if (FcStrCmpIgnoreCase (vv.u.s, lang) == 0)
goto bail_lang;
if (FcStrCmpIgnoreCase (vv.u.s, (const FcChar8 *)"und") == 0)
goto bail_lang;
}
}
}
v.type = FcTypeString;
v.u.s = lang;
FcPatternObjectAddWithBinding (p, FC_LANG_OBJECT, v, FcValueBindingWeak, FcTrue);
}
bail_lang:
FcStrListDone (l);
FcLangSetDestroy (lsund);
}
if (FcPatternObjectGet (p, FC_PRGNAME_OBJECT, 0, &v) == FcResultNoMatch)
{
FcChar8 *prgname = FcGetPrgname ();
if (prgname)
FcPatternObjectAddString (p, FC_PRGNAME_OBJECT, prgname);
}
}
nobjs = FC_MAX_BASE_OBJECT + config->maxObjects + 2;
value = (FcValueList **) malloc (SIZEOF_VOID_P * nobjs);
if (!value)
{
retval = FcFalse;
goto bail1;
}
elt = (FcPatternElt **) malloc (SIZEOF_VOID_P * nobjs);
if (!elt)
{
retval = FcFalse;
goto bail1;
}
tst = (FcTest **) malloc (SIZEOF_VOID_P * nobjs);
if (!tst)
{
retval = FcFalse;
goto bail1;
}
if (FcDebug () & FC_DBG_EDIT)
{
printf ("FcConfigSubstitute ");
FcPatternPrint (p);
}
FamilyTableInit (&data, p);
FcPtrListIterInit (s, &iter);
for (; FcPtrListIterIsValid (s, &iter); FcPtrListIterNext (s, &iter))
{
rs = (FcRuleSet *) FcPtrListIterGetValue (s, &iter);
if (FcDebug () & FC_DBG_EDIT)
{
printf ("\nRule Set: %s\n", rs->name);
}
FcPtrListIterInit (rs->subst[kind], &iter2);
for (; FcPtrListIterIsValid (rs->subst[kind], &iter2); FcPtrListIterNext (rs->subst[kind], &iter2))
{
r = (FcRule *) FcPtrListIterGetValue (rs->subst[kind], &iter2);
for (i = 0; i < nobjs; i++)
{
elt[i] = NULL;
value[i] = NULL;
tst[i] = NULL;
}
for (; r; r = r->next)
{
switch (r->type) {
case FcRuleUnknown:
/* shouldn't be reached */
break;
case FcRuleTest:
object = FC_OBJ_ID (r->u.test->object);
/*
* Check the tests to see if
* they all match the pattern
*/
if (FcDebug () & FC_DBG_EDIT)
{
printf ("FcConfigSubstitute test ");
FcTestPrint (r->u.test);
}
if (kind == FcMatchFont && r->u.test->kind == FcMatchPattern)
{
m = p_pat;
table = NULL;
}
else
{
m = p;
table = &data;
}
if (m)
e = FcPatternObjectFindElt (m, r->u.test->object);
else
e = NULL;
/* different 'kind' won't be the target of edit */
if (!elt[object] && kind == r->u.test->kind)
{
elt[object] = e;
tst[object] = r->u.test;
}
/*
* If there's no such field in the font,
* then FcQualAll matches while FcQualAny does not
*/
if (!e)
{
if (r->u.test->qual == FcQualAll)
{
value[object] = NULL;
continue;
}
else
{
if (FcDebug () & FC_DBG_EDIT)
printf ("No match\n");
goto bail;
}
}
/*
* Check to see if there is a match, mark the location
* to apply match-relative edits
*/
vl = FcConfigMatchValueList (m, p_pat, kind, r->u.test, e->values, table);
/* different 'kind' won't be the target of edit */
if (!value[object] && kind == r->u.test->kind)
value[object] = vl;
if (!vl ||
(r->u.test->qual == FcQualFirst && vl != e->values) ||
(r->u.test->qual == FcQualNotFirst && vl == e->values))
{
if (FcDebug () & FC_DBG_EDIT)
printf ("No match\n");
goto bail;
}
break;
case FcRuleEdit:
object = FC_OBJ_ID (r->u.edit->object);
if (FcDebug () & FC_DBG_EDIT)
{
printf ("Substitute ");
FcEditPrint (r->u.edit);
printf ("\n\n");
}
/*
* Evaluate the list of expressions
*/
l = FcConfigValues (p, p_pat, kind, r->u.edit->expr, r->u.edit->binding);
if (tst[object] && (tst[object]->kind == FcMatchFont || kind == FcMatchPattern))
elt[object] = FcPatternObjectFindElt (p, tst[object]->object);
switch (FC_OP_GET_OP (r->u.edit->op)) {
case FcOpAssign:
/*
* If there was a test, then replace the matched
* value with the new list of values
*/
if (value[object])
{
FcValueList *thisValue = value[object];
FcValueList *nextValue = l;
/*
* Append the new list of values after the current value
*/
FcConfigAdd (&elt[object]->values, thisValue, FcTrue, l, r->u.edit->object, table);
/*
* Delete the marked value
*/
if (thisValue)
FcConfigDel (&elt[object]->values, thisValue, object, table);
/*
* Adjust a pointer into the value list to ensure
* future edits occur at the same place
*/
value[object] = nextValue;
break;
}
/* fall through ... */
case FcOpAssignReplace:
/*
* Delete all of the values and insert
* the new set
*/
FcConfigPatternDel (p, r->u.edit->object, table);
FcConfigPatternAdd (p, r->u.edit->object, l, FcTrue, table);
/*
* Adjust a pointer into the value list as they no
* longer point to anything valid
*/
value[object] = NULL;
break;
case FcOpPrepend:
if (value[object])
{
FcConfigAdd (&elt[object]->values, value[object], FcFalse, l, r->u.edit->object, table);
break;
}
/* fall through ... */
case FcOpPrependFirst:
FcConfigPatternAdd (p, r->u.edit->object, l, FcFalse, table);
break;
case FcOpAppend:
if (value[object])
{
FcConfigAdd (&elt[object]->values, value[object], FcTrue, l, r->u.edit->object, table);
break;
}
/* fall through ... */
case FcOpAppendLast:
FcConfigPatternAdd (p, r->u.edit->object, l, FcTrue, table);
break;
case FcOpDelete:
if (value[object])
{
FcConfigDel (&elt[object]->values, value[object], object, table);
FcValueListDestroy (l);
break;
}
/* fall through ... */
case FcOpDeleteAll:
FcConfigPatternDel (p, r->u.edit->object, table);
FcValueListDestroy (l);
break;
default:
FcValueListDestroy (l);
break;
}
/*
* Now go through the pattern and eliminate
* any properties without data
*/
FcConfigPatternCanon (p, r->u.edit->object);
if (FcDebug () & FC_DBG_EDIT)
{
printf ("FcConfigSubstitute edit");
FcPatternPrint (p);
}
break;
}
}
bail:;
}
}
if (FcDebug () & FC_DBG_EDIT)
{
printf ("FcConfigSubstitute done");
FcPatternPrint (p);
}
bail1:
FamilyTableClear (&data);
if (elt)
free (elt);
if (value)
free (value);
if (tst)
free (tst);
FcConfigDestroy (config);
return retval;
}
FcBool
FcConfigSubstitute (FcConfig *config,
FcPattern *p,
FcMatchKind kind)
{
return FcConfigSubstituteWithPat (config, p, 0, kind);
}
#if defined (_WIN32)
static FcChar8 fontconfig_path[1000] = ""; /* MT-dontcare */
FcChar8 fontconfig_instprefix[1000] = ""; /* MT-dontcare */
# if (defined (PIC) || defined (DLL_EXPORT))
BOOL WINAPI
DllMain (HINSTANCE hinstDLL,
DWORD fdwReason,
LPVOID lpvReserved);
BOOL WINAPI
DllMain (HINSTANCE hinstDLL,
DWORD fdwReason,
LPVOID lpvReserved)
{
FcChar8 *p;
switch (fdwReason) {
case DLL_PROCESS_ATTACH:
if (!GetModuleFileName ((HMODULE) hinstDLL, (LPCH) fontconfig_path,
sizeof (fontconfig_path)))
break;
/* If the fontconfig DLL is in a "bin" or "lib" subfolder,
* assume it's a Unix-style installation tree, and use
* "etc/fonts" in there as FONTCONFIG_PATH. Otherwise use the
* folder where the DLL is as FONTCONFIG_PATH.
*/
p = (FcChar8 *) strrchr ((const char *) fontconfig_path, '\\');
if (p)
{
*p = '\0';
p = (FcChar8 *) strrchr ((const char *) fontconfig_path, '\\');
if (p && (FcStrCmpIgnoreCase (p + 1, (const FcChar8 *) "bin") == 0 ||
FcStrCmpIgnoreCase (p + 1, (const FcChar8 *) "lib") == 0))
*p = '\0';
strcat ((char *) fontconfig_instprefix, (char *) fontconfig_path);
strcat ((char *) fontconfig_path, "\\etc\\fonts");
}
else
fontconfig_path[0] = '\0';
break;
}
return TRUE;
}
# endif /* !PIC */
#undef FONTCONFIG_PATH
#define FONTCONFIG_PATH fontconfig_path
#endif /* !_WIN32 */
#ifndef FONTCONFIG_FILE
#define FONTCONFIG_FILE "fonts.conf"
#endif
static FcChar8 *
FcConfigFileExists (const FcChar8 *dir, const FcChar8 *file)
{
FcChar8 *path;
int size, osize;
if (!dir)
dir = (FcChar8 *) "";
osize = strlen ((char *) dir) + 1 + strlen ((char *) file) + 1;
/*
* workaround valgrind warning because glibc takes advantage of how it knows memory is
* allocated to implement strlen by reading in groups of 4
*/
size = (osize + 3) & ~3;
path = malloc (size);
if (!path)
return 0;
strcpy ((char *) path, (const char *) dir);
/* make sure there's a single separator */
#ifdef _WIN32
if ((!path[0] || (path[strlen((char *) path)-1] != '/' &&
path[strlen((char *) path)-1] != '\\')) &&
!(file[0] == '/' ||
file[0] == '\\' ||
(isalpha (file[0]) && file[1] == ':' && (file[2] == '/' || file[2] == '\\'))))
strcat ((char *) path, "\\");
#else
if ((!path[0] || path[strlen((char *) path)-1] != '/') && file[0] != '/')
strcat ((char *) path, "/");
else
osize--;
#endif
strcat ((char *) path, (char *) file);
if (access ((char *) path, R_OK) == 0)
return path;
FcStrFree (path);
return 0;
}
static FcChar8 **
FcConfigGetPath (void)
{
FcChar8 **path;
FcChar8 *env, *e, *colon;
FcChar8 *dir;
int npath;
int i;
npath = 2; /* default dir + null */
env = (FcChar8 *) getenv ("FONTCONFIG_PATH");
if (env)
{
e = env;
npath++;
while (*e)
if (*e++ == FC_SEARCH_PATH_SEPARATOR)
npath++;
}
path = calloc (npath, sizeof (FcChar8 *));
if (!path)
goto bail0;
i = 0;
if (env)
{
e = env;
while (*e)
{
colon = (FcChar8 *) strchr ((char *) e, FC_SEARCH_PATH_SEPARATOR);
if (!colon)
colon = e + strlen ((char *) e);
path[i] = malloc (colon - e + 1);
if (!path[i])
goto bail1;
strncpy ((char *) path[i], (const char *) e, colon - e);
path[i][colon - e] = '\0';
if (*colon)
e = colon + 1;
else
e = colon;
i++;
}
}
#ifdef _WIN32
if (fontconfig_path[0] == '\0')
{
char *p;
if(!GetModuleFileName(NULL, (LPCH) fontconfig_path, sizeof(fontconfig_path)))
goto bail1;
p = strrchr ((const char *) fontconfig_path, '\\');
if (p) *p = '\0';
strcat ((char *) fontconfig_path, "\\fonts");
}
#endif
dir = (FcChar8 *) FONTCONFIG_PATH;
path[i] = malloc (strlen ((char *) dir) + 1);
if (!path[i])
goto bail1;
strcpy ((char *) path[i], (const char *) dir);
return path;
bail1:
for (i = 0; path[i]; i++)
free (path[i]);
free (path);
bail0:
return 0;
}
static void
FcConfigFreePath (FcChar8 **path)
{
FcChar8 **p;
for (p = path; *p; p++)
free (*p);
free (path);
}
static FcBool _FcConfigHomeEnabled = FcTrue; /* MT-goodenough */
FcChar8 *
FcConfigHome (void)
{
if (_FcConfigHomeEnabled)
{
char *home = getenv ("HOME");
#ifdef _WIN32
if (home == NULL)
home = getenv ("USERPROFILE");
#endif
return (FcChar8 *) home;
}
return 0;
}
FcChar8 *
FcConfigXdgCacheHome (void)
{
const char *env = getenv ("XDG_CACHE_HOME");
FcChar8 *ret = NULL;
if (!_FcConfigHomeEnabled)
return NULL;
if (env && env[0])
ret = FcStrCopy ((const FcChar8 *)env);
else
{
const FcChar8 *home = FcConfigHome ();
size_t len = home ? strlen ((const char *)home) : 0;
ret = malloc (len + 7 + 1);
if (ret)
{
if (home)
memcpy (ret, home, len);
memcpy (&ret[len], FC_DIR_SEPARATOR_S ".cache", 7);
ret[len + 7] = 0;
}
}
return ret;
}
FcChar8 *
FcConfigXdgConfigHome (void)
{
const char *env = getenv ("XDG_CONFIG_HOME");
FcChar8 *ret = NULL;
if (!_FcConfigHomeEnabled)
return NULL;
if (env)
ret = FcStrCopy ((const FcChar8 *)env);
else
{
const FcChar8 *home = FcConfigHome ();
size_t len = home ? strlen ((const char *)home) : 0;
ret = malloc (len + 8 + 1);
if (ret)
{
if (home)
memcpy (ret, home, len);
memcpy (&ret[len], FC_DIR_SEPARATOR_S ".config", 8);
ret[len + 8] = 0;
}
}
return ret;
}
FcChar8 *
FcConfigXdgDataHome (void)
{
const char *env = getenv ("XDG_DATA_HOME");
FcChar8 *ret = NULL;
if (!_FcConfigHomeEnabled)
return NULL;
if (env)
ret = FcStrCopy ((const FcChar8 *)env);
else
{
const FcChar8 *home = FcConfigHome ();
size_t len = home ? strlen ((const char *)home) : 0;
ret = malloc (len + 13 + 1);
if (ret)
{
if (home)
memcpy (ret, home, len);
memcpy (&ret[len], FC_DIR_SEPARATOR_S ".local" FC_DIR_SEPARATOR_S "share", 13);
ret[len + 13] = 0;
}
}
return ret;
}
FcBool
FcConfigEnableHome (FcBool enable)
{
FcBool prev = _FcConfigHomeEnabled;
_FcConfigHomeEnabled = enable;
return prev;
}
FcChar8 *
FcConfigGetFilename (FcConfig *config,
const FcChar8 *url)
{
FcChar8 *file, *dir, **path, **p;
const FcChar8 *sysroot;
config = FcConfigReference (config);
if (!config)
return NULL;
sysroot = FcConfigGetSysRoot (config);
if (!url || !*url)
{
url = (FcChar8 *) getenv ("FONTCONFIG_FILE");
if (!url)
url = (FcChar8 *) FONTCONFIG_FILE;
}
file = 0;
if (FcStrIsAbsoluteFilename(url))
{
if (sysroot)
{
size_t len = strlen ((const char *) sysroot);
/* Workaround to avoid adding sysroot repeatedly */
if (strncmp ((const char *) url, (const char *) sysroot, len) == 0)
sysroot = NULL;
}
file = FcConfigFileExists (sysroot, url);
goto bail;
}
if (*url == '~')
{
dir = FcConfigHome ();
if (dir)
{
FcChar8 *s;
if (sysroot)
s = FcStrBuildFilename (sysroot, dir, NULL);
else
s = dir;
file = FcConfigFileExists (s, url + 1);
if (sysroot)
FcStrFree (s);
}
else
file = 0;
}
else
{
path = FcConfigGetPath ();
if (!path)
{
file = NULL;
goto bail;
}
for (p = path; *p; p++)
{
FcChar8 *s;
if (sysroot)
s = FcStrBuildFilename (sysroot, *p, NULL);
else
s = *p;
file = FcConfigFileExists (s, url);
if (sysroot)
FcStrFree (s);
if (file)
break;
}
FcConfigFreePath (path);
}
bail:
FcConfigDestroy (config);
return file;
}
FcChar8 *
FcConfigFilename (const FcChar8 *url)
{
return FcConfigGetFilename (NULL, url);
}
FcChar8 *
FcConfigRealFilename (FcConfig *config,
const FcChar8 *url)
{
FcChar8 *n = FcConfigGetFilename (config, url);
if (n)
{
FcChar8 buf[FC_PATH_MAX];
ssize_t len;
struct stat sb;
if ((len = FcReadLink (n, buf, sizeof (buf) - 1)) != -1)
{
buf[len] = 0;
/* We try to pick up a config from FONTCONFIG_FILE
* when url is null. don't try to address the real filename
* if it is a named pipe.
*/
if (!url && FcStat (n, &sb) == 0 && S_ISFIFO (sb.st_mode))
return n;
else if (!FcStrIsAbsoluteFilename (buf))
{
FcChar8 *dirname = FcStrDirname (n);
FcStrFree (n);
if (!dirname)
return NULL;
FcChar8 *path = FcStrBuildFilename (dirname, buf, NULL);
FcStrFree (dirname);
if (!path)
return NULL;
n = FcStrCanonFilename (path);
FcStrFree (path);
}
else
{
FcStrFree (n);
n = FcStrdup (buf);
}
}
}
return n;
}
/*
* Manage the application-specific fonts
*/
FcBool
FcConfigAppFontAddFile (FcConfig *config,
const FcChar8 *file)
{
FcFontSet *set;
FcStrSet *subdirs;
FcStrList *sublist;
FcChar8 *subdir;
FcBool ret = FcTrue;
config = FcConfigReference (config);
if (!config)
return FcFalse;
subdirs = FcStrSetCreateEx (FCSS_GROW_BY_64);
if (!subdirs)
{
ret = FcFalse;
goto bail;
}
set = FcConfigGetFonts (config, FcSetApplication);
if (!set)
{
set = FcFontSetCreate ();
if (!set)
{
FcStrSetDestroy (subdirs);
ret = FcFalse;
goto bail;
}
FcConfigSetFonts (config, set, FcSetApplication);
}
if (!FcFileScanConfig (set, subdirs, file, config))
{
FcStrSetDestroy (subdirs);
ret = FcFalse;
goto bail;
}
if ((sublist = FcStrListCreate (subdirs)))
{
while ((subdir = FcStrListNext (sublist)))
{
FcConfigAppFontAddDir (config, subdir);
}
FcStrListDone (sublist);
}
FcStrSetDestroy (subdirs);
bail:
FcConfigDestroy (config);
return ret;
}
FcBool
FcConfigAppFontAddDir (FcConfig *config,
const FcChar8 *dir)
{
FcFontSet *set;
FcStrSet *dirs;
FcBool ret = FcTrue;
config = FcConfigReference (config);
if (!config)
return FcFalse;
dirs = FcStrSetCreateEx (FCSS_GROW_BY_64);
if (!dirs)
{
ret = FcFalse;
goto bail;
}
set = FcConfigGetFonts (config, FcSetApplication);
if (!set)
{
set = FcFontSetCreate ();
if (!set)
{
FcStrSetDestroy (dirs);
ret = FcFalse;
goto bail;
}
FcConfigSetFonts (config, set, FcSetApplication);
}
FcStrSetAddFilename (dirs, dir);
if (!FcConfigAddDirList (config, FcSetApplication, dirs))
{
FcStrSetDestroy (dirs);
ret = FcFalse;
goto bail;
}
FcStrSetDestroy (dirs);
bail:
FcConfigDestroy (config);
return ret;
}
void
FcConfigAppFontClear (FcConfig *config)
{
config = FcConfigReference (config);
if (!config)
return;
FcConfigSetFonts (config, 0, FcSetApplication);
FcConfigDestroy (config);
}
/*
* Manage filename-based font source selectors
*/
FcBool
FcConfigGlobAdd (FcConfig *config,
const FcChar8 *glob,
FcBool accept)
{
FcStrSet *set = accept ? config->acceptGlobs : config->rejectGlobs;
return FcStrSetAdd (set, glob);
}
static FcBool
FcConfigGlobsMatch (const FcStrSet *globs,
const FcChar8 *string)
{
int i;
for (i = 0; i < globs->num; i++)
if (FcStrGlobMatch (globs->strs[i], string))
return FcTrue;
return FcFalse;
}
FcBool
FcConfigAcceptFilename (FcConfig *config,
const FcChar8 *filename)
{
if (FcConfigGlobsMatch (config->acceptGlobs, filename))
return FcTrue;
if (FcConfigGlobsMatch (config->rejectGlobs, filename))
return FcFalse;
return FcTrue;
}
/*
* Manage font-pattern based font source selectors
*/
FcBool
FcConfigPatternsAdd (FcConfig *config,
FcPattern *pattern,
FcBool accept)
{
FcFontSet *set = accept ? config->acceptPatterns : config->rejectPatterns;
return FcFontSetAdd (set, pattern);
}
static FcBool
FcConfigPatternsMatch (const FcFontSet *patterns,
const FcPattern *font)
{
int i;
for (i = 0; i < patterns->nfont; i++)
if (FcListPatternMatchAny (patterns->fonts[i], font))
return FcTrue;
return FcFalse;
}
FcBool
FcConfigAcceptFont (FcConfig *config,
const FcPattern *font)
{
if (FcConfigPatternsMatch (config->acceptPatterns, font))
return FcTrue;
if (FcConfigPatternsMatch (config->rejectPatterns, font))
return FcFalse;
return FcTrue;
}
const FcChar8 *
FcConfigGetSysRoot (const FcConfig *config)
{
if (!config)
{
config = FcConfigGetCurrent ();
if (!config)
return NULL;
}
return config->sysRoot;
}
void
FcConfigSetSysRoot (FcConfig *config,
const FcChar8 *sysroot)
{
FcChar8 *s = NULL;
FcBool init = FcFalse;
int nretry = 3;
retry:
if (!config)
{
/* We can't use FcConfigGetCurrent() here to ensure
* the sysroot is set prior to initialize FcConfig,
* to avoid loading caches from non-sysroot dirs.
* So postpone the initialization later.
*/
config = fc_atomic_ptr_get (&_fcConfig);
if (!config)
{
config = FcConfigCreate ();
if (!config)
return;
init = FcTrue;
}
}
if (sysroot)
{
s = FcConfigRealPath(sysroot);
if (!s)
return;
}
if (config->sysRoot)
FcStrFree (config->sysRoot);
config->sysRoot = s;
if (init)
{
config = FcInitLoadOwnConfigAndFonts (config);
if (!config)
{
/* Something failed. this is usually unlikely. so retrying */
init = FcFalse;
if (--nretry == 0)
{
fprintf (stderr, "Fontconfig warning: Unable to initialize config and retry limit exceeded. sysroot functionality may not work as expected.\n");
return;