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