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