]> git.wh0rd.org Git - fontconfig.git/blob - src/fcformat.c
[fcformat] Support 'default value' for simple tags
[fontconfig.git] / src / fcformat.c
1 /*
2  * Copyright © 2008,2009 Red Hat, Inc.
3  *
4  * Red Hat Author(s): Behdad Esfahbod
5  *
6  * Permission to use, copy, modify, distribute, and sell this software and its
7  * documentation for any purpose is hereby granted without fee, provided that
8  * the above copyright notice appear in all copies and that both that
9  * copyright notice and this permission notice appear in supporting
10  * documentation, and that the name of Keith Packard not be used in
11  * advertising or publicity pertaining to distribution of the software without
12  * specific, written prior permission.  Keith Packard makes no
13  * representations about the suitability of this software for any purpose.  It
14  * is provided "as is" without express or implied warranty.
15  *
16  * KEITH PACKARD DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
17  * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
18  * EVENT SHALL KEITH PACKARD BE LIABLE FOR ANY SPECIAL, INDIRECT OR
19  * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
20  * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
21  * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
22  * PERFORMANCE OF THIS SOFTWARE.
23  */
24
25 #include "fcint.h"
26 #include <stdlib.h>
27 #include <string.h>
28 #include <stdarg.h>
29
30
31 /*
32  * Some ideas for future syntax extensions:
33  *
34  * - array enumeration using '%{[]family,familylang{expr}|decorator}'
35  * - langset enumeration using same syntax as array enumeration
36  * - allow indexing subexprs using '%{[idx]elt1,elt2{subexpr}}'
37  * - conditional/filtering/deletion on binding (using '(w)'/'(s)'/'(=)' notation)
38  */
39
40
41 #define FCMATCH_FORMAT  "%{file:-<unknown filename>|basename}: \"%{family[0]:-<unknown family>}\" \"%{style[0]:-<unknown style>}\""
42 #define FCLIST_FORMAT   "%{?file{%{file}: }}%{=unparse}"
43
44
45 static void
46 message (const char *fmt, ...)
47 {
48     va_list     args;
49     va_start (args, fmt);
50     fprintf (stderr, "Fontconfig: Pattern format error: ");
51     vfprintf (stderr, fmt, args);
52     fprintf (stderr, ".\n");
53     va_end (args);
54 }
55
56
57 typedef struct _FcFormatContext
58 {
59     const FcChar8 *format_orig;
60     const FcChar8 *format;
61     int            format_len;
62     FcChar8       *word;
63     FcBool         word_allocated;
64 } FcFormatContext;
65
66 static FcBool
67 FcFormatContextInit (FcFormatContext *c,
68                      const FcChar8   *format,
69                      FcChar8         *scratch,
70                      int              scratch_len)
71 {
72     c->format_orig = c->format = format;
73     c->format_len = strlen ((const char *) format);
74
75     if (c->format_len < scratch_len)
76     {
77         c->word = scratch;
78         c->word_allocated = FcFalse;
79     }
80     else
81     {
82         c->word = malloc (c->format_len + 1);
83         c->word_allocated = FcTrue;
84     }
85
86     return c->word != NULL;
87 }
88
89 static void
90 FcFormatContextDone (FcFormatContext *c)
91 {
92     if (c && c->word_allocated)
93     {
94         free (c->word);
95     }
96 }
97
98 static FcBool
99 consume_char (FcFormatContext *c,
100               FcChar8          term)
101 {
102     if (*c->format != term)
103         return FcFalse;
104
105     c->format++;
106     return FcTrue;
107 }
108
109 static FcBool
110 expect_char (FcFormatContext *c,
111               FcChar8          term)
112 {
113     FcBool res = consume_char (c, term);
114     if (!res)
115     {
116         if (c->format == c->format_orig + c->format_len)
117             message ("format ended while expecting '%c'",
118                      term);
119         else
120             message ("expected '%c' at %d",
121                      term, c->format - c->format_orig + 1);
122     }
123     return res;
124 }
125
126 static FcBool
127 FcCharIsPunct (const FcChar8 c)
128 {
129     if (c < '0')
130         return FcTrue;
131     if (c <= '9')
132         return FcFalse;
133     if (c < 'A')
134         return FcTrue;
135     if (c <= 'Z')
136         return FcFalse;
137     if (c < 'a')
138         return FcTrue;
139     if (c <= 'z')
140         return FcFalse;
141     if (c <= '~')
142         return FcTrue;
143     return FcFalse;
144 }
145
146 static char escaped_char(const char ch)
147 {
148     switch (ch) {
149     case 'a':   return '\a';
150     case 'b':   return '\b';
151     case 'f':   return '\f';
152     case 'n':   return '\n';
153     case 'r':   return '\r';
154     case 't':   return '\t';
155     case 'v':   return '\v';
156     default:    return ch;
157     }
158 }
159
160 static FcBool
161 read_word (FcFormatContext *c)
162 {
163     FcChar8 *p;
164
165     p = c->word;
166
167     while (*c->format)
168     {
169         if (*c->format == '\\')
170         {
171             c->format++;
172             if (*c->format)
173               *p++ = escaped_char (*c->format++);
174             continue;
175         }
176         else if (FcCharIsPunct (*c->format))
177             break;
178
179         *p++ = *c->format++;
180     }
181     *p = '\0';
182
183     if (p == c->word)
184     {
185         message ("expected identifier at %d",
186                  c->format - c->format_orig + 1);
187         return FcFalse;
188     }
189
190     return FcTrue;
191 }
192
193 static FcBool
194 read_chars (FcFormatContext *c,
195             FcChar8          term)
196 {
197     FcChar8 *p;
198
199     p = c->word;
200
201     while (*c->format && *c->format != '}' && *c->format != term)
202     {
203         if (*c->format == '\\')
204         {
205             c->format++;
206             if (*c->format)
207               *p++ = escaped_char (*c->format++);
208             continue;
209         }
210
211         *p++ = *c->format++;
212     }
213     *p = '\0';
214
215     if (p == c->word)
216     {
217         message ("expected character data at %d",
218                  c->format - c->format_orig + 1);
219         return FcFalse;
220     }
221
222     return FcTrue;
223 }
224
225 static FcBool
226 FcPatternFormatToBuf (FcPattern     *pat,
227                       const FcChar8 *format,
228                       FcStrBuf      *buf);
229
230 static FcBool
231 interpret_builtin (FcFormatContext *c,
232                    FcPattern       *pat,
233                    FcStrBuf        *buf)
234 {
235     FcChar8       *new_str;
236     FcBool         ret;
237
238     if (!expect_char (c, '=') ||
239         !read_word (c))
240         return FcFalse;
241
242     /* try simple builtins first */
243     if (0) { }
244 #define BUILTIN(name, func) \
245     else if (0 == strcmp ((const char *) c->word, name))\
246         do { new_str = func (pat); ret = FcTrue; } while (0)
247     BUILTIN ("unparse",  FcNameUnparse);
248  /* BUILTIN ("verbose",  FcPatternPrint); XXX */
249 #undef BUILTIN
250     else
251         ret = FcFalse;
252
253     if (ret)
254     {
255         if (new_str)
256         {
257             FcStrBufString (buf, new_str);
258             free (new_str);
259             return FcTrue;
260         }
261         else
262             return FcFalse;
263     }
264
265     /* now try our custom formats */
266     if (0) { }
267 #define BUILTIN(name, format) \
268     else if (0 == strcmp ((const char *) c->word, name))\
269         ret = FcPatternFormatToBuf (pat, (const FcChar8 *) format, buf)
270     BUILTIN ("fcmatch",  FCMATCH_FORMAT);
271     BUILTIN ("fclist",   FCLIST_FORMAT);
272 #undef BUILTIN
273     else
274         ret = FcFalse;
275
276     if (!ret)
277         message ("unknown builtin \"%s\"",
278                  c->word);
279
280     return ret;
281 }
282
283 static FcBool
284 interpret_expr (FcFormatContext *c,
285                 FcPattern       *pat,
286                 FcStrBuf        *buf,
287                 FcChar8          term);
288
289 static FcBool
290 interpret_subexpr (FcFormatContext *c,
291                    FcPattern       *pat,
292                    FcStrBuf        *buf)
293 {
294     return expect_char (c, '{') &&
295            interpret_expr (c, pat, buf, '}') &&
296            expect_char (c, '}');
297 }
298
299 static FcBool
300 maybe_interpret_subexpr (FcFormatContext *c,
301                          FcPattern       *pat,
302                          FcStrBuf        *buf)
303 {
304     return (*c->format == '{') ?
305            interpret_subexpr (c, pat, buf) :
306            FcTrue;
307 }
308
309 static FcBool
310 skip_subexpr (FcFormatContext *c);
311
312 static FcBool
313 skip_percent (FcFormatContext *c)
314 {
315     int width;
316
317     if (!expect_char (c, '%'))
318         return FcFalse;
319
320     /* skip an optional width specifier */
321     width = strtol ((const char *) c->format, (char **) &c->format, 10);
322
323     if (!expect_char (c, '{'))
324         return FcFalse;
325
326     while(*c->format && *c->format != '}')
327     {
328         switch (*c->format)
329         {
330         case '\\':
331             c->format++; /* skip over '\\' */
332             if (*c->format)
333                 c->format++;
334             continue;
335         case '{':
336             if (!skip_subexpr (c))
337                 return FcFalse;
338             continue;
339         }
340         c->format++;
341     }
342
343     return expect_char (c, '}');
344 }
345
346 static FcBool
347 skip_expr (FcFormatContext *c)
348 {
349     while(*c->format && *c->format != '}')
350     {
351         switch (*c->format)
352         {
353         case '\\':
354             c->format++; /* skip over '\\' */
355             if (*c->format)
356                 c->format++;
357             continue;
358         case '%':
359             if (!skip_percent (c))
360                 return FcFalse;
361             continue;
362         }
363         c->format++;
364     }
365
366     return FcTrue;
367 }
368
369 static FcBool
370 skip_subexpr (FcFormatContext *c)
371 {
372     return expect_char (c, '{') &&
373            skip_expr (c) &&
374            expect_char (c, '}');
375 }
376
377 static FcBool
378 maybe_skip_subexpr (FcFormatContext *c)
379 {
380     return (*c->format == '{') ?
381            skip_subexpr (c) :
382            FcTrue;
383 }
384
385 static FcBool
386 interpret_filter (FcFormatContext *c,
387                   FcPattern       *pat,
388                   FcStrBuf        *buf)
389 {
390     FcObjectSet  *os;
391     FcPattern    *subpat;
392
393     if (!expect_char (c, '+'))
394         return FcFalse;
395
396     os = FcObjectSetCreate ();
397     if (!os)
398         return FcFalse;
399
400     do
401     {
402         if (!read_word (c) ||
403             !FcObjectSetAdd (os, (const char *) c->word))
404         {
405             FcObjectSetDestroy (os);
406             return FcFalse;
407         }
408     }
409     while (consume_char (c, ','));
410
411     subpat = FcPatternFilter (pat, os);
412     FcObjectSetDestroy (os);
413
414     if (!subpat ||
415         !interpret_subexpr (c, subpat, buf))
416         return FcFalse;
417
418     FcPatternDestroy (subpat);
419     return FcTrue;
420 }
421
422 static FcBool
423 interpret_delete (FcFormatContext *c,
424                   FcPattern       *pat,
425                   FcStrBuf        *buf)
426 {
427     FcPattern    *subpat;
428
429     if (!expect_char (c, '-'))
430         return FcFalse;
431
432     subpat = FcPatternDuplicate (pat);
433     if (!subpat)
434         return FcFalse;
435
436     do
437     {
438         if (!read_word (c))
439         {
440             FcPatternDestroy (subpat);
441             return FcFalse;
442         }
443
444         FcPatternDel (subpat, (const char *) c->word);
445     }
446     while (consume_char (c, ','));
447
448     if (!interpret_subexpr (c, subpat, buf))
449         return FcFalse;
450
451     FcPatternDestroy (subpat);
452     return FcTrue;
453 }
454
455 static FcBool
456 interpret_cond (FcFormatContext *c,
457                 FcPattern       *pat,
458                 FcStrBuf        *buf)
459 {
460     FcBool pass;
461
462     if (!expect_char (c, '?'))
463         return FcFalse;
464
465     pass = FcTrue;
466
467     do
468     {
469         FcBool negate;
470         FcValue v;
471
472         negate = consume_char (c, '!');
473
474         if (!read_word (c))
475             return FcFalse;
476
477         pass = pass &&
478                (negate ^
479                 (FcResultMatch == FcPatternGet (pat,
480                                                 (const char *) c->word,
481                                                 0, &v)));
482     }
483     while (consume_char (c, ','));
484
485     if (pass)
486     {
487         if (!interpret_subexpr  (c, pat, buf) ||
488             !maybe_skip_subexpr (c))
489             return FcFalse;
490     }
491     else
492     {
493         if (!skip_subexpr (c) ||
494             !maybe_interpret_subexpr  (c, pat, buf))
495             return FcFalse;
496     }
497
498     return FcTrue;
499 }
500
501 static FcBool
502 interpret_count (FcFormatContext *c,
503                  FcPattern       *pat,
504                  FcStrBuf        *buf)
505 {
506     int count;
507     FcPatternElt *e;
508     FcChar8 buf_static[64];
509
510     if (!expect_char (c, '#'))
511         return FcFalse;
512
513     if (!read_word (c))
514         return FcFalse;
515
516     count = 0;
517     e = FcPatternObjectFindElt (pat,
518                                 FcObjectFromName ((const char *) c->word));
519     if (e)
520     {
521         FcValueListPtr l;
522         count++;
523         for (l = FcPatternEltValues(e);
524              l->next;
525              l = l->next)
526             count++;
527     }
528
529     snprintf ((char *) buf_static, sizeof (buf_static), "%d", count);
530     FcStrBufString (buf, buf_static);
531
532     return FcTrue;
533 }
534
535 static FcBool
536 interpret_simple (FcFormatContext *c,
537                   FcPattern       *pat,
538                   FcStrBuf        *buf)
539 {
540     FcPatternElt *e;
541     FcBool        add_colon = FcFalse;
542     FcBool        add_elt_name = FcFalse;
543     int           idx;
544     FcChar8      *else_string;
545
546     if (consume_char (c, ':'))
547         add_colon = FcTrue;
548
549     if (!read_word (c))
550         return FcFalse;
551
552     idx = -1;
553     if (consume_char (c, '['))
554     {
555         idx = strtol ((const char *) c->format, (char **) &c->format, 10);
556         if (idx < 0)
557         {
558             message ("expected non-negative number at %d",
559                      c->format-1 - c->format_orig + 1);
560             return FcFalse;
561         }
562         expect_char (c, ']');
563     }
564
565     if (consume_char (c, '='))
566         add_elt_name = FcTrue;
567
568     /* modifiers */
569     else_string = NULL;
570     if (consume_char (c, ':'))
571     {
572         FcChar8 *orig;
573         /* divert the c->word for now */
574         orig = c->word;
575         c->word = c->word + strlen ((const char *) c->word) + 1;
576         /* for now we just support 'default value' */
577         if (!expect_char (c, '-') ||
578             !read_chars (c, '\0'))
579         {
580             c->word = orig;
581             return FcFalse;
582         }
583         else_string = c->word;
584         c->word = orig;
585     }
586
587     e = FcPatternObjectFindElt (pat,
588                                 FcObjectFromName ((const char *) c->word));
589     if (e)
590     {
591         FcValueListPtr l;
592
593         if (add_colon)
594             FcStrBufChar (buf, ':');
595         if (add_elt_name)
596         {
597             FcStrBufString (buf, c->word);
598             FcStrBufChar (buf, '=');
599         }
600
601         l = FcPatternEltValues(e);
602
603         if (idx != -1)
604         {
605             while (l && idx > 0)
606             {
607                 l = FcValueListNext(l);
608                 idx--;
609             }
610             if (l && idx == 0)
611             {
612                 if (!FcNameUnparseValue (buf, &l->value, '\0'))
613                     return FcFalse;
614             }
615             else goto notfound;
616         }
617         else
618         {
619             FcNameUnparseValueList (buf, l, '\0');
620         }
621     }
622     else
623 notfound:
624     {
625         if (else_string)
626             printf ("%s", else_string);
627     }
628
629     return FcTrue;
630 }
631
632 static FcBool
633 cescape (FcFormatContext *c,
634          const FcChar8   *str,
635          FcStrBuf        *buf)
636 {
637     while(*str)
638     {
639         switch (*str)
640         {
641         case '\\':
642         case '"':
643             FcStrBufChar (buf, '\\');
644             break;
645         }
646         FcStrBufChar (buf, *str++);
647     }
648     return FcTrue;
649 }
650
651 static FcBool
652 shescape (FcFormatContext *c,
653           const FcChar8   *str,
654           FcStrBuf        *buf)
655 {
656     FcStrBufChar (buf, '\'');
657     while(*str)
658     {
659         if (*str == '\'')
660             FcStrBufString (buf, (const FcChar8 *) "'\\''");
661         else
662             FcStrBufChar (buf, *str);
663         str++;
664     }
665     FcStrBufChar (buf, '\'');
666     return FcTrue;
667 }
668
669 static FcBool
670 xmlescape (FcFormatContext *c,
671            const FcChar8   *str,
672            FcStrBuf        *buf)
673 {
674     while(*str)
675     {
676         switch (*str)
677         {
678         case '&': FcStrBufString (buf, (const FcChar8 *) "&amp;"); break;
679         case '<': FcStrBufString (buf, (const FcChar8 *) "&lt;");  break;
680         case '>': FcStrBufString (buf, (const FcChar8 *) "&gt;");  break;
681         default:  FcStrBufChar   (buf, *str);                      break;
682         }
683         str++;
684     }
685     return FcTrue;
686 }
687
688 static FcBool
689 delete_chars (FcFormatContext *c,
690               const FcChar8   *str,
691               FcStrBuf        *buf)
692 {
693     /* XXX not UTF-8 aware */
694
695     if (!expect_char (c, '(') ||
696         !read_chars (c, ')') ||
697         !expect_char (c, ')'))
698         return FcFalse;
699
700     while(*str)
701     {
702         FcChar8 *p;
703
704         p = (FcChar8 *) strpbrk ((const char *) str, (const char *) c->word);
705         if (p)
706         {
707             FcStrBufData (buf, str, p - str);
708             str = p + 1;
709         }
710         else
711         {
712             FcStrBufString (buf, str);
713             break;
714         }
715
716     }
717
718     return FcTrue;
719 }
720
721 static FcBool
722 escape_chars (FcFormatContext *c,
723               const FcChar8   *str,
724               FcStrBuf        *buf)
725 {
726     /* XXX not UTF-8 aware */
727
728     if (!expect_char (c, '(') ||
729         !read_chars (c, ')') ||
730         !expect_char (c, ')'))
731         return FcFalse;
732
733     while(*str)
734     {
735         FcChar8 *p;
736
737         p = (FcChar8 *) strpbrk ((const char *) str, (const char *) c->word);
738         if (p)
739         {
740             FcStrBufData (buf, str, p - str);
741             FcStrBufChar (buf, c->word[0]);
742             FcStrBufChar (buf, *p);
743             str = p + 1;
744         }
745         else
746         {
747             FcStrBufString (buf, str);
748             break;
749         }
750
751     }
752
753     return FcTrue;
754 }
755
756 static FcBool
757 translate_chars (FcFormatContext *c,
758                  const FcChar8   *str,
759                  FcStrBuf        *buf)
760 {
761     char *from, *to, repeat;
762     int from_len, to_len;
763
764     /* XXX not UTF-8 aware */
765
766     if (!expect_char (c, '(') ||
767         !read_chars (c, ',') ||
768         !expect_char (c, ','))
769         return FcFalse;
770
771     from = (char *) c->word;
772     from_len = strlen (from);
773     to = from + from_len + 1;
774
775     /* hack: we temporarily divert c->word */
776     c->word = (FcChar8 *) to;
777     if (!read_chars (c, ')'))
778     {
779       c->word = (FcChar8 *) from;
780       return FcFalse;
781     }
782     c->word = (FcChar8 *) from;
783
784     to_len = strlen (to);
785     repeat = to[to_len - 1];
786
787     if (!expect_char (c, ')'))
788         return FcFalse;
789
790     while(*str)
791     {
792         FcChar8 *p;
793
794         p = (FcChar8 *) strpbrk ((const char *) str, (const char *) from);
795         if (p)
796         {
797             int i;
798             FcStrBufData (buf, str, p - str);
799             i = strchr (from, *p) - from;
800             FcStrBufChar (buf, i < to_len ? to[i] : repeat);
801             str = p + 1;
802         }
803         else
804         {
805             FcStrBufString (buf, str);
806             break;
807         }
808
809     }
810
811     return FcTrue;
812 }
813
814 static FcBool
815 interpret_convert (FcFormatContext *c,
816                    FcStrBuf        *buf,
817                    int              start)
818 {
819     const FcChar8 *str;
820     FcChar8       *new_str;
821     FcStrBuf       new_buf;
822     FcChar8        buf_static[8192];
823     FcBool         ret;
824
825     if (!expect_char (c, '|') ||
826         !read_word (c))
827         return FcFalse;
828
829     /* prepare the buffer */
830     FcStrBufChar (buf, '\0');
831     if (buf->failed)
832         return FcFalse;
833     str = buf->buf + start;
834     buf->len = start;
835
836     /* try simple converters first */
837     if (0) { }
838 #define CONVERTER(name, func) \
839     else if (0 == strcmp ((const char *) c->word, name))\
840         do { new_str = func (str); ret = FcTrue; } while (0)
841     CONVERTER  ("downcase",  FcStrDowncase);
842     CONVERTER  ("basename",  FcStrBasename);
843     CONVERTER  ("dirname",   FcStrDirname);
844 #undef CONVERTER
845     else
846         ret = FcFalse;
847
848     if (ret)
849     {
850         if (new_str)
851         {
852             FcStrBufString (buf, new_str);
853             free (new_str);
854             return FcTrue;
855         }
856         else
857             return FcFalse;
858     }
859
860     FcStrBufInit (&new_buf, buf_static, sizeof (buf_static));
861
862     /* now try our custom converters */
863     if (0) { }
864 #define CONVERTER(name, func) \
865     else if (0 == strcmp ((const char *) c->word, name))\
866         ret = func (c, str, &new_buf)
867     CONVERTER ("cescape",   cescape);
868     CONVERTER ("shescape",  shescape);
869     CONVERTER ("xmlescape", xmlescape);
870     CONVERTER ("delete",    delete_chars);
871     CONVERTER ("escape",    escape_chars);
872     CONVERTER ("translate", translate_chars);
873 #undef CONVERTER
874     else
875         ret = FcFalse;
876
877     if (ret)
878     {
879         FcStrBufChar (&new_buf, '\0');
880         FcStrBufString (buf, new_buf.buf);
881     }
882     else
883         message ("unknown converter \"%s\"",
884                  c->word);
885
886     FcStrBufDestroy (&new_buf);
887
888     return ret;
889 }
890
891 static FcBool
892 maybe_interpret_converts (FcFormatContext *c,
893                            FcStrBuf        *buf,
894                            int              start)
895 {
896     while (*c->format == '|')
897         if (!interpret_convert (c, buf, start))
898             return FcFalse;
899
900     return FcTrue;
901 }
902
903 static FcBool
904 align_to_width (FcStrBuf *buf,
905                 int       start,
906                 int       width)
907 {
908     int len;
909
910     if (buf->failed)
911         return FcFalse;
912
913     len = buf->len - start;
914     if (len < -width)
915     {
916         /* left align */
917         while (len++ < -width)
918             FcStrBufChar (buf, ' ');
919     }
920     else if (len < width)
921     {
922         int old_len;
923         old_len = len;
924         /* right align */
925         while (len++ < width)
926             FcStrBufChar (buf, ' ');
927         if (buf->failed)
928             return FcFalse;
929         len = old_len;
930         memmove (buf->buf + buf->len - len,
931                  buf->buf + buf->len - width,
932                  len);
933         memset (buf->buf + buf->len - width,
934                 ' ',
935                 width - len);
936     }
937
938     return !buf->failed;
939 }
940 static FcBool
941 interpret_percent (FcFormatContext *c,
942                    FcPattern       *pat,
943                    FcStrBuf        *buf)
944 {
945     int width, start;
946     FcBool ret;
947
948     if (!expect_char (c, '%'))
949         return FcFalse;
950
951     if (consume_char (c, '%')) /* "%%" */
952     {
953         FcStrBufChar (buf, '%');
954         return FcTrue;
955     }
956
957     /* parse an optional width specifier */
958     width = strtol ((const char *) c->format, (char **) &c->format, 10);
959
960     if (!expect_char (c, '{'))
961         return FcFalse;
962
963     start = buf->len;
964
965     switch (*c->format) {
966     case '=': ret = interpret_builtin (c, pat, buf); break;
967     case '{': ret = interpret_subexpr (c, pat, buf); break;
968     case '+': ret = interpret_filter  (c, pat, buf); break;
969     case '-': ret = interpret_delete  (c, pat, buf); break;
970     case '?': ret = interpret_cond    (c, pat, buf); break;
971     case '#': ret = interpret_count   (c, pat, buf); break;
972     default:  ret = interpret_simple  (c, pat, buf); break;
973     }
974
975     return ret &&
976            maybe_interpret_converts (c, buf, start) &&
977            align_to_width (buf, start, width) &&
978            expect_char (c, '}');
979 }
980
981 static FcBool
982 interpret_expr (FcFormatContext *c,
983                 FcPattern       *pat,
984                 FcStrBuf        *buf,
985                 FcChar8          term)
986 {
987     while (*c->format && *c->format != term)
988     {
989         switch (*c->format)
990         {
991         case '\\':
992             c->format++; /* skip over '\\' */
993             if (*c->format)
994                 FcStrBufChar (buf, escaped_char (*c->format++));
995             continue;
996         case '%':
997             if (!interpret_percent (c, pat, buf))
998                 return FcFalse;
999             continue;
1000         }
1001         FcStrBufChar (buf, *c->format++);
1002     }
1003     return FcTrue;
1004 }
1005
1006 static FcBool
1007 FcPatternFormatToBuf (FcPattern     *pat,
1008                       const FcChar8 *format,
1009                       FcStrBuf      *buf)
1010 {
1011     FcFormatContext c;
1012     FcChar8         word_static[1024];
1013     FcBool          ret;
1014
1015     if (!FcFormatContextInit (&c, format, word_static, sizeof (word_static)))
1016         return FcFalse;
1017
1018     ret = interpret_expr (&c, pat, buf, '\0');
1019
1020     FcFormatContextDone (&c);
1021
1022     return ret;
1023 }
1024
1025 FcChar8 *
1026 FcPatternFormat (FcPattern *pat,
1027                  const FcChar8 *format)
1028 {
1029     FcStrBuf        buf;
1030     FcChar8         buf_static[8192 - 1024];
1031     FcBool          ret;
1032
1033     FcStrBufInit (&buf, buf_static, sizeof (buf_static));
1034
1035     ret = FcPatternFormatToBuf (pat, format, &buf);
1036
1037     if (ret)
1038         return FcStrBufDone (&buf);
1039     else
1040     {
1041         FcStrBufDestroy (&buf);
1042         return NULL;
1043     }
1044 }
1045
1046 #define __fcformat__
1047 #include "fcaliastail.h"
1048 #undef __fcformat__