]> git.wh0rd.org - fontconfig.git/blobdiff - src/fcformat.c
[fcformat] Add list of undocumented language features
[fontconfig.git] / src / fcformat.c
index 44730094c85a788b030cac1a427b4a75ccaf453c..417aba6a0e890e90c688e0a983a07c62094c23ad 100644 (file)
 #include <stdarg.h>
 
 
+/* XXX Document the language.
+ *
+ * These are mostly the features implemented but not documented:
+ *
+ * width   %[[-]width]{tag}
+ * index   %{tag[ids]}
+ * name=   %{tag=|decorator}
+ * :name=  %{:tag=|decorator}
+ * subexpr %{{expr}|decorator1|decorator2}
+ * delete  %{-charset,lang{expr}|decorator}
+ * filter  %{+family,familylang{expr}|decorator}
+ * cond    %{?tag1,tag2,!tag3{}{}}
+ * decorat %{tag|decorator1|decorator2}
+ * default %{parameter:-word}
+ * array   %{[]family,familylang{expr}|decorator}
+ * langset enumeration using the same syntax as arrays
+ *
+ * filters:
+ * basename        FcStrBasename
+ * dirname         FcStrDirname
+ * downcase        FcStrDowncase
+ * shescape
+ * cescape
+ * xmlescape
+ * delete          delete chars
+ * escape          escape chars
+ * translate       translate chars
+ *
+ * builtins:
+ * unparse
+ * fcmatch
+ * fclist
+ * pkgkit
+ */
+
 /*
  * Some ideas for future syntax extensions:
  *
  * - allow indexing subexprs using '%{[idx]elt1,elt2{subexpr}}'
- * - allow indexing simple tags using '%{elt[idx]}'
  * - conditional/filtering/deletion on binding (using '(w)'/'(s)'/'(=)' notation)
  */
 
+
+#define FCMATCH_FORMAT "%{file:-<unknown filename>|basename}: \"%{family[0]:-<unknown family>}\" \"%{style[0]:-<unknown style>}\""
+#define FCLIST_FORMAT  "%{?file{%{file}: }}%{=unparse}"
+#define PKGKIT_FORMAT  "%{[]family{font(%{family|downcase|delete( )})\n}}%{[]lang{font(:lang=%{lang|downcase|translate(_,-)})\n}}"
+
+
 static void
 message (const char *fmt, ...)
 {
@@ -216,30 +256,63 @@ read_chars (FcFormatContext *c,
     return FcTrue;
 }
 
+static FcBool
+FcPatternFormatToBuf (FcPattern     *pat,
+                     const FcChar8 *format,
+                     FcStrBuf      *buf);
+
 static FcBool
 interpret_builtin (FcFormatContext *c,
                   FcPattern       *pat,
                   FcStrBuf        *buf)
 {
-    if (!expect_char (c, '='))
-       return FcFalse;
+    FcChar8       *new_str;
+    FcBool         ret;
 
-    if (!read_word (c))
+    if (!expect_char (c, '=') ||
+       !read_word (c))
        return FcFalse;
+
+    /* try simple builtins first */
+    if (0) { }
 #define BUILTIN(name, func) \
     else if (0 == strcmp ((const char *) c->word, name))\
-       return func (c, pat, buf)
-#if 0
-    BUILTIN  ("unparse",  FcNameUnparse);
-    BUILTIN  ("verbose",  FcPatternPrint);
-    BUILTIN2 ("fcmatch",  FcStrDirname);
-    BUILTIN2 ("fclist",   FcStrDirname);
-    BUILTIN2 ("pkgkit",   FcStrDirname);
-#endif
-
-    message ("unknown builtin \"%s\"",
-            c->word);
-    return FcFalse;
+       do { new_str = func (pat); ret = FcTrue; } while (0)
+    BUILTIN ("unparse",  FcNameUnparse);
+ /* BUILTIN ("verbose",  FcPatternPrint); XXX */
+#undef BUILTIN
+    else
+       ret = FcFalse;
+
+    if (ret)
+    {
+       if (new_str)
+       {
+           FcStrBufString (buf, new_str);
+           free (new_str);
+           return FcTrue;
+       }
+       else
+           return FcFalse;
+    }
+
+    /* now try our custom formats */
+    if (0) { }
+#define BUILTIN(name, format) \
+    else if (0 == strcmp ((const char *) c->word, name))\
+       ret = FcPatternFormatToBuf (pat, (const FcChar8 *) format, buf)
+    BUILTIN ("fcmatch",  FCMATCH_FORMAT);
+    BUILTIN ("fclist",   FCLIST_FORMAT);
+    BUILTIN ("pkgkit",   PKGKIT_FORMAT);
+#undef BUILTIN
+    else
+       ret = FcFalse;
+
+    if (!ret)
+       message ("unknown builtin \"%s\"",
+                c->word);
+
+    return ret;
 }
 
 static FcBool
@@ -438,9 +511,8 @@ interpret_cond (FcFormatContext *c,
 
        pass = pass &&
               (negate ^
-               (FcResultMatch == FcPatternGet (pat,
-                                               (const char *) c->word,
-                                               0, &v)));
+               (FcResultMatch ==
+                FcPatternGet (pat, (const char *) c->word, 0, &v)));
     }
     while (consume_char (c, ','));
 
@@ -494,6 +566,122 @@ interpret_count (FcFormatContext *c,
     return FcTrue;
 }
 
+static FcBool
+interpret_array (FcFormatContext *c,
+                FcPattern       *pat,
+                FcStrBuf        *buf)
+{
+    FcObjectSet   *os;
+    FcPattern     *subpat;
+    const FcChar8 *format_save;
+    int            idx;
+    FcBool         ret, done;
+    FcStrList      *lang_strs;
+
+    if (!expect_char (c, '[') ||
+       !expect_char (c, ']'))
+       return FcFalse;
+
+    os = FcObjectSetCreate ();
+    if (!os)
+       return FcFalse;
+
+    ret = FcTrue;
+
+    do
+    {
+       if (!read_word (c) ||
+           !FcObjectSetAdd (os, (const char *) c->word))
+       {
+           FcObjectSetDestroy (os);
+           return FcFalse;
+       }
+    }
+    while (consume_char (c, ','));
+
+    /* If we have one element and it's of type FcLangSet, we want
+     * to enumerate the languages in it. */
+    lang_strs = NULL;
+    if (os->nobject == 1)
+    {
+       FcLangSet *langset;
+       if (FcResultMatch ==
+           FcPatternGetLangSet (pat, os->objects[0], idx, &langset))
+       {
+           FcStrSet *ss;
+           if (!(ss = FcLangSetGetLangs (langset)) ||
+               !(lang_strs = FcStrListCreate (ss)))
+               goto bail0;
+       }
+    }
+
+    subpat = FcPatternDuplicate (pat);
+    if (!subpat)
+       goto bail0;
+
+    format_save = c->format;
+    idx = 0;
+    do
+    {
+       int i;
+
+       done = FcTrue;
+
+       if (lang_strs)
+       {
+           FcChar8 *lang;
+
+           FcPatternDel (subpat, os->objects[0]);
+           if ((lang = FcStrListNext (lang_strs)))
+           {
+               FcPatternAddString (subpat, os->objects[0], lang);
+               done = FcFalse;
+           }
+       }
+       else
+       {
+           for (i = 0; i < os->nobject; i++)
+           {
+               FcValue v;
+
+               /* XXX this can be optimized by accessing valuelist linked lists
+                * directly and remembering where we were.  Most (all) value lists
+                * in normal uses are pretty short though (language tags are
+                * stored as a LangSet, not separate values.). */
+               FcPatternDel (subpat, os->objects[i]);
+               if (FcResultMatch ==
+                   FcPatternGet (pat, os->objects[i], idx, &v))
+               {
+                   FcPatternAdd (subpat, os->objects[i], v, FcFalse);
+                   done = FcFalse;
+               }
+           }
+       }
+
+       if (!done)
+       {
+           c->format = format_save;
+           ret = interpret_subexpr (c, subpat, buf);
+           if (!ret)
+               goto bail;
+       }
+
+       idx++;
+    } while (!done);
+
+    if (c->format == format_save)
+       skip_subexpr (c);
+
+bail:
+    FcPatternDestroy (subpat);
+bail0:
+    if (lang_strs)
+       FcStrListDone (lang_strs);
+    FcObjectSetDestroy (os);
+
+    return ret;
+}
+
 static FcBool
 interpret_simple (FcFormatContext *c,
                  FcPattern       *pat,
@@ -502,6 +690,8 @@ interpret_simple (FcFormatContext *c,
     FcPatternElt *e;
     FcBool        add_colon = FcFalse;
     FcBool        add_elt_name = FcFalse;
+    int           idx;
+    FcChar8      *else_string;
 
     if (consume_char (c, ':'))
        add_colon = FcTrue;
@@ -509,9 +699,42 @@ interpret_simple (FcFormatContext *c,
     if (!read_word (c))
        return FcFalse;
 
+    idx = -1;
+    if (consume_char (c, '['))
+    {
+       idx = strtol ((const char *) c->format, (char **) &c->format, 10);
+       if (idx < 0)
+       {
+           message ("expected non-negative number at %d",
+                    c->format-1 - c->format_orig + 1);
+           return FcFalse;
+       }
+       if (!expect_char (c, ']'))
+           return FcFalse;
+    }
+
     if (consume_char (c, '='))
        add_elt_name = FcTrue;
 
+    /* modifiers */
+    else_string = NULL;
+    if (consume_char (c, ':'))
+    {
+       FcChar8 *orig;
+       /* divert the c->word for now */
+       orig = c->word;
+       c->word = c->word + strlen ((const char *) c->word) + 1;
+       /* for now we just support 'default value' */
+       if (!expect_char (c, '-') ||
+           !read_chars (c, '\0'))
+       {
+           c->word = orig;
+           return FcFalse;
+       }
+       else_string = c->word;
+       c->word = orig;
+    }
+
     e = FcPatternObjectFindElt (pat,
                                FcObjectFromName ((const char *) c->word));
     if (e)
@@ -527,7 +750,31 @@ interpret_simple (FcFormatContext *c,
        }
 
        l = FcPatternEltValues(e);
-       FcNameUnparseValueList (buf, l, '\0');
+
+       if (idx != -1)
+       {
+           while (l && idx > 0)
+           {
+               l = FcValueListNext(l);
+               idx--;
+           }
+           if (l && idx == 0)
+           {
+               if (!FcNameUnparseValue (buf, &l->value, '\0'))
+                   return FcFalse;
+           }
+           else goto notfound;
+        }
+       else
+       {
+           FcNameUnparseValueList (buf, l, '\0');
+       }
+    }
+    else
+notfound:
+    {
+       if (else_string)
+           printf ("%s", else_string);
     }
 
     return FcTrue;
@@ -535,8 +782,8 @@ interpret_simple (FcFormatContext *c,
 
 static FcBool
 cescape (FcFormatContext *c,
-        FcStrBuf        *buf,
-        const FcChar8   *str)
+        const FcChar8   *str,
+        FcStrBuf        *buf)
 {
     while(*str)
     {
@@ -554,8 +801,8 @@ cescape (FcFormatContext *c,
 
 static FcBool
 shescape (FcFormatContext *c,
-         FcStrBuf        *buf,
-         const FcChar8   *str)
+         const FcChar8   *str,
+         FcStrBuf        *buf)
 {
     FcStrBufChar (buf, '\'');
     while(*str)
@@ -572,8 +819,8 @@ shescape (FcFormatContext *c,
 
 static FcBool
 xmlescape (FcFormatContext *c,
-          FcStrBuf        *buf,
-          const FcChar8   *str)
+          const FcChar8   *str,
+          FcStrBuf        *buf)
 {
     while(*str)
     {
@@ -591,8 +838,8 @@ xmlescape (FcFormatContext *c,
 
 static FcBool
 delete_chars (FcFormatContext *c,
-             FcStrBuf        *buf,
-             const FcChar8   *str)
+             const FcChar8   *str,
+             FcStrBuf        *buf)
 {
     /* XXX not UTF-8 aware */
 
@@ -624,8 +871,8 @@ delete_chars (FcFormatContext *c,
 
 static FcBool
 escape_chars (FcFormatContext *c,
-             FcStrBuf        *buf,
-             const FcChar8   *str)
+             const FcChar8   *str,
+             FcStrBuf        *buf)
 {
     /* XXX not UTF-8 aware */
 
@@ -659,8 +906,8 @@ escape_chars (FcFormatContext *c,
 
 static FcBool
 translate_chars (FcFormatContext *c,
-                FcStrBuf        *buf,
-                const FcChar8   *str)
+                const FcChar8   *str,
+                FcStrBuf        *buf)
 {
     char *from, *to, repeat;
     int from_len, to_len;
@@ -676,7 +923,7 @@ translate_chars (FcFormatContext *c,
     from_len = strlen (from);
     to = from + from_len + 1;
 
-    /* hack: we temporarily diverge c->word */
+    /* hack: we temporarily divert c->word */
     c->word = (FcChar8 *) to;
     if (!read_chars (c, ')'))
     {
@@ -726,19 +973,17 @@ interpret_convert (FcFormatContext *c,
     FcChar8        buf_static[8192];
     FcBool         ret;
 
-    if (!expect_char (c, '|'))
+    if (!expect_char (c, '|') ||
+       !read_word (c))
        return FcFalse;
 
-    /* nul-terminate the buffer */
+    /* prepare the buffer */
     FcStrBufChar (buf, '\0');
     if (buf->failed)
        return FcFalse;
     str = buf->buf + start;
     buf->len = start;
 
-    if (!read_word (c))
-       return FcFalse;
-
     /* try simple converters first */
     if (0) { }
 #define CONVERTER(name, func) \
@@ -755,7 +1000,6 @@ interpret_convert (FcFormatContext *c,
     {
        if (new_str)
        {
-           /* replace in the buffer */
            FcStrBufString (buf, new_str);
            free (new_str);
            return FcTrue;
@@ -770,7 +1014,7 @@ interpret_convert (FcFormatContext *c,
     if (0) { }
 #define CONVERTER(name, func) \
     else if (0 == strcmp ((const char *) c->word, name))\
-       ret = func (c, &new_buf, str)
+       ret = func (c, str, &new_buf)
     CONVERTER ("cescape",   cescape);
     CONVERTER ("shescape",  shescape);
     CONVERTER ("xmlescape", xmlescape);
@@ -876,6 +1120,7 @@ interpret_percent (FcFormatContext *c,
     case '-': ret = interpret_delete  (c, pat, buf); break;
     case '?': ret = interpret_cond    (c, pat, buf); break;
     case '#': ret = interpret_count   (c, pat, buf); break;
+    case '[': ret = interpret_array   (c, pat, buf); break;
     default:  ret = interpret_simple  (c, pat, buf); break;
     }
 
@@ -910,22 +1155,37 @@ interpret_expr (FcFormatContext *c,
     return FcTrue;
 }
 
+static FcBool
+FcPatternFormatToBuf (FcPattern     *pat,
+                     const FcChar8 *format,
+                     FcStrBuf      *buf)
+{
+    FcFormatContext c;
+    FcChar8         word_static[1024];
+    FcBool          ret;
+
+    if (!FcFormatContextInit (&c, format, word_static, sizeof (word_static)))
+       return FcFalse;
+
+    ret = interpret_expr (&c, pat, buf, '\0');
+
+    FcFormatContextDone (&c);
+
+    return ret;
+}
+
 FcChar8 *
-FcPatternFormat (FcPattern *pat, const FcChar8 *format)
+FcPatternFormat (FcPattern *pat,
+                const FcChar8 *format)
 {
     FcStrBuf        buf;
-    FcChar8         word_static[1024];
     FcChar8         buf_static[8192 - 1024];
-    FcFormatContext c;
     FcBool          ret;
 
     FcStrBufInit (&buf, buf_static, sizeof (buf_static));
-    if (!FcFormatContextInit (&c, format, word_static, sizeof (word_static)))
-       return NULL;
 
-    ret = interpret_expr (&c, pat, &buf, '\0');
+    ret = FcPatternFormatToBuf (pat, format, &buf);
 
-    FcFormatContextDone (&c);
     if (ret)
        return FcStrBufDone (&buf);
     else