]> git.wh0rd.org - fontconfig.git/blobdiff - src/fcformat.c
[fcformat] Add support for builtin formats
[fontconfig.git] / src / fcformat.c
index 30c5a8d3bb3c935ff245ac02116c4b0f91ca90f8..334eee6a71bc09ab27cde3a62d9d9eb5649e4f9e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2008 Red Hat, Inc.
+ * Copyright © 2008,2009 Red Hat, Inc.
  *
  * Red Hat Author(s): Behdad Esfahbod
  *
 #include <stdarg.h>
 
 
+/*
+ * Some ideas for future syntax extensions:
+ *
+ * - array enumeration using '%{[]family,familylang{expr}|decorator}'
+ * - langset enumeration using same syntax as array enumeration
+ * - allow indexing simple tags using '%{elt[idx]}'
+ * - allow indexing subexprs using '%{[idx]elt1,elt2{subexpr}}'
+ * - conditional/filtering/deletion on binding (using '(w)'/'(s)'/'(=)' notation)
+ */
+
+
+/* fc-match needs '<unknown filename>', etc handling, as well as printing
+ * printing first value only. */
+#define FCMATCH_FORMAT "%{file|basename}: \"%{family}\" \"%{style}\""
+#define FCLIST_FORMAT  "%{?file{%{file}: }}%{=unparse}"
+
+
 static void
 message (const char *fmt, ...)
 {
     va_list    args;
     va_start (args, fmt);
-    fprintf (stderr, "Fontconfig: Pattern format error:");
+    fprintf (stderr, "Fontconfig: Pattern format error: ");
     vfprintf (stderr, fmt, args);
     fprintf (stderr, ".\n");
     va_end (args);
@@ -45,24 +62,39 @@ typedef struct _FcFormatContext
     const FcChar8 *format_orig;
     const FcChar8 *format;
     int            format_len;
-    FcChar8       *scratch;
+    FcChar8       *word;
+    FcBool         word_allocated;
 } FcFormatContext;
 
-static void
+static FcBool
 FcFormatContextInit (FcFormatContext *c,
-                    const FcChar8   *format)
+                    const FcChar8   *format,
+                    FcChar8         *scratch,
+                    int              scratch_len)
 {
     c->format_orig = c->format = format;
     c->format_len = strlen ((const char *) format);
-    c->scratch = malloc (c->format_len + 1);
+
+    if (c->format_len < scratch_len)
+    {
+       c->word = scratch;
+       c->word_allocated = FcFalse;
+    }
+    else
+    {
+       c->word = malloc (c->format_len + 1);
+       c->word_allocated = FcTrue;
+    }
+
+    return c->word != NULL;
 }
 
 static void
 FcFormatContextDone (FcFormatContext *c)
 {
-    if (c)
+    if (c && c->word_allocated)
     {
-       free (c->scratch);
+       free (c->word);
     }
 }
 
@@ -114,12 +146,26 @@ FcCharIsPunct (const FcChar8 c)
     return FcFalse;
 }
 
+static char escaped_char(const char ch)
+{
+    switch (ch) {
+    case 'a':   return '\a';
+    case 'b':   return '\b';
+    case 'f':   return '\f';
+    case 'n':   return '\n';
+    case 'r':   return '\r';
+    case 't':   return '\t';
+    case 'v':   return '\v';
+    default:    return ch;
+    }
+}
+
 static FcBool
-read_elt_name_to_scratch (FcFormatContext *c)
+read_word (FcFormatContext *c)
 {
     FcChar8 *p;
 
-    p = c->scratch;
+    p = c->word;
 
     while (*c->format)
     {
@@ -127,7 +173,7 @@ read_elt_name_to_scratch (FcFormatContext *c)
        {
            c->format++;
            if (*c->format)
-               c->format++;
+             *p++ = escaped_char (*c->format++);
            continue;
        }
        else if (FcCharIsPunct (*c->format))
@@ -137,9 +183,9 @@ read_elt_name_to_scratch (FcFormatContext *c)
     }
     *p = '\0';
 
-    if (p == c->scratch)
+    if (p == c->word)
     {
-       message ("expected element name at %d",
+       message ("expected identifier at %d",
                 c->format - c->format_orig + 1);
        return FcFalse;
     }
@@ -147,66 +193,368 @@ read_elt_name_to_scratch (FcFormatContext *c)
     return FcTrue;
 }
 
-static void
-interpret (FcFormatContext *c,
-          FcPattern       *pat,
-          FcStrBuf        *buf,
-          FcChar8          term);
+static FcBool
+read_chars (FcFormatContext *c,
+           FcChar8          term)
+{
+    FcChar8 *p;
 
-static void
-interpret_percent (FcFormatContext *c,
+    p = c->word;
+
+    while (*c->format && *c->format != '}' && *c->format != term)
+    {
+       if (*c->format == '\\')
+       {
+           c->format++;
+           if (*c->format)
+             *p++ = escaped_char (*c->format++);
+           continue;
+       }
+
+       *p++ = *c->format++;
+    }
+    *p = '\0';
+
+    if (p == c->word)
+    {
+       message ("expected character data at %d",
+                c->format - c->format_orig + 1);
+       return FcFalse;
+    }
+
+    return FcTrue;
+}
+
+static FcBool
+FcPatternFormatToBuf (FcPattern     *pat,
+                     const FcChar8 *format,
+                     FcStrBuf      *buf);
+
+static FcBool
+interpret_builtin (FcFormatContext *c,
                   FcPattern       *pat,
                   FcStrBuf        *buf)
 {
-    int           width, before;
-    FcChar8      *p;
-    FcPatternElt *e;
+    FcChar8       *new_str;
+    FcBool         ret;
 
-    FcPattern    *subpat = pat;
-    FcBool        add_colon = FcFalse;
-    FcBool        add_elt_name = FcFalse;
+    if (!expect_char (c, '=') ||
+       !read_word (c))
+       return FcFalse;
+
+    /* try simple builtins first */
+    if (0) { }
+#define BUILTIN(name, func) \
+    else if (0 == strcmp ((const char *) c->word, name))\
+       do { new_str = func (pat); ret = FcTrue; } while (0)
+    BUILTIN ("unparse",  FcNameUnparse);
+ /* BUILTIN ("verbose",  FcPatternPrint); */
+#undef BUILTIN
+    else
+       ret = FcFalse;
+
+    if (ret)
+    {
+       if (new_str)
+       {
+           FcStrBufString (buf, new_str);
+           free (new_str);
+           return FcTrue;
+       }
+       else
+           return FcFalse;
+    }
+
+    /* now try our custom formats */
+    if (0) { }
+#define BUILTIN(name, format) \
+    else if (0 == strcmp ((const char *) c->word, name))\
+       ret = FcPatternFormatToBuf (pat, (const FcChar8 *) format, buf)
+    BUILTIN ("fcmatch",  FCMATCH_FORMAT);
+    BUILTIN ("fclist",   FCLIST_FORMAT);
+#undef BUILTIN
+    else
+       ret = FcFalse;
+
+    if (!ret)
+       message ("unknown builtin \"%s\"",
+                c->word);
+
+    return ret;
+}
+
+static FcBool
+interpret_expr (FcFormatContext *c,
+               FcPattern       *pat,
+               FcStrBuf        *buf,
+               FcChar8          term);
+
+static FcBool
+interpret_subexpr (FcFormatContext *c,
+                  FcPattern       *pat,
+                  FcStrBuf        *buf)
+{
+    return expect_char (c, '{') &&
+          interpret_expr (c, pat, buf, '}') &&
+          expect_char (c, '}');
+}
+
+static FcBool
+maybe_interpret_subexpr (FcFormatContext *c,
+                        FcPattern       *pat,
+                        FcStrBuf        *buf)
+{
+    return (*c->format == '{') ?
+          interpret_subexpr (c, pat, buf) :
+          FcTrue;
+}
+
+static FcBool
+skip_subexpr (FcFormatContext *c);
+
+static FcBool
+skip_percent (FcFormatContext *c)
+{
+    int width;
 
     if (!expect_char (c, '%'))
-       return;
+       return FcFalse;
 
-    if (consume_char (c, '%')) /* "%%" */
+    /* skip an optional width specifier */
+    width = strtol ((const char *) c->format, (char **) &c->format, 10);
+
+    if (!expect_char (c, '{'))
+       return FcFalse;
+
+    while(*c->format && *c->format != '}')
     {
-       FcStrBufChar (buf, '%');
-       return;
+       switch (*c->format)
+       {
+       case '\\':
+           c->format++; /* skip over '\\' */
+           if (*c->format)
+               c->format++;
+           continue;
+       case '{':
+           if (!skip_subexpr (c))
+               return FcFalse;
+           continue;
+       }
+       c->format++;
     }
 
-    /* parse an optional width specifier */
-    width = strtol ((const char *) c->format, (char **) &c->format, 10);
+    return expect_char (c, '}');
+}
+
+static FcBool
+skip_expr (FcFormatContext *c)
+{
+    while(*c->format && *c->format != '}')
+    {
+       switch (*c->format)
+       {
+       case '\\':
+           c->format++; /* skip over '\\' */
+           if (*c->format)
+               c->format++;
+           continue;
+       case '%':
+           if (!skip_percent (c))
+               return FcFalse;
+           continue;
+       }
+       c->format++;
+    }
+
+    return FcTrue;
+}
 
-    before = buf->len;
+static FcBool
+skip_subexpr (FcFormatContext *c)
+{
+    return expect_char (c, '{') &&
+          skip_expr (c) &&
+          expect_char (c, '}');
+}
 
-    if (!expect_char (c, '{'))
-       goto bail;
+static FcBool
+maybe_skip_subexpr (FcFormatContext *c)
+{
+    return (*c->format == '{') ?
+          skip_subexpr (c) :
+          FcTrue;
+}
+
+static FcBool
+interpret_filter (FcFormatContext *c,
+                 FcPattern       *pat,
+                 FcStrBuf        *buf)
+{
+    FcObjectSet  *os;
+    FcPattern    *subpat;
 
-    if (consume_char (c, '{'))
+    if (!expect_char (c, '+'))
+       return FcFalse;
+
+    os = FcObjectSetCreate ();
+    if (!os)
+       return FcFalse;
+
+    do
     {
-       /* it's just a subexpression.  no tag involved */
-       interpret (c, pat, buf, '}');
-       expect_char (c, '}');
-       goto filter;
+       if (!read_word (c) ||
+           !FcObjectSetAdd (os, (const char *) c->word))
+       {
+           FcObjectSetDestroy (os);
+           return FcFalse;
+       }
     }
+    while (consume_char (c, ','));
 
-    switch (*c->format) {
-    case ':':
-       add_colon = FcTrue;
-       consume_char (c, ':');
-       break;
+    subpat = FcPatternFilter (pat, os);
+    FcObjectSetDestroy (os);
+
+    if (!subpat ||
+       !interpret_subexpr (c, subpat, buf))
+       return FcFalse;
+
+    FcPatternDestroy (subpat);
+    return FcTrue;
+}
+
+static FcBool
+interpret_delete (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;
+}
 
-parse_tag:
-    if (!read_elt_name_to_scratch (c))
-        goto bail;
+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_simple (FcFormatContext *c,
+                 FcPattern       *pat,
+                 FcStrBuf        *buf)
+{
+    FcPatternElt *e;
+    FcBool        add_colon = FcFalse;
+    FcBool        add_elt_name = FcFalse;
+
+    if (consume_char (c, ':'))
+       add_colon = FcTrue;
+
+    if (!read_word (c))
+       return FcFalse;
 
     if (consume_char (c, '='))
        add_elt_name = FcTrue;
 
     e = FcPatternObjectFindElt (pat,
-                               FcObjectFromName ((const char *) c->scratch));
+                               FcObjectFromName ((const char *) c->word));
     if (e)
     {
        FcValueListPtr l;
@@ -215,7 +563,7 @@ parse_tag:
            FcStrBufChar (buf, ':');
        if (add_elt_name)
        {
-           FcStrBufString (buf, c->scratch);
+           FcStrBufString (buf, c->word);
            FcStrBufChar (buf, '=');
        }
 
@@ -223,68 +571,365 @@ parse_tag:
        FcNameUnparseValueList (buf, l, '\0');
     }
 
-filter:
-    /* handle filters, if any */
-    /* XXX */
+    return FcTrue;
+}
 
-    /* align to width */
-    if (!buf->failed)
+static FcBool
+cescape (FcFormatContext *c,
+        const FcChar8   *str,
+        FcStrBuf        *buf)
+{
+    while(*str)
     {
-       int after, len;
+       switch (*str)
+       {
+       case '\\':
+       case '"':
+           FcStrBufChar (buf, '\\');
+           break;
+       }
+       FcStrBufChar (buf, *str++);
+    }
+    return FcTrue;
+}
 
-       after = buf->len;
+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;
+}
 
-       len = after - before;
+static FcBool
+xmlescape (FcFormatContext *c,
+          const FcChar8   *str,
+          FcStrBuf        *buf)
+{
+    while(*str)
+    {
+       switch (*str)
+       {
+       case '&': FcStrBufString (buf, (const FcChar8 *) "&amp;"); break;
+       case '<': FcStrBufString (buf, (const FcChar8 *) "&lt;");  break;
+       case '>': FcStrBufString (buf, (const FcChar8 *) "&gt;");  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;
 
-       if (len < -width)
+       p = (FcChar8 *) strpbrk ((const char *) str, (const char *) c->word);
+       if (p)
        {
-           /* left align */
-           while (len++ < -width)
-               FcStrBufChar (buf, ' ');
+           FcStrBufData (buf, str, p - str);
+           str = p + 1;
        }
-       else if (len < width)
+       else
        {
-           /* right align */
-           while (len++ < width)
-               FcStrBufChar (buf, ' ');
-           len = after - before;
-           memmove (buf->buf + buf->len - len,
-                    buf->buf + buf->len - width,
-                    len);
-           memset (buf->buf + buf->len - width,
-                   ' ',
-                   width - len);
+           FcStrBufString (buf, str);
+           break;
        }
+
     }
 
-    expect_char (c, '}');
+    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;
 
-bail:
-    if (subpat != pat)
-       FcPatternDestroy (subpat);
+       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 char escaped_char(const char ch)
+static FcBool
+translate_chars (FcFormatContext *c,
+                const FcChar8   *str,
+                FcStrBuf        *buf)
 {
-    switch (ch) {
-    case 'a':   return '\a';
-    case 'b':   return '\b';
-    case 'f':   return '\f';
-    case 'n':   return '\n';
-    case 'r':   return '\r';
-    case 't':   return '\t';
-    case 'v':   return '\v';
-    default:    return ch;
+    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 diverge 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 void
-interpret (FcFormatContext *c,
-          FcPattern       *pat,
-          FcStrBuf        *buf,
-          FcChar8          term)
+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)
+{
+    int len;
+
+    if (buf->failed)
+       return FcFalse;
+
+    len = buf->len - start;
+    if (len < -width)
+    {
+       /* left align */
+       while (len++ < -width)
+           FcStrBufChar (buf, ' ');
+    }
+    else if (len < width)
+    {
+       int old_len;
+       old_len = len;
+       /* right align */
+       while (len++ < width)
+           FcStrBufChar (buf, ' ');
+       if (buf->failed)
+           return FcFalse;
+       len = old_len;
+       memmove (buf->buf + buf->len - len,
+                buf->buf + buf->len - width,
+                len);
+       memset (buf->buf + buf->len - width,
+               ' ',
+               width - len);
+    }
+
+    return !buf->failed;
+}
+static FcBool
+interpret_percent (FcFormatContext *c,
+                  FcPattern       *pat,
+                  FcStrBuf        *buf)
 {
-    for (; *c->format && *c->format != term;)
+    int width, start;
+    FcBool ret;
+
+    if (!expect_char (c, '%'))
+       return FcFalse;
+
+    if (consume_char (c, '%')) /* "%%" */
+    {
+       FcStrBufChar (buf, '%');
+       return FcTrue;
+    }
+
+    /* parse an optional width specifier */
+    width = strtol ((const char *) c->format, (char **) &c->format, 10);
+
+    if (!expect_char (c, '{'))
+       return FcFalse;
+
+    start = buf->len;
+
+    switch (*c->format) {
+    case '=': ret = interpret_builtin (c, pat, buf); break;
+    case '{': ret = interpret_subexpr (c, pat, buf); break;
+    case '+': ret = interpret_filter  (c, pat, buf); break;
+    case '-': ret = interpret_delete  (c, pat, buf); break;
+    case '?': ret = interpret_cond    (c, pat, buf); break;
+    case '#': ret = interpret_count   (c, pat, buf); break;
+    default:  ret = interpret_simple  (c, pat, buf); break;
+    }
+
+    return ret &&
+          maybe_interpret_converts (c, buf, start) &&
+          align_to_width (buf, start, width) &&
+          expect_char (c, '}');
+}
+
+static FcBool
+interpret_expr (FcFormatContext *c,
+               FcPattern       *pat,
+               FcStrBuf        *buf,
+               FcChar8          term)
+{
+    while (*c->format && *c->format != term)
     {
        switch (*c->format)
        {
@@ -294,26 +939,53 @@ interpret (FcFormatContext *c,
                FcStrBufChar (buf, escaped_char (*c->format++));
            continue;
        case '%':
-           interpret_percent (c, pat, buf);
+           if (!interpret_percent (c, pat, buf))
+               return FcFalse;
            continue;
        }
        FcStrBufChar (buf, *c->format++);
     }
+    return FcTrue;
 }
 
-FcChar8 *
-FcPatternFormat (FcPattern *pat, const FcChar8 *format)
+static FcBool
+FcPatternFormatToBuf (FcPattern     *pat,
+                     const FcChar8 *format,
+                     FcStrBuf      *buf)
 {
-    FcStrBuf buf;
     FcFormatContext c;
+    FcChar8         word_static[1024];
+    FcBool          ret;
 
-    FcStrBufInit (&buf, 0, 0);
-    FcFormatContextInit (&c, format);
+    if (!FcFormatContextInit (&c, format, word_static, sizeof (word_static)))
+       return FcFalse;
 
-    interpret (&c, pat, &buf, '\0');
+    ret = interpret_expr (&c, pat, buf, '\0');
 
     FcFormatContextDone (&c);
-    return FcStrBufDone (&buf);
+
+    return ret;
+}
+
+FcChar8 *
+FcPatternFormat (FcPattern *pat,
+                const FcChar8 *format)
+{
+    FcStrBuf        buf;
+    FcChar8         buf_static[8192 - 1024];
+    FcBool          ret;
+
+    FcStrBufInit (&buf, buf_static, sizeof (buf_static));
+
+    ret = FcPatternFormatToBuf (pat, format, &buf);
+
+    if (ret)
+       return FcStrBufDone (&buf);
+    else
+    {
+       FcStrBufDestroy (&buf);
+       return NULL;
+    }
 }
 
 #define __fcformat__