]>
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 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.
16 * KEITH PACKARD DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
17 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
18 * EVENT SHALL KEITH PACKARD 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 /* XXX Document the language.
33 * These are mostly the features implemented but not documented:
35 * width %[[-]width]{tag}
37 * name= %{tag=|decorator}
38 * :name= %{:tag=|decorator}
39 * subexpr %{{expr}|decorator1|decorator2}
40 * delete %{-charset,lang{expr}|decorator}
41 * filter %{+family,familylang{expr}|decorator}
42 * cond %{?tag1,tag2,!tag3{}{}}
43 * decorat %{tag|decorator1|decorator2}
44 * default %{parameter:-word}
45 * array %{[]family,familylang{expr}|decorator}
46 * langset enumeration using the same syntax as arrays
49 * basename FcStrBasename
50 * dirname FcStrDirname
51 * downcase FcStrDowncase
57 * translate translate chars
67 * Some ideas for future syntax extensions:
69 * - allow indexing subexprs using '%{[idx]elt1,elt2{subexpr}}'
70 * - conditional/filtering/deletion on binding (using '(w)'/'(s)'/'(=)' notation)
74 #define FCMATCH_FORMAT "%{file:-<unknown filename>|basename}: \"%{family[0]:-<unknown family>}\" \"%{style[0]:-<unknown style>}\""
75 #define FCLIST_FORMAT "%{?file{%{file}: }}%{=unparse}"
76 #define PKGKIT_FORMAT "%{[]family{font(%{family|downcase|delete( )})\n}}%{[]lang{font(:lang=%{lang|downcase|translate(_,-)})\n}}"
80 message (const char *fmt
, ...)
84 fprintf (stderr
, "Fontconfig: Pattern format error: ");
85 vfprintf (stderr
, fmt
, args
);
86 fprintf (stderr
, ".\n");
91 typedef struct _FcFormatContext
93 const FcChar8
*format_orig
;
94 const FcChar8
*format
;
97 FcBool word_allocated
;
101 FcFormatContextInit (FcFormatContext
*c
,
102 const FcChar8
*format
,
106 c
->format_orig
= c
->format
= format
;
107 c
->format_len
= strlen ((const char *) format
);
109 if (c
->format_len
< scratch_len
)
112 c
->word_allocated
= FcFalse
;
116 c
->word
= malloc (c
->format_len
+ 1);
117 c
->word_allocated
= FcTrue
;
120 return c
->word
!= NULL
;
124 FcFormatContextDone (FcFormatContext
*c
)
126 if (c
&& c
->word_allocated
)
133 consume_char (FcFormatContext
*c
,
136 if (*c
->format
!= term
)
144 expect_char (FcFormatContext
*c
,
147 FcBool res
= consume_char (c
, term
);
150 if (c
->format
== c
->format_orig
+ c
->format_len
)
151 message ("format ended while expecting '%c'",
154 message ("expected '%c' at %d",
155 term
, c
->format
- c
->format_orig
+ 1);
161 FcCharIsPunct (const FcChar8 c
)
180 static char escaped_char(const char ch
)
183 case 'a': return '\a';
184 case 'b': return '\b';
185 case 'f': return '\f';
186 case 'n': return '\n';
187 case 'r': return '\r';
188 case 't': return '\t';
189 case 'v': return '\v';
195 read_word (FcFormatContext
*c
)
203 if (*c
->format
== '\\')
207 *p
++ = escaped_char (*c
->format
++);
210 else if (FcCharIsPunct (*c
->format
))
219 message ("expected identifier at %d",
220 c
->format
- c
->format_orig
+ 1);
228 read_chars (FcFormatContext
*c
,
235 while (*c
->format
&& *c
->format
!= '}' && *c
->format
!= term
)
237 if (*c
->format
== '\\')
241 *p
++ = escaped_char (*c
->format
++);
251 message ("expected character data at %d",
252 c
->format
- c
->format_orig
+ 1);
260 FcPatternFormatToBuf (FcPattern
*pat
,
261 const FcChar8
*format
,
265 interpret_builtin (FcFormatContext
*c
,
272 if (!expect_char (c
, '=') ||
276 /* try simple builtins first */
278 #define BUILTIN(name, func) \
279 else if (0 == strcmp ((const char *) c->word, name))\
280 do { new_str = func (pat); ret = FcTrue; } while (0)
281 BUILTIN ("unparse", FcNameUnparse
);
282 /* BUILTIN ("verbose", FcPatternPrint); XXX */
291 FcStrBufString (buf
, new_str
);
299 /* now try our custom formats */
301 #define BUILTIN(name, format) \
302 else if (0 == strcmp ((const char *) c->word, name))\
303 ret = FcPatternFormatToBuf (pat, (const FcChar8 *) format, buf)
304 BUILTIN ("fcmatch", FCMATCH_FORMAT
);
305 BUILTIN ("fclist", FCLIST_FORMAT
);
306 BUILTIN ("pkgkit", PKGKIT_FORMAT
);
312 message ("unknown builtin \"%s\"",
319 interpret_expr (FcFormatContext
*c
,
325 interpret_subexpr (FcFormatContext
*c
,
329 return expect_char (c
, '{') &&
330 interpret_expr (c
, pat
, buf
, '}') &&
331 expect_char (c
, '}');
335 maybe_interpret_subexpr (FcFormatContext
*c
,
339 return (*c
->format
== '{') ?
340 interpret_subexpr (c
, pat
, buf
) :
345 skip_subexpr (FcFormatContext
*c
);
348 skip_percent (FcFormatContext
*c
)
352 if (!expect_char (c
, '%'))
355 /* skip an optional width specifier */
356 width
= strtol ((const char *) c
->format
, (char **) &c
->format
, 10);
358 if (!expect_char (c
, '{'))
361 while(*c
->format
&& *c
->format
!= '}')
366 c
->format
++; /* skip over '\\' */
371 if (!skip_subexpr (c
))
378 return expect_char (c
, '}');
382 skip_expr (FcFormatContext
*c
)
384 while(*c
->format
&& *c
->format
!= '}')
389 c
->format
++; /* skip over '\\' */
394 if (!skip_percent (c
))
405 skip_subexpr (FcFormatContext
*c
)
407 return expect_char (c
, '{') &&
409 expect_char (c
, '}');
413 maybe_skip_subexpr (FcFormatContext
*c
)
415 return (*c
->format
== '{') ?
421 interpret_filter (FcFormatContext
*c
,
428 if (!expect_char (c
, '+'))
431 os
= FcObjectSetCreate ();
437 if (!read_word (c
) ||
438 !FcObjectSetAdd (os
, (const char *) c
->word
))
440 FcObjectSetDestroy (os
);
444 while (consume_char (c
, ','));
446 subpat
= FcPatternFilter (pat
, os
);
447 FcObjectSetDestroy (os
);
450 !interpret_subexpr (c
, subpat
, buf
))
453 FcPatternDestroy (subpat
);
458 interpret_delete (FcFormatContext
*c
,
464 if (!expect_char (c
, '-'))
467 subpat
= FcPatternDuplicate (pat
);
475 FcPatternDestroy (subpat
);
479 FcPatternDel (subpat
, (const char *) c
->word
);
481 while (consume_char (c
, ','));
483 if (!interpret_subexpr (c
, subpat
, buf
))
486 FcPatternDestroy (subpat
);
491 interpret_cond (FcFormatContext
*c
,
497 if (!expect_char (c
, '?'))
507 negate
= consume_char (c
, '!');
515 FcPatternGet (pat
, (const char *) c
->word
, 0, &v
)));
517 while (consume_char (c
, ','));
521 if (!interpret_subexpr (c
, pat
, buf
) ||
522 !maybe_skip_subexpr (c
))
527 if (!skip_subexpr (c
) ||
528 !maybe_interpret_subexpr (c
, pat
, buf
))
536 interpret_count (FcFormatContext
*c
,
542 FcChar8 buf_static
[64];
544 if (!expect_char (c
, '#'))
551 e
= FcPatternObjectFindElt (pat
,
552 FcObjectFromName ((const char *) c
->word
));
557 for (l
= FcPatternEltValues(e
);
563 snprintf ((char *) buf_static
, sizeof (buf_static
), "%d", count
);
564 FcStrBufString (buf
, buf_static
);
570 interpret_array (FcFormatContext
*c
,
576 const FcChar8
*format_save
;
579 FcStrList
*lang_strs
;
581 if (!expect_char (c
, '[') ||
582 !expect_char (c
, ']'))
585 os
= FcObjectSetCreate ();
593 if (!read_word (c
) ||
594 !FcObjectSetAdd (os
, (const char *) c
->word
))
596 FcObjectSetDestroy (os
);
600 while (consume_char (c
, ','));
602 /* If we have one element and it's of type FcLangSet, we want
603 * to enumerate the languages in it. */
605 if (os
->nobject
== 1)
609 FcPatternGetLangSet (pat
, os
->objects
[0], idx
, &langset
))
612 if (!(ss
= FcLangSetGetLangs (langset
)) ||
613 !(lang_strs
= FcStrListCreate (ss
)))
618 subpat
= FcPatternDuplicate (pat
);
622 format_save
= c
->format
;
634 FcPatternDel (subpat
, os
->objects
[0]);
635 if ((lang
= FcStrListNext (lang_strs
)))
637 FcPatternAddString (subpat
, os
->objects
[0], lang
);
643 for (i
= 0; i
< os
->nobject
; i
++)
647 /* XXX this can be optimized by accessing valuelist linked lists
648 * directly and remembering where we were. Most (all) value lists
649 * in normal uses are pretty short though (language tags are
650 * stored as a LangSet, not separate values.). */
651 FcPatternDel (subpat
, os
->objects
[i
]);
653 FcPatternGet (pat
, os
->objects
[i
], idx
, &v
))
655 FcPatternAdd (subpat
, os
->objects
[i
], v
, FcFalse
);
663 c
->format
= format_save
;
664 ret
= interpret_subexpr (c
, subpat
, buf
);
672 if (c
->format
== format_save
)
676 FcPatternDestroy (subpat
);
679 FcStrListDone (lang_strs
);
680 FcObjectSetDestroy (os
);
686 interpret_simple (FcFormatContext
*c
,
691 FcBool add_colon
= FcFalse
;
692 FcBool add_elt_name
= FcFalse
;
694 FcChar8
*else_string
;
696 if (consume_char (c
, ':'))
703 if (consume_char (c
, '['))
705 idx
= strtol ((const char *) c
->format
, (char **) &c
->format
, 10);
708 message ("expected non-negative number at %d",
709 c
->format
-1 - c
->format_orig
+ 1);
712 if (!expect_char (c
, ']'))
716 if (consume_char (c
, '='))
717 add_elt_name
= FcTrue
;
721 if (consume_char (c
, ':'))
724 /* divert the c->word for now */
726 c
->word
= c
->word
+ strlen ((const char *) c
->word
) + 1;
727 /* for now we just support 'default value' */
728 if (!expect_char (c
, '-') ||
729 !read_chars (c
, '\0'))
734 else_string
= c
->word
;
738 e
= FcPatternObjectFindElt (pat
,
739 FcObjectFromName ((const char *) c
->word
));
745 FcStrBufChar (buf
, ':');
748 FcStrBufString (buf
, c
->word
);
749 FcStrBufChar (buf
, '=');
752 l
= FcPatternEltValues(e
);
758 l
= FcValueListNext(l
);
763 if (!FcNameUnparseValue (buf
, &l
->value
, '\0'))
770 FcNameUnparseValueList (buf
, l
, '\0');
777 printf ("%s", else_string
);
784 cescape (FcFormatContext
*c
,
794 FcStrBufChar (buf
, '\\');
797 FcStrBufChar (buf
, *str
++);
803 shescape (FcFormatContext
*c
,
807 FcStrBufChar (buf
, '\'');
811 FcStrBufString (buf
, (const FcChar8
*) "'\\''");
813 FcStrBufChar (buf
, *str
);
816 FcStrBufChar (buf
, '\'');
821 xmlescape (FcFormatContext
*c
,
829 case '&': FcStrBufString (buf
, (const FcChar8
*) "&"); break;
830 case '<': FcStrBufString (buf
, (const FcChar8
*) "<"); break;
831 case '>': FcStrBufString (buf
, (const FcChar8
*) ">"); break;
832 default: FcStrBufChar (buf
, *str
); break;
840 delete_chars (FcFormatContext
*c
,
844 /* XXX not UTF-8 aware */
846 if (!expect_char (c
, '(') ||
847 !read_chars (c
, ')') ||
848 !expect_char (c
, ')'))
855 p
= (FcChar8
*) strpbrk ((const char *) str
, (const char *) c
->word
);
858 FcStrBufData (buf
, str
, p
- str
);
863 FcStrBufString (buf
, str
);
873 escape_chars (FcFormatContext
*c
,
877 /* XXX not UTF-8 aware */
879 if (!expect_char (c
, '(') ||
880 !read_chars (c
, ')') ||
881 !expect_char (c
, ')'))
888 p
= (FcChar8
*) strpbrk ((const char *) str
, (const char *) c
->word
);
891 FcStrBufData (buf
, str
, p
- str
);
892 FcStrBufChar (buf
, c
->word
[0]);
893 FcStrBufChar (buf
, *p
);
898 FcStrBufString (buf
, str
);
908 translate_chars (FcFormatContext
*c
,
912 char *from
, *to
, repeat
;
913 int from_len
, to_len
;
915 /* XXX not UTF-8 aware */
917 if (!expect_char (c
, '(') ||
918 !read_chars (c
, ',') ||
919 !expect_char (c
, ','))
922 from
= (char *) c
->word
;
923 from_len
= strlen (from
);
924 to
= from
+ from_len
+ 1;
926 /* hack: we temporarily divert c->word */
927 c
->word
= (FcChar8
*) to
;
928 if (!read_chars (c
, ')'))
930 c
->word
= (FcChar8
*) from
;
933 c
->word
= (FcChar8
*) from
;
935 to_len
= strlen (to
);
936 repeat
= to
[to_len
- 1];
938 if (!expect_char (c
, ')'))
945 p
= (FcChar8
*) strpbrk ((const char *) str
, (const char *) from
);
949 FcStrBufData (buf
, str
, p
- str
);
950 i
= strchr (from
, *p
) - from
;
951 FcStrBufChar (buf
, i
< to_len
? to
[i
] : repeat
);
956 FcStrBufString (buf
, str
);
966 interpret_convert (FcFormatContext
*c
,
973 FcChar8 buf_static
[8192];
976 if (!expect_char (c
, '|') ||
980 /* prepare the buffer */
981 FcStrBufChar (buf
, '\0');
984 str
= buf
->buf
+ start
;
987 /* try simple converters first */
989 #define CONVERTER(name, func) \
990 else if (0 == strcmp ((const char *) c->word, name))\
991 do { new_str = func (str); ret = FcTrue; } while (0)
992 CONVERTER ("downcase", FcStrDowncase
);
993 CONVERTER ("basename", FcStrBasename
);
994 CONVERTER ("dirname", FcStrDirname
);
1003 FcStrBufString (buf
, new_str
);
1011 FcStrBufInit (&new_buf
, buf_static
, sizeof (buf_static
));
1013 /* now try our custom converters */
1015 #define CONVERTER(name, func) \
1016 else if (0 == strcmp ((const char *) c->word, name))\
1017 ret = func (c, str, &new_buf)
1018 CONVERTER ("cescape", cescape
);
1019 CONVERTER ("shescape", shescape
);
1020 CONVERTER ("xmlescape", xmlescape
);
1021 CONVERTER ("delete", delete_chars
);
1022 CONVERTER ("escape", escape_chars
);
1023 CONVERTER ("translate", translate_chars
);
1030 FcStrBufChar (&new_buf
, '\0');
1031 FcStrBufString (buf
, new_buf
.buf
);
1034 message ("unknown converter \"%s\"",
1037 FcStrBufDestroy (&new_buf
);
1043 maybe_interpret_converts (FcFormatContext
*c
,
1047 while (*c
->format
== '|')
1048 if (!interpret_convert (c
, buf
, start
))
1055 align_to_width (FcStrBuf
*buf
,
1064 len
= buf
->len
- start
;
1068 while (len
++ < -width
)
1069 FcStrBufChar (buf
, ' ');
1071 else if (len
< width
)
1076 while (len
++ < width
)
1077 FcStrBufChar (buf
, ' ');
1081 memmove (buf
->buf
+ buf
->len
- len
,
1082 buf
->buf
+ buf
->len
- width
,
1084 memset (buf
->buf
+ buf
->len
- width
,
1089 return !buf
->failed
;
1092 interpret_percent (FcFormatContext
*c
,
1099 if (!expect_char (c
, '%'))
1102 if (consume_char (c
, '%')) /* "%%" */
1104 FcStrBufChar (buf
, '%');
1108 /* parse an optional width specifier */
1109 width
= strtol ((const char *) c
->format
, (char **) &c
->format
, 10);
1111 if (!expect_char (c
, '{'))
1116 switch (*c
->format
) {
1117 case '=': ret
= interpret_builtin (c
, pat
, buf
); break;
1118 case '{': ret
= interpret_subexpr (c
, pat
, buf
); break;
1119 case '+': ret
= interpret_filter (c
, pat
, buf
); break;
1120 case '-': ret
= interpret_delete (c
, pat
, buf
); break;
1121 case '?': ret
= interpret_cond (c
, pat
, buf
); break;
1122 case '#': ret
= interpret_count (c
, pat
, buf
); break;
1123 case '[': ret
= interpret_array (c
, pat
, buf
); break;
1124 default: ret
= interpret_simple (c
, pat
, buf
); break;
1128 maybe_interpret_converts (c
, buf
, start
) &&
1129 align_to_width (buf
, start
, width
) &&
1130 expect_char (c
, '}');
1134 interpret_expr (FcFormatContext
*c
,
1139 while (*c
->format
&& *c
->format
!= term
)
1144 c
->format
++; /* skip over '\\' */
1146 FcStrBufChar (buf
, escaped_char (*c
->format
++));
1149 if (!interpret_percent (c
, pat
, buf
))
1153 FcStrBufChar (buf
, *c
->format
++);
1159 FcPatternFormatToBuf (FcPattern
*pat
,
1160 const FcChar8
*format
,
1164 FcChar8 word_static
[1024];
1167 if (!FcFormatContextInit (&c
, format
, word_static
, sizeof (word_static
)))
1170 ret
= interpret_expr (&c
, pat
, buf
, '\0');
1172 FcFormatContextDone (&c
);
1178 FcPatternFormat (FcPattern
*pat
,
1179 const FcChar8
*format
)
1182 FcChar8 buf_static
[8192 - 1024];
1185 FcStrBufInit (&buf
, buf_static
, sizeof (buf_static
));
1187 ret
= FcPatternFormatToBuf (pat
, format
, &buf
);
1190 return FcStrBufDone (&buf
);
1193 FcStrBufDestroy (&buf
);
1198 #define __fcformat__
1199 #include "fcaliastail.h"