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