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