Edit

IABSD.fr/xenocara/lib/fontconfig/src/fcformat.c

Branch :

  • Show log

    Commit

  • Author : matthieu
    Date : 2010-03-25 21:45:57
    Hash : e248f656
    Message : Update to fontconfig 2.8.0. Tested on a full ports build by naddy@.

  • lib/fontconfig/src/fcformat.c
  • /*
     * Copyright © 2008,2009 Red Hat, Inc.
     *
     * Red Hat Author(s): Behdad Esfahbod
     *
     * 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.
     *
     * 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 <stdlib.h>
    #include <string.h>
    #include <stdarg.h>
    
    
    /* The language is documented in doc/fcformat.fncs
     * These are the features implemented:
     *
     * simple	%{elt}
     * width	%width{elt}
     * index	%{elt[idx]}
     * name=	%{elt=}
     * :name=	%{:elt}
     * default	%{elt:-word}
     * count	%{#elt}
     * subexpr	%{{expr}}
     * filter-out	%{-elt1,elt2,elt3{expr}}
     * filter-in	%{+elt1,elt2,elt3{expr}}
     * conditional	%{?elt1,elt2,!elt3{}{}}
     * enumerate	%{[]elt1,elt2{expr}}
     * langset	langset enumeration using the same syntax
     * builtin	%{=blt}
     * convert	%{elt|conv1|conv2|conv3}
     *
     * converters:
     * basename	FcStrBasename
     * dirname	FcStrDirname
     * downcase	FcStrDowncase
     * shescape
     * cescape
     * xmlescape
     * delete	delete chars
     * escape	escape chars
     * translate	translate chars
     *
     * builtins:
     * unparse	FcNameUnparse
     * fcmatch	fc-match default
     * fclist	fc-list default
     * pkgkit	PackageKit package tag format
     *
     *
     * Some ideas for future syntax extensions:
     *
     * - verbose builtin that is like FcPatternPrint
     * - allow indexing subexprs using '%{[idx]elt1,elt2{subexpr}}'
     * - conditional/filtering/deletion on binding (using '(w)'/'(s)'/'(=)' notation)
     */
    
    
    #define FCMATCH_FORMAT	"%{file:-<unknown filename>|basename}: \"%{family[0]:-<unknown family>}\" \"%{style[0]:-<unknown style>}\""
    #define FCLIST_FORMAT	"%{?file{%{file}: }}%{=unparse}"
    #define PKGKIT_FORMAT	"%{[]family{font(%{family|downcase|delete( )})\n}}%{[]lang{font(:lang=%{lang|downcase|translate(_,-)})\n}}"
    
    
    static void
    message (const char *fmt, ...)
    {
        va_list	args;
        va_start (args, fmt);
        fprintf (stderr, "Fontconfig: Pattern format error: ");
        vfprintf (stderr, fmt, args);
        fprintf (stderr, ".\n");
        va_end (args);
    }
    
    
    typedef struct _FcFormatContext
    {
        const FcChar8 *format_orig;
        const FcChar8 *format;
        int            format_len;
        FcChar8       *word;
        FcBool         word_allocated;
    } FcFormatContext;
    
    static FcBool
    FcFormatContextInit (FcFormatContext *c,
    		     const FcChar8   *format,
    		     FcChar8         *scratch,
    		     int              scratch_len)
    {
        c->format_orig = c->format = format;
        c->format_len = strlen ((const char *) format);
    
        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 && c->word_allocated)
        {
    	free (c->word);
        }
    }
    
    static FcBool
    consume_char (FcFormatContext *c,
    	      FcChar8          term)
    {
        if (*c->format != term)
    	return FcFalse;
    
        c->format++;
        return FcTrue;
    }
    
    static FcBool
    expect_char (FcFormatContext *c,
    	      FcChar8          term)
    {
        FcBool res = consume_char (c, term);
        if (!res)
        {
    	if (c->format == c->format_orig + c->format_len)
    	    message ("format ended while expecting '%c'",
    		     term);
    	else
    	    message ("expected '%c' at %d",
    		     term, c->format - c->format_orig + 1);
        }
        return res;
    }
    
    static FcBool
    FcCharIsPunct (const FcChar8 c)
    {
        if (c < '0')
    	return FcTrue;
        if (c <= '9')
    	return FcFalse;
        if (c < 'A')
    	return FcTrue;
        if (c <= 'Z')
    	return FcFalse;
        if (c < 'a')
    	return FcTrue;
        if (c <= 'z')
    	return FcFalse;
        if (c <= '~')
    	return FcTrue;
        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_word (FcFormatContext *c)
    {
        FcChar8 *p;
    
        p = c->word;
    
        while (*c->format)
        {
    	if (*c->format == '\\')
    	{
    	    c->format++;
    	    if (*c->format)
    	      *p++ = escaped_char (*c->format++);
    	    continue;
    	}
    	else if (FcCharIsPunct (*c->format))
    	    break;
    
    	*p++ = *c->format++;
        }
        *p = '\0';
    
        if (p == c->word)
        {
    	message ("expected identifier at %d",
    		 c->format - c->format_orig + 1);
    	return FcFalse;
        }
    
        return FcTrue;
    }
    
    static FcBool
    read_chars (FcFormatContext *c,
    	    FcChar8          term)
    {
        FcChar8 *p;
    
        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)
    {
        FcChar8       *new_str;
        FcBool         ret;
    
        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); XXX */
    #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);
        BUILTIN ("pkgkit",   PKGKIT_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 FcFalse;
    
        /* 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 != '}')
        {
    	switch (*c->format)
    	{
    	case '\\':
    	    c->format++; /* skip over '\\' */
    	    if (*c->format)
    		c->format++;
    	    continue;
    	case '{':
    	    if (!skip_subexpr (c))
    		return FcFalse;
    	    continue;
    	}
    	c->format++;
        }
    
        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;
    }
    
    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
        {
    	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], idx, &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)))
    	    {
    		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))
    		{
    		    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, '\0'))
    	{
    	    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)
    {
        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)
    {
        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;
    
    	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)
    {
        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)
    {
        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_in  (c, pat, buf); break;
        case '-': ret = interpret_filter_out (c, pat, buf); break;
        case '?': ret = interpret_cond       (c, pat, buf); break;
        case '#': ret = interpret_count      (c, pat, buf); break;
        case '[': ret = interpret_enumerate  (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)
    	{
    	case '\\':
    	    c->format++; /* skip over '\\' */
    	    if (*c->format)
    		FcStrBufChar (buf, escaped_char (*c->format++));
    	    continue;
    	case '%':
    	    if (!interpret_percent (c, pat, buf))
    		return FcFalse;
    	    continue;
    	}
    	FcStrBufChar (buf, *c->format++);
        }
        return FcTrue;
    }
    
    static FcBool
    FcPatternFormatToBuf (FcPattern     *pat,
    		      const FcChar8 *format,
    		      FcStrBuf      *buf)
    {
        FcFormatContext c;
        FcChar8         word_static[1024];
        FcBool          ret;
    
        if (!FcFormatContextInit (&c, format, word_static, sizeof (word_static)))
    	return FcFalse;
    
        ret = interpret_expr (&c, pat, buf, '\0');
    
        FcFormatContextDone (&c);
    
        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__
    #include "fcaliastail.h"
    #undef __fcformat__