]>
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 * 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 * pkgkit PackageKit package tag format
68 * Some ideas for future syntax extensions:
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)
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}}"
82 message (const char *fmt
, ...)
86 fprintf (stderr
, "Fontconfig: Pattern format error: ");
87 vfprintf (stderr
, fmt
, args
);
88 fprintf (stderr
, ".\n");
93 typedef struct _FcFormatContext
95 const FcChar8
*format_orig
;
96 const FcChar8
*format
;
99 FcBool word_allocated
;
103 FcFormatContextInit (FcFormatContext
*c
,
104 const FcChar8
*format
,
108 c
->format_orig
= c
->format
= format
;
109 c
->format_len
= strlen ((const char *) format
);
111 if (c
->format_len
< scratch_len
)
114 c
->word_allocated
= FcFalse
;
118 c
->word
= malloc (c
->format_len
+ 1);
119 c
->word_allocated
= FcTrue
;
122 return c
->word
!= NULL
;
126 FcFormatContextDone (FcFormatContext
*c
)
128 if (c
&& c
->word_allocated
)
135 consume_char (FcFormatContext
*c
,
138 if (*c
->format
!= term
)
146 expect_char (FcFormatContext
*c
,
149 FcBool res
= consume_char (c
, term
);
152 if (c
->format
== c
->format_orig
+ c
->format_len
)
153 message ("format ended while expecting '%c'",
156 message ("expected '%c' at %d",
157 term
, c
->format
- c
->format_orig
+ 1);
163 FcCharIsPunct (const FcChar8 c
)
182 static char escaped_char(const char 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';
197 read_word (FcFormatContext
*c
)
205 if (*c
->format
== '\\')
209 *p
++ = escaped_char (*c
->format
++);
212 else if (FcCharIsPunct (*c
->format
))
221 message ("expected identifier at %d",
222 c
->format
- c
->format_orig
+ 1);
230 read_chars (FcFormatContext
*c
,
237 while (*c
->format
&& *c
->format
!= '}' && *c
->format
!= term
)
239 if (*c
->format
== '\\')
243 *p
++ = escaped_char (*c
->format
++);
253 message ("expected character data at %d",
254 c
->format
- c
->format_orig
+ 1);
262 FcPatternFormatToBuf (FcPattern
*pat
,
263 const FcChar8
*format
,
267 interpret_builtin (FcFormatContext
*c
,
274 if (!expect_char (c
, '=') ||
278 /* try simple builtins first */
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 */
293 FcStrBufString (buf
, new_str
);
301 /* now try our custom formats */
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
);
314 message ("unknown builtin \"%s\"",
321 interpret_expr (FcFormatContext
*c
,
327 interpret_subexpr (FcFormatContext
*c
,
331 return expect_char (c
, '{') &&
332 interpret_expr (c
, pat
, buf
, '}') &&
333 expect_char (c
, '}');
337 maybe_interpret_subexpr (FcFormatContext
*c
,
341 return (*c
->format
== '{') ?
342 interpret_subexpr (c
, pat
, buf
) :
347 skip_subexpr (FcFormatContext
*c
);
350 skip_percent (FcFormatContext
*c
)
354 if (!expect_char (c
, '%'))
357 /* skip an optional width specifier */
358 width
= strtol ((const char *) c
->format
, (char **) &c
->format
, 10);
360 if (!expect_char (c
, '{'))
363 while(*c
->format
&& *c
->format
!= '}')
368 c
->format
++; /* skip over '\\' */
373 if (!skip_subexpr (c
))
380 return expect_char (c
, '}');
384 skip_expr (FcFormatContext
*c
)
386 while(*c
->format
&& *c
->format
!= '}')
391 c
->format
++; /* skip over '\\' */
396 if (!skip_percent (c
))
407 skip_subexpr (FcFormatContext
*c
)
409 return expect_char (c
, '{') &&
411 expect_char (c
, '}');
415 maybe_skip_subexpr (FcFormatContext
*c
)
417 return (*c
->format
== '{') ?
423 interpret_filter_in (FcFormatContext
*c
,
430 if (!expect_char (c
, '+'))
433 os
= FcObjectSetCreate ();
439 if (!read_word (c
) ||
440 !FcObjectSetAdd (os
, (const char *) c
->word
))
442 FcObjectSetDestroy (os
);
446 while (consume_char (c
, ','));
448 subpat
= FcPatternFilter (pat
, os
);
449 FcObjectSetDestroy (os
);
452 !interpret_subexpr (c
, subpat
, buf
))
455 FcPatternDestroy (subpat
);
460 interpret_filter_out (FcFormatContext
*c
,
466 if (!expect_char (c
, '-'))
469 subpat
= FcPatternDuplicate (pat
);
477 FcPatternDestroy (subpat
);
481 FcPatternDel (subpat
, (const char *) c
->word
);
483 while (consume_char (c
, ','));
485 if (!interpret_subexpr (c
, subpat
, buf
))
488 FcPatternDestroy (subpat
);
493 interpret_cond (FcFormatContext
*c
,
499 if (!expect_char (c
, '?'))
509 negate
= consume_char (c
, '!');
517 FcPatternGet (pat
, (const char *) c
->word
, 0, &v
)));
519 while (consume_char (c
, ','));
523 if (!interpret_subexpr (c
, pat
, buf
) ||
524 !maybe_skip_subexpr (c
))
529 if (!skip_subexpr (c
) ||
530 !maybe_interpret_subexpr (c
, pat
, buf
))
538 interpret_count (FcFormatContext
*c
,
544 FcChar8 buf_static
[64];
546 if (!expect_char (c
, '#'))
553 e
= FcPatternObjectFindElt (pat
,
554 FcObjectFromName ((const char *) c
->word
));
559 for (l
= FcPatternEltValues(e
);
565 snprintf ((char *) buf_static
, sizeof (buf_static
), "%d", count
);
566 FcStrBufString (buf
, buf_static
);
572 interpret_enumerate (FcFormatContext
*c
,
578 const FcChar8
*format_save
;
581 FcStrList
*lang_strs
;
583 if (!expect_char (c
, '[') ||
584 !expect_char (c
, ']'))
587 os
= FcObjectSetCreate ();
595 if (!read_word (c
) ||
596 !FcObjectSetAdd (os
, (const char *) c
->word
))
598 FcObjectSetDestroy (os
);
602 while (consume_char (c
, ','));
604 /* If we have one element and it's of type FcLangSet, we want
605 * to enumerate the languages in it. */
607 if (os
->nobject
== 1)
611 FcPatternGetLangSet (pat
, os
->objects
[0], idx
, &langset
))
614 if (!(ss
= FcLangSetGetLangs (langset
)) ||
615 !(lang_strs
= FcStrListCreate (ss
)))
620 subpat
= FcPatternDuplicate (pat
);
624 format_save
= c
->format
;
636 FcPatternDel (subpat
, os
->objects
[0]);
637 if ((lang
= FcStrListNext (lang_strs
)))
639 FcPatternAddString (subpat
, os
->objects
[0], lang
);
645 for (i
= 0; i
< os
->nobject
; i
++)
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
]);
655 FcPatternGet (pat
, os
->objects
[i
], idx
, &v
))
657 FcPatternAdd (subpat
, os
->objects
[i
], v
, FcFalse
);
665 c
->format
= format_save
;
666 ret
= interpret_subexpr (c
, subpat
, buf
);
674 if (c
->format
== format_save
)
678 FcPatternDestroy (subpat
);
681 FcStrListDone (lang_strs
);
682 FcObjectSetDestroy (os
);
688 interpret_simple (FcFormatContext
*c
,
693 FcBool add_colon
= FcFalse
;
694 FcBool add_elt_name
= FcFalse
;
696 FcChar8
*else_string
;
698 if (consume_char (c
, ':'))
705 if (consume_char (c
, '['))
707 idx
= strtol ((const char *) c
->format
, (char **) &c
->format
, 10);
710 message ("expected non-negative number at %d",
711 c
->format
-1 - c
->format_orig
+ 1);
714 if (!expect_char (c
, ']'))
718 if (consume_char (c
, '='))
719 add_elt_name
= FcTrue
;
723 if (consume_char (c
, ':'))
726 /* divert the c->word for now */
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'))
736 else_string
= c
->word
;
740 e
= FcPatternObjectFindElt (pat
,
741 FcObjectFromName ((const char *) c
->word
));
742 if (e
|| else_string
)
744 FcValueListPtr l
= NULL
;
747 FcStrBufChar (buf
, ':');
750 FcStrBufString (buf
, c
->word
);
751 FcStrBufChar (buf
, '=');
755 l
= FcPatternEltValues(e
);
761 l
= FcValueListNext(l
);
766 if (!FcNameUnparseValue (buf
, &l
->value
, '\0'))
773 FcNameUnparseValueList (buf
, l
, '\0');
779 FcStrBufString (buf
, else_string
);
787 cescape (FcFormatContext
*c
,
797 FcStrBufChar (buf
, '\\');
800 FcStrBufChar (buf
, *str
++);
806 shescape (FcFormatContext
*c
,
810 FcStrBufChar (buf
, '\'');
814 FcStrBufString (buf
, (const FcChar8
*) "'\\''");
816 FcStrBufChar (buf
, *str
);
819 FcStrBufChar (buf
, '\'');
824 xmlescape (FcFormatContext
*c
,
832 case '&': FcStrBufString (buf
, (const FcChar8
*) "&"); break;
833 case '<': FcStrBufString (buf
, (const FcChar8
*) "<"); break;
834 case '>': FcStrBufString (buf
, (const FcChar8
*) ">"); break;
835 default: FcStrBufChar (buf
, *str
); break;
843 delete_chars (FcFormatContext
*c
,
847 /* XXX not UTF-8 aware */
849 if (!expect_char (c
, '(') ||
850 !read_chars (c
, ')') ||
851 !expect_char (c
, ')'))
858 p
= (FcChar8
*) strpbrk ((const char *) str
, (const char *) c
->word
);
861 FcStrBufData (buf
, str
, p
- str
);
866 FcStrBufString (buf
, str
);
876 escape_chars (FcFormatContext
*c
,
880 /* XXX not UTF-8 aware */
882 if (!expect_char (c
, '(') ||
883 !read_chars (c
, ')') ||
884 !expect_char (c
, ')'))
891 p
= (FcChar8
*) strpbrk ((const char *) str
, (const char *) c
->word
);
894 FcStrBufData (buf
, str
, p
- str
);
895 FcStrBufChar (buf
, c
->word
[0]);
896 FcStrBufChar (buf
, *p
);
901 FcStrBufString (buf
, str
);
911 translate_chars (FcFormatContext
*c
,
915 char *from
, *to
, repeat
;
916 int from_len
, to_len
;
918 /* XXX not UTF-8 aware */
920 if (!expect_char (c
, '(') ||
921 !read_chars (c
, ',') ||
922 !expect_char (c
, ','))
925 from
= (char *) c
->word
;
926 from_len
= strlen (from
);
927 to
= from
+ from_len
+ 1;
929 /* hack: we temporarily divert c->word */
930 c
->word
= (FcChar8
*) to
;
931 if (!read_chars (c
, ')'))
933 c
->word
= (FcChar8
*) from
;
936 c
->word
= (FcChar8
*) from
;
938 to_len
= strlen (to
);
939 repeat
= to
[to_len
- 1];
941 if (!expect_char (c
, ')'))
948 p
= (FcChar8
*) strpbrk ((const char *) str
, (const char *) from
);
952 FcStrBufData (buf
, str
, p
- str
);
953 i
= strchr (from
, *p
) - from
;
954 FcStrBufChar (buf
, i
< to_len
? to
[i
] : repeat
);
959 FcStrBufString (buf
, str
);
969 interpret_convert (FcFormatContext
*c
,
976 FcChar8 buf_static
[8192];
979 if (!expect_char (c
, '|') ||
983 /* prepare the buffer */
984 FcStrBufChar (buf
, '\0');
987 str
= buf
->buf
+ start
;
990 /* try simple converters first */
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
);
1006 FcStrBufString (buf
, new_str
);
1014 FcStrBufInit (&new_buf
, buf_static
, sizeof (buf_static
));
1016 /* now try our custom converters */
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
);
1033 FcStrBufChar (&new_buf
, '\0');
1034 FcStrBufString (buf
, new_buf
.buf
);
1037 message ("unknown converter \"%s\"",
1040 FcStrBufDestroy (&new_buf
);
1046 maybe_interpret_converts (FcFormatContext
*c
,
1050 while (*c
->format
== '|')
1051 if (!interpret_convert (c
, buf
, start
))
1058 align_to_width (FcStrBuf
*buf
,
1067 len
= buf
->len
- start
;
1071 while (len
++ < -width
)
1072 FcStrBufChar (buf
, ' ');
1074 else if (len
< width
)
1079 while (len
++ < width
)
1080 FcStrBufChar (buf
, ' ');
1084 memmove (buf
->buf
+ buf
->len
- len
,
1085 buf
->buf
+ buf
->len
- width
,
1087 memset (buf
->buf
+ buf
->len
- width
,
1092 return !buf
->failed
;
1095 interpret_percent (FcFormatContext
*c
,
1102 if (!expect_char (c
, '%'))
1105 if (consume_char (c
, '%')) /* "%%" */
1107 FcStrBufChar (buf
, '%');
1111 /* parse an optional width specifier */
1112 width
= strtol ((const char *) c
->format
, (char **) &c
->format
, 10);
1114 if (!expect_char (c
, '{'))
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;
1131 maybe_interpret_converts (c
, buf
, start
) &&
1132 align_to_width (buf
, start
, width
) &&
1133 expect_char (c
, '}');
1137 interpret_expr (FcFormatContext
*c
,
1142 while (*c
->format
&& *c
->format
!= term
)
1147 c
->format
++; /* skip over '\\' */
1149 FcStrBufChar (buf
, escaped_char (*c
->format
++));
1152 if (!interpret_percent (c
, pat
, buf
))
1156 FcStrBufChar (buf
, *c
->format
++);
1162 FcPatternFormatToBuf (FcPattern
*pat
,
1163 const FcChar8
*format
,
1167 FcChar8 word_static
[1024];
1170 if (!FcFormatContextInit (&c
, format
, word_static
, sizeof (word_static
)))
1173 ret
= interpret_expr (&c
, pat
, buf
, '\0');
1175 FcFormatContextDone (&c
);
1181 FcPatternFormat (FcPattern
*pat
,
1182 const FcChar8
*format
)
1185 FcChar8 buf_static
[8192 - 1024];
1188 FcStrBufInit (&buf
, buf_static
, sizeof (buf_static
));
1190 ret
= FcPatternFormatToBuf (pat
, format
, &buf
);
1193 return FcStrBufDone (&buf
);
1196 FcStrBufDestroy (&buf
);
1201 #define __fcformat__
1202 #include "fcaliastail.h"