blob: 07e24dfdb89219f882f0b6b0f308e5ec5e966883 [file] [log] [blame]
/*
* $XFree86: $
*
* Copyright © 2002 Keith Packard, member of The XFree86 Project, Inc.
*
* 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 Keith Packard not be used in
* advertising or publicity pertaining to distribution of the software without
* specific, written prior permission. Keith Packard makes no
* representations about the suitability of this software for any purpose. It
* is provided "as is" without express or implied warranty.
*
* KEITH PACKARD DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
* INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
* EVENT SHALL KEITH PACKARD 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 <stdarg.h>
#include "fcint.h"
static xmlParserInputPtr
FcEntityLoader (const char *url, const char *id, xmlParserCtxtPtr ctxt)
{
xmlParserInputPtr ret;
char *file;
file = FcConfigFilename (url);
if (!file)
return 0;
ret = xmlNewInputFromFile (ctxt, file);
free (file);
return ret;
}
xmlDocPtr
FcConfigLoad (const char *file)
{
xmlDocPtr doc;
xmlExternalEntityLoader previous;
previous = xmlGetExternalEntityLoader ();
xmlSetExternalEntityLoader (FcEntityLoader);
doc = xmlParseFile (file);
xmlSetExternalEntityLoader (previous);
return doc;
}
#if 0
int
FcConfigSave (char *file, xmlDocPtr doc)
{
}
#endif
FcTest *
FcTestCreate (FcQual qual, const char *field, FcOp compare, FcExpr *expr)
{
FcTest *test = (FcTest *) malloc (sizeof (FcTest));;
if (test)
{
test->next = 0;
test->qual = qual;
test->field = FcStrCopy (field);
test->op = compare;
test->expr = expr;
}
return test;
}
void
FcTestDestroy (FcTest *test)
{
if (test->next)
FcTestDestroy (test->next);
FcExprDestroy (test->expr);
FcStrFree ((FcChar8 *) test->field);
free (test);
}
FcExpr *
FcExprCreateInteger (int i)
{
FcExpr *e = (FcExpr *) malloc (sizeof (FcExpr));
if (e)
{
e->op = FcOpInteger;
e->u.ival = i;
}
return e;
}
FcExpr *
FcExprCreateDouble (double d)
{
FcExpr *e = (FcExpr *) malloc (sizeof (FcExpr));
if (e)
{
e->op = FcOpDouble;
e->u.dval = d;
}
return e;
}
FcExpr *
FcExprCreateString (const char *s)
{
FcExpr *e = (FcExpr *) malloc (sizeof (FcExpr));
if (e)
{
e->op = FcOpString;
e->u.sval = FcStrCopy (s);
}
return e;
}
FcExpr *
FcExprCreateMatrix (const FcMatrix *m)
{
FcExpr *e = (FcExpr *) malloc (sizeof (FcExpr));
if (e)
{
e->op = FcOpMatrix;
e->u.mval = FcMatrixCopy (m);
}
return e;
}
FcExpr *
FcExprCreateBool (FcBool b)
{
FcExpr *e = (FcExpr *) malloc (sizeof (FcExpr));
if (e)
{
e->op = FcOpBool;
e->u.bval = b;
}
return e;
}
FcExpr *
FcExprCreateNil (void)
{
FcExpr *e = (FcExpr *) malloc (sizeof (FcExpr));
if (e)
{
e->op = FcOpNil;
}
return e;
}
FcExpr *
FcExprCreateField (const char *field)
{
FcExpr *e = (FcExpr *) malloc (sizeof (FcExpr));
if (e)
{
e->op = FcOpField;
e->u.field = FcStrCopy (field);
}
return e;
}
FcExpr *
FcExprCreateConst (const char *constant)
{
FcExpr *e = (FcExpr *) malloc (sizeof (FcExpr));
if (e)
{
e->op = FcOpConst;
e->u.constant = FcStrCopy (constant);
}
return e;
}
FcExpr *
FcExprCreateOp (FcExpr *left, FcOp op, FcExpr *right)
{
FcExpr *e = (FcExpr *) malloc (sizeof (FcExpr));
if (e)
{
e->op = op;
e->u.tree.left = left;
e->u.tree.right = right;
}
return e;
}
void
FcExprDestroy (FcExpr *e)
{
switch (e->op) {
case FcOpInteger:
break;
case FcOpDouble:
break;
case FcOpString:
FcStrFree (e->u.sval);
break;
case FcOpMatrix:
FcMatrixFree (e->u.mval);
break;
case FcOpCharSet:
FcCharSetDestroy (e->u.cval);
break;
case FcOpBool:
break;
case FcOpField:
FcStrFree (e->u.field);
break;
case FcOpConst:
FcStrFree (e->u.constant);
break;
case FcOpAssign:
case FcOpAssignReplace:
case FcOpPrepend:
case FcOpPrependFirst:
case FcOpAppend:
case FcOpAppendLast:
break;
case FcOpOr:
case FcOpAnd:
case FcOpEqual:
case FcOpContains:
case FcOpNotEqual:
case FcOpLess:
case FcOpLessEqual:
case FcOpMore:
case FcOpMoreEqual:
case FcOpPlus:
case FcOpMinus:
case FcOpTimes:
case FcOpDivide:
case FcOpQuest:
case FcOpComma:
FcExprDestroy (e->u.tree.right);
/* fall through */
case FcOpNot:
FcExprDestroy (e->u.tree.left);
break;
case FcOpNil:
case FcOpInvalid:
break;
}
free (e);
}
FcEdit *
FcEditCreate (const char *field, FcOp op, FcExpr *expr)
{
FcEdit *e = (FcEdit *) malloc (sizeof (FcEdit));
if (e)
{
e->next = 0;
e->field = field; /* already saved in grammar */
e->op = op;
e->expr = expr;
}
return e;
}
void
FcEditDestroy (FcEdit *e)
{
if (e->next)
FcEditDestroy (e->next);
FcStrFree ((FcChar8 *) e->field);
if (e->expr)
FcExprDestroy (e->expr);
}
char *
FcConfigSaveField (const char *field)
{
return FcStrCopy (field);
}
static void
FcConfigParseError (char *fmt, ...)
{
va_list args;
va_start (args, fmt);
fprintf (stderr, "font configuration error: ");
vfprintf (stderr, fmt, args);
fprintf (stderr, "\n");
va_end (args);
}
static char *
FcConfigContent (xmlDocPtr doc,
xmlNodePtr node)
{
char *content;
content = xmlNodeListGetString (doc, node->children, 1);
if (!content)
{
FcConfigParseError ("<%s> must have content",
node->name);
return FcFalse;
}
return content;
}
static char *
FcConfigAttr (xmlDocPtr doc,
xmlAttrPtr attr)
{
char *content;
content = xmlNodeListGetString (doc, attr->children, 1);
if (!content)
{
FcConfigParseError ("attribute %s must have a value",
attr->name);
return FcFalse;
}
return content;
}
static struct {
char *name;
FcOp op;
} fcOps[] = {
{ "int", FcOpInteger },
{ "double", FcOpDouble },
{ "string", FcOpString },
{ "matrix", FcOpMatrix },
{ "bool", FcOpBool },
{ "charset", FcOpCharSet },
{ "name", FcOpField },
{ "const", FcOpConst },
{ "field", FcOpField },
{ "if", FcOpQuest },
{ "or", FcOpOr },
{ "and", FcOpAnd },
{ "eq", FcOpEqual },
{ "not_eq", FcOpNotEqual },
{ "less", FcOpLess },
{ "less_eq", FcOpLessEqual },
{ "more", FcOpMore },
{ "more_eq", FcOpMoreEqual },
{ "plus", FcOpPlus },
{ "minus", FcOpMinus },
{ "times", FcOpTimes },
{ "divide", FcOpDivide },
{ "not", FcOpNot },
{ "assign", FcOpAssign },
{ "assign_replace", FcOpAssignReplace },
{ "prepend", FcOpPrepend },
{ "prepend_first", FcOpPrependFirst },
{ "append", FcOpAppend },
{ "append_last", FcOpAppendLast },
};
#define NUM_OPS (sizeof fcOps / sizeof fcOps[0])
static FcOp
FcConfigLexOp (const char *op)
{
int i;
for (i = 0; i < NUM_OPS; i++)
if (!strcmp (op, fcOps[i].name)) return fcOps[i].op;
return FcOpInvalid;
}
static FcBool
FcConfigLexBool (const char *bool)
{
if (*bool == 't' || *bool == 'T')
return FcTrue;
if (*bool == 'y' || *bool == 'Y')
return FcTrue;
if (*bool == '1')
return FcTrue;
return FcFalse;
}
static FcBool
FcConfigParseDir (FcConfig *config,
xmlDocPtr doc,
xmlNodePtr dir)
{
char *content = FcConfigContent (doc, dir);
if (!content)
return FcFalse;
return FcConfigAddDir (config, content);
}
static FcBool
FcConfigParseCache (FcConfig *config,
xmlDocPtr doc,
xmlNodePtr dir)
{
char *content = FcConfigContent (doc, dir);
if (!content)
return FcFalse;
return FcConfigSetCache (config, content);
}
static FcBool
FcConfigParseInclude (FcConfig *config,
xmlDocPtr doc,
xmlNodePtr inc)
{
char *content = FcConfigContent (doc, inc);
xmlAttr *attr;
FcBool complain = FcTrue;
if (!content)
return FcFalse;
for (attr = inc->properties; attr; attr = attr->next)
{
if (attr->type != XML_ATTRIBUTE_NODE)
continue;
if (!strcmp (attr->name, "ignore_missing"))
complain = !FcConfigLexBool (FcConfigAttr (doc, attr));
}
return FcConfigParseAndLoad (config, content, complain);
}
static FcBool
FcConfigParseBlank (FcConfig *config,
xmlDocPtr doc,
xmlNodePtr blank)
{
xmlNode *node;
FcChar32 ucs4;
for (node = blank->children; node; node = node->next)
{
if (node->type != XML_ELEMENT_NODE)
continue;
if (!strcmp (node->name, "int"))
{
ucs4 = (FcChar32) strtol (FcConfigContent (doc, node), 0, 0);
if (!config->blanks)
{
config->blanks = FcBlanksCreate ();
if (!config->blanks)
break;
}
if (!FcBlanksAdd (config->blanks, ucs4))
break;
}
}
if (node)
return FcFalse;
return FcTrue;
}
static FcBool
FcConfigParseConfig (FcConfig *config,
xmlDocPtr doc,
xmlNodePtr cfg)
{
xmlNode *node;
for (node = cfg->children; node; node = node->next)
{
if (node->type != XML_ELEMENT_NODE)
continue;
if (!strcmp (node->name, "blank"))
{
if (!FcConfigParseBlank (config, doc, node))
break;
}
}
if (node)
return FcFalse;
return FcTrue;
}
static FcMatrix *
FcConfigParseMatrix (xmlDocPtr doc,
xmlNodePtr node)
{
static FcMatrix m;
enum { m_xx, m_xy, m_yx, m_yy, m_done } matrix_state = m_xx;
double v;
char *text;
FcMatrixInit (&m);
for (; node; node = node->next)
{
if (node->type != XML_ELEMENT_NODE)
continue;
if (strcmp (node->name, "double"))
continue;
text = FcConfigContent (doc, node);
if (!text)
continue;
v = strtod (text, 0);
switch (matrix_state) {
case m_xx: m.xx = v; break;
case m_xy: m.xy = v; break;
case m_yx: m.yx = v; break;
case m_yy: m.yy = v; break;
default: break;
}
matrix_state++;
}
return &m;
}
static FcExpr *
FcConfigParseExpr (xmlDocPtr doc,
xmlNodePtr expr)
{
FcOp op = FcConfigLexOp (expr->name);
xmlNodePtr node;
FcExpr *l = 0, *e = 0, *r = 0, *c = 0;
switch (op) {
case FcOpInteger:
l = FcExprCreateInteger (strtol (FcConfigContent (doc, expr), 0, 0));
break;
case FcOpDouble:
l = FcExprCreateDouble (strtod (FcConfigContent (doc, expr), 0));
break;
case FcOpString:
l = FcExprCreateString (FcConfigContent (doc, expr));
break;
case FcOpMatrix:
l = FcExprCreateMatrix (FcConfigParseMatrix (doc, expr));
break;
case FcOpBool:
l = FcExprCreateBool (FcConfigLexBool(FcConfigContent (doc, expr)));
break;
case FcOpCharSet:
/* not sure what to do here yet */
break;
case FcOpField:
l = FcExprCreateField (FcConfigContent (doc, expr));
break;
case FcOpConst:
l = FcExprCreateConst (FcConfigContent (doc, expr));
break;
case FcOpQuest:
for (node = expr->children; node; node = node->next)
{
if (node->type != XML_ELEMENT_NODE)
continue;
e = FcConfigParseExpr (doc, node);
if (!e)
break;
if (!l)
l = e;
else if (!c)
c = e;
else if (!r)
r = e;
else
FcExprDestroy (e);
}
e = 0;
if (!node && l && c && r)
{
e = FcExprCreateOp (c, FcOpQuest, r);
if (e)
{
r = e;
c = 0;
e = FcExprCreateOp (l, FcOpQuest, r);
}
if (!e)
node = expr->children;
}
if (node || !l || !c || !r || !e)
{
if (l)
FcExprDestroy (l);
if (c)
FcExprDestroy (c);
if (r)
FcExprDestroy (r);
return 0;
}
break;
default:
for (node = expr->children; node; node = node->next)
{
if (node->type != XML_ELEMENT_NODE)
continue;
e = FcConfigParseExpr (doc, node);
if (!e)
break;
if (!l)
l = e;
else
{
r = e;
e = FcExprCreateOp (l, op, r);
if (!e)
{
FcExprDestroy (r);
break;
}
l = e;
}
}
if (node || !l)
{
if (l)
FcExprDestroy (l);
return 0;
}
/*
* Special case for unary ops
*/
if (!r)
{
e = FcExprCreateOp (l, op, 0);
if (!e)
{
FcExprDestroy (l);
return 0;
}
}
break;
case FcOpInvalid:
return 0;
}
return l;
}
static FcTest *
FcConfigParseTest (xmlDocPtr doc,
xmlNodePtr test)
{
xmlNodePtr node;
xmlAttrPtr attr;
FcQual qual = FcQualAny;
FcOp op = FcOpEqual;
char *field = 0;
FcExpr *expr = 0;
for (attr = test->properties; attr; attr = attr->next)
{
if (attr->type != XML_ATTRIBUTE_NODE)
continue;
if (!strcmp (attr->name, "qual"))
{
char *qual_name = FcConfigAttr (doc, attr);
if (!qual_name)
;
else if (!strcmp (qual_name, "any"))
qual = FcQualAny;
else if (!strcmp (qual_name, "all"))
qual = FcQualAll;
}
else if (!strcmp (attr->name, "name"))
{
field = FcConfigAttr (doc, attr);
}
else if (!strcmp (attr->name, "compare"))
{
char *compare = FcConfigAttr (doc, attr);
if (!compare || (op = FcConfigLexOp (compare)) == FcOpInvalid)
{
FcConfigParseError ("Invalid comparison %s",
compare ? compare : "<missing>");
return 0;
}
}
}
if (attr)
return 0;
for (node = test->children; node; node = node->next)
{
if (node->type != XML_ELEMENT_NODE)
continue;
expr = FcConfigParseExpr (doc, node);
if (!expr)
return 0;
break;
}
if (!expr)
{
FcConfigParseError ("Missing test expression");
return 0;
}
return FcTestCreate (qual, field, op, expr);
}
static FcExpr *
FcConfigParseExprList (xmlDocPtr doc,
xmlNodePtr expr)
{
FcExpr *l, *e, *r;
if (!expr)
return 0;
e = FcConfigParseExprList (doc, expr->next);
if (expr->type == XML_ELEMENT_NODE)
{
r = e;
l = FcConfigParseExpr (doc, expr);
if (!l)
goto bail;
if (r)
{
e = FcExprCreateOp (l, FcOpComma, r);
if (!e)
goto bail;
}
else
e = l;
}
return e;
bail:
if (l)
FcExprDestroy (l);
if (r)
FcExprDestroy (r);
return 0;
}
static FcEdit *
FcConfigParseEdit (xmlDocPtr doc,
xmlNodePtr edit)
{
xmlAttrPtr attr;
char *name = 0;
FcOp mode = FcOpAssign;
FcExpr *e;
FcEdit *ed;
for (attr = edit->properties; attr; attr = attr->next)
{
if (attr->type != XML_ATTRIBUTE_NODE)
continue;
if (!strcmp (attr->name, "name"))
name = FcConfigAttr (doc, attr);
else if (!strcmp (attr->name, "mode"))
mode = FcConfigLexOp (FcConfigAttr (doc, attr));
}
e = FcConfigParseExprList (doc, edit->children);
ed = FcEditCreate (name, mode, e);
if (!ed)
FcExprDestroy (e);
return ed;
}
static FcBool
FcConfigParseMatch (FcConfig *config,
xmlDocPtr doc,
xmlNodePtr match)
{
xmlNodePtr node;
xmlAttrPtr attr;
FcTest *tests = 0, **prevTest = &tests, *test;
FcEdit *edits = 0, **prevEdit = &edits, *edit;
FcMatchKind kind;
FcBool found_kind = FcFalse;
for (node = match->children; node; node = node->next)
{
if (node->type != XML_ELEMENT_NODE)
continue;
if (!strcmp (node->name, "test"))
{
test = FcConfigParseTest (doc, node);
if (!test)
break;
*prevTest = test;
prevTest = &test->next;
}
else if (!strcmp (node->name, "edit"))
{
edit = FcConfigParseEdit (doc, node);
if (!edit)
break;
*prevEdit = edit;
prevEdit = &edit->next;
}
}
for (attr = match->properties; attr; attr = attr->next)
{
if (attr->type != XML_ATTRIBUTE_NODE)
continue;
if (!strcmp (attr->name, "target"))
{
char *target = FcConfigAttr (doc, attr);
if (!target)
{
FcConfigParseError ("Missing match target");
break;
}
else if (!strcmp (target, "pattern"))
{
kind = FcMatchPattern;
found_kind = FcTrue;
}
else if (!strcmp (target, "font"))
{
kind = FcMatchFont;
found_kind = FcTrue;
}
}
}
if (node || attr || !found_kind ||
!FcConfigAddEdit (config, tests, edits, kind))
{
if (tests)
FcTestDestroy (tests);
if (edits)
FcEditDestroy (edits);
return FcFalse;
}
return FcTrue;
}
static FcExpr *
FcConfigParseFamilies (xmlDocPtr doc,
xmlNodePtr family)
{
FcExpr *next = 0, *this = 0, *expr = 0;
if (!family)
return 0;
next = FcConfigParseFamilies (doc, family->next);
if (family->type == XML_ELEMENT_NODE && !strcmp (family->name, "family"))
{
this = FcExprCreateString (FcConfigContent (doc, family));
if (!this)
goto bail;
if (next)
{
expr = FcExprCreateOp (this, FcOpComma, next);
if (!expr)
goto bail;
}
else
expr = this;
}
else
expr = next;
return expr;
bail:
if (expr)
FcExprDestroy (expr);
if (this)
FcExprDestroy (this);
if (next)
FcExprDestroy (next);
return 0;
}
static FcBool
FcConfigParseAlias (FcConfig *config,
xmlDocPtr doc,
xmlNodePtr alias)
{
xmlNodePtr node;
FcExpr *prefer = 0, *accept = 0, *def = 0;
FcExpr *family;
FcEdit *edit = 0, *next;
FcTest *test;
for (node = alias->children; node; node = node->next)
{
if (node->type != XML_ELEMENT_NODE)
continue;
if (!strcmp (node->name, "family"))
family = FcExprCreateString (FcConfigContent (doc, node));
else if (!strcmp (node->name, "prefer"))
prefer = FcConfigParseFamilies (doc, node->children);
else if (!strcmp (node->name, "accept"))
accept = FcConfigParseFamilies (doc, node->children);
else if (!strcmp (node->name, "default"))
def = FcConfigParseFamilies (doc, node->children);
}
if (prefer)
{
edit = FcEditCreate (FcConfigSaveField ("family"),
FcOpPrepend,
prefer);
if (edit)
edit->next = 0;
}
if (accept)
{
next = edit;
edit = FcEditCreate (FcConfigSaveField ("family"),
FcOpAppend,
accept);
if (edit)
edit->next = next;
}
if (def)
{
next = edit;
edit = FcEditCreate (FcConfigSaveField ("family"),
FcOpAppendLast,
def);
if (edit)
edit->next = next;
}
if (edit)
{
test = FcTestCreate (FcQualAny,
FcConfigSaveField ("family"),
FcOpEqual,
family);
if (test)
FcConfigAddEdit (config, test, edit, FcMatchPattern);
}
return FcTrue;
}
FcBool
FcConfigParse (FcConfig *config,
xmlDocPtr doc)
{
xmlNodePtr cur;
xmlNodePtr node;
cur = xmlDocGetRootElement (doc);
for (node = cur->children; node; node = node->next)
{
if (node->type != XML_ELEMENT_NODE)
continue;
if (!strcmp (node->name, "dir"))
{
if (!FcConfigParseDir (config, doc, node))
break;
}
else if (!strcmp (node->name, "cache"))
{
if (!FcConfigParseCache (config, doc, node))
break;
}
else if (!strcmp (node->name, "include"))
{
if (!FcConfigParseInclude (config, doc, node))
break;
}
else if (!strcmp (node->name, "config"))
{
if (!FcConfigParseConfig (config, doc, node))
break;
}
else if (!strcmp (node->name, "match"))
{
if (!FcConfigParseMatch (config, doc, node))
break;
}
else if (!strcmp (node->name, "alias"))
{
if (!FcConfigParseAlias (config, doc, node))
break;
}
else
{
FcConfigParseError ("invalid element %s", node->name);
break;
}
}
if (node)
return FcFalse;
return FcTrue;
}
FcBool
FcConfigParseAndLoad (FcConfig *config,
const char *file,
FcBool complain)
{
xmlDocPtr doc;
FcBool ret;
doc = FcConfigLoad (file);
if (doc)
{
ret = FcConfigAddConfigFile (config, file);
if (ret)
ret = FcConfigParse (config, doc);
xmlFreeDoc (doc);
return ret;
}
if (complain)
{
if (file)
FcConfigParseError ("Cannot load config file \"%s\"", file);
else
FcConfigParseError ("Cannot load default config file");
return FcFalse;
}
return FcTrue;
}