fcformat.c revision a6844aab
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
81static void
82message (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
93typedef 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
102static FcBool
103FcFormatContextInit (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
125static void
126FcFormatContextDone (FcFormatContext *c)
127{
128    if (c && c->word_allocated)
129    {
130	free (c->word);
131    }
132}
133
134static FcBool
135consume_char (FcFormatContext *c,
136	      FcChar8          term)
137{
138    if (*c->format != term)
139	return FcFalse;
140
141    c->format++;
142    return FcTrue;
143}
144
145static FcBool
146expect_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
162static FcBool
163FcCharIsPunct (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
182static 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
196static FcBool
197read_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
229static FcBool
230read_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
261static FcBool
262FcPatternFormatToBuf (FcPattern     *pat,
263		      const FcChar8 *format,
264		      FcStrBuf      *buf);
265
266static FcBool
267interpret_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
320static FcBool
321interpret_expr (FcFormatContext *c,
322		FcPattern       *pat,
323		FcStrBuf        *buf,
324		FcChar8          term);
325
326static FcBool
327interpret_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
336static FcBool
337maybe_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
346static FcBool
347skip_subexpr (FcFormatContext *c);
348
349static FcBool
350skip_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
383static FcBool
384skip_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
406static FcBool
407skip_subexpr (FcFormatContext *c)
408{
409    return expect_char (c, '{') &&
410	   skip_expr (c) &&
411	   expect_char (c, '}');
412}
413
414static FcBool
415maybe_skip_subexpr (FcFormatContext *c)
416{
417    return (*c->format == '{') ?
418	   skip_subexpr (c) :
419	   FcTrue;
420}
421
422static FcBool
423interpret_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
459static FcBool
460interpret_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
492static FcBool
493interpret_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
537static FcBool
538interpret_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
571static FcBool
572interpret_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
677bail:
678    FcPatternDestroy (subpat);
679bail0:
680    if (lang_strs)
681	FcStrListDone (lang_strs);
682    FcObjectSetDestroy (os);
683
684    return ret;
685}
686
687static FcBool
688interpret_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
786static FcBool
787cescape (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
805static FcBool
806shescape (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
823static FcBool
824xmlescape (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
842static FcBool
843delete_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
875static FcBool
876escape_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
910static FcBool
911translate_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
968static FcBool
969interpret_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
1045static FcBool
1046maybe_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
1057static FcBool
1058align_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}
1094static FcBool
1095interpret_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
1136static FcBool
1137interpret_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
1161static FcBool
1162FcPatternFormatToBuf (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
1180FcChar8 *
1181FcPatternFormat (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__
1204