]> git.wh0rd.org - fontconfig.git/blame - src/fcformat.c
[fcformat] Add simple converters
[fontconfig.git] / src / fcformat.c
CommitLineData
0c93b91d
BE
1/*
2 * Copyright © 2008 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 * 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.
23 */
24
25#include "fcint.h"
26#include <stdlib.h>
27#include <string.h>
28#include <stdarg.h>
29
30
2017a5eb
BE
31/*
32 * Some ideas for future syntax extensions:
33 *
34 * - allow indexing subexprs using '%{[idx]elt1,elt2{subexpr}}'
35 * - allow indexing simple tags using '%{elt[idx]}'
36 * - conditional/filtering/deletion on binding (using '(w)'/'(s)' notation)
37 */
38
0c93b91d
BE
39static void
40message (const char *fmt, ...)
41{
42 va_list args;
43 va_start (args, fmt);
8c31a243 44 fprintf (stderr, "Fontconfig: Pattern format error: ");
0c93b91d 45 vfprintf (stderr, fmt, args);
d6506ff6 46 fprintf (stderr, ".\n");
0c93b91d
BE
47 va_end (args);
48}
49
50
27b3e2dd 51typedef struct _FcFormatContext
0c93b91d 52{
27b3e2dd
BE
53 const FcChar8 *format_orig;
54 const FcChar8 *format;
55 int format_len;
2017a5eb 56 FcChar8 *word;
27b3e2dd 57} FcFormatContext;
c493c3b7 58
8c31a243 59static FcBool
27b3e2dd
BE
60FcFormatContextInit (FcFormatContext *c,
61 const FcChar8 *format)
62{
63 c->format_orig = c->format = format;
64 c->format_len = strlen ((const char *) format);
2017a5eb 65 c->word = malloc (c->format_len + 1);
8c31a243 66
2017a5eb 67 return c->word != NULL;
27b3e2dd 68}
c493c3b7 69
27b3e2dd
BE
70static void
71FcFormatContextDone (FcFormatContext *c)
72{
73 if (c)
74 {
2017a5eb 75 free (c->word);
27b3e2dd
BE
76 }
77}
c493c3b7 78
27b3e2dd
BE
79static FcBool
80consume_char (FcFormatContext *c,
81 FcChar8 term)
82{
83 if (*c->format != term)
d6506ff6
BE
84 return FcFalse;
85
86 c->format++;
87 return FcTrue;
88}
89
90static FcBool
91expect_char (FcFormatContext *c,
92 FcChar8 term)
93{
94 FcBool res = consume_char (c, term);
95 if (!res)
96 {
97 if (c->format == c->format_orig + c->format_len)
98 message ("format ended while expecting '%c'",
99 term);
100 else
101 message ("expected '%c' at %d",
102 term, c->format - c->format_orig + 1);
103 }
104 return res;
105}
106
107static FcBool
108FcCharIsPunct (const FcChar8 c)
109{
110 if (c < '0')
111 return FcTrue;
112 if (c <= '9')
113 return FcFalse;
114 if (c < 'A')
115 return FcTrue;
116 if (c <= 'Z')
117 return FcFalse;
118 if (c < 'a')
119 return FcTrue;
120 if (c <= 'z')
121 return FcFalse;
122 if (c <= '~')
123 return FcTrue;
124 return FcFalse;
125}
126
127static FcBool
2017a5eb 128read_word (FcFormatContext *c)
d6506ff6
BE
129{
130 FcChar8 *p;
131
2017a5eb 132 p = c->word;
d6506ff6
BE
133
134 while (*c->format)
135 {
136 if (*c->format == '\\')
137 {
138 c->format++;
139 if (*c->format)
140 c->format++;
141 continue;
142 }
143 else if (FcCharIsPunct (*c->format))
144 break;
145
146 *p++ = *c->format++;
147 }
148 *p = '\0';
149
2017a5eb 150 if (p == c->word)
0c93b91d 151 {
d6506ff6
BE
152 message ("expected element name at %d",
153 c->format - c->format_orig + 1);
27b3e2dd
BE
154 return FcFalse;
155 }
0c93b91d 156
27b3e2dd
BE
157 return FcTrue;
158}
0c93b91d 159
8c31a243 160static FcBool
7717b25f
BE
161interpret_expr (FcFormatContext *c,
162 FcPattern *pat,
163 FcStrBuf *buf,
164 FcChar8 term);
d6506ff6 165
8c31a243
BE
166static FcBool
167interpret_subexpr (FcFormatContext *c,
27b3e2dd
BE
168 FcPattern *pat,
169 FcStrBuf *buf)
170{
8c31a243 171 return expect_char (c, '{') &&
7717b25f 172 interpret_expr (c, pat, buf, '}') &&
8c31a243
BE
173 expect_char (c, '}');
174}
0c93b91d 175
7717b25f
BE
176static FcBool
177maybe_interpret_subexpr (FcFormatContext *c,
178 FcPattern *pat,
179 FcStrBuf *buf)
180{
181 return (*c->format == '{') ?
182 interpret_subexpr (c, pat, buf) :
183 FcTrue;
184}
185
186static FcBool
187skip_subexpr (FcFormatContext *c);
188
189static FcBool
190skip_percent (FcFormatContext *c)
191{
2017a5eb
BE
192 int width;
193
7717b25f
BE
194 if (!expect_char (c, '%'))
195 return FcFalse;
196
197 /* skip an optional width specifier */
2017a5eb 198 width = strtol ((const char *) c->format, (char **) &c->format, 10);
7717b25f
BE
199
200 if (!expect_char (c, '{'))
201 return FcFalse;
202
203 while(*c->format && *c->format != '}')
204 {
205 switch (*c->format)
206 {
207 case '\\':
208 c->format++; /* skip over '\\' */
209 if (*c->format)
210 c->format++;
211 continue;
212 case '{':
213 if (!skip_subexpr (c))
214 return FcFalse;
215 continue;
216 }
217 c->format++;
218 }
219
220 return expect_char (c, '}');
221}
222
223static FcBool
224skip_expr (FcFormatContext *c)
225{
226 while(*c->format && *c->format != '}')
227 {
228 switch (*c->format)
229 {
230 case '\\':
231 c->format++; /* skip over '\\' */
232 if (*c->format)
233 c->format++;
234 continue;
235 case '%':
236 if (!skip_percent (c))
237 return FcFalse;
238 continue;
239 }
240 c->format++;
241 }
242
243 return FcTrue;
244}
245
246static FcBool
247skip_subexpr (FcFormatContext *c)
248{
249 return expect_char (c, '{') &&
250 skip_expr (c) &&
251 expect_char (c, '}');
252}
253
254static FcBool
255maybe_skip_subexpr (FcFormatContext *c)
256{
257 return (*c->format == '{') ?
258 skip_subexpr (c) :
259 FcTrue;
260}
261
8c31a243 262static FcBool
2017a5eb
BE
263interpret_simple (FcFormatContext *c,
264 FcPattern *pat,
265 FcStrBuf *buf)
8c31a243
BE
266{
267 FcPatternElt *e;
d6506ff6
BE
268 FcBool add_colon = FcFalse;
269 FcBool add_elt_name = FcFalse;
270
8c31a243 271 if (consume_char (c, ':'))
d6506ff6 272 add_colon = FcTrue;
d6506ff6 273
2017a5eb 274 if (!read_word (c))
8c31a243 275 return FcFalse;
d6506ff6
BE
276
277 if (consume_char (c, '='))
278 add_elt_name = FcTrue;
279
280 e = FcPatternObjectFindElt (pat,
2017a5eb 281 FcObjectFromName ((const char *) c->word));
27b3e2dd
BE
282 if (e)
283 {
284 FcValueListPtr l;
d6506ff6
BE
285
286 if (add_colon)
287 FcStrBufChar (buf, ':');
288 if (add_elt_name)
289 {
2017a5eb 290 FcStrBufString (buf, c->word);
d6506ff6
BE
291 FcStrBufChar (buf, '=');
292 }
293
27b3e2dd
BE
294 l = FcPatternEltValues(e);
295 FcNameUnparseValueList (buf, l, '\0');
c493c3b7
BE
296 }
297
8c31a243
BE
298 return FcTrue;
299}
300
301static FcBool
302interpret_filter (FcFormatContext *c,
303 FcPattern *pat,
304 FcStrBuf *buf)
305{
306 FcObjectSet *os;
307 FcPattern *subpat;
308
309 if (!expect_char (c, '+'))
310 return FcFalse;
311
312 os = FcObjectSetCreate ();
313 if (!os)
314 return FcFalse;
315
316 do
317 {
2017a5eb
BE
318 if (!read_word (c) ||
319 !FcObjectSetAdd (os, (const char *) c->word))
8c31a243
BE
320 {
321 FcObjectSetDestroy (os);
322 return FcFalse;
323 }
324 }
325 while (consume_char (c, ','));
326
327 subpat = FcPatternFilter (pat, os);
328 FcObjectSetDestroy (os);
329
330 if (!subpat ||
331 !interpret_subexpr (c, subpat, buf))
332 return FcFalse;
333
334 FcPatternDestroy (subpat);
335 return FcTrue;
336}
337
338static FcBool
339interpret_delete (FcFormatContext *c,
340 FcPattern *pat,
341 FcStrBuf *buf)
342{
343 FcPattern *subpat;
344
345 if (!expect_char (c, '-'))
346 return FcFalse;
347
348 subpat = FcPatternDuplicate (pat);
349 if (!subpat)
350 return FcFalse;
351
352 do
353 {
2017a5eb 354 if (!read_word (c))
8c31a243
BE
355 {
356 FcPatternDestroy (subpat);
357 return FcFalse;
358 }
359
2017a5eb 360 FcPatternDel (subpat, (const char *) c->word);
8c31a243
BE
361 }
362 while (consume_char (c, ','));
363
364 if (!interpret_subexpr (c, subpat, buf))
365 return FcFalse;
366
367 FcPatternDestroy (subpat);
368 return FcTrue;
369}
370
7717b25f 371static FcBool
2017a5eb
BE
372interpret_cond (FcFormatContext *c,
373 FcPattern *pat,
374 FcStrBuf *buf)
7717b25f
BE
375{
376 FcBool pass;
377
378 if (!expect_char (c, '?'))
379 return FcFalse;
380
381 pass = FcTrue;
382
383 do
384 {
385 FcBool negate;
386 FcValue v;
387
388 negate = consume_char (c, '!');
389
2017a5eb 390 if (!read_word (c))
7717b25f
BE
391 return FcFalse;
392
393 pass = pass &&
394 (negate ^
2017a5eb
BE
395 (FcResultMatch == FcPatternGet (pat,
396 (const char *) c->word,
397 0, &v)));
7717b25f
BE
398 }
399 while (consume_char (c, ','));
400
401 if (pass)
402 {
403 if (!interpret_subexpr (c, pat, buf) ||
404 !maybe_skip_subexpr (c))
405 return FcFalse;
406 }
407 else
408 {
409 if (!skip_subexpr (c) ||
410 !maybe_interpret_subexpr (c, pat, buf))
411 return FcFalse;
412 }
413
414 return FcTrue;
415}
416
2017a5eb
BE
417static FcChar8 *
418convert (FcFormatContext *c,
419 const FcChar8 *str)
420{
421 if (!read_word (c))
422 return NULL;
423 else if (0 == strcmp ((const char *) c->word, "downcase"))
424 return FcStrDowncase (str);
425 else if (0 == strcmp ((const char *) c->word, "basename"))
426 return FcStrBasename (str);
427 else if (0 == strcmp ((const char *) c->word, "dirname"))
428 return FcStrDirname (str);
429
430 message ("unknown converter \"%s\"",
431 c->word);
432 return NULL;
433}
434
435static FcBool
436maybe_interpret_converts (FcFormatContext *c,
437 FcStrBuf *buf,
438 int start)
439{
440 while (consume_char (c, '|'))
441 {
442 const FcChar8 *str;
443 FcChar8 *new_str;
444
445 /* nul-terminate the buffer */
446 FcStrBufChar (buf, '\0');
447 if (buf->failed)
448 return FcFalse;
449 str = buf->buf + start;
450
451 if (!(new_str = convert (c, str)))
452 return FcFalse;
453
454 /* replace in the buffer */
455 buf->len = start;
456 FcStrBufString (buf, new_str);
457 free (new_str);
458 }
459
460 return FcTrue;
461}
462
463static FcBool
464align_to_width (FcStrBuf *buf,
465 int start,
466 int width)
467{
468 int len;
469
470 if (buf->failed)
471 return FcFalse;
472
473 len = buf->len - start;
474 if (len < -width)
475 {
476 /* left align */
477 while (len++ < -width)
478 FcStrBufChar (buf, ' ');
479 }
480 else if (len < width)
481 {
482 int old_len;
483 old_len = len;
484 /* right align */
485 while (len++ < width)
486 FcStrBufChar (buf, ' ');
487 if (buf->failed)
488 return FcFalse;
489 len = old_len;
490 memmove (buf->buf + buf->len - len,
491 buf->buf + buf->len - width,
492 len);
493 memset (buf->buf + buf->len - width,
494 ' ',
495 width - len);
496 }
497
498 return !buf->failed;
499}
8c31a243
BE
500static FcBool
501interpret_percent (FcFormatContext *c,
502 FcPattern *pat,
503 FcStrBuf *buf)
504{
2017a5eb
BE
505 int width, start;
506 FcBool ret;
8c31a243
BE
507
508 if (!expect_char (c, '%'))
509 return FcFalse;
510
511 if (consume_char (c, '%')) /* "%%" */
512 {
513 FcStrBufChar (buf, '%');
514 return FcTrue;
515 }
516
517 /* parse an optional width specifier */
518 width = strtol ((const char *) c->format, (char **) &c->format, 10);
519
8c31a243
BE
520 if (!expect_char (c, '{'))
521 return FcFalse;
522
2017a5eb 523 start = buf->len;
7717b25f 524
2017a5eb
BE
525 switch (*c->format) {
526 case '{': ret = interpret_subexpr (c, pat, buf); break;
527 case '+': ret = interpret_filter (c, pat, buf); break;
528 case '-': ret = interpret_delete (c, pat, buf); break;
529 case '?': ret = interpret_cond (c, pat, buf); break;
530 default: ret = interpret_simple (c, pat, buf); break;
0c93b91d 531 }
c493c3b7 532
2017a5eb
BE
533 return ret &&
534 maybe_interpret_converts (c, buf, start) &&
535 align_to_width (buf, start, width) &&
536 expect_char (c, '}');
0c93b91d
BE
537}
538
539static char escaped_char(const char ch)
540{
541 switch (ch) {
542 case 'a': return '\a';
543 case 'b': return '\b';
544 case 'f': return '\f';
545 case 'n': return '\n';
546 case 'r': return '\r';
547 case 't': return '\t';
548 case 'v': return '\v';
549 default: return ch;
550 }
551}
552
8c31a243 553static FcBool
7717b25f
BE
554interpret_expr (FcFormatContext *c,
555 FcPattern *pat,
556 FcStrBuf *buf,
557 FcChar8 term)
0c93b91d 558{
7717b25f 559 while (*c->format && *c->format != term)
0c93b91d 560 {
27b3e2dd 561 switch (*c->format)
0c93b91d
BE
562 {
563 case '\\':
27b3e2dd
BE
564 c->format++; /* skip over '\\' */
565 if (*c->format)
566 FcStrBufChar (buf, escaped_char (*c->format++));
0c93b91d
BE
567 continue;
568 case '%':
8c31a243
BE
569 if (!interpret_percent (c, pat, buf))
570 return FcFalse;
0c93b91d
BE
571 continue;
572 }
27b3e2dd 573 FcStrBufChar (buf, *c->format++);
0c93b91d 574 }
8c31a243 575 return FcTrue;
0c93b91d
BE
576}
577
578FcChar8 *
579FcPatternFormat (FcPattern *pat, const FcChar8 *format)
580{
0c93b91d 581 FcStrBuf buf;
27b3e2dd 582 FcFormatContext c;
8c31a243 583 FcBool ret;
0c93b91d
BE
584
585 FcStrBufInit (&buf, 0, 0);
8c31a243
BE
586 if (!FcFormatContextInit (&c, format))
587 return NULL;
0c93b91d 588
7717b25f 589 ret = interpret_expr (&c, pat, &buf, '\0');
8c31a243
BE
590 if (buf.failed)
591 ret = FcFalse;
0c93b91d 592
27b3e2dd 593 FcFormatContextDone (&c);
8c31a243
BE
594 if (ret)
595 return FcStrBufDone (&buf);
596 else
597 {
598 FcStrBufDestroy (&buf);
599 return NULL;
600 }
0c93b91d
BE
601}
602
603#define __fcformat__
604#include "fcaliastail.h"
605#undef __fcformat__