]>
git.wh0rd.org - fontconfig.git/blob - src/fcformat.c
2 * Copyright © 2008,2009 Red Hat, Inc.
4 * Red Hat Author(s): Behdad Esfahbod
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.
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.
31 /* The language is documented in doc/fcformat.fncs
32 * These are the features implemented:
39 * default %{elt:-word}
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
48 * convert %{elt|conv1|conv2|conv3}
51 * basename FcStrBasename
52 * dirname FcStrDirname
53 * downcase FcStrDowncase
59 * translate translate chars
62 * unparse FcNameUnparse
63 * fcmatch fc-match default
64 * fclist fc-list default
65 * fccat fc-cat default
66 * pkgkit PackageKit package tag format
69 * Some ideas for future syntax extensions:
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)
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}}"
85 message (const char *fmt
, ...)
89 fprintf (stderr
, "Fontconfig: Pattern format error: ");
90 vfprintf (stderr
, fmt
, args
);
91 fprintf (stderr
, ".\n");
96 typedef struct _FcFormatContext
98 const FcChar8
*format_orig
;
99 const FcChar8
*format
;
102 FcBool word_allocated
;
106 FcFormatContextInit (FcFormatContext
*c
,
107 const FcChar8
*format
,
111 c
->format_orig
= c
->format
= format
;
112 c
->format_len
= strlen ((const char *) format
);
114 if (c
->format_len
< scratch_len
)
117 c
->word_allocated
= FcFalse
;
121 c
->word
= malloc (c
->format_len
+ 1);
122 c
->word_allocated
= FcTrue
;
125 return c
->word
!= NULL
;
129 FcFormatContextDone (FcFormatContext
*c
)
131 if (c
&& c
->word_allocated
)
138 consume_char (FcFormatContext
*c
,
141 if (*c
->format
!= term
)
149 expect_char (FcFormatContext
*c
,
152 FcBool res
= consume_char (c
, term
);
155 if (c
->format
== c
->format_orig
+ c
->format_len
)
156 message ("format ended while expecting '%c'",
159 message ("expected '%c' at %d",
160 term
, c
->format
- c
->format_orig
+ 1);
166 FcCharIsPunct (const FcChar8 c
)
185 static char escaped_char(const char 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';
200 read_word (FcFormatContext
*c
)
208 if (*c
->format
== '\\')
212 *p
++ = escaped_char (*c
->format
++);
215 else if (FcCharIsPunct (*c
->format
))
224 message ("expected identifier at %d",
225 c
->format
- c
->format_orig
+ 1);
233 read_chars (FcFormatContext
*c
,
240 while (*c
->format
&& *c
->format
!= '}' && *c
->format
!= term
)
242 if (*c
->format
== '\\')
246 *p
++ = escaped_char (*c
->format
++);
256 message ("expected character data at %d",
257 c
->format
- c
->format_orig
+ 1);
265 FcPatternFormatToBuf (FcPattern
*pat
,
266 const FcChar8
*format
,
270 interpret_builtin (FcFormatContext
*c
,
277 if (!expect_char (c
, '=') ||
281 /* try simple builtins first */
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 */
296 FcStrBufString (buf
, new_str
);
304 /* now try our custom formats */
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
);
318 message ("unknown builtin \"%s\"",
325 interpret_expr (FcFormatContext
*c
,
331 interpret_subexpr (FcFormatContext
*c
,
335 return expect_char (c
, '{') &&
336 interpret_expr (c
, pat
, buf
, '}') &&
337 expect_char (c
, '}');
341 maybe_interpret_subexpr (FcFormatContext
*c
,
345 return (*c
->format
== '{') ?
346 interpret_subexpr (c
, pat
, buf
) :
351 skip_subexpr (FcFormatContext
*c
);
354 skip_percent (FcFormatContext
*c
)
358 if (!expect_char (c
, '%'))
361 /* skip an optional width specifier */
362 width
= strtol ((const char *) c
->format
, (char **) &c
->format
, 10);
364 if (!expect_char (c
, '{'))
367 while(*c
->format
&& *c
->format
!= '}')
372 c
->format
++; /* skip over '\\' */
377 if (!skip_subexpr (c
))
384 return expect_char (c
, '}');
388 skip_expr (FcFormatContext
*c
)
390 while(*c
->format
&& *c
->format
!= '}')
395 c
->format
++; /* skip over '\\' */
400 if (!skip_percent (c
))
411 skip_subexpr (FcFormatContext
*c
)
413 return expect_char (c
, '{') &&
415 expect_char (c
, '}');
419 maybe_skip_subexpr (FcFormatContext
*c
)
421 return (*c
->format
== '{') ?
427 interpret_filter_in (FcFormatContext
*c
,
434 if (!expect_char (c
, '+'))
437 os
= FcObjectSetCreate ();
444 if (!read_word (c
) ||
445 !FcObjectSetAdd (os
, (const char *) c
->word
))
447 FcObjectSetDestroy (os
);
451 while (consume_char (c
, ','));
453 subpat
= FcPatternFilter (pat
, os
);
454 FcObjectSetDestroy (os
);
457 !interpret_subexpr (c
, subpat
, buf
))
460 FcPatternDestroy (subpat
);
465 interpret_filter_out (FcFormatContext
*c
,
471 if (!expect_char (c
, '-'))
474 subpat
= FcPatternDuplicate (pat
);
482 FcPatternDestroy (subpat
);
486 FcPatternDel (subpat
, (const char *) c
->word
);
488 while (consume_char (c
, ','));
490 if (!interpret_subexpr (c
, subpat
, buf
))
493 FcPatternDestroy (subpat
);
498 interpret_cond (FcFormatContext
*c
,
504 if (!expect_char (c
, '?'))
514 negate
= consume_char (c
, '!');
522 FcPatternGet (pat
, (const char *) c
->word
, 0, &v
)));
524 while (consume_char (c
, ','));
528 if (!interpret_subexpr (c
, pat
, buf
) ||
529 !maybe_skip_subexpr (c
))
534 if (!skip_subexpr (c
) ||
535 !maybe_interpret_subexpr (c
, pat
, buf
))
543 interpret_count (FcFormatContext
*c
,
549 FcChar8 buf_static
[64];
551 if (!expect_char (c
, '#'))
558 e
= FcPatternObjectFindElt (pat
,
559 FcObjectFromName ((const char *) c
->word
));
564 for (l
= FcPatternEltValues(e
);
570 snprintf ((char *) buf_static
, sizeof (buf_static
), "%d", count
);
571 FcStrBufString (buf
, buf_static
);
577 interpret_enumerate (FcFormatContext
*c
,
583 const FcChar8
*format_save
;
586 FcStrList
*lang_strs
;
588 if (!expect_char (c
, '[') ||
589 !expect_char (c
, ']'))
592 os
= FcObjectSetCreate ();
600 if (!read_word (c
) ||
601 !FcObjectSetAdd (os
, (const char *) c
->word
))
603 FcObjectSetDestroy (os
);
607 while (consume_char (c
, ','));
609 /* If we have one element and it's of type FcLangSet, we want
610 * to enumerate the languages in it. */
612 if (os
->nobject
== 1)
616 FcPatternGetLangSet (pat
, os
->objects
[0], 0, &langset
))
619 if (!(ss
= FcLangSetGetLangs (langset
)) ||
620 !(lang_strs
= FcStrListCreate (ss
)))
625 subpat
= FcPatternDuplicate (pat
);
629 format_save
= c
->format
;
641 FcPatternDel (subpat
, os
->objects
[0]);
642 if ((lang
= FcStrListNext (lang_strs
)))
645 FcPatternAddString (subpat
, os
->objects
[0], lang
);
651 for (i
= 0; i
< os
->nobject
; i
++)
655 /* XXX this can be optimized by accessing valuelist linked lists
656 * directly and remembering where we were. Most (all) value lists
657 * in normal uses are pretty short though (language tags are
658 * stored as a LangSet, not separate values.). */
659 FcPatternDel (subpat
, os
->objects
[i
]);
661 FcPatternGet (pat
, os
->objects
[i
], idx
, &v
))
664 FcPatternAdd (subpat
, os
->objects
[i
], v
, FcFalse
);
672 c
->format
= format_save
;
673 ret
= interpret_subexpr (c
, subpat
, buf
);
681 if (c
->format
== format_save
)
685 FcPatternDestroy (subpat
);
688 FcStrListDone (lang_strs
);
689 FcObjectSetDestroy (os
);
695 interpret_simple (FcFormatContext
*c
,
700 FcBool add_colon
= FcFalse
;
701 FcBool add_elt_name
= FcFalse
;
703 FcChar8
*else_string
;
705 if (consume_char (c
, ':'))
712 if (consume_char (c
, '['))
714 idx
= strtol ((const char *) c
->format
, (char **) &c
->format
, 10);
717 message ("expected non-negative number at %d",
718 c
->format
-1 - c
->format_orig
+ 1);
721 if (!expect_char (c
, ']'))
725 if (consume_char (c
, '='))
726 add_elt_name
= FcTrue
;
730 if (consume_char (c
, ':'))
733 /* divert the c->word for now */
735 c
->word
= c
->word
+ strlen ((const char *) c
->word
) + 1;
736 /* for now we just support 'default value' */
737 if (!expect_char (c
, '-') ||
738 !read_chars (c
, '|'))
743 else_string
= c
->word
;
747 e
= FcPatternObjectFindElt (pat
,
748 FcObjectFromName ((const char *) c
->word
));
749 if (e
|| else_string
)
751 FcValueListPtr l
= NULL
;
754 FcStrBufChar (buf
, ':');
757 FcStrBufString (buf
, c
->word
);
758 FcStrBufChar (buf
, '=');
762 l
= FcPatternEltValues(e
);
768 l
= FcValueListNext(l
);
773 if (!FcNameUnparseValue (buf
, &l
->value
, '\0'))
780 FcNameUnparseValueList (buf
, l
, '\0');
786 FcStrBufString (buf
, else_string
);
794 cescape (FcFormatContext
*c
,
798 /* XXX escape \n etc? */
806 FcStrBufChar (buf
, '\\');
809 FcStrBufChar (buf
, *str
++);
815 shescape (FcFormatContext
*c
,
819 FcStrBufChar (buf
, '\'');
823 FcStrBufString (buf
, (const FcChar8
*) "'\\''");
825 FcStrBufChar (buf
, *str
);
828 FcStrBufChar (buf
, '\'');
833 xmlescape (FcFormatContext
*c
,
837 /* XXX escape \n etc? */
843 case '&': FcStrBufString (buf
, (const FcChar8
*) "&"); break;
844 case '<': FcStrBufString (buf
, (const FcChar8
*) "<"); break;
845 case '>': FcStrBufString (buf
, (const FcChar8
*) ">"); break;
846 default: FcStrBufChar (buf
, *str
); break;
854 delete_chars (FcFormatContext
*c
,
858 /* XXX not UTF-8 aware */
860 if (!expect_char (c
, '(') ||
861 !read_chars (c
, ')') ||
862 !expect_char (c
, ')'))
869 p
= (FcChar8
*) strpbrk ((const char *) str
, (const char *) c
->word
);
872 FcStrBufData (buf
, str
, p
- str
);
877 FcStrBufString (buf
, str
);
887 escape_chars (FcFormatContext
*c
,
891 /* XXX not UTF-8 aware */
893 if (!expect_char (c
, '(') ||
894 !read_chars (c
, ')') ||
895 !expect_char (c
, ')'))
902 p
= (FcChar8
*) strpbrk ((const char *) str
, (const char *) c
->word
);
905 FcStrBufData (buf
, str
, p
- str
);
906 FcStrBufChar (buf
, c
->word
[0]);
907 FcStrBufChar (buf
, *p
);
912 FcStrBufString (buf
, str
);
922 translate_chars (FcFormatContext
*c
,
926 char *from
, *to
, repeat
;
927 int from_len
, to_len
;
929 /* XXX not UTF-8 aware */
931 if (!expect_char (c
, '(') ||
932 !read_chars (c
, ',') ||
933 !expect_char (c
, ','))
936 from
= (char *) c
->word
;
937 from_len
= strlen (from
);
938 to
= from
+ from_len
+ 1;
940 /* hack: we temporarily divert c->word */
941 c
->word
= (FcChar8
*) to
;
942 if (!read_chars (c
, ')'))
944 c
->word
= (FcChar8
*) from
;
947 c
->word
= (FcChar8
*) from
;
949 to_len
= strlen (to
);
950 repeat
= to
[to_len
- 1];
952 if (!expect_char (c
, ')'))
959 p
= (FcChar8
*) strpbrk ((const char *) str
, (const char *) from
);
963 FcStrBufData (buf
, str
, p
- str
);
964 i
= strchr (from
, *p
) - from
;
965 FcStrBufChar (buf
, i
< to_len
? to
[i
] : repeat
);
970 FcStrBufString (buf
, str
);
980 interpret_convert (FcFormatContext
*c
,
987 FcChar8 buf_static
[8192];
990 if (!expect_char (c
, '|') ||
994 /* prepare the buffer */
995 FcStrBufChar (buf
, '\0');
998 str
= buf
->buf
+ start
;
1001 /* try simple converters first */
1003 #define CONVERTER(name, func) \
1004 else if (0 == strcmp ((const char *) c->word, name))\
1005 do { new_str = func (str); ret = FcTrue; } while (0)
1006 CONVERTER ("downcase", FcStrDowncase
);
1007 CONVERTER ("basename", FcStrBasename
);
1008 CONVERTER ("dirname", FcStrDirname
);
1017 FcStrBufString (buf
, new_str
);
1025 FcStrBufInit (&new_buf
, buf_static
, sizeof (buf_static
));
1027 /* now try our custom converters */
1029 #define CONVERTER(name, func) \
1030 else if (0 == strcmp ((const char *) c->word, name))\
1031 ret = func (c, str, &new_buf)
1032 CONVERTER ("cescape", cescape
);
1033 CONVERTER ("shescape", shescape
);
1034 CONVERTER ("xmlescape", xmlescape
);
1035 CONVERTER ("delete", delete_chars
);
1036 CONVERTER ("escape", escape_chars
);
1037 CONVERTER ("translate", translate_chars
);
1044 FcStrBufChar (&new_buf
, '\0');
1045 FcStrBufString (buf
, new_buf
.buf
);
1048 message ("unknown converter \"%s\"",
1051 FcStrBufDestroy (&new_buf
);
1057 maybe_interpret_converts (FcFormatContext
*c
,
1061 while (*c
->format
== '|')
1062 if (!interpret_convert (c
, buf
, start
))
1069 align_to_width (FcStrBuf
*buf
,
1078 len
= buf
->len
- start
;
1082 while (len
++ < -width
)
1083 FcStrBufChar (buf
, ' ');
1085 else if (len
< width
)
1090 while (len
++ < width
)
1091 FcStrBufChar (buf
, ' ');
1095 memmove (buf
->buf
+ buf
->len
- len
,
1096 buf
->buf
+ buf
->len
- width
,
1098 memset (buf
->buf
+ buf
->len
- width
,
1103 return !buf
->failed
;
1106 interpret_percent (FcFormatContext
*c
,
1113 if (!expect_char (c
, '%'))
1116 if (consume_char (c
, '%')) /* "%%" */
1118 FcStrBufChar (buf
, '%');
1122 /* parse an optional width specifier */
1123 width
= strtol ((const char *) c
->format
, (char **) &c
->format
, 10);
1125 if (!expect_char (c
, '{'))
1130 switch (*c
->format
) {
1131 case '=': ret
= interpret_builtin (c
, pat
, buf
); break;
1132 case '{': ret
= interpret_subexpr (c
, pat
, buf
); break;
1133 case '+': ret
= interpret_filter_in (c
, pat
, buf
); break;
1134 case '-': ret
= interpret_filter_out (c
, pat
, buf
); break;
1135 case '?': ret
= interpret_cond (c
, pat
, buf
); break;
1136 case '#': ret
= interpret_count (c
, pat
, buf
); break;
1137 case '[': ret
= interpret_enumerate (c
, pat
, buf
); break;
1138 default: ret
= interpret_simple (c
, pat
, buf
); break;
1142 maybe_interpret_converts (c
, buf
, start
) &&
1143 align_to_width (buf
, start
, width
) &&
1144 expect_char (c
, '}');
1148 interpret_expr (FcFormatContext
*c
,
1153 while (*c
->format
&& *c
->format
!= term
)
1158 c
->format
++; /* skip over '\\' */
1160 FcStrBufChar (buf
, escaped_char (*c
->format
++));
1163 if (!interpret_percent (c
, pat
, buf
))
1167 FcStrBufChar (buf
, *c
->format
++);
1173 FcPatternFormatToBuf (FcPattern
*pat
,
1174 const FcChar8
*format
,
1178 FcChar8 word_static
[1024];
1181 if (!FcFormatContextInit (&c
, format
, word_static
, sizeof (word_static
)))
1184 ret
= interpret_expr (&c
, pat
, buf
, '\0');
1186 FcFormatContextDone (&c
);
1192 FcPatternFormat (FcPattern
*pat
,
1193 const FcChar8
*format
)
1196 FcChar8 buf_static
[8192 - 1024];
1199 FcStrBufInit (&buf
, buf_static
, sizeof (buf_static
));
1201 ret
= FcPatternFormatToBuf (pat
, format
, &buf
);
1204 return FcStrBufDone (&buf
);
1207 FcStrBufDestroy (&buf
);
1212 #define __fcformat__
1213 #include "fcaliastail.h"