+static FcBool
+skip_subexpr (FcFormatContext *c)
+{
+ return expect_char (c, '{') &&
+ skip_expr (c) &&
+ expect_char (c, '}');
+}
+
+static FcBool
+maybe_skip_subexpr (FcFormatContext *c)
+{
+ return (*c->format == '{') ?
+ skip_subexpr (c) :
+ FcTrue;
+}
+
+static FcBool
+interpret_filter_in (FcFormatContext *c,
+ FcPattern *pat,
+ FcStrBuf *buf)
+{
+ FcObjectSet *os;
+ FcPattern *subpat;
+
+ if (!expect_char (c, '+'))
+ return FcFalse;
+
+ os = FcObjectSetCreate ();
+ if (!os)
+ return FcFalse;
+
+ do
+ {
+ /* XXX binding */
+ if (!read_word (c) ||
+ !FcObjectSetAdd (os, (const char *) c->word))
+ {
+ FcObjectSetDestroy (os);
+ return FcFalse;
+ }
+ }
+ while (consume_char (c, ','));
+
+ subpat = FcPatternFilter (pat, os);
+ FcObjectSetDestroy (os);
+
+ if (!subpat ||
+ !interpret_subexpr (c, subpat, buf))
+ return FcFalse;
+
+ FcPatternDestroy (subpat);
+ return FcTrue;
+}
+
+static FcBool
+interpret_filter_out (FcFormatContext *c,
+ FcPattern *pat,
+ FcStrBuf *buf)
+{
+ FcPattern *subpat;
+
+ if (!expect_char (c, '-'))
+ return FcFalse;
+
+ subpat = FcPatternDuplicate (pat);
+ if (!subpat)
+ return FcFalse;
+
+ do
+ {
+ if (!read_word (c))
+ {
+ FcPatternDestroy (subpat);
+ return FcFalse;
+ }
+
+ FcPatternDel (subpat, (const char *) c->word);
+ }
+ while (consume_char (c, ','));
+
+ if (!interpret_subexpr (c, subpat, buf))
+ return FcFalse;
+
+ FcPatternDestroy (subpat);
+ return FcTrue;
+}
+
+static FcBool
+interpret_cond (FcFormatContext *c,
+ FcPattern *pat,
+ FcStrBuf *buf)
+{
+ FcBool pass;
+
+ if (!expect_char (c, '?'))
+ return FcFalse;
+
+ pass = FcTrue;
+
+ do
+ {
+ FcBool negate;
+ FcValue v;
+
+ negate = consume_char (c, '!');
+
+ if (!read_word (c))
+ return FcFalse;
+
+ pass = pass &&
+ (negate ^
+ (FcResultMatch ==
+ FcPatternGet (pat, (const char *) c->word, 0, &v)));
+ }
+ while (consume_char (c, ','));
+
+ if (pass)
+ {
+ if (!interpret_subexpr (c, pat, buf) ||
+ !maybe_skip_subexpr (c))
+ return FcFalse;
+ }
+ else
+ {
+ if (!skip_subexpr (c) ||
+ !maybe_interpret_subexpr (c, pat, buf))
+ return FcFalse;
+ }
+
+ return FcTrue;
+}
+
+static FcBool
+interpret_count (FcFormatContext *c,
+ FcPattern *pat,
+ FcStrBuf *buf)
+{
+ int count;
+ FcPatternElt *e;
+ FcChar8 buf_static[64];
+
+ if (!expect_char (c, '#'))
+ return FcFalse;
+
+ if (!read_word (c))
+ return FcFalse;
+
+ count = 0;
+ e = FcPatternObjectFindElt (pat,
+ FcObjectFromName ((const char *) c->word));
+ if (e)
+ {
+ FcValueListPtr l;
+ count++;
+ for (l = FcPatternEltValues(e);
+ l->next;
+ l = l->next)
+ count++;
+ }
+
+ snprintf ((char *) buf_static, sizeof (buf_static), "%d", count);
+ FcStrBufString (buf, buf_static);
+
+ return FcTrue;
+}
+
+static FcBool
+interpret_enumerate (FcFormatContext *c,
+ FcPattern *pat,
+ FcStrBuf *buf)
+{
+ FcObjectSet *os;
+ FcPattern *subpat;
+ const FcChar8 *format_save;
+ int idx;
+ FcBool ret, done;
+ FcStrList *lang_strs;
+
+ if (!expect_char (c, '[') ||
+ !expect_char (c, ']'))
+ return FcFalse;
+
+ os = FcObjectSetCreate ();
+ if (!os)
+ return FcFalse;
+
+ ret = FcTrue;
+
+ do
+ {
+ if (!read_word (c) ||
+ !FcObjectSetAdd (os, (const char *) c->word))
+ {
+ FcObjectSetDestroy (os);
+ return FcFalse;
+ }
+ }
+ while (consume_char (c, ','));
+
+ /* If we have one element and it's of type FcLangSet, we want
+ * to enumerate the languages in it. */
+ lang_strs = NULL;
+ if (os->nobject == 1)
+ {
+ FcLangSet *langset;
+ if (FcResultMatch ==
+ FcPatternGetLangSet (pat, os->objects[0], 0, &langset))
+ {
+ FcStrSet *ss;
+ if (!(ss = FcLangSetGetLangs (langset)) ||
+ !(lang_strs = FcStrListCreate (ss)))
+ goto bail0;
+ }
+ }
+
+ subpat = FcPatternDuplicate (pat);
+ if (!subpat)
+ goto bail0;
+
+ format_save = c->format;
+ idx = 0;
+ do
+ {
+ int i;
+
+ done = FcTrue;
+
+ if (lang_strs)
+ {
+ FcChar8 *lang;
+
+ FcPatternDel (subpat, os->objects[0]);
+ if ((lang = FcStrListNext (lang_strs)))
+ {
+ /* XXX binding? */
+ FcPatternAddString (subpat, os->objects[0], lang);
+ done = FcFalse;
+ }
+ }
+ else
+ {
+ for (i = 0; i < os->nobject; i++)
+ {
+ FcValue v;
+
+ /* XXX this can be optimized by accessing valuelist linked lists
+ * directly and remembering where we were. Most (all) value lists
+ * in normal uses are pretty short though (language tags are
+ * stored as a LangSet, not separate values.). */
+ FcPatternDel (subpat, os->objects[i]);
+ if (FcResultMatch ==
+ FcPatternGet (pat, os->objects[i], idx, &v))
+ {
+ /* XXX binding */
+ FcPatternAdd (subpat, os->objects[i], v, FcFalse);
+ done = FcFalse;
+ }
+ }
+ }
+
+ if (!done)
+ {
+ c->format = format_save;
+ ret = interpret_subexpr (c, subpat, buf);
+ if (!ret)
+ goto bail;
+ }
+
+ idx++;
+ } while (!done);
+
+ if (c->format == format_save)
+ skip_subexpr (c);
+
+bail:
+ FcPatternDestroy (subpat);
+bail0:
+ if (lang_strs)
+ FcStrListDone (lang_strs);
+ FcObjectSetDestroy (os);
+
+ return ret;
+}
+
+static FcBool
+interpret_simple (FcFormatContext *c,
+ FcPattern *pat,
+ FcStrBuf *buf)
+{
+ FcPatternElt *e;
+ FcBool add_colon = FcFalse;
+ FcBool add_elt_name = FcFalse;
+ int idx;
+ FcChar8 *else_string;
+
+ if (consume_char (c, ':'))
+ add_colon = FcTrue;
+
+ if (!read_word (c))
+ return FcFalse;
+
+ idx = -1;
+ if (consume_char (c, '['))
+ {
+ idx = strtol ((const char *) c->format, (char **) &c->format, 10);
+ if (idx < 0)
+ {
+ message ("expected non-negative number at %d",
+ c->format-1 - c->format_orig + 1);
+ return FcFalse;
+ }
+ if (!expect_char (c, ']'))
+ return FcFalse;
+ }
+
+ if (consume_char (c, '='))
+ add_elt_name = FcTrue;
+
+ /* modifiers */
+ else_string = NULL;
+ if (consume_char (c, ':'))
+ {
+ FcChar8 *orig;
+ /* divert the c->word for now */
+ orig = c->word;
+ c->word = c->word + strlen ((const char *) c->word) + 1;
+ /* for now we just support 'default value' */
+ if (!expect_char (c, '-') ||
+ !read_chars (c, '|'))
+ {
+ c->word = orig;
+ return FcFalse;
+ }
+ else_string = c->word;
+ c->word = orig;
+ }
+
+ e = FcPatternObjectFindElt (pat,
+ FcObjectFromName ((const char *) c->word));
+ if (e || else_string)
+ {
+ FcValueListPtr l = NULL;
+
+ if (add_colon)
+ FcStrBufChar (buf, ':');
+ if (add_elt_name)
+ {
+ FcStrBufString (buf, c->word);
+ FcStrBufChar (buf, '=');
+ }
+
+ if (e)
+ l = FcPatternEltValues(e);
+
+ if (idx != -1)
+ {
+ while (l && idx > 0)
+ {
+ l = FcValueListNext(l);
+ idx--;
+ }
+ if (l && idx == 0)
+ {
+ if (!FcNameUnparseValue (buf, &l->value, '\0'))
+ return FcFalse;
+ }
+ else goto notfound;
+ }
+ else if (l)
+ {
+ FcNameUnparseValueList (buf, l, '\0');
+ }
+ else
+ {
+ notfound:
+ if (else_string)
+ FcStrBufString (buf, else_string);
+ }
+ }
+
+ return FcTrue;
+}
+
+static FcBool
+cescape (FcFormatContext *c,
+ const FcChar8 *str,
+ FcStrBuf *buf)
+{
+ /* XXX escape \n etc? */
+
+ while(*str)
+ {
+ switch (*str)
+ {
+ case '\\':
+ case '"':
+ FcStrBufChar (buf, '\\');
+ break;
+ }
+ FcStrBufChar (buf, *str++);
+ }
+ return FcTrue;
+}
+
+static FcBool
+shescape (FcFormatContext *c,
+ const FcChar8 *str,
+ FcStrBuf *buf)
+{
+ FcStrBufChar (buf, '\'');
+ while(*str)
+ {
+ if (*str == '\'')
+ FcStrBufString (buf, (const FcChar8 *) "'\\''");
+ else
+ FcStrBufChar (buf, *str);
+ str++;
+ }
+ FcStrBufChar (buf, '\'');
+ return FcTrue;
+}
+
+static FcBool
+xmlescape (FcFormatContext *c,
+ const FcChar8 *str,
+ FcStrBuf *buf)
+{
+ /* XXX escape \n etc? */
+
+ while(*str)
+ {
+ switch (*str)
+ {
+ case '&': FcStrBufString (buf, (const FcChar8 *) "&"); break;
+ case '<': FcStrBufString (buf, (const FcChar8 *) "<"); break;
+ case '>': FcStrBufString (buf, (const FcChar8 *) ">"); break;
+ default: FcStrBufChar (buf, *str); break;
+ }
+ str++;
+ }
+ return FcTrue;
+}
+
+static FcBool
+delete_chars (FcFormatContext *c,
+ const FcChar8 *str,
+ FcStrBuf *buf)
+{
+ /* XXX not UTF-8 aware */
+
+ if (!expect_char (c, '(') ||
+ !read_chars (c, ')') ||
+ !expect_char (c, ')'))
+ return FcFalse;
+
+ while(*str)
+ {
+ FcChar8 *p;
+
+ p = (FcChar8 *) strpbrk ((const char *) str, (const char *) c->word);
+ if (p)
+ {
+ FcStrBufData (buf, str, p - str);
+ str = p + 1;
+ }
+ else
+ {
+ FcStrBufString (buf, str);
+ break;
+ }
+
+ }
+
+ return FcTrue;
+}
+
+static FcBool
+escape_chars (FcFormatContext *c,
+ const FcChar8 *str,
+ FcStrBuf *buf)
+{
+ /* XXX not UTF-8 aware */
+
+ if (!expect_char (c, '(') ||
+ !read_chars (c, ')') ||
+ !expect_char (c, ')'))
+ return FcFalse;
+
+ while(*str)
+ {
+ FcChar8 *p;
+
+ p = (FcChar8 *) strpbrk ((const char *) str, (const char *) c->word);
+ if (p)
+ {
+ FcStrBufData (buf, str, p - str);
+ FcStrBufChar (buf, c->word[0]);
+ FcStrBufChar (buf, *p);
+ str = p + 1;
+ }
+ else
+ {
+ FcStrBufString (buf, str);
+ break;
+ }
+
+ }
+
+ return FcTrue;
+}
+
+static FcBool
+translate_chars (FcFormatContext *c,
+ const FcChar8 *str,
+ FcStrBuf *buf)
+{
+ char *from, *to, repeat;
+ int from_len, to_len;
+
+ /* XXX not UTF-8 aware */
+
+ if (!expect_char (c, '(') ||
+ !read_chars (c, ',') ||
+ !expect_char (c, ','))
+ return FcFalse;
+
+ from = (char *) c->word;
+ from_len = strlen (from);
+ to = from + from_len + 1;
+
+ /* hack: we temporarily divert c->word */
+ c->word = (FcChar8 *) to;
+ if (!read_chars (c, ')'))
+ {
+ c->word = (FcChar8 *) from;
+ return FcFalse;
+ }
+ c->word = (FcChar8 *) from;
+
+ to_len = strlen (to);
+ repeat = to[to_len - 1];
+
+ if (!expect_char (c, ')'))
+ return FcFalse;
+
+ while(*str)
+ {
+ FcChar8 *p;
+
+ p = (FcChar8 *) strpbrk ((const char *) str, (const char *) from);
+ if (p)
+ {
+ int i;
+ FcStrBufData (buf, str, p - str);
+ i = strchr (from, *p) - from;
+ FcStrBufChar (buf, i < to_len ? to[i] : repeat);
+ str = p + 1;
+ }
+ else
+ {
+ FcStrBufString (buf, str);
+ break;
+ }
+
+ }
+
+ return FcTrue;
+}
+
+static FcBool
+interpret_convert (FcFormatContext *c,
+ FcStrBuf *buf,
+ int start)
+{
+ const FcChar8 *str;
+ FcChar8 *new_str;
+ FcStrBuf new_buf;
+ FcChar8 buf_static[8192];
+ FcBool ret;
+
+ if (!expect_char (c, '|') ||
+ !read_word (c))
+ return FcFalse;
+
+ /* prepare the buffer */
+ FcStrBufChar (buf, '\0');
+ if (buf->failed)
+ return FcFalse;
+ str = buf->buf + start;
+ buf->len = start;
+
+ /* try simple converters first */
+ if (0) { }
+#define CONVERTER(name, func) \
+ else if (0 == strcmp ((const char *) c->word, name))\
+ do { new_str = func (str); ret = FcTrue; } while (0)
+ CONVERTER ("downcase", FcStrDowncase);
+ CONVERTER ("basename", FcStrBasename);
+ CONVERTER ("dirname", FcStrDirname);
+#undef CONVERTER
+ else
+ ret = FcFalse;
+
+ if (ret)
+ {
+ if (new_str)
+ {
+ FcStrBufString (buf, new_str);
+ free (new_str);
+ return FcTrue;
+ }
+ else
+ return FcFalse;
+ }
+
+ FcStrBufInit (&new_buf, buf_static, sizeof (buf_static));
+
+ /* now try our custom converters */
+ if (0) { }
+#define CONVERTER(name, func) \
+ else if (0 == strcmp ((const char *) c->word, name))\
+ ret = func (c, str, &new_buf)
+ CONVERTER ("cescape", cescape);
+ CONVERTER ("shescape", shescape);
+ CONVERTER ("xmlescape", xmlescape);
+ CONVERTER ("delete", delete_chars);
+ CONVERTER ("escape", escape_chars);
+ CONVERTER ("translate", translate_chars);
+#undef CONVERTER
+ else
+ ret = FcFalse;
+
+ if (ret)
+ {
+ FcStrBufChar (&new_buf, '\0');
+ FcStrBufString (buf, new_buf.buf);
+ }
+ else
+ message ("unknown converter \"%s\"",
+ c->word);
+
+ FcStrBufDestroy (&new_buf);
+
+ return ret;
+}
+
+static FcBool
+maybe_interpret_converts (FcFormatContext *c,
+ FcStrBuf *buf,
+ int start)
+{
+ while (*c->format == '|')
+ if (!interpret_convert (c, buf, start))
+ return FcFalse;
+
+ return FcTrue;
+}
+
+static FcBool
+align_to_width (FcStrBuf *buf,
+ int start,
+ int width)