fcformat.c revision 6a285edb
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
84static void
85message (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
96typedef 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
105static FcBool
106FcFormatContextInit (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
128static void
129FcFormatContextDone (FcFormatContext *c)
130{
131    if (c && c->word_allocated)
132    {
133	free (c->word);
134    }
135}
136
137static FcBool
138consume_char (FcFormatContext *c,
139	      FcChar8          term)
140{
141    if (*c->format != term)
142	return FcFalse;
143
144    c->format++;
145    return FcTrue;
146}
147
148static FcBool
149expect_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
165static FcBool
166FcCharIsPunct (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
185static 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
199static FcBool
200read_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
232static FcBool
233read_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
264static FcBool
265FcPatternFormatToBuf (FcPattern     *pat,
266		      const FcChar8 *format,
267		      FcStrBuf      *buf);
268
269static FcBool
270interpret_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
324static FcBool
325interpret_expr (FcFormatContext *c,
326		FcPattern       *pat,
327		FcStrBuf        *buf,
328		FcChar8          term);
329
330static FcBool
331interpret_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
340static FcBool
341maybe_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
350static FcBool
351skip_subexpr (FcFormatContext *c);
352
353static FcBool
354skip_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
386static FcBool
387skip_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
409static FcBool
410skip_subexpr (FcFormatContext *c)
411{
412    return expect_char (c, '{') &&
413	   skip_expr (c) &&
414	   expect_char (c, '}');
415}
416
417static FcBool
418maybe_skip_subexpr (FcFormatContext *c)
419{
420    return (*c->format == '{') ?
421	   skip_subexpr (c) :
422	   FcTrue;
423}
424
425static FcBool
426interpret_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
463static FcBool
464interpret_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
496static FcBool
497interpret_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
541static FcBool
542interpret_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
575static FcBool
576interpret_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
683bail:
684    FcPatternDestroy (subpat);
685bail0:
686    if (lang_strs)
687	FcStrListDone (lang_strs);
688    FcObjectSetDestroy (os);
689
690    return ret;
691}
692
693static FcBool
694interpret_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, NULL))
773		    return FcFalse;
774	    }
775	    else goto notfound;
776        }
777	else if (l)
778	{
779	    FcNameUnparseValueList (buf, l, NULL);
780	}
781	else
782	{
783    notfound:
784	    if (else_string)
785		FcStrBufString (buf, else_string);
786	}
787    }
788
789    return FcTrue;
790}
791
792static FcBool
793cescape (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
813static FcBool
814shescape (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
831static FcBool
832xmlescape (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
852static FcBool
853delete_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
885static FcBool
886escape_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
920static FcBool
921translate_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
978static FcBool
979interpret_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
1055static FcBool
1056maybe_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
1067static FcBool
1068align_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}
1104static FcBool
1105interpret_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
1146static FcBool
1147interpret_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
1171static FcBool
1172FcPatternFormatToBuf (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
1190FcChar8 *
1191FcPatternFormat (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__
1221