1111 lines
24 KiB
C
1111 lines
24 KiB
C
/*
|
|
* fontconfig/src/fclang.c
|
|
*
|
|
* Copyright © 2002 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.
|
|
*/
|
|
|
|
#include "fcint.h"
|
|
#include "fcftint.h"
|
|
|
|
/* Objects MT-safe for readonly access. */
|
|
|
|
typedef struct {
|
|
const FcChar8 lang[16];
|
|
const FcCharSet charset;
|
|
} FcLangCharSet;
|
|
|
|
typedef struct {
|
|
int begin;
|
|
int end;
|
|
} FcLangCharSetRange;
|
|
|
|
#include "../fc-lang/fclang.h"
|
|
|
|
struct _FcLangSet {
|
|
FcStrSet *extra;
|
|
FcChar32 map_size;
|
|
FcChar32 map[NUM_LANG_SET_MAP];
|
|
};
|
|
|
|
static int FcLangSetIndex (const FcChar8 *lang);
|
|
|
|
|
|
static void
|
|
FcLangSetBitSet (FcLangSet *ls,
|
|
unsigned int id)
|
|
{
|
|
unsigned int bucket;
|
|
|
|
id = fcLangCharSetIndices[id];
|
|
bucket = id >> 5;
|
|
if (bucket >= ls->map_size)
|
|
return; /* shouldn't happen really */
|
|
|
|
ls->map[bucket] |= ((FcChar32) 1U << (id & 0x1f));
|
|
}
|
|
|
|
static FcBool
|
|
FcLangSetBitGet (const FcLangSet *ls,
|
|
unsigned int id)
|
|
{
|
|
unsigned int bucket;
|
|
|
|
id = fcLangCharSetIndices[id];
|
|
bucket = id >> 5;
|
|
if (bucket >= ls->map_size)
|
|
return FcFalse;
|
|
|
|
return ((ls->map[bucket] >> (id & 0x1f)) & 1) ? FcTrue : FcFalse;
|
|
}
|
|
|
|
static void
|
|
FcLangSetBitReset (FcLangSet *ls,
|
|
unsigned int id)
|
|
{
|
|
unsigned int bucket;
|
|
|
|
id = fcLangCharSetIndices[id];
|
|
bucket = id >> 5;
|
|
if (bucket >= ls->map_size)
|
|
return; /* shouldn't happen really */
|
|
|
|
ls->map[bucket] &= ~((FcChar32) 1U << (id & 0x1f));
|
|
}
|
|
|
|
FcLangSet *
|
|
FcFreeTypeLangSet (const FcCharSet *charset,
|
|
const FcChar8 *exclusiveLang)
|
|
{
|
|
int i, j;
|
|
FcChar32 missing;
|
|
const FcCharSet *exclusiveCharset = 0;
|
|
FcLangSet *ls;
|
|
|
|
if (exclusiveLang)
|
|
exclusiveCharset = FcLangGetCharSet (exclusiveLang);
|
|
ls = FcLangSetCreate ();
|
|
if (!ls)
|
|
return 0;
|
|
if (FcDebug() & FC_DBG_LANGSET)
|
|
{
|
|
printf ("font charset");
|
|
FcCharSetPrint (charset);
|
|
printf ("\n");
|
|
}
|
|
for (i = 0; i < NUM_LANG_CHAR_SET; i++)
|
|
{
|
|
if (FcDebug() & FC_DBG_LANGSET)
|
|
{
|
|
printf ("%s charset", fcLangCharSets[i].lang);
|
|
FcCharSetPrint (&fcLangCharSets[i].charset);
|
|
printf ("\n");
|
|
}
|
|
|
|
/*
|
|
* Check for Han charsets to make fonts
|
|
* which advertise support for a single language
|
|
* not support other Han languages
|
|
*/
|
|
if (exclusiveCharset &&
|
|
FcFreeTypeIsExclusiveLang (fcLangCharSets[i].lang))
|
|
{
|
|
if (fcLangCharSets[i].charset.num != exclusiveCharset->num)
|
|
continue;
|
|
|
|
for (j = 0; j < fcLangCharSets[i].charset.num; j++)
|
|
if (FcCharSetLeaf(&fcLangCharSets[i].charset, j) !=
|
|
FcCharSetLeaf(exclusiveCharset, j))
|
|
continue;
|
|
}
|
|
missing = FcCharSetSubtractCount (&fcLangCharSets[i].charset, charset);
|
|
if (FcDebug() & FC_DBG_SCANV)
|
|
{
|
|
if (missing && missing < 10)
|
|
{
|
|
FcCharSet *missed = FcCharSetSubtract (&fcLangCharSets[i].charset,
|
|
charset);
|
|
FcChar32 ucs4;
|
|
FcChar32 map[FC_CHARSET_MAP_SIZE];
|
|
FcChar32 next;
|
|
|
|
printf ("\n%s(%u) ", fcLangCharSets[i].lang, missing);
|
|
printf ("{");
|
|
for (ucs4 = FcCharSetFirstPage (missed, map, &next);
|
|
ucs4 != FC_CHARSET_DONE;
|
|
ucs4 = FcCharSetNextPage (missed, map, &next))
|
|
{
|
|
int i, j;
|
|
for (i = 0; i < FC_CHARSET_MAP_SIZE; i++)
|
|
if (map[i])
|
|
{
|
|
for (j = 0; j < 32; j++)
|
|
if (map[i] & (1U << j))
|
|
printf (" %04x", ucs4 + i * 32 + j);
|
|
}
|
|
}
|
|
printf (" }\n\t");
|
|
FcCharSetDestroy (missed);
|
|
}
|
|
else
|
|
printf ("%s(%u) ", fcLangCharSets[i].lang, missing);
|
|
}
|
|
if (!missing)
|
|
FcLangSetBitSet (ls, i);
|
|
}
|
|
|
|
if (FcDebug() & FC_DBG_SCANV)
|
|
printf ("\n");
|
|
|
|
|
|
return ls;
|
|
}
|
|
|
|
FcChar8 *
|
|
FcLangNormalize (const FcChar8 *lang)
|
|
{
|
|
FcChar8 *result = NULL, *s, *orig;
|
|
char *territory, *encoding, *modifier;
|
|
size_t llen, tlen = 0, mlen = 0;
|
|
|
|
if (!lang || !*lang)
|
|
return NULL;
|
|
|
|
/* might be called without initialization */
|
|
FcInitDebug ();
|
|
|
|
if (FcStrCmpIgnoreCase (lang, (const FcChar8 *)"C") == 0 ||
|
|
FcStrCmpIgnoreCase (lang, (const FcChar8 *)"C.UTF-8") == 0 ||
|
|
FcStrCmpIgnoreCase (lang, (const FcChar8 *)"C.utf8") == 0 ||
|
|
FcStrCmpIgnoreCase (lang, (const FcChar8 *)"POSIX") == 0)
|
|
{
|
|
result = FcStrCopy ((const FcChar8 *)"en");
|
|
goto bail;
|
|
}
|
|
|
|
s = FcStrCopy (lang);
|
|
if (!s)
|
|
goto bail;
|
|
|
|
/* from the comments in glibc:
|
|
*
|
|
* LOCALE can consist of up to four recognized parts for the XPG syntax:
|
|
*
|
|
* language[_territory[.codeset]][@modifier]
|
|
*
|
|
* Beside the first all of them are allowed to be missing. If the
|
|
* full specified locale is not found, the less specific one are
|
|
* looked for. The various part will be stripped off according to
|
|
* the following order:
|
|
* (1) codeset
|
|
* (2) normalized codeset
|
|
* (3) territory
|
|
* (4) modifier
|
|
*
|
|
* So since we don't take care of the codeset part here, what patterns
|
|
* we need to deal with is:
|
|
*
|
|
* 1. language_territory@modifier
|
|
* 2. language@modifier
|
|
* 3. language
|
|
*
|
|
* then. and maybe no need to try language_territory here.
|
|
*/
|
|
modifier = strchr ((const char *) s, '@');
|
|
if (modifier)
|
|
{
|
|
*modifier = 0;
|
|
modifier++;
|
|
mlen = strlen (modifier);
|
|
}
|
|
encoding = strchr ((const char *) s, '.');
|
|
if (encoding)
|
|
{
|
|
*encoding = 0;
|
|
encoding++;
|
|
if (modifier)
|
|
{
|
|
memmove (encoding, modifier, mlen + 1);
|
|
modifier = encoding;
|
|
}
|
|
}
|
|
territory = strchr ((const char *) s, '_');
|
|
if (!territory)
|
|
territory = strchr ((const char *) s, '-');
|
|
if (territory)
|
|
{
|
|
*territory = 0;
|
|
territory++;
|
|
tlen = strlen (territory);
|
|
}
|
|
llen = strlen ((const char *) s);
|
|
if (llen < 2 || llen > 3)
|
|
{
|
|
fprintf (stderr, "Fontconfig warning: ignoring %s: not a valid language tag\n",
|
|
lang);
|
|
goto bail0;
|
|
}
|
|
if (territory && (tlen < 2 || tlen > 3) &&
|
|
!(territory[0] == 'z' && tlen < 5))
|
|
{
|
|
fprintf (stderr, "Fontconfig warning: ignoring %s: not a valid region tag\n",
|
|
lang);
|
|
goto bail0;
|
|
}
|
|
if (territory)
|
|
territory[-1] = '-';
|
|
if (modifier)
|
|
modifier[-1] = '@';
|
|
orig = FcStrDowncase (s);
|
|
if (!orig)
|
|
goto bail0;
|
|
if (territory)
|
|
{
|
|
if (FcDebug () & FC_DBG_LANGSET)
|
|
printf("Checking the existence of %s.orth\n", s);
|
|
if (FcLangSetIndex (s) < 0)
|
|
{
|
|
memmove (territory - 1, territory + tlen, (mlen > 0 ? mlen + 1 : 0) + 1);
|
|
if (modifier)
|
|
modifier = territory;
|
|
}
|
|
else
|
|
{
|
|
result = s;
|
|
/* we'll miss the opportunity to reduce the correct size
|
|
* of the allocated memory for the string after that.
|
|
*/
|
|
s = NULL;
|
|
goto bail1;
|
|
}
|
|
}
|
|
if (modifier)
|
|
{
|
|
if (FcDebug () & FC_DBG_LANGSET)
|
|
printf("Checking the existence of %s.orth\n", s);
|
|
if (FcLangSetIndex (s) < 0)
|
|
modifier[-1] = 0;
|
|
else
|
|
{
|
|
result = s;
|
|
/* we'll miss the opportunity to reduce the correct size
|
|
* of the allocated memory for the string after that.
|
|
*/
|
|
s = NULL;
|
|
goto bail1;
|
|
}
|
|
}
|
|
if (FcDebug () & FC_DBG_LANGSET)
|
|
printf("Checking the existence of %s.orth\n", s);
|
|
if (FcLangSetIndex (s) < 0)
|
|
{
|
|
/* there seems no languages matched in orth.
|
|
* add the language as is for fallback.
|
|
*/
|
|
result = orig;
|
|
orig = NULL;
|
|
}
|
|
else
|
|
{
|
|
result = s;
|
|
/* we'll miss the opportunity to reduce the correct size
|
|
* of the allocated memory for the string after that.
|
|
*/
|
|
s = NULL;
|
|
}
|
|
bail1:
|
|
if (orig)
|
|
FcStrFree (orig);
|
|
bail0:
|
|
if (s)
|
|
free (s);
|
|
bail:
|
|
if (FcDebug () & FC_DBG_LANGSET)
|
|
{
|
|
if (result)
|
|
printf ("normalized: %s -> %s\n", lang, result);
|
|
else
|
|
printf ("Unable to normalize %s\n", lang);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
#define FcLangEnd(c) ((c) == '-' || (c) == '\0')
|
|
|
|
FcLangResult
|
|
FcLangCompare (const FcChar8 *s1, const FcChar8 *s2)
|
|
{
|
|
FcChar8 c1, c2;
|
|
FcLangResult result = FcLangDifferentLang;
|
|
const FcChar8 *s1_orig = s1;
|
|
FcBool is_und;
|
|
|
|
is_und = FcToLower (s1[0]) == 'u' &&
|
|
FcToLower (s1[1]) == 'n' &&
|
|
FcToLower (s1[2]) == 'd' &&
|
|
FcLangEnd (s1[3]);
|
|
|
|
for (;;)
|
|
{
|
|
c1 = *s1++;
|
|
c2 = *s2++;
|
|
|
|
c1 = FcToLower (c1);
|
|
c2 = FcToLower (c2);
|
|
if (c1 != c2)
|
|
{
|
|
if (!is_und && FcLangEnd (c1) && FcLangEnd (c2))
|
|
result = FcLangDifferentTerritory;
|
|
return result;
|
|
}
|
|
else if (!c1)
|
|
{
|
|
return is_und ? result : FcLangEqual;
|
|
}
|
|
else if (c1 == '-')
|
|
{
|
|
if (!is_und)
|
|
result = FcLangDifferentTerritory;
|
|
}
|
|
|
|
/* If we parsed past "und-", then do not consider it undefined anymore,
|
|
* as there's *something* specified. */
|
|
if (is_und && s1 - s1_orig == 4)
|
|
is_und = FcFalse;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Return FcTrue when super contains sub.
|
|
*
|
|
* super contains sub if super and sub have the same
|
|
* language and either the same country or one
|
|
* is missing the country
|
|
*/
|
|
|
|
static FcBool
|
|
FcLangContains (const FcChar8 *super, const FcChar8 *sub)
|
|
{
|
|
FcChar8 c1, c2;
|
|
|
|
for (;;)
|
|
{
|
|
c1 = *super++;
|
|
c2 = *sub++;
|
|
|
|
c1 = FcToLower (c1);
|
|
c2 = FcToLower (c2);
|
|
if (c1 != c2)
|
|
{
|
|
/* see if super has a country while sub is missing one */
|
|
if (c1 == '-' && c2 == '\0')
|
|
return FcTrue;
|
|
/* see if sub has a country while super is missing one */
|
|
if (c1 == '\0' && c2 == '-')
|
|
return FcTrue;
|
|
return FcFalse;
|
|
}
|
|
else if (!c1)
|
|
return FcTrue;
|
|
}
|
|
}
|
|
|
|
const FcCharSet *
|
|
FcLangGetCharSet (const FcChar8 *lang)
|
|
{
|
|
int i;
|
|
int country = -1;
|
|
|
|
for (i = 0; i < NUM_LANG_CHAR_SET; i++)
|
|
{
|
|
switch (FcLangCompare (lang, fcLangCharSets[i].lang)) {
|
|
case FcLangEqual:
|
|
return &fcLangCharSets[i].charset;
|
|
case FcLangDifferentTerritory:
|
|
if (country == -1)
|
|
country = i;
|
|
case FcLangDifferentLang:
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
if (country == -1)
|
|
return 0;
|
|
return &fcLangCharSets[country].charset;
|
|
}
|
|
|
|
FcStrSet *
|
|
FcGetLangs (void)
|
|
{
|
|
FcStrSet *langs;
|
|
int i;
|
|
|
|
langs = FcStrSetCreate();
|
|
if (!langs)
|
|
return 0;
|
|
|
|
for (i = 0; i < NUM_LANG_CHAR_SET; i++)
|
|
FcStrSetAdd (langs, fcLangCharSets[i].lang);
|
|
|
|
return langs;
|
|
}
|
|
|
|
FcLangSet *
|
|
FcLangSetCreate (void)
|
|
{
|
|
FcLangSet *ls;
|
|
|
|
ls = malloc (sizeof (FcLangSet));
|
|
if (!ls)
|
|
return 0;
|
|
memset (ls->map, '\0', sizeof (ls->map));
|
|
ls->map_size = NUM_LANG_SET_MAP;
|
|
ls->extra = 0;
|
|
return ls;
|
|
}
|
|
|
|
void
|
|
FcLangSetDestroy (FcLangSet *ls)
|
|
{
|
|
if (!ls)
|
|
return;
|
|
|
|
if (ls->extra)
|
|
FcStrSetDestroy (ls->extra);
|
|
free (ls);
|
|
}
|
|
|
|
FcLangSet *
|
|
FcLangSetCopy (const FcLangSet *ls)
|
|
{
|
|
FcLangSet *new;
|
|
|
|
if (!ls)
|
|
return NULL;
|
|
|
|
new = FcLangSetCreate ();
|
|
if (!new)
|
|
goto bail0;
|
|
memset (new->map, '\0', sizeof (new->map));
|
|
memcpy (new->map, ls->map, FC_MIN (sizeof (new->map), ls->map_size * sizeof (ls->map[0])));
|
|
if (ls->extra)
|
|
{
|
|
FcStrList *list;
|
|
FcChar8 *extra;
|
|
|
|
new->extra = FcStrSetCreate ();
|
|
if (!new->extra)
|
|
goto bail1;
|
|
|
|
list = FcStrListCreate (ls->extra);
|
|
if (!list)
|
|
goto bail1;
|
|
|
|
while ((extra = FcStrListNext (list)))
|
|
if (!FcStrSetAdd (new->extra, extra))
|
|
{
|
|
FcStrListDone (list);
|
|
goto bail1;
|
|
}
|
|
FcStrListDone (list);
|
|
}
|
|
return new;
|
|
bail1:
|
|
FcLangSetDestroy (new);
|
|
bail0:
|
|
return 0;
|
|
}
|
|
|
|
/* When the language isn't found, the return value r is such that:
|
|
* 1) r < 0
|
|
* 2) -r -1 is the index of the first language in fcLangCharSets that comes
|
|
* after the 'lang' argument in lexicographic order.
|
|
*
|
|
* The -1 is necessary to avoid problems with language id 0 (otherwise, we
|
|
* wouldn't be able to distinguish between “language found, id is 0” and
|
|
* “language not found, sorts right before the language with id 0”).
|
|
*/
|
|
static int
|
|
FcLangSetIndex (const FcChar8 *lang)
|
|
{
|
|
int low, high, mid = 0;
|
|
int cmp = 0;
|
|
FcChar8 firstChar = FcToLower(lang[0]);
|
|
FcChar8 secondChar = firstChar ? FcToLower(lang[1]) : '\0';
|
|
|
|
if (firstChar < 'a')
|
|
{
|
|
low = 0;
|
|
high = fcLangCharSetRanges[0].begin;
|
|
}
|
|
else if(firstChar > 'z')
|
|
{
|
|
low = fcLangCharSetRanges[25].begin;
|
|
high = NUM_LANG_CHAR_SET - 1;
|
|
}
|
|
else
|
|
{
|
|
low = fcLangCharSetRanges[firstChar - 'a'].begin;
|
|
high = fcLangCharSetRanges[firstChar - 'a'].end;
|
|
/* no matches */
|
|
if (low > high)
|
|
return -(low+1); /* one past next entry after where it would be */
|
|
}
|
|
|
|
while (low <= high)
|
|
{
|
|
mid = (high + low) >> 1;
|
|
if(fcLangCharSets[mid].lang[0] != firstChar)
|
|
cmp = FcStrCmpIgnoreCase(fcLangCharSets[mid].lang, lang);
|
|
else
|
|
{ /* fast path for resolving 2-letter languages (by far the most common) after
|
|
* finding the first char (probably already true because of the hash table) */
|
|
cmp = fcLangCharSets[mid].lang[1] - secondChar;
|
|
if (cmp == 0 &&
|
|
(fcLangCharSets[mid].lang[2] != '\0' ||
|
|
lang[2] != '\0'))
|
|
{
|
|
cmp = FcStrCmpIgnoreCase(fcLangCharSets[mid].lang+2,
|
|
lang+2);
|
|
}
|
|
}
|
|
if (cmp == 0)
|
|
return mid;
|
|
if (cmp < 0)
|
|
low = mid + 1;
|
|
else
|
|
high = mid - 1;
|
|
}
|
|
if (cmp < 0)
|
|
mid++;
|
|
return -(mid + 1);
|
|
}
|
|
|
|
FcBool
|
|
FcLangSetAdd (FcLangSet *ls, const FcChar8 *lang)
|
|
{
|
|
int id;
|
|
|
|
id = FcLangSetIndex (lang);
|
|
if (id >= 0)
|
|
{
|
|
FcLangSetBitSet (ls, id);
|
|
return FcTrue;
|
|
}
|
|
if (!ls->extra)
|
|
{
|
|
ls->extra = FcStrSetCreate ();
|
|
if (!ls->extra)
|
|
return FcFalse;
|
|
}
|
|
return FcStrSetAdd (ls->extra, lang);
|
|
}
|
|
|
|
FcBool
|
|
FcLangSetDel (FcLangSet *ls, const FcChar8 *lang)
|
|
{
|
|
int id;
|
|
|
|
id = FcLangSetIndex (lang);
|
|
if (id >= 0)
|
|
{
|
|
FcLangSetBitReset (ls, id);
|
|
}
|
|
else if (ls->extra)
|
|
{
|
|
FcStrSetDel (ls->extra, lang);
|
|
}
|
|
return FcTrue;
|
|
}
|
|
|
|
FcLangResult
|
|
FcLangSetHasLang (const FcLangSet *ls, const FcChar8 *lang)
|
|
{
|
|
int id;
|
|
FcLangResult best, r;
|
|
int i;
|
|
|
|
id = FcLangSetIndex (lang);
|
|
if (id < 0)
|
|
id = -id - 1;
|
|
else if (FcLangSetBitGet (ls, id))
|
|
return FcLangEqual;
|
|
best = FcLangDifferentLang;
|
|
for (i = id - 1; i >= 0; i--)
|
|
{
|
|
r = FcLangCompare (lang, fcLangCharSets[i].lang);
|
|
if (r == FcLangDifferentLang)
|
|
break;
|
|
if (FcLangSetBitGet (ls, i) && r < best)
|
|
best = r;
|
|
}
|
|
for (i = id; i < NUM_LANG_CHAR_SET; i++)
|
|
{
|
|
r = FcLangCompare (lang, fcLangCharSets[i].lang);
|
|
if (r == FcLangDifferentLang)
|
|
break;
|
|
if (FcLangSetBitGet (ls, i) && r < best)
|
|
best = r;
|
|
}
|
|
if (ls->extra)
|
|
{
|
|
FcStrList *list = FcStrListCreate (ls->extra);
|
|
FcChar8 *extra;
|
|
|
|
if (list)
|
|
{
|
|
while (best > FcLangEqual && (extra = FcStrListNext (list)))
|
|
{
|
|
r = FcLangCompare (lang, extra);
|
|
if (r < best)
|
|
best = r;
|
|
}
|
|
FcStrListDone (list);
|
|
}
|
|
}
|
|
return best;
|
|
}
|
|
|
|
static FcLangResult
|
|
FcLangSetCompareStrSet (const FcLangSet *ls, FcStrSet *set)
|
|
{
|
|
FcStrList *list = FcStrListCreate (set);
|
|
FcLangResult r, best = FcLangDifferentLang;
|
|
FcChar8 *extra;
|
|
|
|
if (list)
|
|
{
|
|
while (best > FcLangEqual && (extra = FcStrListNext (list)))
|
|
{
|
|
r = FcLangSetHasLang (ls, extra);
|
|
if (r < best)
|
|
best = r;
|
|
}
|
|
FcStrListDone (list);
|
|
}
|
|
return best;
|
|
}
|
|
|
|
FcLangResult
|
|
FcLangSetCompare (const FcLangSet *lsa, const FcLangSet *lsb)
|
|
{
|
|
int i, j, count;
|
|
FcLangResult best, r;
|
|
FcChar32 aInCountrySet, bInCountrySet;
|
|
|
|
count = FC_MIN (lsa->map_size, lsb->map_size);
|
|
count = FC_MIN (NUM_LANG_SET_MAP, count);
|
|
for (i = 0; i < count; i++)
|
|
if (lsa->map[i] & lsb->map[i])
|
|
return FcLangEqual;
|
|
best = FcLangDifferentLang;
|
|
for (j = 0; j < NUM_COUNTRY_SET; j++)
|
|
{
|
|
aInCountrySet = 0;
|
|
bInCountrySet = 0;
|
|
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
aInCountrySet |= lsa->map[i] & fcLangCountrySets[j][i];
|
|
bInCountrySet |= lsb->map[i] & fcLangCountrySets[j][i];
|
|
|
|
if (aInCountrySet && bInCountrySet)
|
|
{
|
|
best = FcLangDifferentTerritory;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (lsa->extra)
|
|
{
|
|
r = FcLangSetCompareStrSet (lsb, lsa->extra);
|
|
if (r < best)
|
|
best = r;
|
|
}
|
|
if (best > FcLangEqual && lsb->extra)
|
|
{
|
|
r = FcLangSetCompareStrSet (lsa, lsb->extra);
|
|
if (r < best)
|
|
best = r;
|
|
}
|
|
return best;
|
|
}
|
|
|
|
/*
|
|
* Used in computing values -- mustn't allocate any storage
|
|
*/
|
|
FcLangSet *
|
|
FcLangSetPromote (const FcChar8 *lang, FcValuePromotionBuffer *vbuf)
|
|
{
|
|
int id;
|
|
typedef struct {
|
|
FcLangSet ls;
|
|
FcStrSet strs;
|
|
FcChar8 *str;
|
|
} FcLangSetPromotionBuffer;
|
|
FcLangSetPromotionBuffer *buf = (FcLangSetPromotionBuffer *) vbuf;
|
|
|
|
FC_ASSERT_STATIC (sizeof (FcLangSetPromotionBuffer) <= sizeof (FcValuePromotionBuffer));
|
|
|
|
memset (buf->ls.map, '\0', sizeof (buf->ls.map));
|
|
buf->ls.map_size = NUM_LANG_SET_MAP;
|
|
buf->ls.extra = 0;
|
|
if (lang)
|
|
{
|
|
id = FcLangSetIndex (lang);
|
|
if (id >= 0)
|
|
{
|
|
FcLangSetBitSet (&buf->ls, id);
|
|
}
|
|
else
|
|
{
|
|
buf->ls.extra = &buf->strs;
|
|
buf->strs.num = 1;
|
|
buf->strs.size = 1;
|
|
buf->strs.strs = &buf->str;
|
|
FcRefInit (&buf->strs.ref, 1);
|
|
buf->str = (FcChar8 *) lang;
|
|
}
|
|
}
|
|
return &buf->ls;
|
|
}
|
|
|
|
FcChar32
|
|
FcLangSetHash (const FcLangSet *ls)
|
|
{
|
|
FcChar32 h = 0;
|
|
int i, count;
|
|
|
|
count = FC_MIN (ls->map_size, NUM_LANG_SET_MAP);
|
|
for (i = 0; i < count; i++)
|
|
h ^= ls->map[i];
|
|
if (ls->extra)
|
|
h ^= ls->extra->num;
|
|
return h;
|
|
}
|
|
|
|
FcLangSet *
|
|
FcNameParseLangSet (const FcChar8 *string)
|
|
{
|
|
FcChar8 lang[32], c = 0;
|
|
int i;
|
|
FcLangSet *ls;
|
|
|
|
ls = FcLangSetCreate ();
|
|
if (!ls)
|
|
goto bail0;
|
|
|
|
for(;;)
|
|
{
|
|
for(i = 0; i < 31;i++)
|
|
{
|
|
c = *string++;
|
|
if(c == '\0' || c == '|')
|
|
break; /* end of this code */
|
|
lang[i] = c;
|
|
}
|
|
lang[i] = '\0';
|
|
if (!FcLangSetAdd (ls, lang))
|
|
goto bail1;
|
|
if(c == '\0')
|
|
break;
|
|
}
|
|
return ls;
|
|
bail1:
|
|
FcLangSetDestroy (ls);
|
|
bail0:
|
|
return 0;
|
|
}
|
|
|
|
FcBool
|
|
FcNameUnparseLangSet (FcStrBuf *buf, const FcLangSet *ls)
|
|
{
|
|
int i, bit, count;
|
|
FcChar32 bits;
|
|
FcBool first = FcTrue;
|
|
|
|
count = FC_MIN (ls->map_size, NUM_LANG_SET_MAP);
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
if ((bits = ls->map[i]))
|
|
{
|
|
for (bit = 0; bit <= 31; bit++)
|
|
if (bits & (1U << bit))
|
|
{
|
|
int id = (i << 5) | bit;
|
|
if (!first)
|
|
if (!FcStrBufChar (buf, '|'))
|
|
return FcFalse;
|
|
if (!FcStrBufString (buf, fcLangCharSets[fcLangCharSetIndicesInv[id]].lang))
|
|
return FcFalse;
|
|
first = FcFalse;
|
|
}
|
|
}
|
|
}
|
|
if (ls->extra)
|
|
{
|
|
FcStrList *list = FcStrListCreate (ls->extra);
|
|
FcChar8 *extra;
|
|
|
|
if (!list)
|
|
return FcFalse;
|
|
while ((extra = FcStrListNext (list)))
|
|
{
|
|
if (!first)
|
|
if (!FcStrBufChar (buf, '|'))
|
|
{
|
|
FcStrListDone (list);
|
|
return FcFalse;
|
|
}
|
|
if (!FcStrBufString (buf, extra))
|
|
{
|
|
FcStrListDone (list);
|
|
return FcFalse;
|
|
}
|
|
first = FcFalse;
|
|
}
|
|
FcStrListDone (list);
|
|
}
|
|
return FcTrue;
|
|
}
|
|
|
|
FcBool
|
|
FcLangSetEqual (const FcLangSet *lsa, const FcLangSet *lsb)
|
|
{
|
|
int i, count;
|
|
|
|
count = FC_MIN (lsa->map_size, lsb->map_size);
|
|
count = FC_MIN (NUM_LANG_SET_MAP, count);
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
if (lsa->map[i] != lsb->map[i])
|
|
return FcFalse;
|
|
}
|
|
if (!lsa->extra && !lsb->extra)
|
|
return FcTrue;
|
|
if (lsa->extra && lsb->extra)
|
|
return FcStrSetEqual (lsa->extra, lsb->extra);
|
|
return FcFalse;
|
|
}
|
|
|
|
static FcBool
|
|
FcLangSetContainsLang (const FcLangSet *ls, const FcChar8 *lang)
|
|
{
|
|
int id;
|
|
int i;
|
|
|
|
id = FcLangSetIndex (lang);
|
|
if (id < 0)
|
|
id = -id - 1;
|
|
else if (FcLangSetBitGet (ls, id))
|
|
return FcTrue;
|
|
/*
|
|
* search up and down among equal languages for a match
|
|
*/
|
|
for (i = id - 1; i >= 0; i--)
|
|
{
|
|
if (FcLangCompare (fcLangCharSets[i].lang, lang) == FcLangDifferentLang)
|
|
break;
|
|
if (FcLangSetBitGet (ls, i) &&
|
|
FcLangContains (fcLangCharSets[i].lang, lang))
|
|
return FcTrue;
|
|
}
|
|
for (i = id; i < NUM_LANG_CHAR_SET; i++)
|
|
{
|
|
if (FcLangCompare (fcLangCharSets[i].lang, lang) == FcLangDifferentLang)
|
|
break;
|
|
if (FcLangSetBitGet (ls, i) &&
|
|
FcLangContains (fcLangCharSets[i].lang, lang))
|
|
return FcTrue;
|
|
}
|
|
if (ls->extra)
|
|
{
|
|
FcStrList *list = FcStrListCreate (ls->extra);
|
|
FcChar8 *extra;
|
|
|
|
if (list)
|
|
{
|
|
while ((extra = FcStrListNext (list)))
|
|
{
|
|
if (FcLangContains (extra, lang))
|
|
break;
|
|
}
|
|
FcStrListDone (list);
|
|
if (extra)
|
|
return FcTrue;
|
|
}
|
|
}
|
|
return FcFalse;
|
|
}
|
|
|
|
/*
|
|
* return FcTrue if lsa contains every language in lsb
|
|
*/
|
|
FcBool
|
|
FcLangSetContains (const FcLangSet *lsa, const FcLangSet *lsb)
|
|
{
|
|
int i, j, count;
|
|
FcChar32 missing;
|
|
|
|
if (FcDebug() & FC_DBG_MATCHV)
|
|
{
|
|
printf ("FcLangSet "); FcLangSetPrint (lsa);
|
|
printf (" contains "); FcLangSetPrint (lsb);
|
|
printf ("\n");
|
|
}
|
|
/*
|
|
* check bitmaps for missing language support
|
|
*/
|
|
count = FC_MIN (lsa->map_size, lsb->map_size);
|
|
count = FC_MIN (NUM_LANG_SET_MAP, count);
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
missing = lsb->map[i] & ~lsa->map[i];
|
|
if (missing)
|
|
{
|
|
for (j = 0; j < 32; j++)
|
|
if (missing & (1U << j))
|
|
{
|
|
if (!FcLangSetContainsLang (lsa,
|
|
fcLangCharSets[fcLangCharSetIndicesInv[i*32 + j]].lang))
|
|
{
|
|
if (FcDebug() & FC_DBG_MATCHV)
|
|
printf ("\tMissing bitmap %s\n", fcLangCharSets[fcLangCharSetIndicesInv[i*32+j]].lang);
|
|
return FcFalse;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (lsb->extra)
|
|
{
|
|
FcStrList *list = FcStrListCreate (lsb->extra);
|
|
FcChar8 *extra;
|
|
|
|
if (list)
|
|
{
|
|
while ((extra = FcStrListNext (list)))
|
|
{
|
|
if (!FcLangSetContainsLang (lsa, extra))
|
|
{
|
|
if (FcDebug() & FC_DBG_MATCHV)
|
|
printf ("\tMissing string %s\n", extra);
|
|
break;
|
|
}
|
|
}
|
|
FcStrListDone (list);
|
|
if (extra)
|
|
return FcFalse;
|
|
}
|
|
}
|
|
return FcTrue;
|
|
}
|
|
|
|
FcBool
|
|
FcLangSetSerializeAlloc (FcSerialize *serialize, const FcLangSet *l)
|
|
{
|
|
if (!FcSerializeAlloc (serialize, l, sizeof (FcLangSet)))
|
|
return FcFalse;
|
|
return FcTrue;
|
|
}
|
|
|
|
FcLangSet *
|
|
FcLangSetSerialize(FcSerialize *serialize, const FcLangSet *l)
|
|
{
|
|
FcLangSet *l_serialize = FcSerializePtr (serialize, l);
|
|
|
|
if (!l_serialize)
|
|
return NULL;
|
|
memset (l_serialize->map, '\0', sizeof (l_serialize->map));
|
|
memcpy (l_serialize->map, l->map, FC_MIN (sizeof (l_serialize->map), l->map_size * sizeof (l->map[0])));
|
|
l_serialize->map_size = NUM_LANG_SET_MAP;
|
|
l_serialize->extra = NULL; /* We don't serialize ls->extra */
|
|
return l_serialize;
|
|
}
|
|
|
|
FcStrSet *
|
|
FcLangSetGetLangs (const FcLangSet *ls)
|
|
{
|
|
FcStrSet *langs;
|
|
int i;
|
|
|
|
langs = FcStrSetCreate();
|
|
if (!langs)
|
|
return 0;
|
|
|
|
for (i = 0; i < NUM_LANG_CHAR_SET; i++)
|
|
if (FcLangSetBitGet (ls, i))
|
|
FcStrSetAdd (langs, fcLangCharSets[i].lang);
|
|
|
|
if (ls->extra)
|
|
{
|
|
FcStrList *list = FcStrListCreate (ls->extra);
|
|
FcChar8 *extra;
|
|
|
|
if (list)
|
|
{
|
|
while ((extra = FcStrListNext (list)))
|
|
FcStrSetAdd (langs, extra);
|
|
|
|
FcStrListDone (list);
|
|
}
|
|
}
|
|
|
|
return langs;
|
|
}
|
|
|
|
static FcLangSet *
|
|
FcLangSetOperate(const FcLangSet *a,
|
|
const FcLangSet *b,
|
|
FcBool (*func) (FcLangSet *ls,
|
|
const FcChar8 *s))
|
|
{
|
|
FcLangSet *langset = FcLangSetCopy (a);
|
|
FcStrSet *set = FcLangSetGetLangs (b);
|
|
FcStrList *sl = FcStrListCreate (set);
|
|
FcChar8 *str;
|
|
|
|
FcStrSetDestroy (set);
|
|
while ((str = FcStrListNext (sl)))
|
|
{
|
|
func (langset, str);
|
|
}
|
|
FcStrListDone (sl);
|
|
|
|
return langset;
|
|
}
|
|
|
|
FcLangSet *
|
|
FcLangSetUnion (const FcLangSet *a, const FcLangSet *b)
|
|
{
|
|
return FcLangSetOperate(a, b, FcLangSetAdd);
|
|
}
|
|
|
|
FcLangSet *
|
|
FcLangSetSubtract (const FcLangSet *a, const FcLangSet *b)
|
|
{
|
|
return FcLangSetOperate(a, b, FcLangSetDel);
|
|
}
|
|
|
|
#define __fclang__
|
|
#include "fcaliastail.h"
|
|
#include "fcftaliastail.h"
|
|
#undef __fclang__
|